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/1185] 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 a53295ce7c..b03e885282 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/1185] 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 5df8aacb0a..f006cadd28 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/1185] 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 ce662e61f4..016e91e4d3 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/1185] 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 b03e885282..cab31b62d9 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/1185] 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 cab31b62d9..b4705fca46 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/1185] 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 4b2e25961d..82c7b6052d 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/1185] 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 7e90cfff5c..173e884610 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/1185] 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 73f459660e..8eba760a1d 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/1185] 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 b4705fca46..a9aba7c142 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/1185] 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 173e884610..1cd27c5e6f 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/1185] 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 1cd27c5e6f..20d3c81aed 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/1185] 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 08ed44f008..3112729d64 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 0000000000..d42caf7cf2 --- /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/1185] 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 20d3c81aed..7b7942f974 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/1185] 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 7b7942f974..ece4b6625c 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/1185] 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 ece4b6625c..ba224f46ae 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/1185] 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 ba224f46ae..ba0013cc01 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/1185] 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 ba0013cc01..30d4412d2b 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/1185] 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 30d4412d2b..f16081dfb7 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/1185] 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 f16081dfb7..2dfa022194 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/1185] 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 2dfa022194..b5def9a0a1 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/1185] 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 b5def9a0a1..993ad2fa1e 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/1185] 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 d42caf7cf2..1083912663 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 993ad2fa1e..c3dec02e3b 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/1185] 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 c3dec02e3b..6027694720 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/1185] 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 6027694720..f06264c081 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/1185] 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 f06264c081..3a1fefd861 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/1185] 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 3a1fefd861..d58eae379f 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/1185] 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 51be2ebb0e..8efb6584d9 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/1185] 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 a9aba7c142..4a25b72e17 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/1185] 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 4a25b72e17..746dbf06b5 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/1185] 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 746dbf06b5..c7210ad6a9 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/1185] 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 c7210ad6a9..3e27638b9b 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 f006cadd28..f541153eaa 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 d58eae379f..6479dfdd07 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/1185] 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 8efb6584d9..a77418c17a 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/1185] 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 809561875e..a41991fbae 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/1185] 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 125c5ce051..66d28da1a8 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/1185] 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 a77418c17a..79480dfb52 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/1185] 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 9c0fb26b9e..04a6ac12bc 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/1185] 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 d7a09ee149..bb4efb4c5e 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/1185] 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 a4baa166cc..be6fbc0cea 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 52c3b0051c..86ab18b5db 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 947433cddb..3ce58cd813 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 dc289af326..26febf1d2d 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 194e6171d6..9b5a5fe8fc 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 b7b8d42c67..75f8033fa1 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 6275718c80..5b8dda53e7 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 55d6ef218d..9d7dfcef36 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 eb70578a3d..9cfe426b8a 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 ae1e4d9c11..a9439ed1b5 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 b0916eb4de..ed00f4283a 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 10b5e41506..75f4f8a80f 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 ed3d67f93b..ef133d8cd8 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 376e8be2a7..b73a1f2c90 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 5eb04fbc9d..56a89472a6 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 4b34dd406b..b770f60e1b 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 606e54b567..03ca948792 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 4089dde924..d6c6479a32 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 49535a406c..54c11830c9 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 41f4457f60..00ccbe4d41 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 eb1bb6c450..2965fb5a5d 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 1092147f7a..f32f78c6eb 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 0d4bb4b97a..bdda234ed9 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 08dac02b38..0a1a1776af 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 6d004d9fa9..f0af01becd 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 52e98ff14b..e066deef06 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 639e360093..7d559d1c15 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 d65ea43bcb..dcb3639d89 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 a4cc03093f..f7c7d66d89 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 d7b417d580..6eb2c25269 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 ae21de6d63..114ee0730c 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 44acaa2ecc..0009d4998d 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 3d29bf55f6..2853ce2577 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 90bf26f788..e2bade978d 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 295eb2588f..ec7c76382c 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 be7b380cfb..6fa50e71ac 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/1185] 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 045a0cc9e1..e066d8ba8a 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 639879cb11..496430ad4c 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 bfbb2c8bf9..5392433f7c 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 80ee65e931..f191e29863 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 42fb84bb03..a58e9d52e3 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 400e35a173..33c38900ef 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/1185] 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 74f2a0a6db..9b712c1f78 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/1185] 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 8eba760a1d..a3197d0c55 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/1185] 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 69425c08dc..2b597a72df 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/1185] 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 d06b2a76c4..0b5058e9c7 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 f84a922c72..d45a9ee605 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 bdda234ed9..5c19a19b6a 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/1185] 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 ddfc3dc2a5..26b688de25 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/1185] 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 5bc31efd34..4ca725924e 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/1185] 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 23c2abcc04..2025c52428 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/1185] Use uvloop, if available --- pyrogram/__init__.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 531da7229f..f6bd53210a 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/1185] 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 f6bd53210a..298b52c230 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/1185] 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 2025c52428..e60807387e 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/1185] 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 6479dfdd07..a41ff5fab6 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/1185] 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 8c34ea2977..61b499a38f 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/1185] 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 1083912663..539976d66e 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 ca12c92b28..981e61cda7 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/1185] 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 47a6682a26..cbbf3c2078 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/1185] 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 f541153eaa..7ca479a5d5 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/1185] 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 7ca479a5d5..062f1d2b20 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/1185] 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 39a91db34b..c4fc7e3a77 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/1185] 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 c4fc7e3a77..cee391410f 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/1185] 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 be4848238c..3a27594221 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/1185] 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 867cd3f6df..fb14d37949 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/1185] 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 fb14d37949..e56a023321 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/1185] 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 e56a023321..6ab9b17a1b 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/1185] 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 3a27594221..5161d3703b 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/1185] 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 5161d3703b..ae3a976333 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/1185] 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 5122cab9f4..635ec3751c 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/1185] 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 6ab9b17a1b..8ea4b62963 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/1185] 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 f43ca6a517..d8f470599e 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/1185] 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 57d90b1126..d8eff6a4f8 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 a851ef584e..295a32c552 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 e9bc533e6e..9e6f49c954 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 c9597a62e7..8f647818ac 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 d98fefde44..558671b725 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 f6644a01ca..2769ccb92b 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 b1eeec793d..9b6b414468 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 c913732865..51de2a6fb2 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/1185] 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 8d9ad00f54..13d4678782 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 ba68c77b96..bd9cd0fde3 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/1185] 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 02170ab9ec..ee6372688f 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/1185] 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 51e07f9199..bd43808f23 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 086d8dc21d..5993f1c992 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/1185] 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 34e892e01d..fb3b559400 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/1185] 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 fb3b559400..fcd8f8e173 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/1185] 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 9e09a8b1b3..91f3dd451f 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/1185] 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 efe53c19ee..11aa5d1f1c 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/1185] 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 f191e29863..a8aac70f01 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/1185] 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 55e9fea6ed..625879003b 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/1185] 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 5566b17979..49bba1c7c1 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/1185] 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 8704247bba..f6a099531d 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/1185] 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 91ee8375ac..c6d38153a5 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/1185] 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 f0598d128b..3aefe341a5 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/1185] 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 fe8b5d5d5f..17ed949532 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/1185] 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 17ed949532..58bf4fdc12 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/1185] 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 095b08abcf..61cd8c7c3b 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/1185] 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 26a0011b54..38b01fe9a1 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/1185] 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 38b01fe9a1..74ea37fd6c 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/1185] 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 3dc78c607d..f01a523c53 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/1185] 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 772132ce3d..ebea756a2c 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/1185] 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 924c5df7d4..3c0081a7b5 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/1185] 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 e687ced15f..a8b5b07bc8 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 843a9bb8bf..447d13eacb 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/1185] - 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 e6dcd4b663..8a22ad7e82 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 3e67e3cdbc..cb2dda3963 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 edcad039e3..b1ee933962 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 390c3b2c0f..11dd657f37 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 c3c23333eb..3c046cf962 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 943856818d..c84acd2391 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 8f1eea9568..ccfa89ee2b 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/1185] Add async_generator requirement --- requirements.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index f9c6874cfb..81df21efaf 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/1185] 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 fb4552b761..2dacc26ed5 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 4d5974252b..7d2f44e338 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 bdd8d1177c..f521ebc6be 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 6058cd17b0..712254e9ef 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 ab5879885a..1a97799864 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 5b12da4543..a48b6acf4f 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 394ddd28bc..b3c2d773c0 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/1185] 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 45bab7388b..7d5e7a4b0b 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 ad4f8b4a0d..e58bc17e03 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 401a5aa686..0f3593b058 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 e9d2084477..f90b7f46fa 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 772814255f..f17eac930d 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/1185] 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 1bf0255b0c..44d7d13736 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 ea23f8d385..e333dee41d 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 f60f7815e8..e9ab937592 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 d734ee3557..267a71362a 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 172d7c80b4..7525e2f934 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 ff6020df98..3c202b55dc 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 712c09f10d..fbc6c98407 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 458192b8f4..8b6c8e6137 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/1185] 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 c868acb524..422cc34d77 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 ec30b866dc..de1cd63391 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/1185] 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 15d7637b68..d33b5bb995 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/1185] 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 80b719af24..d7414530aa 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 d1ac8d6af0..ce923b658b 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/1185] 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 3c202b55dc..aecffe288a 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/1185] 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 e333dee41d..73208b25c8 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/1185] 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 863856f35a..267aade492 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 7d2f44e338..aa6ca91236 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 ae9925eb86..88e6924460 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 09a132b6b8..a7b9b75165 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 17b22d6fe1..f966705fac 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 bd56c428c632d3f44d0a1b3df8842dc074092bbd Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 23 Feb 2019 12:09:27 +0100 Subject: [PATCH 0101/1185] 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 2ec22f5a6c..37307111e1 100644 --- a/pyrogram/client/types/update.py +++ b/pyrogram/client/types/update.py @@ -17,11 +17,11 @@ # along with Pyrogram. If not, see . -class StopPropagation(StopIteration): +class StopPropagation(StopAsyncIteration): pass -class ContinuePropagation(StopIteration): +class ContinuePropagation(StopAsyncIteration): pass From 99af3a4180077518d1115e43bee59f92fbb48529 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 3 Mar 2019 17:11:55 +0100 Subject: [PATCH 0102/1185] 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 f58d3ceb57..5181b0299d 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 0103/1185] 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 843b719771..afcde68d8b 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 0104/1185] 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 5b91e81cf2..f9e3294fdb 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 0105/1185] 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 8a1dc699fa..ca65619ccc 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 0106/1185] 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 bf0401da63..0327615ac0 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 0107/1185] 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 1e10a7b33a..3432858940 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 7b3524b23d..65f2ff3a6d 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 9c1c02aceb..737960ca72 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 0108/1185] 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 6f654099b9..bda5f4372a 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 22e090fc58..8fd89dc6ee 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 2d9aa23baa..a5ae56fe1c 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 b37255a22a..fac74f8953 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 507cf6f935..ffe70fd9c4 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 ea3a0ac476..f620b25b6b 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 9511c548b6..b9f004958a 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 123a79fc24..ebdae53471 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 40c5306635..32b6af5934 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 7c36800e29..090bd63cb1 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 5686dfdc50..0030223368 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 00a717e467..a9c72df858 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 588bb446fb..3d8bfaf2b0 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 9c0a372c49..d9aec531ac 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 adb86e94fc..1174c639c0 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 0109/1185] 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 7e9f51a1e2..ae05c1f2d7 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 0110/1185] 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 d1e195fab6..962b8782bf 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 0111/1185] 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 8ecb092904..f1358b86b5 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 0112/1185] 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 65f2ff3a6d..88a661d0d9 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 3e7fcb02b2..6fd1975ddb 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 8d0089c331..3f0c299770 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 0e6ffa8b71..dda1f3f3b9 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 0113/1185] 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 af02a12dc2..5a155b94af 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 0114/1185] 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 30a5333fe3..767d768c0b 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 c0c3eb8cbf..5e225846c3 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 0115/1185] 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 39cdfaeb99..12f5fe12ff 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 d0c87eb2f1..15877992b4 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 0116/1185] 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 9dc4cf3983..9eb42867c9 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 0117/1185] 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 b1366b2625..ce8ae2fd80 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 b3d6b41ca8fda79c60b43d5dfb214ab0fa20a8df Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 5 Jun 2019 11:28:29 +0200 Subject: [PATCH 0118/1185] Fix Syncer not creating Event and Lock objects inside the current loop --- pyrogram/client/ext/syncer.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/ext/syncer.py b/pyrogram/client/ext/syncer.py index a9fc422187..c5f15c1a4c 100644 --- a/pyrogram/client/ext/syncer.py +++ b/pyrogram/client/ext/syncer.py @@ -33,11 +33,17 @@ class Syncer: INTERVAL = 20 clients = {} - event = asyncio.Event() - lock = asyncio.Lock() + event = None + lock = None @classmethod async def add(cls, client): + if cls.event is None: + cls.event = asyncio.Event() + + if cls.lock is None: + cls.lock = asyncio.Lock() + with await cls.lock: cls.sync(client) From 9bd9d7797b0e6bc1f29563506e7749f5cbf8ccdd Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 5 Jun 2019 11:58:29 +0200 Subject: [PATCH 0119/1185] Replace "with await" with "async with" --- pyrogram/client/client.py | 4 ++-- pyrogram/client/ext/syncer.py | 6 +++--- pyrogram/connection/transport/tcp/tcp.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index b624174619..d81c3707e8 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -1555,7 +1555,7 @@ async def get_file(self, media_type: int, is_big: bool, progress: callable, progress_args: tuple = ()) -> str: - with await self.media_sessions_lock: + async with self.media_sessions_lock: session = self.media_sessions.get(dc_id, None) if session is None: @@ -1670,7 +1670,7 @@ async def get_file(self, media_type: int, ) elif isinstance(r, types.upload.FileCdnRedirect): - with await self.media_sessions_lock: + async with self.media_sessions_lock: cdn_session = self.media_sessions.get(r.dc_id, None) if cdn_session is None: diff --git a/pyrogram/client/ext/syncer.py b/pyrogram/client/ext/syncer.py index c5f15c1a4c..88caa1606f 100644 --- a/pyrogram/client/ext/syncer.py +++ b/pyrogram/client/ext/syncer.py @@ -44,7 +44,7 @@ async def add(cls, client): if cls.lock is None: cls.lock = asyncio.Lock() - with await cls.lock: + async with cls.lock: cls.sync(client) cls.clients[id(client)] = client @@ -54,7 +54,7 @@ async def add(cls, client): @classmethod async def remove(cls, client): - with await cls.lock: + async with cls.lock: cls.sync(client) del cls.clients[id(client)] @@ -77,7 +77,7 @@ async def worker(cls): try: await asyncio.wait_for(cls.event.wait(), cls.INTERVAL) except asyncio.TimeoutError: - with await cls.lock: + async with cls.lock: for client in cls.clients.values(): cls.sync(client) else: diff --git a/pyrogram/connection/transport/tcp/tcp.py b/pyrogram/connection/transport/tcp/tcp.py index 237c5c599c..0d33fcd97c 100644 --- a/pyrogram/connection/transport/tcp/tcp.py +++ b/pyrogram/connection/transport/tcp/tcp.py @@ -92,7 +92,7 @@ def close(self): self.socket.close() async def send(self, data: bytes): - with await self.lock: + async with self.lock: self.writer.write(data) await self.writer.drain() From 2ba445d21e2dff947e7d1e7d0fcfd71b597cf306 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 21 Jun 2019 21:48:35 +0200 Subject: [PATCH 0120/1185] Fix asyncio lock not being awaited properly --- pyrogram/client/ext/dispatcher.py | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/pyrogram/client/ext/dispatcher.py b/pyrogram/client/ext/dispatcher.py index 92b1d64054..a2a81495f1 100644 --- a/pyrogram/client/ext/dispatcher.py +++ b/pyrogram/client/ext/dispatcher.py @@ -114,19 +114,25 @@ async def stop(self): log.info("Stopped {} UpdateWorkerTasks".format(self.workers)) def add_handler(self, handler, group: int): - with self.lock: - if group not in self.groups: - self.groups[group] = [] - self.groups = OrderedDict(sorted(self.groups.items())) + async def fn(): + async with self.lock: + if group not in self.groups: + self.groups[group] = [] + self.groups = OrderedDict(sorted(self.groups.items())) - self.groups[group].append(handler) + self.groups[group].append(handler) + + asyncio.get_event_loop().run_until_complete(fn()) def remove_handler(self, handler, group: int): - with self.lock: - if group not in self.groups: - raise ValueError("Group {} does not exist. Handler was not removed.".format(group)) + async def fn(): + async with self.lock: + if group not in self.groups: + raise ValueError("Group {} does not exist. Handler was not removed.".format(group)) + + self.groups[group].remove(handler) - self.groups[group].remove(handler) + asyncio.get_event_loop().run_until_complete(fn()) async def update_worker(self): while True: @@ -145,7 +151,7 @@ async def update_worker(self): else (None, type(None)) ) - with self.lock: + async with self.lock: for group in self.groups.values(): for handler in group: args = None From 633e11531a882d0cd9cafdf437f9e99e43a21271 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 23 Jun 2019 13:56:12 +0200 Subject: [PATCH 0121/1185] Fix coroutine scheduling when adding/removing handlers --- pyrogram/client/ext/dispatcher.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/ext/dispatcher.py b/pyrogram/client/ext/dispatcher.py index c2c3556992..167e9d6725 100644 --- a/pyrogram/client/ext/dispatcher.py +++ b/pyrogram/client/ext/dispatcher.py @@ -130,7 +130,7 @@ async def fn(): for lock in self.locks_list: lock.release() - asyncio.get_event_loop().run_until_complete(fn()) + asyncio.get_event_loop().create_task(fn()) def remove_handler(self, handler, group: int): async def fn(): @@ -146,7 +146,7 @@ async def fn(): for lock in self.locks_list: lock.release() - asyncio.get_event_loop().run_until_complete(fn()) + asyncio.get_event_loop().create_task(fn()) async def update_worker(self, lock): while True: From 656aa4a7cacf3fa55393aea8daf2f393b801ed17 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 24 Jun 2019 17:33:33 +0200 Subject: [PATCH 0122/1185] Enable scheduling of more than 1 updates worker --- pyrogram/client/client.py | 24 +++++++++++++++--------- pyrogram/client/ext/base_client.py | 3 +-- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 99ba180590..e7da91172a 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -17,9 +17,7 @@ # along with Pyrogram. If not, see . import asyncio -import base64 import inspect -import json import logging import math import mimetypes @@ -325,7 +323,12 @@ async def start(self): await self.session.stop() raise e - self.updates_worker_task = asyncio.ensure_future(self.updates_worker()) + for _ in range(Client.UPDATES_WORKERS): + self.updates_worker_tasks.append( + asyncio.ensure_future(self.updates_worker()) + ) + + log.info("Started {} UpdatesWorkerTasks".format(Client.UPDATES_WORKERS)) for _ in range(Client.DOWNLOAD_WORKERS): self.download_worker_tasks.append( @@ -367,8 +370,15 @@ async def stop(self): log.info("Stopped {} DownloadWorkerTasks".format(Client.DOWNLOAD_WORKERS)) - self.updates_queue.put_nowait(None) - await self.updates_worker_task + for _ in range(Client.UPDATES_WORKERS): + self.updates_queue.put_nowait(None) + + for task in self.updates_worker_tasks: + await task + + self.updates_worker_tasks.clear() + + log.info("Stopped {} UpdatesWorkerTasks".format(Client.UPDATES_WORKERS)) for media_session in self.media_sessions.values(): await media_session.stop() @@ -862,8 +872,6 @@ async def download_worker(self): done.set() async def updates_worker(self): - log.info("UpdatesWorkerTask started") - while True: updates = await self.updates_queue.get() @@ -946,8 +954,6 @@ async def updates_worker(self): except Exception as e: log.error(e, exc_info=True) - log.info("UpdatesWorkerTask stopped") - async def send(self, data: TLObject, retries: int = Session.MAX_RETRIES, diff --git a/pyrogram/client/ext/base_client.py b/pyrogram/client/ext/base_client.py index 0b1f9bff52..3d51303184 100644 --- a/pyrogram/client/ext/base_client.py +++ b/pyrogram/client/ext/base_client.py @@ -24,7 +24,6 @@ from pathlib import Path from pyrogram import __version__ - from ..style import Markdown, HTML from ...session.internals import MsgId @@ -105,7 +104,7 @@ def __init__(self): self.takeout_id = None self.updates_queue = asyncio.Queue() - self.updates_worker_task = None + self.updates_worker_tasks = [] self.download_queue = asyncio.Queue() self.download_worker_tasks = [] From ee1f6e2c9f421d0fddc1e88a5d12d9120d444456 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 4 Jul 2019 12:57:07 +0200 Subject: [PATCH 0123/1185] Fix errors and warnings when using Pyrogram async with Python <3.5.3 --- pyrogram/client/client.py | 5 ++++- pyrogram/client/methods/chats/archive_chats.py | 18 +++++++++++------- .../client/methods/chats/iter_chat_members.py | 4 ++-- pyrogram/client/methods/chats/iter_dialogs.py | 4 ++-- .../client/methods/chats/unarchive_chats.py | 18 +++++++++++------- .../client/methods/messages/iter_history.py | 4 ++-- .../methods/users/iter_profile_photos.py | 4 ++-- 7 files changed, 34 insertions(+), 23 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index e7da91172a..faac9051fe 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -447,7 +447,8 @@ def run(self, coroutine=None): Raises: RPCError: In case of a Telegram RPC error. """ - run = asyncio.get_event_loop().run_until_complete + loop = asyncio.get_event_loop() + run = loop.run_until_complete run(self.start()) @@ -460,6 +461,8 @@ def run(self, coroutine=None): if self.is_started: run(self.stop()) + loop.close() + return coroutine def add_handler(self, handler: Handler, group: int = 0): diff --git a/pyrogram/client/methods/chats/archive_chats.py b/pyrogram/client/methods/chats/archive_chats.py index 3f53b25e3b..379860d59c 100644 --- a/pyrogram/client/methods/chats/archive_chats.py +++ b/pyrogram/client/methods/chats/archive_chats.py @@ -19,7 +19,6 @@ from typing import Union, List from pyrogram.api import functions, types - from ...ext import BaseClient @@ -45,14 +44,19 @@ async def archive_chats( if not isinstance(chat_ids, list): chat_ids = [chat_ids] + folder_peers = [] + + for chat in chat_ids: + folder_peers.append( + types.InputFolderPeer( + peer=await self.resolve_peer(chat), + folder_id=1 + ) + ) + await self.send( functions.folders.EditPeerFolders( - folder_peers=[ - types.InputFolderPeer( - peer=await self.resolve_peer(chat), - folder_id=1 - ) for chat in chat_ids - ] + folder_peers=folder_peers ) ) diff --git a/pyrogram/client/methods/chats/iter_chat_members.py b/pyrogram/client/methods/chats/iter_chat_members.py index c4db15214d..bdd613fdc3 100644 --- a/pyrogram/client/methods/chats/iter_chat_members.py +++ b/pyrogram/client/methods/chats/iter_chat_members.py @@ -17,7 +17,7 @@ # along with Pyrogram. If not, see . from string import ascii_lowercase -from typing import Union, AsyncGenerator, Optional +from typing import Union, Generator, Optional import pyrogram from async_generator import async_generator, yield_ @@ -47,7 +47,7 @@ async def iter_chat_members( limit: int = 0, query: str = "", filter: str = Filters.ALL - ) -> Optional[AsyncGenerator["pyrogram.ChatMember", None]]: + ) -> Optional[Generator["pyrogram.ChatMember", None, None]]: """Iterate through the members of a chat sequentially. This convenience method does the same as repeatedly calling :meth:`~Client.get_chat_members` in a loop, thus saving you diff --git a/pyrogram/client/methods/chats/iter_dialogs.py b/pyrogram/client/methods/chats/iter_dialogs.py index bbad9b35ee..3162d31d92 100644 --- a/pyrogram/client/methods/chats/iter_dialogs.py +++ b/pyrogram/client/methods/chats/iter_dialogs.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import AsyncGenerator, Optional +from typing import Generator, Optional import pyrogram from async_generator import async_generator, yield_ @@ -30,7 +30,7 @@ async def iter_dialogs( self, limit: int = 0, offset_date: int = None - ) -> Optional[AsyncGenerator["pyrogram.Dialog", None]]: + ) -> Optional[Generator["pyrogram.Dialog", None, None]]: """Iterate through a user's dialogs sequentially. This convenience method does the same as repeatedly calling :meth:`~Client.get_dialogs` in a loop, thus saving diff --git a/pyrogram/client/methods/chats/unarchive_chats.py b/pyrogram/client/methods/chats/unarchive_chats.py index 56768dba3b..8c47557e4b 100644 --- a/pyrogram/client/methods/chats/unarchive_chats.py +++ b/pyrogram/client/methods/chats/unarchive_chats.py @@ -19,7 +19,6 @@ from typing import Union, List from pyrogram.api import functions, types - from ...ext import BaseClient @@ -45,14 +44,19 @@ async def unarchive_chats( if not isinstance(chat_ids, list): chat_ids = [chat_ids] + folder_peers = [] + + for chat in chat_ids: + folder_peers.append( + types.InputFolderPeer( + peer=await self.resolve_peer(chat), + folder_id=0 + ) + ) + await self.send( functions.folders.EditPeerFolders( - folder_peers=[ - types.InputFolderPeer( - peer=await self.resolve_peer(chat), - folder_id=0 - ) for chat in chat_ids - ] + folder_peers=folder_peers ) ) diff --git a/pyrogram/client/methods/messages/iter_history.py b/pyrogram/client/methods/messages/iter_history.py index 2dca96fe66..6d69273b31 100644 --- a/pyrogram/client/methods/messages/iter_history.py +++ b/pyrogram/client/methods/messages/iter_history.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Union, Optional, AsyncGenerator +from typing import Union, Optional, Generator import pyrogram from async_generator import async_generator, yield_ @@ -34,7 +34,7 @@ async def iter_history( offset_id: int = 0, offset_date: int = 0, reverse: bool = False - ) -> Optional[AsyncGenerator["pyrogram.Message", None]]: + ) -> Optional[Generator["pyrogram.Message", None, None]]: """Iterate through a chat history sequentially. This convenience method does the same as repeatedly calling :meth:`~Client.get_history` in a loop, thus saving diff --git a/pyrogram/client/methods/users/iter_profile_photos.py b/pyrogram/client/methods/users/iter_profile_photos.py index bfe3e7f023..4e703e0725 100644 --- a/pyrogram/client/methods/users/iter_profile_photos.py +++ b/pyrogram/client/methods/users/iter_profile_photos.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Union, AsyncGenerator, Optional +from typing import Union, Generator, Optional import pyrogram from async_generator import async_generator, yield_ @@ -31,7 +31,7 @@ async def iter_profile_photos( chat_id: Union[int, str], offset: int = 0, limit: int = 0, - ) -> Optional[AsyncGenerator["pyrogram.Message", None]]: + ) -> Optional[Generator["pyrogram.Message", None, None]]: """Iterate through a chat or a user profile photos sequentially. This convenience method does the same as repeatedly calling :meth:`~Client.get_profile_photos` in a loop, thus From d5f31a8473e788de115b428569b3204ebf0baddd Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 11 Jul 2019 04:20:23 +0200 Subject: [PATCH 0124/1185] Update asyncio-dev version --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index f98d0ad5a5..7b4ece92c8 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -24,7 +24,7 @@ # Monkey patch the standard "typing" module because Python versions from 3.5.0 to 3.5.2 have a broken one. sys.modules["typing"] = typing -__version__ = "0.15.1-asyncio" +__version__ = "0.16.0.asyncio-dev" __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)" __copyright__ = "Copyright (C) 2017-2019 Dan " From 9940dd678fd4f982e066488eb5595871e79b309c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 15 Jul 2019 00:51:32 +0200 Subject: [PATCH 0125/1185] Replace ensure_future usages to create_task --- pyrogram/client/client.py | 8 ++++---- pyrogram/client/ext/dispatcher.py | 2 +- pyrogram/client/ext/syncer.py | 2 +- pyrogram/session/session.py | 10 +++++----- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 2376aa362f..3590261187 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -325,14 +325,14 @@ async def start(self): for _ in range(Client.UPDATES_WORKERS): self.updates_worker_tasks.append( - asyncio.ensure_future(self.updates_worker()) + asyncio.create_task(self.updates_worker()) ) log.info("Started {} UpdatesWorkerTasks".format(Client.UPDATES_WORKERS)) for _ in range(Client.DOWNLOAD_WORKERS): self.download_worker_tasks.append( - asyncio.ensure_future(self.download_worker()) + asyncio.create_task(self.download_worker()) ) log.info("Started {} DownloadWorkerTasks".format(Client.DOWNLOAD_WORKERS)) @@ -1397,7 +1397,7 @@ async def worker(session): return try: - await asyncio.ensure_future(session.send(data)) + await asyncio.create_task(session.send(data)) except Exception as e: log.error(e) @@ -1418,7 +1418,7 @@ async def worker(session): file_id = file_id or self.rnd_id() md5_sum = md5() if not is_big and not is_missing_part else None pool = [Session(self, self.storage.dc_id, self.storage.auth_key, is_media=True) for _ in range(pool_size)] - workers = [asyncio.ensure_future(worker(session)) for session in pool for _ in range(workers_count)] + workers = [asyncio.create_task(worker(session)) for session in pool for _ in range(workers_count)] queue = asyncio.Queue(16) try: diff --git a/pyrogram/client/ext/dispatcher.py b/pyrogram/client/ext/dispatcher.py index 70bee2464d..98d9b86552 100644 --- a/pyrogram/client/ext/dispatcher.py +++ b/pyrogram/client/ext/dispatcher.py @@ -98,7 +98,7 @@ async def start(self): self.locks_list.append(asyncio.Lock()) self.update_worker_tasks.append( - asyncio.ensure_future(self.update_worker(self.locks_list[-1])) + asyncio.create_task(self.update_worker(self.locks_list[-1])) ) log.info("Started {} UpdateWorkerTasks".format(self.workers)) diff --git a/pyrogram/client/ext/syncer.py b/pyrogram/client/ext/syncer.py index 8b48e6e2ab..bf54f57b6f 100644 --- a/pyrogram/client/ext/syncer.py +++ b/pyrogram/client/ext/syncer.py @@ -59,7 +59,7 @@ async def remove(cls, client): @classmethod def start(cls): cls.event.clear() - asyncio.ensure_future(cls.worker()) + asyncio.create_task(cls.worker()) @classmethod def stop(cls): diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index ff65483cfe..429de1998c 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -123,8 +123,8 @@ async def start(self): try: await self.connection.connect() - self.net_worker_task = asyncio.ensure_future(self.net_worker()) - self.recv_task = asyncio.ensure_future(self.recv()) + self.net_worker_task = asyncio.create_task(self.net_worker()) + self.recv_task = asyncio.create_task(self.recv()) self.current_salt = FutureSalt(0, 0, Session.INITIAL_SALT) self.current_salt = FutureSalt( @@ -137,7 +137,7 @@ async def start(self): self.current_salt = \ (await self._send(functions.GetFutureSalts(num=1), timeout=self.START_TIMEOUT)).salts[0] - self.next_salt_task = asyncio.ensure_future(self.next_salt()) + self.next_salt_task = asyncio.create_task(self.next_salt()) if not self.is_cdn: await self._send( @@ -157,7 +157,7 @@ async def start(self): timeout=self.START_TIMEOUT ) - self.ping_task = asyncio.ensure_future(self.ping()) + self.ping_task = asyncio.create_task(self.ping()) log.info("Session initialized: Layer {}".format(layer)) log.info("Device: {} - {}".format(self.client.device_model, self.client.app_version)) @@ -351,7 +351,7 @@ async def recv(self): log.warning("Server sent \"{}\"".format(Int.read(BytesIO(packet)))) if self.is_connected.is_set(): - asyncio.ensure_future(self.restart()) + asyncio.create_task(self.restart()) break From 4d324abbb5d11040028ef6aa62572b52e1e39a38 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 15 Jul 2019 00:54:35 +0200 Subject: [PATCH 0126/1185] Don't automatically install uvloop. Let people do that People are reporting uvloop would crash with weird core-dumped errors when using other asyncio libs, such as aiohttp. Plus, this was a bad idea and people should install uvloop themselves before running their codes. --- pyrogram/__init__.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 7b4ece92c8..407bbcff7f 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -28,13 +28,6 @@ __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)" __copyright__ = "Copyright (C) 2017-2019 Dan " -try: - import uvloop -except ImportError: - pass -else: - uvloop.install() - from .errors import RPCError from .client import * from .client.handlers import * From c30e8f9c551ce89efbef1e23cf192467ac8afdce Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 15 Jul 2019 01:26:29 +0200 Subject: [PATCH 0127/1185] Don't start the client in case run() is called with a coroutine as arg --- pyrogram/client/client.py | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 3590261187..c1065a42b1 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -17,7 +17,6 @@ # along with Pyrogram. If not, see . import asyncio -import inspect import logging import math import mimetypes @@ -452,21 +451,17 @@ def run(self, coroutine=None): loop = asyncio.get_event_loop() run = loop.run_until_complete - run(self.start()) - - run( - coroutine if inspect.iscoroutine(coroutine) - else coroutine() if coroutine - else self.idle() - ) + if coroutine is not None: + run(coroutine) + else: + run(self.start()) + run(self.idle()) - if self.is_started: - run(self.stop()) + # TODO: Uncomment this once idle() gets refactored + # run(self.stop()) loop.close() - return coroutine - def add_handler(self, handler: Handler, group: int = 0): """Register an update handler. From 8700e3a0f3a2e4e12c9c5dfaa5382e765e1149ba Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 31 Jul 2019 13:33:04 +0200 Subject: [PATCH 0128/1185] Fix some methods not being defined using async --- pyrogram/client/ext/base_client.py | 18 ++++++------- .../bots_and_keyboards/callback_query.py | 26 +++++++++---------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/pyrogram/client/ext/base_client.py b/pyrogram/client/ext/base_client.py index 640e7e6557..230c681f72 100644 --- a/pyrogram/client/ext/base_client.py +++ b/pyrogram/client/ext/base_client.py @@ -146,29 +146,29 @@ async def get_chat_members_count(self, *args, **kwargs): async def answer_inline_query(self, *args, **kwargs): pass - def guess_mime_type(self, *args, **kwargs): + async def get_profile_photos(self, *args, **kwargs): pass - def guess_extension(self, *args, **kwargs): + async def edit_message_text(self, *args, **kwargs): pass - def get_profile_photos(self, *args, **kwargs): + async def edit_inline_text(self, *args, **kwargs): pass - def edit_message_text(self, *args, **kwargs): + async def edit_message_media(self, *args, **kwargs): pass - def edit_inline_text(self, *args, **kwargs): + async def edit_inline_media(self, *args, **kwargs): pass - def edit_message_media(self, *args, **kwargs): + async def edit_message_reply_markup(self, *args, **kwargs): pass - def edit_inline_media(self, *args, **kwargs): + async def edit_inline_reply_markup(self, *args, **kwargs): pass - def edit_message_reply_markup(self, *args, **kwargs): + def guess_mime_type(self, *args, **kwargs): pass - def edit_inline_reply_markup(self, *args, **kwargs): + def guess_extension(self, *args, **kwargs): pass diff --git a/pyrogram/client/types/bots_and_keyboards/callback_query.py b/pyrogram/client/types/bots_and_keyboards/callback_query.py index 0264a67c6d..27cebd7e46 100644 --- a/pyrogram/client/types/bots_and_keyboards/callback_query.py +++ b/pyrogram/client/types/bots_and_keyboards/callback_query.py @@ -129,7 +129,7 @@ async def _parse(client, callback_query, users) -> "CallbackQuery": client=client ) - def answer(self, text: str = None, show_alert: bool = None, url: str = None, cache_time: int = 0): + async def answer(self, text: str = None, show_alert: bool = None, url: str = None, cache_time: int = 0): """Bound method *answer* of :obj:`CallbackQuery`. Use this method as a shortcut for: @@ -165,7 +165,7 @@ def answer(self, text: str = None, show_alert: bool = None, url: str = None, cac The maximum amount of time in seconds that the result of the callback query may be cached client-side. Telegram apps will support caching starting in version 3.14. Defaults to 0. """ - return self._client.answer_callback_query( + return await self._client.answer_callback_query( callback_query_id=self.id, text=text, show_alert=show_alert, @@ -173,7 +173,7 @@ def answer(self, text: str = None, show_alert: bool = None, url: str = None, cac cache_time=cache_time ) - def edit_message_text( + async def edit_message_text( self, text: str, parse_mode: Union[str, None] = object, @@ -209,7 +209,7 @@ def edit_message_text( RPCError: In case of a Telegram RPC error. """ if self.inline_message_id is None: - return self._client.edit_message_text( + return await self._client.edit_message_text( chat_id=self.message.chat.id, message_id=self.message.message_id, text=text, @@ -218,7 +218,7 @@ def edit_message_text( reply_markup=reply_markup ) else: - return self._client.edit_inline_text( + return await self._client.edit_inline_text( inline_message_id=self.inline_message_id, text=text, parse_mode=parse_mode, @@ -226,7 +226,7 @@ def edit_message_text( reply_markup=reply_markup ) - def edit_message_caption( + async def edit_message_caption( self, caption: str, parse_mode: Union[str, None] = object, @@ -257,9 +257,9 @@ def edit_message_caption( Raises: RPCError: In case of a Telegram RPC error. """ - return self.edit_message_text(caption, parse_mode, reply_markup) + return await self.edit_message_text(caption, parse_mode, reply_markup) - def edit_message_media( + async def edit_message_media( self, media: "pyrogram.InputMedia", reply_markup: "pyrogram.InlineKeyboardMarkup" = None @@ -283,20 +283,20 @@ def edit_message_media( RPCError: In case of a Telegram RPC error. """ if self.inline_message_id is None: - return self._client.edit_message_media( + return await self._client.edit_message_media( chat_id=self.message.chat.id, message_id=self.message.message_id, media=media, reply_markup=reply_markup ) else: - return self._client.edit_inline_media( + return await self._client.edit_inline_media( inline_message_id=self.inline_message_id, media=media, reply_markup=reply_markup ) - def edit_message_reply_markup( + async def edit_message_reply_markup( self, reply_markup: "pyrogram.InlineKeyboardMarkup" = None ) -> Union["pyrogram.Message", bool]: @@ -316,13 +316,13 @@ def edit_message_reply_markup( RPCError: In case of a Telegram RPC error. """ if self.inline_message_id is None: - return self._client.edit_message_reply_markup( + return await self._client.edit_message_reply_markup( chat_id=self.message.chat.id, message_id=self.message.message_id, reply_markup=reply_markup ) else: - return self._client.edit_inline_reply_markup( + return await self._client.edit_inline_reply_markup( inline_message_id=self.inline_message_id, reply_markup=reply_markup ) From eddff4769cd2098bb51f82e674e78a9050b535c9 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 1 Aug 2019 10:43:09 +0200 Subject: [PATCH 0129/1185] Add missing async/await --- pyrogram/client/parser/parser.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyrogram/client/parser/parser.py b/pyrogram/client/parser/parser.py index 371c47912c..21d09b42aa 100644 --- a/pyrogram/client/parser/parser.py +++ b/pyrogram/client/parser/parser.py @@ -30,7 +30,7 @@ def __init__(self, client: Union["pyrogram.BaseClient", None]): self.html = HTML(client) self.markdown = Markdown(client) - def parse(self, text: str, mode: Union[str, None] = object): + async def parse(self, text: str, mode: Union[str, None] = object): text = str(text).strip() if mode == object: @@ -48,13 +48,13 @@ def parse(self, text: str, mode: Union[str, None] = object): mode = mode.lower() if mode == "combined": - return self.markdown.parse(text) + return await self.markdown.parse(text) if mode in ["markdown", "md"]: - return self.markdown.parse(text, True) + return await self.markdown.parse(text, True) if mode == "html": - return self.html.parse(text) + return await self.html.parse(text) raise ValueError('parse_mode must be one of {} or None. Not "{}"'.format( ", ".join('"{}"'.format(m) for m in pyrogram.Client.PARSE_MODES[:-1]), From 73e8b8c66e10ce4ab6bd1d75c62e935ae83d33ba Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 1 Aug 2019 20:18:17 +0200 Subject: [PATCH 0130/1185] Update read_history.py --- pyrogram/client/methods/messages/read_history.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyrogram/client/methods/messages/read_history.py b/pyrogram/client/methods/messages/read_history.py index f5dc8630ee..7fa99dc920 100644 --- a/pyrogram/client/methods/messages/read_history.py +++ b/pyrogram/client/methods/messages/read_history.py @@ -23,7 +23,7 @@ class ReadHistory(BaseClient): - def read_history( + async def read_history( self, chat_id: Union[int, str], max_id: int = 0 @@ -53,7 +53,7 @@ def read_history( app.read_history("pyrogramlounge", 123456) """ - peer = self.resolve_peer(chat_id) + peer = await self.resolve_peer(chat_id) if isinstance(peer, types.InputPeerChannel): q = functions.channels.ReadHistory( @@ -66,6 +66,6 @@ def read_history( max_id=max_id ) - self.send(q) + await self.send(q) return True From 94603f1ff255f3b44158a4f1b4acb2dad8ac5542 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 3 Aug 2019 10:36:57 +0200 Subject: [PATCH 0131/1185] Replace create_task with ensure_future for compatibility --- pyrogram/client/ext/dispatcher.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/ext/dispatcher.py b/pyrogram/client/ext/dispatcher.py index 98d9b86552..855fefe7d2 100644 --- a/pyrogram/client/ext/dispatcher.py +++ b/pyrogram/client/ext/dispatcher.py @@ -130,7 +130,7 @@ async def fn(): for lock in self.locks_list: lock.release() - asyncio.get_event_loop().create_task(fn()) + asyncio.ensure_future(fn()) def remove_handler(self, handler, group: int): async def fn(): @@ -146,7 +146,7 @@ async def fn(): for lock in self.locks_list: lock.release() - asyncio.get_event_loop().create_task(fn()) + asyncio.ensure_future(fn()) async def update_worker(self, lock): while True: From adda199c779ccad60eb91f7cc07df57071939954 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 3 Aug 2019 10:37:48 +0200 Subject: [PATCH 0132/1185] Revert "Replace ensure_future usages to create_task" This reverts commit 9940dd67 --- pyrogram/client/client.py | 8 ++++---- pyrogram/client/ext/dispatcher.py | 2 +- pyrogram/client/ext/syncer.py | 2 +- pyrogram/session/session.py | 10 +++++----- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 8b71312d7d..55f207433c 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -352,14 +352,14 @@ async def start(self): for _ in range(Client.UPDATES_WORKERS): self.updates_worker_tasks.append( - asyncio.create_task(self.updates_worker()) + asyncio.ensure_future(self.updates_worker()) ) log.info("Started {} UpdatesWorkerTasks".format(Client.UPDATES_WORKERS)) for _ in range(Client.DOWNLOAD_WORKERS): self.download_worker_tasks.append( - asyncio.create_task(self.download_worker()) + asyncio.ensure_future(self.download_worker()) ) log.info("Started {} DownloadWorkerTasks".format(Client.DOWNLOAD_WORKERS)) @@ -1623,7 +1623,7 @@ async def worker(session): return try: - await asyncio.create_task(session.send(data)) + await asyncio.ensure_future(session.send(data)) except Exception as e: log.error(e) @@ -1644,7 +1644,7 @@ async def worker(session): file_id = file_id or self.rnd_id() md5_sum = md5() if not is_big and not is_missing_part else None pool = [Session(self, self.storage.dc_id, self.storage.auth_key, is_media=True) for _ in range(pool_size)] - workers = [asyncio.create_task(worker(session)) for session in pool for _ in range(workers_count)] + workers = [asyncio.ensure_future(worker(session)) for session in pool for _ in range(workers_count)] queue = asyncio.Queue(16) try: diff --git a/pyrogram/client/ext/dispatcher.py b/pyrogram/client/ext/dispatcher.py index 855fefe7d2..d4388dddfe 100644 --- a/pyrogram/client/ext/dispatcher.py +++ b/pyrogram/client/ext/dispatcher.py @@ -98,7 +98,7 @@ async def start(self): self.locks_list.append(asyncio.Lock()) self.update_worker_tasks.append( - asyncio.create_task(self.update_worker(self.locks_list[-1])) + asyncio.ensure_future(self.update_worker(self.locks_list[-1])) ) log.info("Started {} UpdateWorkerTasks".format(self.workers)) diff --git a/pyrogram/client/ext/syncer.py b/pyrogram/client/ext/syncer.py index bf54f57b6f..8b48e6e2ab 100644 --- a/pyrogram/client/ext/syncer.py +++ b/pyrogram/client/ext/syncer.py @@ -59,7 +59,7 @@ async def remove(cls, client): @classmethod def start(cls): cls.event.clear() - asyncio.create_task(cls.worker()) + asyncio.ensure_future(cls.worker()) @classmethod def stop(cls): diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index b137706473..e4699f7d38 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -123,8 +123,8 @@ async def start(self): try: await self.connection.connect() - self.net_worker_task = asyncio.create_task(self.net_worker()) - self.recv_task = asyncio.create_task(self.recv()) + self.net_worker_task = asyncio.ensure_future(self.net_worker()) + self.recv_task = asyncio.ensure_future(self.recv()) self.current_salt = FutureSalt(0, 0, Session.INITIAL_SALT) self.current_salt = FutureSalt( @@ -137,7 +137,7 @@ async def start(self): self.current_salt = \ (await self._send(functions.GetFutureSalts(num=1), timeout=self.START_TIMEOUT)).salts[0] - self.next_salt_task = asyncio.create_task(self.next_salt()) + self.next_salt_task = asyncio.ensure_future(self.next_salt()) if not self.is_cdn: await self._send( @@ -157,7 +157,7 @@ async def start(self): timeout=self.START_TIMEOUT ) - self.ping_task = asyncio.create_task(self.ping()) + self.ping_task = asyncio.ensure_future(self.ping()) log.info("Session initialized: Layer {}".format(layer)) log.info("Device: {} - {}".format(self.client.device_model, self.client.app_version)) @@ -351,7 +351,7 @@ async def recv(self): log.warning("Server sent \"{}\"".format(Int.read(BytesIO(packet)))) if self.is_connected.is_set(): - asyncio.create_task(self.restart()) + asyncio.ensure_future(self.restart()) break From 5cfc412af22c72867cf7593adbb7d225566aec96 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 7 Aug 2019 14:08:06 +0200 Subject: [PATCH 0133/1185] Add missing await --- pyrogram/client/methods/messages/edit_inline_text.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/methods/messages/edit_inline_text.py b/pyrogram/client/methods/messages/edit_inline_text.py index 2495183278..3e2ab5d2b8 100644 --- a/pyrogram/client/methods/messages/edit_inline_text.py +++ b/pyrogram/client/methods/messages/edit_inline_text.py @@ -76,6 +76,6 @@ async def edit_inline_text( id=utils.unpack_inline_message_id(inline_message_id), no_webpage=disable_web_page_preview or None, reply_markup=reply_markup.write() if reply_markup else None, - **self.parser.parse(text, parse_mode) + **await self.parser.parse(text, parse_mode) ) ) From 2031df15fe061067564fcb6b88199351a0db1134 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 23 Aug 2019 11:52:12 +0200 Subject: [PATCH 0134/1185] Update inline_query_result_photo.py --- pyrogram/client/types/inline_mode/inline_query_result_photo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/types/inline_mode/inline_query_result_photo.py b/pyrogram/client/types/inline_mode/inline_query_result_photo.py index ffcc21c019..df06a2a9e9 100644 --- a/pyrogram/client/types/inline_mode/inline_query_result_photo.py +++ b/pyrogram/client/types/inline_mode/inline_query_result_photo.py @@ -121,7 +121,7 @@ def write(self): if self.input_message_content else types.InputBotInlineMessageMediaAuto( reply_markup=self.reply_markup.write() if self.reply_markup else None, - **(Parser(None)).parse(self.caption, self.parse_mode) + **await(Parser(None)).parse(self.caption, self.parse_mode) ) ) ) From fe6c5e542d8c0dcc65913bf63758df506475532b Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 23 Aug 2019 12:25:09 +0200 Subject: [PATCH 0135/1185] Add missing async and await keywords --- .../client/types/inline_mode/inline_query_result_photo.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/types/inline_mode/inline_query_result_photo.py b/pyrogram/client/types/inline_mode/inline_query_result_photo.py index df06a2a9e9..35b8a87475 100644 --- a/pyrogram/client/types/inline_mode/inline_query_result_photo.py +++ b/pyrogram/client/types/inline_mode/inline_query_result_photo.py @@ -91,7 +91,7 @@ def __init__( self.reply_markup = reply_markup self.input_message_content = input_message_content - def write(self): + async def write(self): photo = types.InputWebDocument( url=self.photo_url, size=0, @@ -117,7 +117,7 @@ def write(self): thumb=thumb, content=photo, send_message=( - self.input_message_content.write(self.reply_markup) + await self.input_message_content.write(self.reply_markup) if self.input_message_content else types.InputBotInlineMessageMediaAuto( reply_markup=self.reply_markup.write() if self.reply_markup else None, From 1ade49a13a887d0f0cd76bc85dc0e789624187a6 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 26 Aug 2019 22:09:36 +0200 Subject: [PATCH 0136/1185] Fix error on serializing None when int is expected --- pyrogram/client/methods/chats/iter_dialogs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/methods/chats/iter_dialogs.py b/pyrogram/client/methods/chats/iter_dialogs.py index 8265a9df03..80ce44f57e 100644 --- a/pyrogram/client/methods/chats/iter_dialogs.py +++ b/pyrogram/client/methods/chats/iter_dialogs.py @@ -29,7 +29,7 @@ class IterDialogs(BaseClient): async def iter_dialogs( self, limit: int = 0, - offset_date: int = None + offset_date: int = 0 ) -> Optional[Generator["pyrogram.Dialog", None, None]]: """Iterate through a user's dialogs sequentially. From aa937a704d0bad9fdf2f893b2a85cc1a9a40d9b4 Mon Sep 17 00:00:00 2001 From: YoilyL Date: Mon, 9 Sep 2019 16:56:57 +0300 Subject: [PATCH 0137/1185] fixed memory leak when session.send coroutine is cancelled (#311) added that when session.send coroutine is cancelled (or if any other exception is raised) the result should still be removed from the results list --- pyrogram/session/session.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index b5f690815d..3dbedef43f 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -382,8 +382,8 @@ async def _send(self, data: TLObject, wait_response: bool = True, timeout: float await asyncio.wait_for(self.results[msg_id].event.wait(), timeout) except asyncio.TimeoutError: pass - - result = self.results.pop(msg_id).value + finally: + result = self.results.pop(msg_id).value if result is None: raise TimeoutError From bc7d29237d40dba92c3cbd1254b3d2561b313814 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 25 Sep 2019 18:41:06 +0200 Subject: [PATCH 0138/1185] Small style fix --- pyrogram/client/client.py | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index cb1abcaac8..a0bf5af5b2 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -1881,19 +1881,23 @@ async def worker(session): for session in pool: await session.stop() - async def get_file(self, media_type: int, - dc_id: int, - document_id: int, - access_hash: int, - thumb_size: str, - peer_id: int, - peer_access_hash: int, volume_id: int, - local_id: int, - file_ref: str,file_size: int, - - is_big: bool, - progress: callable, - progress_args: tuple = ()) -> str: + async def get_file( + self, + media_type: int, + dc_id: int, + document_id: int, + access_hash: int, + thumb_size: str, + peer_id: int, + peer_access_hash: int, + volume_id: int, + local_id: int, + file_ref: str, + file_size: int, + is_big: bool, + progress: callable, + progress_args: tuple = () + ) -> str: async with self.media_sessions_lock: session = self.media_sessions.get(dc_id, None) From 353811ebd3afb65d442a807de6e4e7a2a112f47e Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 6 Dec 2019 21:14:15 +0100 Subject: [PATCH 0139/1185] Add missing await --- pyrogram/client/methods/chats/set_chat_photo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/methods/chats/set_chat_photo.py b/pyrogram/client/methods/chats/set_chat_photo.py index df67923e4f..4c91247585 100644 --- a/pyrogram/client/methods/chats/set_chat_photo.py +++ b/pyrogram/client/methods/chats/set_chat_photo.py @@ -59,7 +59,7 @@ async def set_chat_photo( peer = await self.resolve_peer(chat_id) if os.path.exists(photo): - photo = types.InputChatUploadedPhoto(file=self.save_file(photo)) + photo = types.InputChatUploadedPhoto(file=await self.save_file(photo)) else: photo = utils.get_input_media_from_file_id(photo) photo = types.InputChatPhoto(id=photo.id) From 8e9a7a33bd7113f7773669a421c70f8adcfdb61f Mon Sep 17 00:00:00 2001 From: Alisson Lauffer Date: Wed, 20 Nov 2019 02:51:42 -0300 Subject: [PATCH 0140/1185] Add missing awaits --- pyrogram/client/methods/chats/add_chat_members.py | 12 ++++++------ pyrogram/client/methods/chats/create_channel.py | 4 ++-- pyrogram/client/methods/chats/create_group.py | 6 +++--- pyrogram/client/methods/chats/create_supergroup.py | 4 ++-- pyrogram/client/methods/chats/delete_channel.py | 6 +++--- pyrogram/client/methods/chats/delete_supergroup.py | 6 +++--- .../client/methods/chats/set_chat_permissions.py | 2 +- pyrogram/client/methods/messages/send_poll.py | 6 +++--- pyrogram/client/methods/users/get_common_chats.py | 6 +++--- 9 files changed, 26 insertions(+), 26 deletions(-) diff --git a/pyrogram/client/methods/chats/add_chat_members.py b/pyrogram/client/methods/chats/add_chat_members.py index 8dbad1a387..ace35cf854 100644 --- a/pyrogram/client/methods/chats/add_chat_members.py +++ b/pyrogram/client/methods/chats/add_chat_members.py @@ -23,7 +23,7 @@ class AddChatMembers(BaseClient): - def add_chat_members( + async def add_chat_members( self, chat_id: Union[int, str], user_ids: Union[Union[int, str], List[Union[int, str]]], @@ -60,26 +60,26 @@ def add_chat_members( # Change forward_limit (for basic groups only) app.add_chat_members(chat_id, user_id, forward_limit=25) """ - peer = self.resolve_peer(chat_id) + peer = await self.resolve_peer(chat_id) if not isinstance(user_ids, list): user_ids = [user_ids] if isinstance(peer, types.InputPeerChat): for user_id in user_ids: - self.send( + await self.send( functions.messages.AddChatUser( chat_id=peer.chat_id, - user_id=self.resolve_peer(user_id), + user_id=await self.resolve_peer(user_id), fwd_limit=forward_limit ) ) else: - self.send( + await self.send( functions.channels.InviteToChannel( channel=peer, users=[ - self.resolve_peer(user_id) + await self.resolve_peer(user_id) for user_id in user_ids ] ) diff --git a/pyrogram/client/methods/chats/create_channel.py b/pyrogram/client/methods/chats/create_channel.py index 9dde878143..c768ccceb3 100644 --- a/pyrogram/client/methods/chats/create_channel.py +++ b/pyrogram/client/methods/chats/create_channel.py @@ -22,7 +22,7 @@ class CreateChannel(BaseClient): - def create_channel( + async def create_channel( self, title: str, description: str = "" @@ -44,7 +44,7 @@ def create_channel( app.create_channel("Channel Title", "Channel Description") """ - r = self.send( + r = await self.send( functions.channels.CreateChannel( title=title, about=description, diff --git a/pyrogram/client/methods/chats/create_group.py b/pyrogram/client/methods/chats/create_group.py index a9013d8103..c69d8ff8af 100644 --- a/pyrogram/client/methods/chats/create_group.py +++ b/pyrogram/client/methods/chats/create_group.py @@ -24,7 +24,7 @@ class CreateGroup(BaseClient): - def create_group( + async def create_group( self, title: str, users: Union[Union[int, str], List[Union[int, str]]] @@ -55,10 +55,10 @@ def create_group( if not isinstance(users, list): users = [users] - r = self.send( + r = await self.send( functions.messages.CreateChat( title=title, - users=[self.resolve_peer(u) for u in users] + users=[await self.resolve_peer(u) for u in users] ) ) diff --git a/pyrogram/client/methods/chats/create_supergroup.py b/pyrogram/client/methods/chats/create_supergroup.py index 28b3fd1b90..ddeb42bfcd 100644 --- a/pyrogram/client/methods/chats/create_supergroup.py +++ b/pyrogram/client/methods/chats/create_supergroup.py @@ -22,7 +22,7 @@ class CreateSupergroup(BaseClient): - def create_supergroup( + async def create_supergroup( self, title: str, description: str = "" @@ -48,7 +48,7 @@ def create_supergroup( app.create_supergroup("Supergroup Title", "Supergroup Description") """ - r = self.send( + r = await self.send( functions.channels.CreateChannel( title=title, about=description, diff --git a/pyrogram/client/methods/chats/delete_channel.py b/pyrogram/client/methods/chats/delete_channel.py index 74fbea13c0..b306773b5a 100644 --- a/pyrogram/client/methods/chats/delete_channel.py +++ b/pyrogram/client/methods/chats/delete_channel.py @@ -24,7 +24,7 @@ class DeleteChannel(BaseClient): - def delete_channel(self, chat_id: Union[int, str]) -> bool: + async def delete_channel(self, chat_id: Union[int, str]) -> bool: """Delete a channel. Parameters: @@ -39,9 +39,9 @@ def delete_channel(self, chat_id: Union[int, str]) -> bool: app.delete_channel(channel_id) """ - self.send( + await self.send( functions.channels.DeleteChannel( - channel=self.resolve_peer(chat_id) + channel=await self.resolve_peer(chat_id) ) ) diff --git a/pyrogram/client/methods/chats/delete_supergroup.py b/pyrogram/client/methods/chats/delete_supergroup.py index a1eb198d95..3c291424b4 100644 --- a/pyrogram/client/methods/chats/delete_supergroup.py +++ b/pyrogram/client/methods/chats/delete_supergroup.py @@ -24,7 +24,7 @@ class DeleteSupergroup(BaseClient): - def delete_supergroup(self, chat_id: Union[int, str]) -> bool: + async def delete_supergroup(self, chat_id: Union[int, str]) -> bool: """Delete a supergroup. Parameters: @@ -39,9 +39,9 @@ def delete_supergroup(self, chat_id: Union[int, str]) -> bool: app.delete_supergroup(supergroup_id) """ - self.send( + await self.send( functions.channels.DeleteChannel( - channel=self.resolve_peer(chat_id) + channel=await self.resolve_peer(chat_id) ) ) diff --git a/pyrogram/client/methods/chats/set_chat_permissions.py b/pyrogram/client/methods/chats/set_chat_permissions.py index 158cc26960..e1494ac3ea 100644 --- a/pyrogram/client/methods/chats/set_chat_permissions.py +++ b/pyrogram/client/methods/chats/set_chat_permissions.py @@ -107,7 +107,7 @@ async def set_chat_permissions( r = await self.send( functions.messages.EditChatDefaultBannedRights( - peer=self.resolve_peer(chat_id), + peer=await self.resolve_peer(chat_id), banned_rights=types.ChatBannedRights( until_date=0, send_messages=send_messages, diff --git a/pyrogram/client/methods/messages/send_poll.py b/pyrogram/client/methods/messages/send_poll.py index a684dda909..50ba9cc10c 100644 --- a/pyrogram/client/methods/messages/send_poll.py +++ b/pyrogram/client/methods/messages/send_poll.py @@ -24,7 +24,7 @@ class SendPoll(BaseClient): - def send_poll( + async def send_poll( self, chat_id: Union[int, str], question: str, @@ -75,9 +75,9 @@ def send_poll( app.send_poll(chat_id, "Is this a poll question?", ["Yes", "No", "Maybe"]) """ - r = self.send( + r = await self.send( functions.messages.SendMedia( - peer=self.resolve_peer(chat_id), + peer=await self.resolve_peer(chat_id), media=types.InputMediaPoll( poll=types.Poll( id=0, diff --git a/pyrogram/client/methods/users/get_common_chats.py b/pyrogram/client/methods/users/get_common_chats.py index f2c9ac00e8..2207e09fad 100644 --- a/pyrogram/client/methods/users/get_common_chats.py +++ b/pyrogram/client/methods/users/get_common_chats.py @@ -24,7 +24,7 @@ class GetCommonChats(BaseClient): - def get_common_chats(self, user_id: Union[int, str]) -> list: + async def get_common_chats(self, user_id: Union[int, str]) -> list: """Get the common chats you have with a user. Parameters: @@ -46,10 +46,10 @@ def get_common_chats(self, user_id: Union[int, str]) -> list: print(common) """ - peer = self.resolve_peer(user_id) + peer = await self.resolve_peer(user_id) if isinstance(peer, types.InputPeerUser): - r = self.send( + r = await self.send( functions.messages.GetCommonChats( user_id=peer, max_id=0, From 2daa5932c6641f8c3d3fa2445d64a731d7b77fdd Mon Sep 17 00:00:00 2001 From: Shrimadhav U K Date: Mon, 23 Dec 2019 19:44:58 +0530 Subject: [PATCH 0141/1185] Add missing asyncio keywords (#319) * fix missing await * fix empty file reference * one more await, and file reference --- .../client/methods/messages/edit_message_media.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pyrogram/client/methods/messages/edit_message_media.py b/pyrogram/client/methods/messages/edit_message_media.py index 579e3c4473..2042a6751c 100644 --- a/pyrogram/client/methods/messages/edit_message_media.py +++ b/pyrogram/client/methods/messages/edit_message_media.py @@ -108,7 +108,7 @@ async def edit_message_media( peer=await self.resolve_peer(chat_id), media=types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(media.media) or "video/mp4", - thumb=None if media.thumb is None else self.save_file(media.thumb), + thumb=None if media.thumb is None else await self.save_file(media.thumb), file=await self.save_file(media.media), attributes=[ types.DocumentAttributeVideo( @@ -145,7 +145,7 @@ async def edit_message_media( peer=await self.resolve_peer(chat_id), media=types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(media.media) or "audio/mpeg", - thumb=None if media.thumb is None else self.save_file(media.thumb), + thumb=None if media.thumb is None else await self.save_file(media.thumb), file=await self.save_file(media.media), attributes=[ types.DocumentAttributeAudio( @@ -165,7 +165,7 @@ async def edit_message_media( id=types.InputDocument( id=media.document.id, access_hash=media.document.access_hash, - file_reference=b"" + file_reference=media.document.file_reference ) ) elif media.media.startswith("http"): @@ -203,7 +203,7 @@ async def edit_message_media( id=types.InputDocument( id=media.document.id, access_hash=media.document.access_hash, - file_reference=b"" + file_reference=media.document.file_reference ) ) elif media.media.startswith("http"): @@ -219,7 +219,7 @@ async def edit_message_media( peer=await self.resolve_peer(chat_id), media=types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(media.media) or "application/zip", - thumb=None if media.thumb is None else self.save_file(media.thumb), + thumb=None if media.thumb is None else await self.save_file(media.thumb), file=await self.save_file(media.media), attributes=[ types.DocumentAttributeFilename( @@ -234,7 +234,7 @@ async def edit_message_media( id=types.InputDocument( id=media.document.id, access_hash=media.document.access_hash, - file_reference=b"" + file_reference=media.document.file_reference ) ) elif media.media.startswith("http"): From e316d18bf4bfcae7113588abcc5e021577a39f12 Mon Sep 17 00:00:00 2001 From: Yusuf_M_Thon_iD <32301831+Sunda001@users.noreply.github.com> Date: Sat, 1 Feb 2020 20:10:46 +0700 Subject: [PATCH 0142/1185] Add missing file_ref in set_chat_photo (#343) --- pyrogram/client/methods/chats/set_chat_photo.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/pyrogram/client/methods/chats/set_chat_photo.py b/pyrogram/client/methods/chats/set_chat_photo.py index 4c91247585..689cc15f79 100644 --- a/pyrogram/client/methods/chats/set_chat_photo.py +++ b/pyrogram/client/methods/chats/set_chat_photo.py @@ -27,7 +27,8 @@ class SetChatPhoto(BaseClient): async def set_chat_photo( self, chat_id: Union[int, str], - photo: str + photo: str, + file_ref: str = None, ) -> bool: """Set a new profile photo for the chat. @@ -40,6 +41,10 @@ async def set_chat_photo( photo (``str``): New chat photo. You can pass a :obj:`Photo` file_id or a file path to upload a new photo from your local machine. + + file_ref (``str``, *optional*): + A valid file reference obtained by a recently fetched media message. + To be used in combination with a file id in case a file reference is needed. Returns: ``bool``: True on success. @@ -54,14 +59,14 @@ async def set_chat_photo( app.set_chat_photo(chat_id, "photo.jpg") # Set chat photo using an exiting Photo file_id - app.set_chat_photo(chat_id, photo.file_id) + app.set_chat_photo(chat_id, photo.file_id, photo.file_ref) """ peer = await self.resolve_peer(chat_id) if os.path.exists(photo): photo = types.InputChatUploadedPhoto(file=await self.save_file(photo)) else: - photo = utils.get_input_media_from_file_id(photo) + photo = utils.get_input_media_from_file_id(photo, file_ref, 2) photo = types.InputChatPhoto(id=photo.id) if isinstance(peer, types.InputPeerChat): From df5de3e5836e77792aca22509d8f94bd041ae9b5 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 4 Feb 2020 17:03:33 +0100 Subject: [PATCH 0143/1185] Revert "Add missing file_ref in set_chat_photo (#343)" (#366) This reverts commit e316d18bf4bfcae7113588abcc5e021577a39f12. --- pyrogram/client/methods/chats/set_chat_photo.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/pyrogram/client/methods/chats/set_chat_photo.py b/pyrogram/client/methods/chats/set_chat_photo.py index 689cc15f79..4c91247585 100644 --- a/pyrogram/client/methods/chats/set_chat_photo.py +++ b/pyrogram/client/methods/chats/set_chat_photo.py @@ -27,8 +27,7 @@ class SetChatPhoto(BaseClient): async def set_chat_photo( self, chat_id: Union[int, str], - photo: str, - file_ref: str = None, + photo: str ) -> bool: """Set a new profile photo for the chat. @@ -41,10 +40,6 @@ async def set_chat_photo( photo (``str``): New chat photo. You can pass a :obj:`Photo` file_id or a file path to upload a new photo from your local machine. - - file_ref (``str``, *optional*): - A valid file reference obtained by a recently fetched media message. - To be used in combination with a file id in case a file reference is needed. Returns: ``bool``: True on success. @@ -59,14 +54,14 @@ async def set_chat_photo( app.set_chat_photo(chat_id, "photo.jpg") # Set chat photo using an exiting Photo file_id - app.set_chat_photo(chat_id, photo.file_id, photo.file_ref) + app.set_chat_photo(chat_id, photo.file_id) """ peer = await self.resolve_peer(chat_id) if os.path.exists(photo): photo = types.InputChatUploadedPhoto(file=await self.save_file(photo)) else: - photo = utils.get_input_media_from_file_id(photo, file_ref, 2) + photo = utils.get_input_media_from_file_id(photo) photo = types.InputChatPhoto(id=photo.id) if isinstance(peer, types.InputPeerChat): From 73d9af51efe10e86dc35f579d4ec79eac99f3ae2 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 20 Feb 2020 13:54:51 +0100 Subject: [PATCH 0144/1185] Don't use the "recent" filter when passing a query argument --- pyrogram/client/methods/chats/iter_chat_members.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyrogram/client/methods/chats/iter_chat_members.py b/pyrogram/client/methods/chats/iter_chat_members.py index ba621d3ec6..aaa7ab06a2 100644 --- a/pyrogram/client/methods/chats/iter_chat_members.py +++ b/pyrogram/client/methods/chats/iter_chat_members.py @@ -101,7 +101,9 @@ def iter_chat_members( filter = ( Filters.RECENT - if self.get_chat_members_count(chat_id) <= 10000 and filter == Filters.ALL + if (not query + and filter == Filters.ALL + and self.get_chat_members_count(chat_id) <= 10000) else filter ) From 7be86f8ea38a9e9caf5124ebc5632a9eaf38a886 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 20 Feb 2020 20:07:00 +0100 Subject: [PATCH 0145/1185] Update development version --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 8739fe118f..5e56f41d43 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "0.16.0" +__version__ = "0.17.0-dev" __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)" __copyright__ = "Copyright (C) 2017-2020 Dan " From 28cee8d01f8f0e97de4cf1ddb86029ea79c5ced3 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 20 Feb 2020 20:41:08 +0100 Subject: [PATCH 0146/1185] Do not ever use "recent" filtering automatically That code existed to improve members fetching performance for channels/supergroups with less than 10k+1 members, but it was causing troubles when fetching members based on a query string and for channels with less than 10k+1 subscribers --- pyrogram/client/methods/chats/iter_chat_members.py | 8 -------- 1 file changed, 8 deletions(-) diff --git a/pyrogram/client/methods/chats/iter_chat_members.py b/pyrogram/client/methods/chats/iter_chat_members.py index aaa7ab06a2..0f71c9ad01 100644 --- a/pyrogram/client/methods/chats/iter_chat_members.py +++ b/pyrogram/client/methods/chats/iter_chat_members.py @@ -99,14 +99,6 @@ def iter_chat_members( limit = min(200, total) resolved_chat_id = self.resolve_peer(chat_id) - filter = ( - Filters.RECENT - if (not query - and filter == Filters.ALL - and self.get_chat_members_count(chat_id) <= 10000) - else filter - ) - if filter not in QUERYABLE_FILTERS: queries = [""] From f867c660832765811a889bb4c3a4fa749c6ac4ec Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 26 Feb 2020 23:31:01 +0100 Subject: [PATCH 0147/1185] Fix stop_transmission example --- 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 ecb65da0ef..d212e5c9ee 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -1101,12 +1101,12 @@ def stop_transmission(self): # Example to stop transmission once the upload progress reaches 50% # Useless in practice, but shows how to stop on command - def progress(client, current, total): + def progress(current, total, client): if (current * 100 / total) > 50: client.stop_transmission() with app: - app.send_document("me", "files.zip", progress=progress) + app.send_document("me", "files.zip", progress=progress, progress_args=(app,)) """ raise Client.StopTransmission From 4a3b1a0e37809b9d63a95837bb035c011894041d Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 21 Mar 2020 15:30:12 +0100 Subject: [PATCH 0148/1185] Update .gitignore --- .gitignore | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index ce3407dd4a..eb4fcccc55 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ -# User's personal information +# Development *.session config.ini +main.py +unknown_errors.txt # Pyrogram generated code pyrogram/errors/exceptions/ From 1996fb1481d3de784139afb68241ec26fc5b27ef Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 21 Mar 2020 15:43:32 +0100 Subject: [PATCH 0149/1185] Update Copyright --- compiler/api/compiler.py | 26 ++++++------- compiler/docs/compiler.py | 26 ++++++------- compiler/error/compiler.py | 26 ++++++------- docs/releases.py | 26 ++++++------- docs/sitemap.py | 26 ++++++------- docs/source/conf.py | 26 ++++++------- examples/bot_keyboards.py | 18 --------- examples/callback_queries.py | 18 --------- examples/echobot.py | 18 --------- examples/get_chat_members.py | 18 --------- examples/get_dialogs.py | 18 --------- examples/get_history.py | 18 --------- examples/hello_world.py | 18 --------- examples/inline_queries.py | 18 --------- examples/raw_updates.py | 18 --------- examples/use_inline_bots.py | 18 --------- examples/welcomebot.py | 18 --------- pyrogram/__init__.py | 26 ++++++------- pyrogram/api/__init__.py | 26 ++++++------- pyrogram/api/core/__init__.py | 26 ++++++------- pyrogram/api/core/future_salt.py | 26 ++++++------- pyrogram/api/core/future_salts.py | 26 ++++++------- pyrogram/api/core/gzip_packed.py | 26 ++++++------- pyrogram/api/core/list.py | 26 ++++++------- pyrogram/api/core/message.py | 26 ++++++------- pyrogram/api/core/msg_container.py | 26 ++++++------- pyrogram/api/core/primitives/__init__.py | 26 ++++++------- pyrogram/api/core/primitives/bool.py | 26 ++++++------- pyrogram/api/core/primitives/bytes.py | 26 ++++++------- pyrogram/api/core/primitives/double.py | 26 ++++++------- pyrogram/api/core/primitives/int.py | 26 ++++++------- pyrogram/api/core/primitives/string.py | 26 ++++++------- pyrogram/api/core/primitives/vector.py | 26 ++++++------- pyrogram/api/core/tl_object.py | 26 ++++++------- pyrogram/client/__init__.py | 26 ++++++------- pyrogram/client/client.py | 26 ++++++------- pyrogram/client/ext/__init__.py | 26 ++++++------- pyrogram/client/ext/base_client.py | 26 ++++++------- pyrogram/client/ext/dispatcher.py | 26 ++++++------- pyrogram/client/ext/emoji.py | 27 +++++++------ pyrogram/client/ext/file_data.py | 27 +++++++------ pyrogram/client/ext/syncer.py | 26 ++++++------- pyrogram/client/ext/utils.py | 39 +++++++++---------- pyrogram/client/filters/__init__.py | 26 ++++++------- pyrogram/client/filters/filter.py | 27 +++++++------ pyrogram/client/filters/filters.py | 26 ++++++------- pyrogram/client/handlers/__init__.py | 26 ++++++------- .../client/handlers/callback_query_handler.py | 26 ++++++------- .../handlers/deleted_messages_handler.py | 26 ++++++------- .../client/handlers/disconnect_handler.py | 26 ++++++------- pyrogram/client/handlers/handler.py | 27 +++++++------ .../client/handlers/inline_query_handler.py | 26 ++++++------- pyrogram/client/handlers/message_handler.py | 26 ++++++------- pyrogram/client/handlers/poll_handler.py | 26 ++++++------- .../client/handlers/raw_update_handler.py | 26 ++++++------- .../client/handlers/user_status_handler.py | 26 ++++++------- pyrogram/client/methods/__init__.py | 26 ++++++------- pyrogram/client/methods/bots/__init__.py | 26 ++++++------- .../methods/bots/answer_callback_query.py | 26 ++++++------- .../methods/bots/answer_inline_query.py | 26 ++++++------- .../methods/bots/get_game_high_scores.py | 26 ++++++------- .../methods/bots/get_inline_bot_results.py | 26 ++++++------- .../methods/bots/request_callback_answer.py | 26 ++++++------- pyrogram/client/methods/bots/send_game.py | 26 ++++++------- .../methods/bots/send_inline_bot_result.py | 26 ++++++------- .../client/methods/bots/set_game_score.py | 26 ++++++------- pyrogram/client/methods/chats/__init__.py | 26 ++++++------- .../client/methods/chats/add_chat_members.py | 26 ++++++------- .../client/methods/chats/archive_chats.py | 26 ++++++------- .../client/methods/chats/create_channel.py | 26 ++++++------- pyrogram/client/methods/chats/create_group.py | 26 ++++++------- .../client/methods/chats/create_supergroup.py | 26 ++++++------- .../client/methods/chats/delete_channel.py | 26 ++++++------- .../client/methods/chats/delete_chat_photo.py | 26 ++++++------- .../client/methods/chats/delete_supergroup.py | 26 ++++++------- .../methods/chats/delete_user_history.py | 26 ++++++------- .../methods/chats/export_chat_invite_link.py | 26 ++++++------- pyrogram/client/methods/chats/get_chat.py | 26 ++++++------- .../client/methods/chats/get_chat_member.py | 26 ++++++------- .../client/methods/chats/get_chat_members.py | 26 ++++++------- .../methods/chats/get_chat_members_count.py | 26 ++++++------- pyrogram/client/methods/chats/get_dialogs.py | 26 ++++++------- .../client/methods/chats/get_dialogs_count.py | 26 ++++++------- .../client/methods/chats/get_nearby_chats.py | 26 ++++++------- .../client/methods/chats/iter_chat_members.py | 26 ++++++------- pyrogram/client/methods/chats/iter_dialogs.py | 26 ++++++------- pyrogram/client/methods/chats/join_chat.py | 26 ++++++------- .../client/methods/chats/kick_chat_member.py | 26 ++++++------- pyrogram/client/methods/chats/leave_chat.py | 26 ++++++------- .../client/methods/chats/pin_chat_message.py | 26 ++++++------- .../methods/chats/promote_chat_member.py | 26 ++++++------- .../methods/chats/restrict_chat_member.py | 26 ++++++------- .../methods/chats/set_administrator_title.py | 26 ++++++------- .../methods/chats/set_chat_description.py | 26 ++++++------- .../methods/chats/set_chat_permissions.py | 26 ++++++------- .../client/methods/chats/set_chat_photo.py | 26 ++++++------- .../client/methods/chats/set_chat_title.py | 26 ++++++------- .../client/methods/chats/set_slow_mode.py | 26 ++++++------- .../client/methods/chats/unarchive_chats.py | 26 ++++++------- .../client/methods/chats/unban_chat_member.py | 26 ++++++------- .../methods/chats/unpin_chat_message.py | 26 ++++++------- .../methods/chats/update_chat_username.py | 26 ++++++------- pyrogram/client/methods/contacts/__init__.py | 26 ++++++------- .../client/methods/contacts/add_contacts.py | 26 ++++++------- .../methods/contacts/delete_contacts.py | 26 ++++++------- .../client/methods/contacts/get_contacts.py | 26 ++++++------- .../methods/contacts/get_contacts_count.py | 26 ++++++------- .../client/methods/decorators/__init__.py | 26 ++++++------- .../methods/decorators/on_callback_query.py | 26 ++++++------- .../methods/decorators/on_deleted_messages.py | 26 ++++++------- .../methods/decorators/on_disconnect.py | 26 ++++++------- .../methods/decorators/on_inline_query.py | 26 ++++++------- .../client/methods/decorators/on_message.py | 26 ++++++------- pyrogram/client/methods/decorators/on_poll.py | 26 ++++++------- .../methods/decorators/on_raw_update.py | 26 ++++++------- .../methods/decorators/on_user_status.py | 26 ++++++------- pyrogram/client/methods/messages/__init__.py | 26 ++++++------- .../methods/messages/delete_messages.py | 26 ++++++------- .../client/methods/messages/download_media.py | 26 ++++++------- .../methods/messages/edit_inline_caption.py | 26 ++++++------- .../methods/messages/edit_inline_media.py | 26 ++++++------- .../messages/edit_inline_reply_markup.py | 26 ++++++------- .../methods/messages/edit_inline_text.py | 26 ++++++------- .../methods/messages/edit_message_caption.py | 26 ++++++------- .../methods/messages/edit_message_media.py | 26 ++++++------- .../messages/edit_message_reply_markup.py | 26 ++++++------- .../methods/messages/edit_message_text.py | 26 ++++++------- .../methods/messages/forward_messages.py | 26 ++++++------- .../client/methods/messages/get_history.py | 26 ++++++------- .../methods/messages/get_history_count.py | 26 ++++++------- .../client/methods/messages/get_messages.py | 26 ++++++------- .../client/methods/messages/iter_history.py | 26 ++++++------- .../client/methods/messages/read_history.py | 26 ++++++------- .../client/methods/messages/retract_vote.py | 26 ++++++------- .../client/methods/messages/send_animation.py | 26 ++++++------- .../client/methods/messages/send_audio.py | 26 ++++++------- .../methods/messages/send_cached_media.py | 26 ++++++------- .../methods/messages/send_chat_action.py | 26 ++++++------- .../client/methods/messages/send_contact.py | 26 ++++++------- .../client/methods/messages/send_document.py | 26 ++++++------- .../client/methods/messages/send_location.py | 26 ++++++------- .../methods/messages/send_media_group.py | 26 ++++++------- .../client/methods/messages/send_message.py | 26 ++++++------- .../client/methods/messages/send_photo.py | 26 ++++++------- pyrogram/client/methods/messages/send_poll.py | 26 ++++++------- .../client/methods/messages/send_sticker.py | 26 ++++++------- .../client/methods/messages/send_venue.py | 26 ++++++------- .../client/methods/messages/send_video.py | 26 ++++++------- .../methods/messages/send_video_note.py | 26 ++++++------- .../client/methods/messages/send_voice.py | 26 ++++++------- pyrogram/client/methods/messages/stop_poll.py | 26 ++++++------- pyrogram/client/methods/messages/vote_poll.py | 26 ++++++------- pyrogram/client/methods/password/__init__.py | 26 ++++++------- .../methods/password/change_cloud_password.py | 26 ++++++------- .../methods/password/enable_cloud_password.py | 26 ++++++------- .../methods/password/remove_cloud_password.py | 26 ++++++------- pyrogram/client/methods/password/utils.py | 26 ++++++------- pyrogram/client/methods/users/__init__.py | 26 ++++++------- pyrogram/client/methods/users/block_user.py | 26 ++++++------- .../methods/users/delete_profile_photos.py | 26 ++++++------- .../client/methods/users/get_common_chats.py | 26 ++++++------- pyrogram/client/methods/users/get_me.py | 26 ++++++------- .../methods/users/get_profile_photos.py | 26 ++++++------- .../methods/users/get_profile_photos_count.py | 26 ++++++------- pyrogram/client/methods/users/get_users.py | 26 ++++++------- .../methods/users/iter_profile_photos.py | 26 ++++++------- .../client/methods/users/set_profile_photo.py | 26 ++++++------- pyrogram/client/methods/users/unblock_user.py | 26 ++++++------- .../client/methods/users/update_profile.py | 26 ++++++------- .../client/methods/users/update_username.py | 26 ++++++------- pyrogram/client/parser/__init__.py | 26 ++++++------- pyrogram/client/parser/html.py | 26 ++++++------- pyrogram/client/parser/markdown.py | 26 ++++++------- pyrogram/client/parser/parser.py | 26 ++++++------- pyrogram/client/parser/utils.py | 26 ++++++------- pyrogram/client/storage/__init__.py | 26 ++++++------- pyrogram/client/storage/file_storage.py | 26 ++++++------- pyrogram/client/storage/memory_storage.py | 26 ++++++------- pyrogram/client/storage/schema.sql | 20 ++++++++++ pyrogram/client/storage/sqlite_storage.py | 26 ++++++------- pyrogram/client/storage/storage.py | 26 ++++++------- pyrogram/client/types/__init__.py | 26 ++++++------- .../client/types/authorization/__init__.py | 26 ++++++------- .../client/types/authorization/sent_code.py | 26 ++++++------- .../types/authorization/terms_of_service.py | 26 ++++++------- .../types/bots_and_keyboards/__init__.py | 26 ++++++------- .../types/bots_and_keyboards/callback_game.py | 26 ++++++------- .../bots_and_keyboards/callback_query.py | 26 ++++++------- .../types/bots_and_keyboards/force_reply.py | 26 ++++++------- .../bots_and_keyboards/game_high_score.py | 26 ++++++------- .../inline_keyboard_button.py | 26 ++++++------- .../inline_keyboard_markup.py | 26 ++++++------- .../bots_and_keyboards/keyboard_button.py | 26 ++++++------- .../reply_keyboard_markup.py | 26 ++++++------- .../reply_keyboard_remove.py | 26 ++++++------- pyrogram/client/types/inline_mode/__init__.py | 26 ++++++------- .../client/types/inline_mode/inline_query.py | 26 ++++++------- .../types/inline_mode/inline_query_result.py | 26 ++++++------- .../inline_query_result_animation.py | 26 ++++++------- .../inline_query_result_article.py | 26 ++++++------- .../inline_mode/inline_query_result_photo.py | 26 ++++++------- pyrogram/client/types/input_media/__init__.py | 26 ++++++------- .../client/types/input_media/input_media.py | 26 ++++++------- .../input_media/input_media_animation.py | 26 ++++++------- .../types/input_media/input_media_audio.py | 26 ++++++------- .../types/input_media/input_media_document.py | 26 ++++++------- .../types/input_media/input_media_photo.py | 26 ++++++------- .../types/input_media/input_media_video.py | 26 ++++++------- .../types/input_media/input_phone_contact.py | 26 ++++++------- .../types/input_message_content/__init__.py | 26 ++++++------- .../input_message_content.py | 26 ++++++------- .../input_text_message_content.py | 26 ++++++------- pyrogram/client/types/list.py | 26 ++++++------- .../types/messages_and_media/__init__.py | 26 ++++++------- .../types/messages_and_media/animation.py | 26 ++++++------- .../client/types/messages_and_media/audio.py | 26 ++++++------- .../types/messages_and_media/contact.py | 26 ++++++------- .../types/messages_and_media/document.py | 26 ++++++------- .../client/types/messages_and_media/game.py | 26 ++++++------- .../types/messages_and_media/location.py | 26 ++++++------- .../types/messages_and_media/message.py | 26 ++++++------- .../messages_and_media/message_entity.py | 26 ++++++------- .../client/types/messages_and_media/photo.py | 26 ++++++------- .../client/types/messages_and_media/poll.py | 26 ++++++------- .../types/messages_and_media/poll_option.py | 26 ++++++------- .../types/messages_and_media/sticker.py | 26 ++++++------- .../messages_and_media/stripped_thumbnail.py | 26 ++++++------- .../types/messages_and_media/thumbnail.py | 26 ++++++------- .../client/types/messages_and_media/venue.py | 26 ++++++------- .../client/types/messages_and_media/video.py | 26 ++++++------- .../types/messages_and_media/video_note.py | 26 ++++++------- .../client/types/messages_and_media/voice.py | 26 ++++++------- .../types/messages_and_media/webpage.py | 26 ++++++------- pyrogram/client/types/object.py | 26 ++++++------- pyrogram/client/types/update.py | 27 +++++++------ .../client/types/user_and_chats/__init__.py | 26 ++++++------- pyrogram/client/types/user_and_chats/chat.py | 26 ++++++------- .../types/user_and_chats/chat_member.py | 26 ++++++------- .../types/user_and_chats/chat_permissions.py | 26 ++++++------- .../client/types/user_and_chats/chat_photo.py | 26 ++++++------- .../types/user_and_chats/chat_preview.py | 26 ++++++------- .../client/types/user_and_chats/dialog.py | 26 ++++++------- .../types/user_and_chats/restriction.py | 26 ++++++------- pyrogram/client/types/user_and_chats/user.py | 26 ++++++------- pyrogram/connection/__init__.py | 26 ++++++------- pyrogram/connection/connection.py | 26 ++++++------- pyrogram/connection/transport/__init__.py | 26 ++++++------- pyrogram/connection/transport/tcp/__init__.py | 26 ++++++------- pyrogram/connection/transport/tcp/tcp.py | 26 ++++++------- .../connection/transport/tcp/tcp_abridged.py | 26 ++++++------- .../transport/tcp/tcp_abridged_o.py | 26 ++++++------- pyrogram/connection/transport/tcp/tcp_full.py | 26 ++++++------- .../transport/tcp/tcp_intermediate.py | 26 ++++++------- .../transport/tcp/tcp_intermediate_o.py | 26 ++++++------- pyrogram/crypto/__init__.py | 26 ++++++------- pyrogram/crypto/aes.py | 26 ++++++------- pyrogram/crypto/kdf.py | 26 ++++++------- pyrogram/crypto/prime.py | 26 ++++++------- pyrogram/crypto/rsa.py | 26 ++++++------- pyrogram/errors/__init__.py | 26 ++++++------- pyrogram/errors/rpc_error.py | 26 ++++++------- pyrogram/session/__init__.py | 26 ++++++------- pyrogram/session/auth.py | 26 ++++++------- pyrogram/session/internals/__init__.py | 26 ++++++------- pyrogram/session/internals/data_center.py | 27 +++++++------ pyrogram/session/internals/msg_factory.py | 26 ++++++------- pyrogram/session/internals/msg_id.py | 26 ++++++------- pyrogram/session/internals/seq_no.py | 26 ++++++------- pyrogram/session/session.py | 26 ++++++------- setup.py | 26 ++++++------- 270 files changed, 3380 insertions(+), 3565 deletions(-) diff --git a/compiler/api/compiler.py b/compiler/api/compiler.py index 7c6d8ffd61..f6cb57423b 100644 --- a/compiler/api/compiler.py +++ b/compiler/api/compiler.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import os import re diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index db3fec546f..346b47c739 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import ast import os diff --git a/compiler/error/compiler.py b/compiler/error/compiler.py index 3730ec26a3..222fdfcabc 100644 --- a/compiler/error/compiler.py +++ b/compiler/error/compiler.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import csv import os diff --git a/docs/releases.py b/docs/releases.py index 164c7d2f4d..9b7388f994 100644 --- a/docs/releases.py +++ b/docs/releases.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import shutil from datetime import datetime diff --git a/docs/sitemap.py b/docs/sitemap.py index 9f4c7758ac..4b08f218a5 100644 --- a/docs/sitemap.py +++ b/docs/sitemap.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import datetime import os diff --git a/docs/source/conf.py b/docs/source/conf.py index e60d4822fe..40caccb0d3 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import os import sys diff --git a/examples/bot_keyboards.py b/examples/bot_keyboards.py index 9cdbb16b6e..e1ff1e7e3f 100644 --- a/examples/bot_keyboards.py +++ b/examples/bot_keyboards.py @@ -1,21 +1,3 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan -# -# 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 . - """This example will show you how to send normal and inline keyboards (as bot). You must log-in as a regular bot in order to send keyboards (use the token from @BotFather). diff --git a/examples/callback_queries.py b/examples/callback_queries.py index 77cf5b3416..f4a87b0041 100644 --- a/examples/callback_queries.py +++ b/examples/callback_queries.py @@ -1,21 +1,3 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan -# -# 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 . - """This example shows how to handle callback queries, i.e.: queries coming from inline button presses. It uses the @on_callback_query decorator to register a CallbackQueryHandler. diff --git a/examples/echobot.py b/examples/echobot.py index b8386e1561..c60ae2917b 100644 --- a/examples/echobot.py +++ b/examples/echobot.py @@ -1,21 +1,3 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan -# -# 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 . - """This simple echo bot replies to every private text message. It uses the @on_message decorator to register a MessageHandler and applies two filters on it: diff --git a/examples/get_chat_members.py b/examples/get_chat_members.py index 3eb7d98bfa..468ac7dec5 100644 --- a/examples/get_chat_members.py +++ b/examples/get_chat_members.py @@ -1,21 +1,3 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan -# -# 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 . - """This example shows how to get all the members of a chat.""" from pyrogram import Client diff --git a/examples/get_dialogs.py b/examples/get_dialogs.py index 2efdade209..92da8834c9 100644 --- a/examples/get_dialogs.py +++ b/examples/get_dialogs.py @@ -1,21 +1,3 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan -# -# 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 . - """This example shows how to get the full dialogs list (as user).""" from pyrogram import Client diff --git a/examples/get_history.py b/examples/get_history.py index b94b2c8b9e..e8bb14e31d 100644 --- a/examples/get_history.py +++ b/examples/get_history.py @@ -1,21 +1,3 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan -# -# 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 . - """This example shows how to get the full message history of a chat, starting from the latest message""" from pyrogram import Client diff --git a/examples/hello_world.py b/examples/hello_world.py index 925d354277..19d0ffe76f 100644 --- a/examples/hello_world.py +++ b/examples/hello_world.py @@ -1,21 +1,3 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan -# -# 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 . - """This example demonstrates a basic API usage""" from pyrogram import Client diff --git a/examples/inline_queries.py b/examples/inline_queries.py index 84c1357e74..d86d90d5c7 100644 --- a/examples/inline_queries.py +++ b/examples/inline_queries.py @@ -1,21 +1,3 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan -# -# 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 . - """This example shows how to handle inline queries. Two results are generated when users invoke the bot inline mode, e.g.: @pyrogrambot hi. It uses the @on_inline_query decorator to register an InlineQueryHandler. diff --git a/examples/raw_updates.py b/examples/raw_updates.py index 26c9254543..27d87eb37b 100644 --- a/examples/raw_updates.py +++ b/examples/raw_updates.py @@ -1,21 +1,3 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan -# -# 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 . - """This example shows how to handle raw updates""" from pyrogram import Client diff --git a/examples/use_inline_bots.py b/examples/use_inline_bots.py index 041ad5cf3c..5681df8778 100644 --- a/examples/use_inline_bots.py +++ b/examples/use_inline_bots.py @@ -1,21 +1,3 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan -# -# 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 . - """This example shows how to query an inline bot (as user)""" from pyrogram import Client diff --git a/examples/welcomebot.py b/examples/welcomebot.py index 9252ad85f6..35f72afff8 100644 --- a/examples/welcomebot.py +++ b/examples/welcomebot.py @@ -1,21 +1,3 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan -# -# 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 . - """This is the Welcome Bot in @PyrogramChat. It uses the Emoji module to easily add emojis in your text messages and Filters diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 5e56f41d43..60db23254e 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . __version__ = "0.17.0-dev" __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)" diff --git a/pyrogram/api/__init__.py b/pyrogram/api/__init__.py index 2d3a54ca8f..da5b075e7b 100644 --- a/pyrogram/api/__init__.py +++ b/pyrogram/api/__init__.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from importlib import import_module diff --git a/pyrogram/api/core/__init__.py b/pyrogram/api/core/__init__.py index 65af114d22..22484f6fb2 100644 --- a/pyrogram/api/core/__init__.py +++ b/pyrogram/api/core/__init__.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from .future_salt import FutureSalt from .future_salts import FutureSalts diff --git a/pyrogram/api/core/future_salt.py b/pyrogram/api/core/future_salt.py index a175244eb8..cc9f7bc0fa 100644 --- a/pyrogram/api/core/future_salt.py +++ b/pyrogram/api/core/future_salt.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from io import BytesIO diff --git a/pyrogram/api/core/future_salts.py b/pyrogram/api/core/future_salts.py index 80cd775e43..f22b464301 100644 --- a/pyrogram/api/core/future_salts.py +++ b/pyrogram/api/core/future_salts.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from io import BytesIO diff --git a/pyrogram/api/core/gzip_packed.py b/pyrogram/api/core/gzip_packed.py index 693c497456..1920c67de7 100644 --- a/pyrogram/api/core/gzip_packed.py +++ b/pyrogram/api/core/gzip_packed.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from gzip import compress, decompress from io import BytesIO diff --git a/pyrogram/api/core/list.py b/pyrogram/api/core/list.py index bf8670d007..0e083f17a6 100644 --- a/pyrogram/api/core/list.py +++ b/pyrogram/api/core/list.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from .tl_object import TLObject diff --git a/pyrogram/api/core/message.py b/pyrogram/api/core/message.py index 23b1e1c4d8..787aa3785d 100644 --- a/pyrogram/api/core/message.py +++ b/pyrogram/api/core/message.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from io import BytesIO diff --git a/pyrogram/api/core/msg_container.py b/pyrogram/api/core/msg_container.py index 06e412cbfb..dc6f755d74 100644 --- a/pyrogram/api/core/msg_container.py +++ b/pyrogram/api/core/msg_container.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from io import BytesIO diff --git a/pyrogram/api/core/primitives/__init__.py b/pyrogram/api/core/primitives/__init__.py index 6621102b0a..f7b1c89ec8 100644 --- a/pyrogram/api/core/primitives/__init__.py +++ b/pyrogram/api/core/primitives/__init__.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from .bool import Bool, BoolFalse, BoolTrue from .bytes import Bytes diff --git a/pyrogram/api/core/primitives/bool.py b/pyrogram/api/core/primitives/bool.py index d62e3fb98d..966372255f 100644 --- a/pyrogram/api/core/primitives/bool.py +++ b/pyrogram/api/core/primitives/bool.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from io import BytesIO diff --git a/pyrogram/api/core/primitives/bytes.py b/pyrogram/api/core/primitives/bytes.py index 429eb4eb39..298ea5440a 100644 --- a/pyrogram/api/core/primitives/bytes.py +++ b/pyrogram/api/core/primitives/bytes.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from io import BytesIO diff --git a/pyrogram/api/core/primitives/double.py b/pyrogram/api/core/primitives/double.py index 4d7261aa92..42cf0031ee 100644 --- a/pyrogram/api/core/primitives/double.py +++ b/pyrogram/api/core/primitives/double.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from io import BytesIO from struct import unpack, pack diff --git a/pyrogram/api/core/primitives/int.py b/pyrogram/api/core/primitives/int.py index a71cd5b209..bbaf7f2fcf 100644 --- a/pyrogram/api/core/primitives/int.py +++ b/pyrogram/api/core/primitives/int.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from io import BytesIO diff --git a/pyrogram/api/core/primitives/string.py b/pyrogram/api/core/primitives/string.py index 4f25d1047a..a0995c5b9a 100644 --- a/pyrogram/api/core/primitives/string.py +++ b/pyrogram/api/core/primitives/string.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from io import BytesIO diff --git a/pyrogram/api/core/primitives/vector.py b/pyrogram/api/core/primitives/vector.py index b7b95f09e8..2c60f5766c 100644 --- a/pyrogram/api/core/primitives/vector.py +++ b/pyrogram/api/core/primitives/vector.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from io import BytesIO diff --git a/pyrogram/api/core/tl_object.py b/pyrogram/api/core/tl_object.py index 94c0a47fa8..d9d5722f1b 100644 --- a/pyrogram/api/core/tl_object.py +++ b/pyrogram/api/core/tl_object.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from collections import OrderedDict from io import BytesIO diff --git a/pyrogram/client/__init__.py b/pyrogram/client/__init__.py index 6285eed1f1..d0b82f9184 100644 --- a/pyrogram/client/__init__.py +++ b/pyrogram/client/__init__.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from .client import Client from .ext import BaseClient, Emoji diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index d212e5c9ee..42a3f3304d 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import logging import math diff --git a/pyrogram/client/ext/__init__.py b/pyrogram/client/ext/__init__.py index c00a925f13..8b44ca5805 100644 --- a/pyrogram/client/ext/__init__.py +++ b/pyrogram/client/ext/__init__.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from .base_client import BaseClient from .dispatcher import Dispatcher diff --git a/pyrogram/client/ext/base_client.py b/pyrogram/client/ext/base_client.py index c1e85e96bb..750dc3fc19 100644 --- a/pyrogram/client/ext/base_client.py +++ b/pyrogram/client/ext/base_client.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import os import platform diff --git a/pyrogram/client/ext/dispatcher.py b/pyrogram/client/ext/dispatcher.py index 2026365348..20be359eef 100644 --- a/pyrogram/client/ext/dispatcher.py +++ b/pyrogram/client/ext/dispatcher.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import logging import threading diff --git a/pyrogram/client/ext/emoji.py b/pyrogram/client/ext/emoji.py index 7845569888..97bfc52961 100644 --- a/pyrogram/client/ext/emoji.py +++ b/pyrogram/client/ext/emoji.py @@ -1,21 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . - +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . class Emoji: HELMET_WITH_WHITE_CROSS_TYPE_1_2 = "\u26d1\U0001f3fb" diff --git a/pyrogram/client/ext/file_data.py b/pyrogram/client/ext/file_data.py index 5839e68b59..ea9de6e1ba 100644 --- a/pyrogram/client/ext/file_data.py +++ b/pyrogram/client/ext/file_data.py @@ -1,21 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . - +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . class FileData: def __init__( diff --git a/pyrogram/client/ext/syncer.py b/pyrogram/client/ext/syncer.py index 1011596b58..bfe99c9814 100644 --- a/pyrogram/client/ext/syncer.py +++ b/pyrogram/client/ext/syncer.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import logging import time diff --git a/pyrogram/client/ext/utils.py b/pyrogram/client/ext/utils.py index 39e5fd0f91..b46a8038fb 100644 --- a/pyrogram/client/ext/utils.py +++ b/pyrogram/client/ext/utils.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import base64 import struct @@ -31,13 +31,12 @@ def decode_file_id(s: str) -> bytes: s = base64.urlsafe_b64decode(s + "=" * (-len(s) % 4)) r = b"" - try: - assert s[-1] == 2 - skip = 1 - except AssertionError: - assert s[-2] == 22 - assert s[-1] == 4 - skip = 2 + major = s[-1] + minor = s[-2] if major != 2 else 0 + + assert minor in (0, 22, 24) + + skip = 2 if minor else 1 i = 0 diff --git a/pyrogram/client/filters/__init__.py b/pyrogram/client/filters/__init__.py index 37a9e3c3ac..bdb72abc79 100644 --- a/pyrogram/client/filters/__init__.py +++ b/pyrogram/client/filters/__init__.py @@ -1,19 +1,19 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from .filters import Filters diff --git a/pyrogram/client/filters/filter.py b/pyrogram/client/filters/filter.py index 112a814d16..eb89b3c387 100644 --- a/pyrogram/client/filters/filter.py +++ b/pyrogram/client/filters/filter.py @@ -1,21 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . - +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . class Filter: def __call__(self, message): diff --git a/pyrogram/client/filters/filters.py b/pyrogram/client/filters/filters.py index ba63434ab4..2463764074 100644 --- a/pyrogram/client/filters/filters.py +++ b/pyrogram/client/filters/filters.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import re from typing import Callable diff --git a/pyrogram/client/handlers/__init__.py b/pyrogram/client/handlers/__init__.py index df1fcd4e48..25acbedc9f 100644 --- a/pyrogram/client/handlers/__init__.py +++ b/pyrogram/client/handlers/__init__.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from .callback_query_handler import CallbackQueryHandler from .deleted_messages_handler import DeletedMessagesHandler diff --git a/pyrogram/client/handlers/callback_query_handler.py b/pyrogram/client/handlers/callback_query_handler.py index 2f3ffd5c8d..99aa2e70b7 100644 --- a/pyrogram/client/handlers/callback_query_handler.py +++ b/pyrogram/client/handlers/callback_query_handler.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from .handler import Handler diff --git a/pyrogram/client/handlers/deleted_messages_handler.py b/pyrogram/client/handlers/deleted_messages_handler.py index 14896505c3..7312ba90f3 100644 --- a/pyrogram/client/handlers/deleted_messages_handler.py +++ b/pyrogram/client/handlers/deleted_messages_handler.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from .handler import Handler diff --git a/pyrogram/client/handlers/disconnect_handler.py b/pyrogram/client/handlers/disconnect_handler.py index 27f18d65db..f4aec6b22d 100644 --- a/pyrogram/client/handlers/disconnect_handler.py +++ b/pyrogram/client/handlers/disconnect_handler.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from .handler import Handler diff --git a/pyrogram/client/handlers/handler.py b/pyrogram/client/handlers/handler.py index 5604124eef..0eb132d1e9 100644 --- a/pyrogram/client/handlers/handler.py +++ b/pyrogram/client/handlers/handler.py @@ -1,21 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . - +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . class Handler: def __init__(self, callback: callable, filters=None): diff --git a/pyrogram/client/handlers/inline_query_handler.py b/pyrogram/client/handlers/inline_query_handler.py index 51cf9888c0..aaa63c351c 100644 --- a/pyrogram/client/handlers/inline_query_handler.py +++ b/pyrogram/client/handlers/inline_query_handler.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from .handler import Handler diff --git a/pyrogram/client/handlers/message_handler.py b/pyrogram/client/handlers/message_handler.py index df82086037..f5a3b6e935 100644 --- a/pyrogram/client/handlers/message_handler.py +++ b/pyrogram/client/handlers/message_handler.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from .handler import Handler diff --git a/pyrogram/client/handlers/poll_handler.py b/pyrogram/client/handlers/poll_handler.py index e5649c8fb3..9dc90c4f56 100644 --- a/pyrogram/client/handlers/poll_handler.py +++ b/pyrogram/client/handlers/poll_handler.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from .handler import Handler diff --git a/pyrogram/client/handlers/raw_update_handler.py b/pyrogram/client/handlers/raw_update_handler.py index 936ec4f95f..fa01ced53c 100644 --- a/pyrogram/client/handlers/raw_update_handler.py +++ b/pyrogram/client/handlers/raw_update_handler.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from .handler import Handler diff --git a/pyrogram/client/handlers/user_status_handler.py b/pyrogram/client/handlers/user_status_handler.py index 538d1dabb7..94404d69f2 100644 --- a/pyrogram/client/handlers/user_status_handler.py +++ b/pyrogram/client/handlers/user_status_handler.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from .handler import Handler diff --git a/pyrogram/client/methods/__init__.py b/pyrogram/client/methods/__init__.py index f753bb5a17..54516e98fd 100644 --- a/pyrogram/client/methods/__init__.py +++ b/pyrogram/client/methods/__init__.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from .bots import Bots from .chats import Chats diff --git a/pyrogram/client/methods/bots/__init__.py b/pyrogram/client/methods/bots/__init__.py index be933b0bb0..215df97c05 100644 --- a/pyrogram/client/methods/bots/__init__.py +++ b/pyrogram/client/methods/bots/__init__.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from .answer_callback_query import AnswerCallbackQuery from .answer_inline_query import AnswerInlineQuery diff --git a/pyrogram/client/methods/bots/answer_callback_query.py b/pyrogram/client/methods/bots/answer_callback_query.py index 54c2f5df31..53cd6c4991 100644 --- a/pyrogram/client/methods/bots/answer_callback_query.py +++ b/pyrogram/client/methods/bots/answer_callback_query.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from pyrogram.api import functions from pyrogram.client.ext import BaseClient diff --git a/pyrogram/client/methods/bots/answer_inline_query.py b/pyrogram/client/methods/bots/answer_inline_query.py index 4b43e89c60..2f95c9b9f7 100644 --- a/pyrogram/client/methods/bots/answer_inline_query.py +++ b/pyrogram/client/methods/bots/answer_inline_query.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import List diff --git a/pyrogram/client/methods/bots/get_game_high_scores.py b/pyrogram/client/methods/bots/get_game_high_scores.py index 25d87ae5f4..1cebc8a624 100644 --- a/pyrogram/client/methods/bots/get_game_high_scores.py +++ b/pyrogram/client/methods/bots/get_game_high_scores.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union, List diff --git a/pyrogram/client/methods/bots/get_inline_bot_results.py b/pyrogram/client/methods/bots/get_inline_bot_results.py index 5db1904f1e..aa27b7c9f3 100644 --- a/pyrogram/client/methods/bots/get_inline_bot_results.py +++ b/pyrogram/client/methods/bots/get_inline_bot_results.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union diff --git a/pyrogram/client/methods/bots/request_callback_answer.py b/pyrogram/client/methods/bots/request_callback_answer.py index 9c9e6412b0..6178b94064 100644 --- a/pyrogram/client/methods/bots/request_callback_answer.py +++ b/pyrogram/client/methods/bots/request_callback_answer.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union diff --git a/pyrogram/client/methods/bots/send_game.py b/pyrogram/client/methods/bots/send_game.py index 1a4bc40ed1..e9513ac8b8 100644 --- a/pyrogram/client/methods/bots/send_game.py +++ b/pyrogram/client/methods/bots/send_game.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union diff --git a/pyrogram/client/methods/bots/send_inline_bot_result.py b/pyrogram/client/methods/bots/send_inline_bot_result.py index 4d6d92073a..9b2cdf605e 100644 --- a/pyrogram/client/methods/bots/send_inline_bot_result.py +++ b/pyrogram/client/methods/bots/send_inline_bot_result.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union diff --git a/pyrogram/client/methods/bots/set_game_score.py b/pyrogram/client/methods/bots/set_game_score.py index 62ee052d3f..25d8fc0b5a 100644 --- a/pyrogram/client/methods/bots/set_game_score.py +++ b/pyrogram/client/methods/bots/set_game_score.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union diff --git a/pyrogram/client/methods/chats/__init__.py b/pyrogram/client/methods/chats/__init__.py index 76a456cfbc..8c7fb3402c 100644 --- a/pyrogram/client/methods/chats/__init__.py +++ b/pyrogram/client/methods/chats/__init__.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from .add_chat_members import AddChatMembers from .archive_chats import ArchiveChats diff --git a/pyrogram/client/methods/chats/add_chat_members.py b/pyrogram/client/methods/chats/add_chat_members.py index 33af6f46a4..b04d5555b0 100644 --- a/pyrogram/client/methods/chats/add_chat_members.py +++ b/pyrogram/client/methods/chats/add_chat_members.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union, List diff --git a/pyrogram/client/methods/chats/archive_chats.py b/pyrogram/client/methods/chats/archive_chats.py index 1aea591ed7..3c1cabf74a 100644 --- a/pyrogram/client/methods/chats/archive_chats.py +++ b/pyrogram/client/methods/chats/archive_chats.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union, List diff --git a/pyrogram/client/methods/chats/create_channel.py b/pyrogram/client/methods/chats/create_channel.py index d63aa614f6..5986f7035b 100644 --- a/pyrogram/client/methods/chats/create_channel.py +++ b/pyrogram/client/methods/chats/create_channel.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# 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 diff --git a/pyrogram/client/methods/chats/create_group.py b/pyrogram/client/methods/chats/create_group.py index aa2585c4bd..43ec6e7fd3 100644 --- a/pyrogram/client/methods/chats/create_group.py +++ b/pyrogram/client/methods/chats/create_group.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union, List diff --git a/pyrogram/client/methods/chats/create_supergroup.py b/pyrogram/client/methods/chats/create_supergroup.py index c51922c7ed..139064ec5b 100644 --- a/pyrogram/client/methods/chats/create_supergroup.py +++ b/pyrogram/client/methods/chats/create_supergroup.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# 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 diff --git a/pyrogram/client/methods/chats/delete_channel.py b/pyrogram/client/methods/chats/delete_channel.py index 149d8f142b..fd07b0e6da 100644 --- a/pyrogram/client/methods/chats/delete_channel.py +++ b/pyrogram/client/methods/chats/delete_channel.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union diff --git a/pyrogram/client/methods/chats/delete_chat_photo.py b/pyrogram/client/methods/chats/delete_chat_photo.py index 3909bb6c53..655d6fd6a9 100644 --- a/pyrogram/client/methods/chats/delete_chat_photo.py +++ b/pyrogram/client/methods/chats/delete_chat_photo.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union diff --git a/pyrogram/client/methods/chats/delete_supergroup.py b/pyrogram/client/methods/chats/delete_supergroup.py index a0d361178b..df4649e561 100644 --- a/pyrogram/client/methods/chats/delete_supergroup.py +++ b/pyrogram/client/methods/chats/delete_supergroup.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union diff --git a/pyrogram/client/methods/chats/delete_user_history.py b/pyrogram/client/methods/chats/delete_user_history.py index 1b569497aa..03d87ca43e 100644 --- a/pyrogram/client/methods/chats/delete_user_history.py +++ b/pyrogram/client/methods/chats/delete_user_history.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union diff --git a/pyrogram/client/methods/chats/export_chat_invite_link.py b/pyrogram/client/methods/chats/export_chat_invite_link.py index ab4b08c567..671c1ade9b 100644 --- a/pyrogram/client/methods/chats/export_chat_invite_link.py +++ b/pyrogram/client/methods/chats/export_chat_invite_link.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union diff --git a/pyrogram/client/methods/chats/get_chat.py b/pyrogram/client/methods/chats/get_chat.py index eab49529df..14adc1a78c 100644 --- a/pyrogram/client/methods/chats/get_chat.py +++ b/pyrogram/client/methods/chats/get_chat.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union diff --git a/pyrogram/client/methods/chats/get_chat_member.py b/pyrogram/client/methods/chats/get_chat_member.py index 261caf2d09..9a7bdeff33 100644 --- a/pyrogram/client/methods/chats/get_chat_member.py +++ b/pyrogram/client/methods/chats/get_chat_member.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union diff --git a/pyrogram/client/methods/chats/get_chat_members.py b/pyrogram/client/methods/chats/get_chat_members.py index 7b184be173..a8ae405487 100644 --- a/pyrogram/client/methods/chats/get_chat_members.py +++ b/pyrogram/client/methods/chats/get_chat_members.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import logging import time diff --git a/pyrogram/client/methods/chats/get_chat_members_count.py b/pyrogram/client/methods/chats/get_chat_members_count.py index e82d4bda7d..ad77acc1bf 100644 --- a/pyrogram/client/methods/chats/get_chat_members_count.py +++ b/pyrogram/client/methods/chats/get_chat_members_count.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union diff --git a/pyrogram/client/methods/chats/get_dialogs.py b/pyrogram/client/methods/chats/get_dialogs.py index 4c55c57b02..6234538b72 100644 --- a/pyrogram/client/methods/chats/get_dialogs.py +++ b/pyrogram/client/methods/chats/get_dialogs.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import logging import time diff --git a/pyrogram/client/methods/chats/get_dialogs_count.py b/pyrogram/client/methods/chats/get_dialogs_count.py index 5d49815646..7b81182eda 100644 --- a/pyrogram/client/methods/chats/get_dialogs_count.py +++ b/pyrogram/client/methods/chats/get_dialogs_count.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from pyrogram.api import functions, types from ...ext import BaseClient diff --git a/pyrogram/client/methods/chats/get_nearby_chats.py b/pyrogram/client/methods/chats/get_nearby_chats.py index 75f7a88ad2..1ccab72976 100644 --- a/pyrogram/client/methods/chats/get_nearby_chats.py +++ b/pyrogram/client/methods/chats/get_nearby_chats.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import List diff --git a/pyrogram/client/methods/chats/iter_chat_members.py b/pyrogram/client/methods/chats/iter_chat_members.py index 0f71c9ad01..0bc903052b 100644 --- a/pyrogram/client/methods/chats/iter_chat_members.py +++ b/pyrogram/client/methods/chats/iter_chat_members.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from string import ascii_lowercase from typing import Union, Generator diff --git a/pyrogram/client/methods/chats/iter_dialogs.py b/pyrogram/client/methods/chats/iter_dialogs.py index 8bf3468d22..a2eddcb9d8 100644 --- a/pyrogram/client/methods/chats/iter_dialogs.py +++ b/pyrogram/client/methods/chats/iter_dialogs.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Generator diff --git a/pyrogram/client/methods/chats/join_chat.py b/pyrogram/client/methods/chats/join_chat.py index f0b942a18c..c379bf03c5 100644 --- a/pyrogram/client/methods/chats/join_chat.py +++ b/pyrogram/client/methods/chats/join_chat.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# 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 diff --git a/pyrogram/client/methods/chats/kick_chat_member.py b/pyrogram/client/methods/chats/kick_chat_member.py index eb2b66286d..55a177f466 100644 --- a/pyrogram/client/methods/chats/kick_chat_member.py +++ b/pyrogram/client/methods/chats/kick_chat_member.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union diff --git a/pyrogram/client/methods/chats/leave_chat.py b/pyrogram/client/methods/chats/leave_chat.py index d70b4fae84..2cc1c05755 100644 --- a/pyrogram/client/methods/chats/leave_chat.py +++ b/pyrogram/client/methods/chats/leave_chat.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union diff --git a/pyrogram/client/methods/chats/pin_chat_message.py b/pyrogram/client/methods/chats/pin_chat_message.py index ce1b080cac..44191a2d8b 100644 --- a/pyrogram/client/methods/chats/pin_chat_message.py +++ b/pyrogram/client/methods/chats/pin_chat_message.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union diff --git a/pyrogram/client/methods/chats/promote_chat_member.py b/pyrogram/client/methods/chats/promote_chat_member.py index 3e28a1177c..70b4f4e26c 100644 --- a/pyrogram/client/methods/chats/promote_chat_member.py +++ b/pyrogram/client/methods/chats/promote_chat_member.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union diff --git a/pyrogram/client/methods/chats/restrict_chat_member.py b/pyrogram/client/methods/chats/restrict_chat_member.py index 0c432c5f4d..528ad1bca4 100644 --- a/pyrogram/client/methods/chats/restrict_chat_member.py +++ b/pyrogram/client/methods/chats/restrict_chat_member.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union diff --git a/pyrogram/client/methods/chats/set_administrator_title.py b/pyrogram/client/methods/chats/set_administrator_title.py index fb2265c508..361a4e1c2f 100644 --- a/pyrogram/client/methods/chats/set_administrator_title.py +++ b/pyrogram/client/methods/chats/set_administrator_title.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union diff --git a/pyrogram/client/methods/chats/set_chat_description.py b/pyrogram/client/methods/chats/set_chat_description.py index 736e493c1d..312b63eb86 100644 --- a/pyrogram/client/methods/chats/set_chat_description.py +++ b/pyrogram/client/methods/chats/set_chat_description.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union diff --git a/pyrogram/client/methods/chats/set_chat_permissions.py b/pyrogram/client/methods/chats/set_chat_permissions.py index 225bda5336..ce2851f8ed 100644 --- a/pyrogram/client/methods/chats/set_chat_permissions.py +++ b/pyrogram/client/methods/chats/set_chat_permissions.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union diff --git a/pyrogram/client/methods/chats/set_chat_photo.py b/pyrogram/client/methods/chats/set_chat_photo.py index 788d726df7..461b3474aa 100644 --- a/pyrogram/client/methods/chats/set_chat_photo.py +++ b/pyrogram/client/methods/chats/set_chat_photo.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import os from typing import Union diff --git a/pyrogram/client/methods/chats/set_chat_title.py b/pyrogram/client/methods/chats/set_chat_title.py index 389e868ee0..9d6a2d246a 100644 --- a/pyrogram/client/methods/chats/set_chat_title.py +++ b/pyrogram/client/methods/chats/set_chat_title.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union diff --git a/pyrogram/client/methods/chats/set_slow_mode.py b/pyrogram/client/methods/chats/set_slow_mode.py index 3ff8fc1714..cf6c7096a3 100644 --- a/pyrogram/client/methods/chats/set_slow_mode.py +++ b/pyrogram/client/methods/chats/set_slow_mode.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union diff --git a/pyrogram/client/methods/chats/unarchive_chats.py b/pyrogram/client/methods/chats/unarchive_chats.py index d58996c604..b004e4bbb9 100644 --- a/pyrogram/client/methods/chats/unarchive_chats.py +++ b/pyrogram/client/methods/chats/unarchive_chats.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union, List diff --git a/pyrogram/client/methods/chats/unban_chat_member.py b/pyrogram/client/methods/chats/unban_chat_member.py index dbe3f1cbe6..fc0c975116 100644 --- a/pyrogram/client/methods/chats/unban_chat_member.py +++ b/pyrogram/client/methods/chats/unban_chat_member.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union diff --git a/pyrogram/client/methods/chats/unpin_chat_message.py b/pyrogram/client/methods/chats/unpin_chat_message.py index abe7dde9aa..6defd99f9d 100644 --- a/pyrogram/client/methods/chats/unpin_chat_message.py +++ b/pyrogram/client/methods/chats/unpin_chat_message.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union diff --git a/pyrogram/client/methods/chats/update_chat_username.py b/pyrogram/client/methods/chats/update_chat_username.py index 03002e8dd7..ff4db61b75 100644 --- a/pyrogram/client/methods/chats/update_chat_username.py +++ b/pyrogram/client/methods/chats/update_chat_username.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union diff --git a/pyrogram/client/methods/contacts/__init__.py b/pyrogram/client/methods/contacts/__init__.py index f1371e7e06..7c84decce4 100644 --- a/pyrogram/client/methods/contacts/__init__.py +++ b/pyrogram/client/methods/contacts/__init__.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from .add_contacts import AddContacts from .delete_contacts import DeleteContacts diff --git a/pyrogram/client/methods/contacts/add_contacts.py b/pyrogram/client/methods/contacts/add_contacts.py index 8b6649dbc3..a5bd4a933b 100644 --- a/pyrogram/client/methods/contacts/add_contacts.py +++ b/pyrogram/client/methods/contacts/add_contacts.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import List diff --git a/pyrogram/client/methods/contacts/delete_contacts.py b/pyrogram/client/methods/contacts/delete_contacts.py index a1e4c2d3c0..27e6cfff76 100644 --- a/pyrogram/client/methods/contacts/delete_contacts.py +++ b/pyrogram/client/methods/contacts/delete_contacts.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import List diff --git a/pyrogram/client/methods/contacts/get_contacts.py b/pyrogram/client/methods/contacts/get_contacts.py index dd90d36e76..84d9c7d4ff 100644 --- a/pyrogram/client/methods/contacts/get_contacts.py +++ b/pyrogram/client/methods/contacts/get_contacts.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import logging import time diff --git a/pyrogram/client/methods/contacts/get_contacts_count.py b/pyrogram/client/methods/contacts/get_contacts_count.py index fcfaf03547..b7871fde6a 100644 --- a/pyrogram/client/methods/contacts/get_contacts_count.py +++ b/pyrogram/client/methods/contacts/get_contacts_count.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from pyrogram.api import functions from ...ext import BaseClient diff --git a/pyrogram/client/methods/decorators/__init__.py b/pyrogram/client/methods/decorators/__init__.py index 3d1ade565b..2c0a749a85 100644 --- a/pyrogram/client/methods/decorators/__init__.py +++ b/pyrogram/client/methods/decorators/__init__.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from .on_callback_query import OnCallbackQuery from .on_deleted_messages import OnDeletedMessages diff --git a/pyrogram/client/methods/decorators/on_callback_query.py b/pyrogram/client/methods/decorators/on_callback_query.py index ba1e2d79f8..c7bcae2676 100644 --- a/pyrogram/client/methods/decorators/on_callback_query.py +++ b/pyrogram/client/methods/decorators/on_callback_query.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Callable diff --git a/pyrogram/client/methods/decorators/on_deleted_messages.py b/pyrogram/client/methods/decorators/on_deleted_messages.py index e40f3c6ec6..8a7e1e55ec 100644 --- a/pyrogram/client/methods/decorators/on_deleted_messages.py +++ b/pyrogram/client/methods/decorators/on_deleted_messages.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Callable diff --git a/pyrogram/client/methods/decorators/on_disconnect.py b/pyrogram/client/methods/decorators/on_disconnect.py index f567d848f7..0df6584837 100644 --- a/pyrogram/client/methods/decorators/on_disconnect.py +++ b/pyrogram/client/methods/decorators/on_disconnect.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Callable diff --git a/pyrogram/client/methods/decorators/on_inline_query.py b/pyrogram/client/methods/decorators/on_inline_query.py index f0d6cffc59..4a9b1128ea 100644 --- a/pyrogram/client/methods/decorators/on_inline_query.py +++ b/pyrogram/client/methods/decorators/on_inline_query.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Callable diff --git a/pyrogram/client/methods/decorators/on_message.py b/pyrogram/client/methods/decorators/on_message.py index 9cf05e2403..9d2791c279 100644 --- a/pyrogram/client/methods/decorators/on_message.py +++ b/pyrogram/client/methods/decorators/on_message.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Callable diff --git a/pyrogram/client/methods/decorators/on_poll.py b/pyrogram/client/methods/decorators/on_poll.py index 7c1e7186ea..2326b3e558 100644 --- a/pyrogram/client/methods/decorators/on_poll.py +++ b/pyrogram/client/methods/decorators/on_poll.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Callable diff --git a/pyrogram/client/methods/decorators/on_raw_update.py b/pyrogram/client/methods/decorators/on_raw_update.py index 75c7edfff7..749b9b5401 100644 --- a/pyrogram/client/methods/decorators/on_raw_update.py +++ b/pyrogram/client/methods/decorators/on_raw_update.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Callable diff --git a/pyrogram/client/methods/decorators/on_user_status.py b/pyrogram/client/methods/decorators/on_user_status.py index cd36547c94..ea09c8a495 100644 --- a/pyrogram/client/methods/decorators/on_user_status.py +++ b/pyrogram/client/methods/decorators/on_user_status.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Callable diff --git a/pyrogram/client/methods/messages/__init__.py b/pyrogram/client/methods/messages/__init__.py index c59cb2c165..147e225a91 100644 --- a/pyrogram/client/methods/messages/__init__.py +++ b/pyrogram/client/methods/messages/__init__.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from .delete_messages import DeleteMessages from .download_media import DownloadMedia diff --git a/pyrogram/client/methods/messages/delete_messages.py b/pyrogram/client/methods/messages/delete_messages.py index 2100fadfda..a6af6a694f 100644 --- a/pyrogram/client/methods/messages/delete_messages.py +++ b/pyrogram/client/methods/messages/delete_messages.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union, Iterable diff --git a/pyrogram/client/methods/messages/download_media.py b/pyrogram/client/methods/messages/download_media.py index 39b5723339..07f7768dea 100644 --- a/pyrogram/client/methods/messages/download_media.py +++ b/pyrogram/client/methods/messages/download_media.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import binascii import os diff --git a/pyrogram/client/methods/messages/edit_inline_caption.py b/pyrogram/client/methods/messages/edit_inline_caption.py index 3ac239e7bd..58cd05b252 100644 --- a/pyrogram/client/methods/messages/edit_inline_caption.py +++ b/pyrogram/client/methods/messages/edit_inline_caption.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union diff --git a/pyrogram/client/methods/messages/edit_inline_media.py b/pyrogram/client/methods/messages/edit_inline_media.py index 565334b0b5..700804d997 100644 --- a/pyrogram/client/methods/messages/edit_inline_media.py +++ b/pyrogram/client/methods/messages/edit_inline_media.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# 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 diff --git a/pyrogram/client/methods/messages/edit_inline_reply_markup.py b/pyrogram/client/methods/messages/edit_inline_reply_markup.py index 4537e52efd..44e6197ccd 100644 --- a/pyrogram/client/methods/messages/edit_inline_reply_markup.py +++ b/pyrogram/client/methods/messages/edit_inline_reply_markup.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# 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 diff --git a/pyrogram/client/methods/messages/edit_inline_text.py b/pyrogram/client/methods/messages/edit_inline_text.py index b0cad38686..59b5ab7383 100644 --- a/pyrogram/client/methods/messages/edit_inline_text.py +++ b/pyrogram/client/methods/messages/edit_inline_text.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union diff --git a/pyrogram/client/methods/messages/edit_message_caption.py b/pyrogram/client/methods/messages/edit_message_caption.py index e3c3346a7f..a7c5b94c6b 100644 --- a/pyrogram/client/methods/messages/edit_message_caption.py +++ b/pyrogram/client/methods/messages/edit_message_caption.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union diff --git a/pyrogram/client/methods/messages/edit_message_media.py b/pyrogram/client/methods/messages/edit_message_media.py index 4ede29617c..3b573cffd3 100644 --- a/pyrogram/client/methods/messages/edit_message_media.py +++ b/pyrogram/client/methods/messages/edit_message_media.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import os from typing import Union diff --git a/pyrogram/client/methods/messages/edit_message_reply_markup.py b/pyrogram/client/methods/messages/edit_message_reply_markup.py index d2eec3ca84..35c6cb3e6b 100644 --- a/pyrogram/client/methods/messages/edit_message_reply_markup.py +++ b/pyrogram/client/methods/messages/edit_message_reply_markup.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union diff --git a/pyrogram/client/methods/messages/edit_message_text.py b/pyrogram/client/methods/messages/edit_message_text.py index 80b60e4cf4..df5ebace1b 100644 --- a/pyrogram/client/methods/messages/edit_message_text.py +++ b/pyrogram/client/methods/messages/edit_message_text.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union diff --git a/pyrogram/client/methods/messages/forward_messages.py b/pyrogram/client/methods/messages/forward_messages.py index d098e405c7..b3931d963c 100644 --- a/pyrogram/client/methods/messages/forward_messages.py +++ b/pyrogram/client/methods/messages/forward_messages.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union, Iterable, List diff --git a/pyrogram/client/methods/messages/get_history.py b/pyrogram/client/methods/messages/get_history.py index 30539c731f..bf348ac9a3 100644 --- a/pyrogram/client/methods/messages/get_history.py +++ b/pyrogram/client/methods/messages/get_history.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import logging import time diff --git a/pyrogram/client/methods/messages/get_history_count.py b/pyrogram/client/methods/messages/get_history_count.py index 0479d4f285..cbdb136569 100644 --- a/pyrogram/client/methods/messages/get_history_count.py +++ b/pyrogram/client/methods/messages/get_history_count.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import logging from typing import Union diff --git a/pyrogram/client/methods/messages/get_messages.py b/pyrogram/client/methods/messages/get_messages.py index b692a7e3a1..a18d84c2eb 100644 --- a/pyrogram/client/methods/messages/get_messages.py +++ b/pyrogram/client/methods/messages/get_messages.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import logging import time diff --git a/pyrogram/client/methods/messages/iter_history.py b/pyrogram/client/methods/messages/iter_history.py index d8ce0661b9..641f500096 100644 --- a/pyrogram/client/methods/messages/iter_history.py +++ b/pyrogram/client/methods/messages/iter_history.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union, Generator diff --git a/pyrogram/client/methods/messages/read_history.py b/pyrogram/client/methods/messages/read_history.py index bf98e31fd7..f23fa800b4 100644 --- a/pyrogram/client/methods/messages/read_history.py +++ b/pyrogram/client/methods/messages/read_history.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union diff --git a/pyrogram/client/methods/messages/retract_vote.py b/pyrogram/client/methods/messages/retract_vote.py index 1197053c16..fbb020b204 100644 --- a/pyrogram/client/methods/messages/retract_vote.py +++ b/pyrogram/client/methods/messages/retract_vote.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union diff --git a/pyrogram/client/methods/messages/send_animation.py b/pyrogram/client/methods/messages/send_animation.py index d7e9ee3f44..99e86fba6e 100644 --- a/pyrogram/client/methods/messages/send_animation.py +++ b/pyrogram/client/methods/messages/send_animation.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import os from typing import Union diff --git a/pyrogram/client/methods/messages/send_audio.py b/pyrogram/client/methods/messages/send_audio.py index e97c76231e..2d34f86176 100644 --- a/pyrogram/client/methods/messages/send_audio.py +++ b/pyrogram/client/methods/messages/send_audio.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import os from typing import Union diff --git a/pyrogram/client/methods/messages/send_cached_media.py b/pyrogram/client/methods/messages/send_cached_media.py index 00890477a3..f50f27700c 100644 --- a/pyrogram/client/methods/messages/send_cached_media.py +++ b/pyrogram/client/methods/messages/send_cached_media.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union diff --git a/pyrogram/client/methods/messages/send_chat_action.py b/pyrogram/client/methods/messages/send_chat_action.py index f648a30916..5d0dabb913 100644 --- a/pyrogram/client/methods/messages/send_chat_action.py +++ b/pyrogram/client/methods/messages/send_chat_action.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import json from typing import Union diff --git a/pyrogram/client/methods/messages/send_contact.py b/pyrogram/client/methods/messages/send_contact.py index b8223ed941..95ff6f3844 100644 --- a/pyrogram/client/methods/messages/send_contact.py +++ b/pyrogram/client/methods/messages/send_contact.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union diff --git a/pyrogram/client/methods/messages/send_document.py b/pyrogram/client/methods/messages/send_document.py index 882e94221f..182d2985db 100644 --- a/pyrogram/client/methods/messages/send_document.py +++ b/pyrogram/client/methods/messages/send_document.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import os from typing import Union diff --git a/pyrogram/client/methods/messages/send_location.py b/pyrogram/client/methods/messages/send_location.py index f064d50f52..04b614ce16 100644 --- a/pyrogram/client/methods/messages/send_location.py +++ b/pyrogram/client/methods/messages/send_location.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union diff --git a/pyrogram/client/methods/messages/send_media_group.py b/pyrogram/client/methods/messages/send_media_group.py index 93a970d020..055c7b7c7a 100644 --- a/pyrogram/client/methods/messages/send_media_group.py +++ b/pyrogram/client/methods/messages/send_media_group.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import logging import os diff --git a/pyrogram/client/methods/messages/send_message.py b/pyrogram/client/methods/messages/send_message.py index 7e1ee849f4..105796b9da 100644 --- a/pyrogram/client/methods/messages/send_message.py +++ b/pyrogram/client/methods/messages/send_message.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union diff --git a/pyrogram/client/methods/messages/send_photo.py b/pyrogram/client/methods/messages/send_photo.py index f5a5e6e092..4d6a18a3d0 100644 --- a/pyrogram/client/methods/messages/send_photo.py +++ b/pyrogram/client/methods/messages/send_photo.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import os from typing import Union diff --git a/pyrogram/client/methods/messages/send_poll.py b/pyrogram/client/methods/messages/send_poll.py index 57cbf53541..f5293c3f4a 100644 --- a/pyrogram/client/methods/messages/send_poll.py +++ b/pyrogram/client/methods/messages/send_poll.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union, List diff --git a/pyrogram/client/methods/messages/send_sticker.py b/pyrogram/client/methods/messages/send_sticker.py index c4f77bb00f..76a42d3d8a 100644 --- a/pyrogram/client/methods/messages/send_sticker.py +++ b/pyrogram/client/methods/messages/send_sticker.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import os from typing import Union diff --git a/pyrogram/client/methods/messages/send_venue.py b/pyrogram/client/methods/messages/send_venue.py index 9eb86c6c34..98ff4103d0 100644 --- a/pyrogram/client/methods/messages/send_venue.py +++ b/pyrogram/client/methods/messages/send_venue.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union diff --git a/pyrogram/client/methods/messages/send_video.py b/pyrogram/client/methods/messages/send_video.py index 656b7715ff..8335b90279 100644 --- a/pyrogram/client/methods/messages/send_video.py +++ b/pyrogram/client/methods/messages/send_video.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import os from typing import Union diff --git a/pyrogram/client/methods/messages/send_video_note.py b/pyrogram/client/methods/messages/send_video_note.py index 69ca35da4d..64bde11b54 100644 --- a/pyrogram/client/methods/messages/send_video_note.py +++ b/pyrogram/client/methods/messages/send_video_note.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import os from typing import Union diff --git a/pyrogram/client/methods/messages/send_voice.py b/pyrogram/client/methods/messages/send_voice.py index 0facc2cefa..753e380671 100644 --- a/pyrogram/client/methods/messages/send_voice.py +++ b/pyrogram/client/methods/messages/send_voice.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import os from typing import Union diff --git a/pyrogram/client/methods/messages/stop_poll.py b/pyrogram/client/methods/messages/stop_poll.py index 01c67b56ca..4d133f7fa6 100644 --- a/pyrogram/client/methods/messages/stop_poll.py +++ b/pyrogram/client/methods/messages/stop_poll.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union diff --git a/pyrogram/client/methods/messages/vote_poll.py b/pyrogram/client/methods/messages/vote_poll.py index c67fc8a221..335ca7cbaa 100644 --- a/pyrogram/client/methods/messages/vote_poll.py +++ b/pyrogram/client/methods/messages/vote_poll.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union, List diff --git a/pyrogram/client/methods/password/__init__.py b/pyrogram/client/methods/password/__init__.py index 0614a7a5af..ca7074082a 100644 --- a/pyrogram/client/methods/password/__init__.py +++ b/pyrogram/client/methods/password/__init__.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from .change_cloud_password import ChangeCloudPassword from .enable_cloud_password import EnableCloudPassword diff --git a/pyrogram/client/methods/password/change_cloud_password.py b/pyrogram/client/methods/password/change_cloud_password.py index 2e6f0b3acf..20625657b2 100644 --- a/pyrogram/client/methods/password/change_cloud_password.py +++ b/pyrogram/client/methods/password/change_cloud_password.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import os diff --git a/pyrogram/client/methods/password/enable_cloud_password.py b/pyrogram/client/methods/password/enable_cloud_password.py index fc50c23a48..c8052aa8b1 100644 --- a/pyrogram/client/methods/password/enable_cloud_password.py +++ b/pyrogram/client/methods/password/enable_cloud_password.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import os diff --git a/pyrogram/client/methods/password/remove_cloud_password.py b/pyrogram/client/methods/password/remove_cloud_password.py index 9ae88a749f..21ebb8b371 100644 --- a/pyrogram/client/methods/password/remove_cloud_password.py +++ b/pyrogram/client/methods/password/remove_cloud_password.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from pyrogram.api import functions, types from .utils import compute_check diff --git a/pyrogram/client/methods/password/utils.py b/pyrogram/client/methods/password/utils.py index 12582f0e8a..30c3679684 100644 --- a/pyrogram/client/methods/password/utils.py +++ b/pyrogram/client/methods/password/utils.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import hashlib import os diff --git a/pyrogram/client/methods/users/__init__.py b/pyrogram/client/methods/users/__init__.py index 1746787e99..1980303fef 100644 --- a/pyrogram/client/methods/users/__init__.py +++ b/pyrogram/client/methods/users/__init__.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from .block_user import BlockUser from .delete_profile_photos import DeleteProfilePhotos diff --git a/pyrogram/client/methods/users/block_user.py b/pyrogram/client/methods/users/block_user.py index 37a17a6753..b61507f931 100644 --- a/pyrogram/client/methods/users/block_user.py +++ b/pyrogram/client/methods/users/block_user.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union diff --git a/pyrogram/client/methods/users/delete_profile_photos.py b/pyrogram/client/methods/users/delete_profile_photos.py index b222db9ef7..66ad219fe3 100644 --- a/pyrogram/client/methods/users/delete_profile_photos.py +++ b/pyrogram/client/methods/users/delete_profile_photos.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import List, Union diff --git a/pyrogram/client/methods/users/get_common_chats.py b/pyrogram/client/methods/users/get_common_chats.py index b352fd696f..323c5e876f 100644 --- a/pyrogram/client/methods/users/get_common_chats.py +++ b/pyrogram/client/methods/users/get_common_chats.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union diff --git a/pyrogram/client/methods/users/get_me.py b/pyrogram/client/methods/users/get_me.py index 2108991832..1814fa6dee 100644 --- a/pyrogram/client/methods/users/get_me.py +++ b/pyrogram/client/methods/users/get_me.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# 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 diff --git a/pyrogram/client/methods/users/get_profile_photos.py b/pyrogram/client/methods/users/get_profile_photos.py index 89ef6c131b..ec23e651c7 100644 --- a/pyrogram/client/methods/users/get_profile_photos.py +++ b/pyrogram/client/methods/users/get_profile_photos.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union, List diff --git a/pyrogram/client/methods/users/get_profile_photos_count.py b/pyrogram/client/methods/users/get_profile_photos_count.py index 0308249170..a927d8bd9e 100644 --- a/pyrogram/client/methods/users/get_profile_photos_count.py +++ b/pyrogram/client/methods/users/get_profile_photos_count.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union diff --git a/pyrogram/client/methods/users/get_users.py b/pyrogram/client/methods/users/get_users.py index 56f72a99ea..b115cb032b 100644 --- a/pyrogram/client/methods/users/get_users.py +++ b/pyrogram/client/methods/users/get_users.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Iterable, Union, List diff --git a/pyrogram/client/methods/users/iter_profile_photos.py b/pyrogram/client/methods/users/iter_profile_photos.py index 751e12ae0e..fb09cff7eb 100644 --- a/pyrogram/client/methods/users/iter_profile_photos.py +++ b/pyrogram/client/methods/users/iter_profile_photos.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union, Generator diff --git a/pyrogram/client/methods/users/set_profile_photo.py b/pyrogram/client/methods/users/set_profile_photo.py index 116beb4bf9..ae627110b0 100644 --- a/pyrogram/client/methods/users/set_profile_photo.py +++ b/pyrogram/client/methods/users/set_profile_photo.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from pyrogram.api import functions from ...ext import BaseClient diff --git a/pyrogram/client/methods/users/unblock_user.py b/pyrogram/client/methods/users/unblock_user.py index e4c912676b..8459cfd63c 100644 --- a/pyrogram/client/methods/users/unblock_user.py +++ b/pyrogram/client/methods/users/unblock_user.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union diff --git a/pyrogram/client/methods/users/update_profile.py b/pyrogram/client/methods/users/update_profile.py index e9d9927624..19ec6d6606 100644 --- a/pyrogram/client/methods/users/update_profile.py +++ b/pyrogram/client/methods/users/update_profile.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from pyrogram.api import functions from ...ext import BaseClient diff --git a/pyrogram/client/methods/users/update_username.py b/pyrogram/client/methods/users/update_username.py index 7c029b7746..88536842cc 100644 --- a/pyrogram/client/methods/users/update_username.py +++ b/pyrogram/client/methods/users/update_username.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union diff --git a/pyrogram/client/parser/__init__.py b/pyrogram/client/parser/__init__.py index 2d9c6af828..ad5c727ab4 100644 --- a/pyrogram/client/parser/__init__.py +++ b/pyrogram/client/parser/__init__.py @@ -1,19 +1,19 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from .parser import Parser diff --git a/pyrogram/client/parser/html.py b/pyrogram/client/parser/html.py index b93a9d79e1..d614fb2f1f 100644 --- a/pyrogram/client/parser/html.py +++ b/pyrogram/client/parser/html.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import html import logging diff --git a/pyrogram/client/parser/markdown.py b/pyrogram/client/parser/markdown.py index ac1fe30ac2..5f4ab2583d 100644 --- a/pyrogram/client/parser/markdown.py +++ b/pyrogram/client/parser/markdown.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import html import re diff --git a/pyrogram/client/parser/parser.py b/pyrogram/client/parser/parser.py index 7c492e77ad..968b95f1b7 100644 --- a/pyrogram/client/parser/parser.py +++ b/pyrogram/client/parser/parser.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from collections import OrderedDict from typing import Union diff --git a/pyrogram/client/parser/utils.py b/pyrogram/client/parser/utils.py index 9865768884..0babc9d7dd 100644 --- a/pyrogram/client/parser/utils.py +++ b/pyrogram/client/parser/utils.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import re from struct import unpack diff --git a/pyrogram/client/storage/__init__.py b/pyrogram/client/storage/__init__.py index c362ac16f0..05987e2f3c 100644 --- a/pyrogram/client/storage/__init__.py +++ b/pyrogram/client/storage/__init__.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from .file_storage import FileStorage from .memory_storage import MemoryStorage diff --git a/pyrogram/client/storage/file_storage.py b/pyrogram/client/storage/file_storage.py index 449847b829..07716ce5e5 100644 --- a/pyrogram/client/storage/file_storage.py +++ b/pyrogram/client/storage/file_storage.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import base64 import json diff --git a/pyrogram/client/storage/memory_storage.py b/pyrogram/client/storage/memory_storage.py index 8912b73076..b698d2cef9 100644 --- a/pyrogram/client/storage/memory_storage.py +++ b/pyrogram/client/storage/memory_storage.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import base64 import logging diff --git a/pyrogram/client/storage/schema.sql b/pyrogram/client/storage/schema.sql index 52dccee3dd..aa76c4b021 100644 --- a/pyrogram/client/storage/schema.sql +++ b/pyrogram/client/storage/schema.sql @@ -1,3 +1,23 @@ +/* + * Pyrogram - Telegram MTProto API Client Library for Python + * Copyright (C) 2017-2020 Dan + * + * 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 . + */ + CREATE TABLE sessions ( dc_id INTEGER PRIMARY KEY, test_mode INTEGER, diff --git a/pyrogram/client/storage/sqlite_storage.py b/pyrogram/client/storage/sqlite_storage.py index 1525a3b90b..14279b0a27 100644 --- a/pyrogram/client/storage/sqlite_storage.py +++ b/pyrogram/client/storage/sqlite_storage.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import inspect import sqlite3 diff --git a/pyrogram/client/storage/storage.py b/pyrogram/client/storage/storage.py index 15bc61a3e1..8cd7e6f39e 100644 --- a/pyrogram/client/storage/storage.py +++ b/pyrogram/client/storage/storage.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import base64 import struct diff --git a/pyrogram/client/types/__init__.py b/pyrogram/client/types/__init__.py index a1f04f3ea6..1fa7c9cecf 100644 --- a/pyrogram/client/types/__init__.py +++ b/pyrogram/client/types/__init__.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from .bots_and_keyboards import * from .inline_mode import * diff --git a/pyrogram/client/types/authorization/__init__.py b/pyrogram/client/types/authorization/__init__.py index a9fa5bcd01..18d57c935b 100644 --- a/pyrogram/client/types/authorization/__init__.py +++ b/pyrogram/client/types/authorization/__init__.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from .terms_of_service import TermsOfService from .sent_code import SentCode diff --git a/pyrogram/client/types/authorization/sent_code.py b/pyrogram/client/types/authorization/sent_code.py index 95b94637a9..f1fd49da22 100644 --- a/pyrogram/client/types/authorization/sent_code.py +++ b/pyrogram/client/types/authorization/sent_code.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from pyrogram.api import types from ..object import Object diff --git a/pyrogram/client/types/authorization/terms_of_service.py b/pyrogram/client/types/authorization/terms_of_service.py index 7a88bb99a4..ab7e1348df 100644 --- a/pyrogram/client/types/authorization/terms_of_service.py +++ b/pyrogram/client/types/authorization/terms_of_service.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import List diff --git a/pyrogram/client/types/bots_and_keyboards/__init__.py b/pyrogram/client/types/bots_and_keyboards/__init__.py index da8905ff72..2acd8c0b71 100644 --- a/pyrogram/client/types/bots_and_keyboards/__init__.py +++ b/pyrogram/client/types/bots_and_keyboards/__init__.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from .callback_game import CallbackGame from .callback_query import CallbackQuery diff --git a/pyrogram/client/types/bots_and_keyboards/callback_game.py b/pyrogram/client/types/bots_and_keyboards/callback_game.py index a833705f35..9fe8421d34 100644 --- a/pyrogram/client/types/bots_and_keyboards/callback_game.py +++ b/pyrogram/client/types/bots_and_keyboards/callback_game.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from ..object import Object diff --git a/pyrogram/client/types/bots_and_keyboards/callback_query.py b/pyrogram/client/types/bots_and_keyboards/callback_query.py index 9a7809ac82..34b8b52cdb 100644 --- a/pyrogram/client/types/bots_and_keyboards/callback_query.py +++ b/pyrogram/client/types/bots_and_keyboards/callback_query.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from base64 import b64encode from struct import pack diff --git a/pyrogram/client/types/bots_and_keyboards/force_reply.py b/pyrogram/client/types/bots_and_keyboards/force_reply.py index bc76d41fbe..14870318f6 100644 --- a/pyrogram/client/types/bots_and_keyboards/force_reply.py +++ b/pyrogram/client/types/bots_and_keyboards/force_reply.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from pyrogram.api.types import ReplyKeyboardForceReply diff --git a/pyrogram/client/types/bots_and_keyboards/game_high_score.py b/pyrogram/client/types/bots_and_keyboards/game_high_score.py index 89d2fa4999..1e9b715764 100644 --- a/pyrogram/client/types/bots_and_keyboards/game_high_score.py +++ b/pyrogram/client/types/bots_and_keyboards/game_high_score.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import pyrogram diff --git a/pyrogram/client/types/bots_and_keyboards/inline_keyboard_button.py b/pyrogram/client/types/bots_and_keyboards/inline_keyboard_button.py index f0d6cf0bdf..0b186b401f 100644 --- a/pyrogram/client/types/bots_and_keyboards/inline_keyboard_button.py +++ b/pyrogram/client/types/bots_and_keyboards/inline_keyboard_button.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union diff --git a/pyrogram/client/types/bots_and_keyboards/inline_keyboard_markup.py b/pyrogram/client/types/bots_and_keyboards/inline_keyboard_markup.py index fcf5840cbc..fb79b4db29 100644 --- a/pyrogram/client/types/bots_and_keyboards/inline_keyboard_markup.py +++ b/pyrogram/client/types/bots_and_keyboards/inline_keyboard_markup.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import List diff --git a/pyrogram/client/types/bots_and_keyboards/keyboard_button.py b/pyrogram/client/types/bots_and_keyboards/keyboard_button.py index 847ea027ab..022f19def1 100644 --- a/pyrogram/client/types/bots_and_keyboards/keyboard_button.py +++ b/pyrogram/client/types/bots_and_keyboards/keyboard_button.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from pyrogram.api.types import KeyboardButton as RawKeyboardButton from pyrogram.api.types import KeyboardButtonRequestPhone, KeyboardButtonRequestGeoLocation diff --git a/pyrogram/client/types/bots_and_keyboards/reply_keyboard_markup.py b/pyrogram/client/types/bots_and_keyboards/reply_keyboard_markup.py index d918b9a6a5..f56087b95d 100644 --- a/pyrogram/client/types/bots_and_keyboards/reply_keyboard_markup.py +++ b/pyrogram/client/types/bots_and_keyboards/reply_keyboard_markup.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import List, Union diff --git a/pyrogram/client/types/bots_and_keyboards/reply_keyboard_remove.py b/pyrogram/client/types/bots_and_keyboards/reply_keyboard_remove.py index e7c05d83f9..2143870a0d 100644 --- a/pyrogram/client/types/bots_and_keyboards/reply_keyboard_remove.py +++ b/pyrogram/client/types/bots_and_keyboards/reply_keyboard_remove.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from pyrogram.api.types import ReplyKeyboardHide diff --git a/pyrogram/client/types/inline_mode/__init__.py b/pyrogram/client/types/inline_mode/__init__.py index f47e4050e4..68da92d751 100644 --- a/pyrogram/client/types/inline_mode/__init__.py +++ b/pyrogram/client/types/inline_mode/__init__.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from .inline_query import InlineQuery from .inline_query_result import InlineQueryResult diff --git a/pyrogram/client/types/inline_mode/inline_query.py b/pyrogram/client/types/inline_mode/inline_query.py index 37799cd35b..44fea75d66 100644 --- a/pyrogram/client/types/inline_mode/inline_query.py +++ b/pyrogram/client/types/inline_mode/inline_query.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import List diff --git a/pyrogram/client/types/inline_mode/inline_query_result.py b/pyrogram/client/types/inline_mode/inline_query_result.py index f96237247b..c815aedf01 100644 --- a/pyrogram/client/types/inline_mode/inline_query_result.py +++ b/pyrogram/client/types/inline_mode/inline_query_result.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from uuid import uuid4 diff --git a/pyrogram/client/types/inline_mode/inline_query_result_animation.py b/pyrogram/client/types/inline_mode/inline_query_result_animation.py index eb1b43a66b..756ee91aae 100644 --- a/pyrogram/client/types/inline_mode/inline_query_result_animation.py +++ b/pyrogram/client/types/inline_mode/inline_query_result_animation.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union 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 2879ae99a3..900bf477f1 100644 --- a/pyrogram/client/types/inline_mode/inline_query_result_article.py +++ b/pyrogram/client/types/inline_mode/inline_query_result_article.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from pyrogram.api import types from .inline_query_result import InlineQueryResult diff --git a/pyrogram/client/types/inline_mode/inline_query_result_photo.py b/pyrogram/client/types/inline_mode/inline_query_result_photo.py index c0cbb6db00..5905c14e2e 100644 --- a/pyrogram/client/types/inline_mode/inline_query_result_photo.py +++ b/pyrogram/client/types/inline_mode/inline_query_result_photo.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union diff --git a/pyrogram/client/types/input_media/__init__.py b/pyrogram/client/types/input_media/__init__.py index c1308638cc..101703a17e 100644 --- a/pyrogram/client/types/input_media/__init__.py +++ b/pyrogram/client/types/input_media/__init__.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from .input_media import InputMedia from .input_media_animation import InputMediaAnimation diff --git a/pyrogram/client/types/input_media/input_media.py b/pyrogram/client/types/input_media/input_media.py index 73e80f719f..7d8497324c 100644 --- a/pyrogram/client/types/input_media/input_media.py +++ b/pyrogram/client/types/input_media/input_media.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from ..object import Object diff --git a/pyrogram/client/types/input_media/input_media_animation.py b/pyrogram/client/types/input_media/input_media_animation.py index 9b2f65b619..ee74668996 100644 --- a/pyrogram/client/types/input_media/input_media_animation.py +++ b/pyrogram/client/types/input_media/input_media_animation.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union diff --git a/pyrogram/client/types/input_media/input_media_audio.py b/pyrogram/client/types/input_media/input_media_audio.py index 7283cda73a..66cd9e1fb4 100644 --- a/pyrogram/client/types/input_media/input_media_audio.py +++ b/pyrogram/client/types/input_media/input_media_audio.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union diff --git a/pyrogram/client/types/input_media/input_media_document.py b/pyrogram/client/types/input_media/input_media_document.py index 79f5f32eec..7ff3ca0b2e 100644 --- a/pyrogram/client/types/input_media/input_media_document.py +++ b/pyrogram/client/types/input_media/input_media_document.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union diff --git a/pyrogram/client/types/input_media/input_media_photo.py b/pyrogram/client/types/input_media/input_media_photo.py index 26dd23c756..f40bdef76c 100644 --- a/pyrogram/client/types/input_media/input_media_photo.py +++ b/pyrogram/client/types/input_media/input_media_photo.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union diff --git a/pyrogram/client/types/input_media/input_media_video.py b/pyrogram/client/types/input_media/input_media_video.py index 3f0fe4eaeb..6b986a32e9 100644 --- a/pyrogram/client/types/input_media/input_media_video.py +++ b/pyrogram/client/types/input_media/input_media_video.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union diff --git a/pyrogram/client/types/input_media/input_phone_contact.py b/pyrogram/client/types/input_media/input_phone_contact.py index 147e9967e9..e2c3aca142 100644 --- a/pyrogram/client/types/input_media/input_phone_contact.py +++ b/pyrogram/client/types/input_media/input_phone_contact.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from pyrogram.api.types import InputPhoneContact as RawInputPhoneContact diff --git a/pyrogram/client/types/input_message_content/__init__.py b/pyrogram/client/types/input_message_content/__init__.py index 4297734b24..3f82b6b47d 100644 --- a/pyrogram/client/types/input_message_content/__init__.py +++ b/pyrogram/client/types/input_message_content/__init__.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from .input_message_content import InputMessageContent from .input_text_message_content import InputTextMessageContent 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 dc72e56712..f97e3cd9ad 100644 --- a/pyrogram/client/types/input_message_content/input_message_content.py +++ b/pyrogram/client/types/input_message_content/input_message_content.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from ..object import Object 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 600ce2f5a1..699ab80d7d 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 @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union diff --git a/pyrogram/client/types/list.py b/pyrogram/client/types/list.py index b2767991d1..118c4304f3 100644 --- a/pyrogram/client/types/list.py +++ b/pyrogram/client/types/list.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from .object import Object diff --git a/pyrogram/client/types/messages_and_media/__init__.py b/pyrogram/client/types/messages_and_media/__init__.py index 570d1208d1..e514fb8d94 100644 --- a/pyrogram/client/types/messages_and_media/__init__.py +++ b/pyrogram/client/types/messages_and_media/__init__.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from .animation import Animation from .audio import Audio diff --git a/pyrogram/client/types/messages_and_media/animation.py b/pyrogram/client/types/messages_and_media/animation.py index addc3df1bd..497f943e2e 100644 --- a/pyrogram/client/types/messages_and_media/animation.py +++ b/pyrogram/client/types/messages_and_media/animation.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from struct import pack from typing import List diff --git a/pyrogram/client/types/messages_and_media/audio.py b/pyrogram/client/types/messages_and_media/audio.py index 9e843f816e..bd36012635 100644 --- a/pyrogram/client/types/messages_and_media/audio.py +++ b/pyrogram/client/types/messages_and_media/audio.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from struct import pack from typing import List diff --git a/pyrogram/client/types/messages_and_media/contact.py b/pyrogram/client/types/messages_and_media/contact.py index 878fad82ee..c2289ee144 100644 --- a/pyrogram/client/types/messages_and_media/contact.py +++ b/pyrogram/client/types/messages_and_media/contact.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import pyrogram diff --git a/pyrogram/client/types/messages_and_media/document.py b/pyrogram/client/types/messages_and_media/document.py index 34e6a34da4..ec6a5edc07 100644 --- a/pyrogram/client/types/messages_and_media/document.py +++ b/pyrogram/client/types/messages_and_media/document.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from struct import pack from typing import List diff --git a/pyrogram/client/types/messages_and_media/game.py b/pyrogram/client/types/messages_and_media/game.py index eac393289a..f828a2707e 100644 --- a/pyrogram/client/types/messages_and_media/game.py +++ b/pyrogram/client/types/messages_and_media/game.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# 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 types diff --git a/pyrogram/client/types/messages_and_media/location.py b/pyrogram/client/types/messages_and_media/location.py index 01811d1f5e..38af642f83 100644 --- a/pyrogram/client/types/messages_and_media/location.py +++ b/pyrogram/client/types/messages_and_media/location.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import pyrogram diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index afc1bb0f5c..897cf3455b 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from functools import partial from typing import List, Match, Union diff --git a/pyrogram/client/types/messages_and_media/message_entity.py b/pyrogram/client/types/messages_and_media/message_entity.py index 3f8f9f41f8..a1e39a0f62 100644 --- a/pyrogram/client/types/messages_and_media/message_entity.py +++ b/pyrogram/client/types/messages_and_media/message_entity.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import pyrogram diff --git a/pyrogram/client/types/messages_and_media/photo.py b/pyrogram/client/types/messages_and_media/photo.py index 65de8fc768..7a0230d684 100644 --- a/pyrogram/client/types/messages_and_media/photo.py +++ b/pyrogram/client/types/messages_and_media/photo.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from struct import pack from typing import List diff --git a/pyrogram/client/types/messages_and_media/poll.py b/pyrogram/client/types/messages_and_media/poll.py index fffd690b08..d1dd2b219c 100644 --- a/pyrogram/client/types/messages_and_media/poll.py +++ b/pyrogram/client/types/messages_and_media/poll.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import List, Union diff --git a/pyrogram/client/types/messages_and_media/poll_option.py b/pyrogram/client/types/messages_and_media/poll_option.py index e2d4e4eccd..da7daff327 100644 --- a/pyrogram/client/types/messages_and_media/poll_option.py +++ b/pyrogram/client/types/messages_and_media/poll_option.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import pyrogram from ..object import Object diff --git a/pyrogram/client/types/messages_and_media/sticker.py b/pyrogram/client/types/messages_and_media/sticker.py index ef7a24f268..b434e451d2 100644 --- a/pyrogram/client/types/messages_and_media/sticker.py +++ b/pyrogram/client/types/messages_and_media/sticker.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# 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 diff --git a/pyrogram/client/types/messages_and_media/stripped_thumbnail.py b/pyrogram/client/types/messages_and_media/stripped_thumbnail.py index eb3da9739a..546f9a486a 100644 --- a/pyrogram/client/types/messages_and_media/stripped_thumbnail.py +++ b/pyrogram/client/types/messages_and_media/stripped_thumbnail.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# 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 types diff --git a/pyrogram/client/types/messages_and_media/thumbnail.py b/pyrogram/client/types/messages_and_media/thumbnail.py index 99c1bb1bdb..c48b8fb572 100644 --- a/pyrogram/client/types/messages_and_media/thumbnail.py +++ b/pyrogram/client/types/messages_and_media/thumbnail.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from struct import pack from typing import Union, List diff --git a/pyrogram/client/types/messages_and_media/venue.py b/pyrogram/client/types/messages_and_media/venue.py index a772a57853..a638bd4cdc 100644 --- a/pyrogram/client/types/messages_and_media/venue.py +++ b/pyrogram/client/types/messages_and_media/venue.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# 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 types diff --git a/pyrogram/client/types/messages_and_media/video.py b/pyrogram/client/types/messages_and_media/video.py index de33c919b0..59ce08bcc3 100644 --- a/pyrogram/client/types/messages_and_media/video.py +++ b/pyrogram/client/types/messages_and_media/video.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from struct import pack from typing import List diff --git a/pyrogram/client/types/messages_and_media/video_note.py b/pyrogram/client/types/messages_and_media/video_note.py index a5a7715139..1feb5a2d06 100644 --- a/pyrogram/client/types/messages_and_media/video_note.py +++ b/pyrogram/client/types/messages_and_media/video_note.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from struct import pack from typing import List diff --git a/pyrogram/client/types/messages_and_media/voice.py b/pyrogram/client/types/messages_and_media/voice.py index bc4fa58e0f..dec82af9a3 100644 --- a/pyrogram/client/types/messages_and_media/voice.py +++ b/pyrogram/client/types/messages_and_media/voice.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from struct import pack diff --git a/pyrogram/client/types/messages_and_media/webpage.py b/pyrogram/client/types/messages_and_media/webpage.py index 81c7681338..ebebfc1c87 100644 --- a/pyrogram/client/types/messages_and_media/webpage.py +++ b/pyrogram/client/types/messages_and_media/webpage.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# 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 types diff --git a/pyrogram/client/types/object.py b/pyrogram/client/types/object.py index 9ffe4e5af8..87b32dfa9f 100644 --- a/pyrogram/client/types/object.py +++ b/pyrogram/client/types/object.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from collections import OrderedDict from datetime import datetime diff --git a/pyrogram/client/types/update.py b/pyrogram/client/types/update.py index 48bbf42b6c..1e70944a5f 100644 --- a/pyrogram/client/types/update.py +++ b/pyrogram/client/types/update.py @@ -1,21 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . - +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . class StopPropagation(StopIteration): pass diff --git a/pyrogram/client/types/user_and_chats/__init__.py b/pyrogram/client/types/user_and_chats/__init__.py index d3f6ba7efe..371757eefe 100644 --- a/pyrogram/client/types/user_and_chats/__init__.py +++ b/pyrogram/client/types/user_and_chats/__init__.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from .chat import Chat from .chat_member import ChatMember diff --git a/pyrogram/client/types/user_and_chats/chat.py b/pyrogram/client/types/user_and_chats/chat.py index fd324622d7..3829e303f7 100644 --- a/pyrogram/client/types/user_and_chats/chat.py +++ b/pyrogram/client/types/user_and_chats/chat.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import Union, List diff --git a/pyrogram/client/types/user_and_chats/chat_member.py b/pyrogram/client/types/user_and_chats/chat_member.py index a71b21b677..a0157c2835 100644 --- a/pyrogram/client/types/user_and_chats/chat_member.py +++ b/pyrogram/client/types/user_and_chats/chat_member.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import pyrogram diff --git a/pyrogram/client/types/user_and_chats/chat_permissions.py b/pyrogram/client/types/user_and_chats/chat_permissions.py index 2910c618dc..1732692245 100644 --- a/pyrogram/client/types/user_and_chats/chat_permissions.py +++ b/pyrogram/client/types/user_and_chats/chat_permissions.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from pyrogram.api import types from ..object import Object diff --git a/pyrogram/client/types/user_and_chats/chat_photo.py b/pyrogram/client/types/user_and_chats/chat_photo.py index 29af93f3e2..1966b1be60 100644 --- a/pyrogram/client/types/user_and_chats/chat_photo.py +++ b/pyrogram/client/types/user_and_chats/chat_photo.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from struct import pack diff --git a/pyrogram/client/types/user_and_chats/chat_preview.py b/pyrogram/client/types/user_and_chats/chat_preview.py index 3c1d331527..fa48a319f3 100644 --- a/pyrogram/client/types/user_and_chats/chat_preview.py +++ b/pyrogram/client/types/user_and_chats/chat_preview.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from typing import List diff --git a/pyrogram/client/types/user_and_chats/dialog.py b/pyrogram/client/types/user_and_chats/dialog.py index 90942595e0..bbe06a2c69 100644 --- a/pyrogram/client/types/user_and_chats/dialog.py +++ b/pyrogram/client/types/user_and_chats/dialog.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import pyrogram diff --git a/pyrogram/client/types/user_and_chats/restriction.py b/pyrogram/client/types/user_and_chats/restriction.py index 5c91ce662b..abf04b7787 100644 --- a/pyrogram/client/types/user_and_chats/restriction.py +++ b/pyrogram/client/types/user_and_chats/restriction.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from pyrogram.api import types from ..object import Object diff --git a/pyrogram/client/types/user_and_chats/user.py b/pyrogram/client/types/user_and_chats/user.py index 1bf9ae854a..6607aad7cb 100644 --- a/pyrogram/client/types/user_and_chats/user.py +++ b/pyrogram/client/types/user_and_chats/user.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import html from typing import List diff --git a/pyrogram/connection/__init__.py b/pyrogram/connection/__init__.py index f0a1dd1db9..5bc87d33fa 100644 --- a/pyrogram/connection/__init__.py +++ b/pyrogram/connection/__init__.py @@ -1,19 +1,19 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from .connection import Connection diff --git a/pyrogram/connection/connection.py b/pyrogram/connection/connection.py index 7e3b1aa5b8..50fb3b7f32 100644 --- a/pyrogram/connection/connection.py +++ b/pyrogram/connection/connection.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import logging import threading diff --git a/pyrogram/connection/transport/__init__.py b/pyrogram/connection/transport/__init__.py index 01eadf8afb..f63b55875d 100644 --- a/pyrogram/connection/transport/__init__.py +++ b/pyrogram/connection/transport/__init__.py @@ -1,19 +1,19 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from .tcp import * diff --git a/pyrogram/connection/transport/tcp/__init__.py b/pyrogram/connection/transport/tcp/__init__.py index c0e74bebdd..9628d99e76 100644 --- a/pyrogram/connection/transport/tcp/__init__.py +++ b/pyrogram/connection/transport/tcp/__init__.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from .tcp_abridged import TCPAbridged from .tcp_abridged_o import TCPAbridgedO diff --git a/pyrogram/connection/transport/tcp/tcp.py b/pyrogram/connection/transport/tcp/tcp.py index e641fd8195..dbb01946ff 100644 --- a/pyrogram/connection/transport/tcp/tcp.py +++ b/pyrogram/connection/transport/tcp/tcp.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import ipaddress import logging diff --git a/pyrogram/connection/transport/tcp/tcp_abridged.py b/pyrogram/connection/transport/tcp/tcp_abridged.py index e247510e58..e828aa4c4b 100644 --- a/pyrogram/connection/transport/tcp/tcp_abridged.py +++ b/pyrogram/connection/transport/tcp/tcp_abridged.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import logging diff --git a/pyrogram/connection/transport/tcp/tcp_abridged_o.py b/pyrogram/connection/transport/tcp/tcp_abridged_o.py index 27eb912eaf..8417735c46 100644 --- a/pyrogram/connection/transport/tcp/tcp_abridged_o.py +++ b/pyrogram/connection/transport/tcp/tcp_abridged_o.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import logging import os diff --git a/pyrogram/connection/transport/tcp/tcp_full.py b/pyrogram/connection/transport/tcp/tcp_full.py index b33def6f34..366c1e65d4 100644 --- a/pyrogram/connection/transport/tcp/tcp_full.py +++ b/pyrogram/connection/transport/tcp/tcp_full.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import logging from binascii import crc32 diff --git a/pyrogram/connection/transport/tcp/tcp_intermediate.py b/pyrogram/connection/transport/tcp/tcp_intermediate.py index 5b80d7ea38..42d48f9d6d 100644 --- a/pyrogram/connection/transport/tcp/tcp_intermediate.py +++ b/pyrogram/connection/transport/tcp/tcp_intermediate.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import logging from struct import pack, unpack diff --git a/pyrogram/connection/transport/tcp/tcp_intermediate_o.py b/pyrogram/connection/transport/tcp/tcp_intermediate_o.py index 5ecac542b2..46b8d92816 100644 --- a/pyrogram/connection/transport/tcp/tcp_intermediate_o.py +++ b/pyrogram/connection/transport/tcp/tcp_intermediate_o.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import logging import os diff --git a/pyrogram/crypto/__init__.py b/pyrogram/crypto/__init__.py index 0c28f5e6a0..0774980d25 100644 --- a/pyrogram/crypto/__init__.py +++ b/pyrogram/crypto/__init__.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from .aes import AES from .kdf import KDF diff --git a/pyrogram/crypto/aes.py b/pyrogram/crypto/aes.py index e47df50034..47c9c0949c 100644 --- a/pyrogram/crypto/aes.py +++ b/pyrogram/crypto/aes.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import logging diff --git a/pyrogram/crypto/kdf.py b/pyrogram/crypto/kdf.py index c36e70899e..cb4ee96388 100644 --- a/pyrogram/crypto/kdf.py +++ b/pyrogram/crypto/kdf.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from hashlib import sha256 diff --git a/pyrogram/crypto/prime.py b/pyrogram/crypto/prime.py index 6ba90247dc..0ca59ea75d 100644 --- a/pyrogram/crypto/prime.py +++ b/pyrogram/crypto/prime.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from random import randint diff --git a/pyrogram/crypto/rsa.py b/pyrogram/crypto/rsa.py index 6f3dd8f1a6..268a91e476 100644 --- a/pyrogram/crypto/rsa.py +++ b/pyrogram/crypto/rsa.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from collections import namedtuple diff --git a/pyrogram/errors/__init__.py b/pyrogram/errors/__init__.py index 1b355c47a1..de049e372d 100644 --- a/pyrogram/errors/__init__.py +++ b/pyrogram/errors/__init__.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from .exceptions import * from .rpc_error import UnknownError diff --git a/pyrogram/errors/rpc_error.py b/pyrogram/errors/rpc_error.py index db2d050622..2586d8737e 100644 --- a/pyrogram/errors/rpc_error.py +++ b/pyrogram/errors/rpc_error.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import re from datetime import datetime diff --git a/pyrogram/session/__init__.py b/pyrogram/session/__init__.py index 5be5c00242..69d414c07b 100644 --- a/pyrogram/session/__init__.py +++ b/pyrogram/session/__init__.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from .auth import Auth from .session import Session diff --git a/pyrogram/session/auth.py b/pyrogram/session/auth.py index 72154220fd..c3d0b99cc7 100644 --- a/pyrogram/session/auth.py +++ b/pyrogram/session/auth.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import logging import time diff --git a/pyrogram/session/internals/__init__.py b/pyrogram/session/internals/__init__.py index cbffd59997..be75ecc7e3 100644 --- a/pyrogram/session/internals/__init__.py +++ b/pyrogram/session/internals/__init__.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from .data_center import DataCenter from .msg_factory import MsgFactory diff --git a/pyrogram/session/internals/data_center.py b/pyrogram/session/internals/data_center.py index 04f9e0137b..85e9c72ed7 100644 --- a/pyrogram/session/internals/data_center.py +++ b/pyrogram/session/internals/data_center.py @@ -1,21 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . - +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . class DataCenter: TEST = { diff --git a/pyrogram/session/internals/msg_factory.py b/pyrogram/session/internals/msg_factory.py index c39990cef1..e5144fe402 100644 --- a/pyrogram/session/internals/msg_factory.py +++ b/pyrogram/session/internals/msg_factory.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from pyrogram.api.functions import Ping from pyrogram.api.types import MsgsAck, HttpWait diff --git a/pyrogram/session/internals/msg_id.py b/pyrogram/session/internals/msg_id.py index 033ae3204b..e52815e94d 100644 --- a/pyrogram/session/internals/msg_id.py +++ b/pyrogram/session/internals/msg_id.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from threading import Lock from time import time diff --git a/pyrogram/session/internals/seq_no.py b/pyrogram/session/internals/seq_no.py index f224b96784..cded8343ee 100644 --- a/pyrogram/session/internals/seq_no.py +++ b/pyrogram/session/internals/seq_no.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from threading import Lock diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index 2306efc270..e2659871e2 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import logging import threading diff --git a/setup.py b/setup.py index 285a62bbfd..c516640d28 100644 --- a/setup.py +++ b/setup.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import os import re From c892e85d522e88dd2f55f29acae6728f10ba759c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 21 Mar 2020 16:01:48 +0100 Subject: [PATCH 0150/1185] Remove repeated word --- docs/source/topics/serializing.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/topics/serializing.rst b/docs/source/topics/serializing.rst index 4c0b23271f..b4d66f4675 100644 --- a/docs/source/topics/serializing.rst +++ b/docs/source/topics/serializing.rst @@ -10,7 +10,7 @@ For Humans - str(obj) If you want a nicely formatted, human readable JSON representation of any object in the API -- namely, any object from :doc:`Pyrogram types <../api/types/index>`, :doc:`raw functions <../telegram/functions/index>` and -:doc:`raw types <../telegram/types/index>` -- you can use use ``str(obj)``. +:doc:`raw types <../telegram/types/index>` -- you can use ``str(obj)``. .. code-block:: python From a2652f02b5ac27120a1e2c5de275788e085bd0b5 Mon Sep 17 00:00:00 2001 From: trenoduro Date: Sat, 21 Mar 2020 16:03:29 +0100 Subject: [PATCH 0151/1185] Fix RPCError 400 QUIZ_CORRECT_ANSWER_EMPTY (#367) * Fix RPCError 400 QUIZ_CORRECT_ANSWER_EMPTY * Fix RPCError 400 QUIZ_CORRECT_ANSWER_EMPTY --- pyrogram/client/methods/messages/send_poll.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/methods/messages/send_poll.py b/pyrogram/client/methods/messages/send_poll.py index f5293c3f4a..e0baf5a86f 100644 --- a/pyrogram/client/methods/messages/send_poll.py +++ b/pyrogram/client/methods/messages/send_poll.py @@ -110,7 +110,7 @@ def send_poll( public_voters=not is_anonymous or None, quiz=type == "quiz" or None ), - correct_answers=[bytes([correct_option_id])] if correct_option_id else None + correct_answers=None if correct_option_id is None else [bytes([correct_option_id])] ), message="", silent=disable_notification or None, From b913590cea6d103c18a583555e10a6f81a96a833 Mon Sep 17 00:00:00 2001 From: Yusuf_M_Thon_iD <32301831+Sunda001@users.noreply.github.com> Date: Sat, 21 Mar 2020 22:03:54 +0700 Subject: [PATCH 0152/1185] add missing file_ref in set_chat_photo (#369) --- pyrogram/client/methods/chats/set_chat_photo.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/pyrogram/client/methods/chats/set_chat_photo.py b/pyrogram/client/methods/chats/set_chat_photo.py index 461b3474aa..3a9967111b 100644 --- a/pyrogram/client/methods/chats/set_chat_photo.py +++ b/pyrogram/client/methods/chats/set_chat_photo.py @@ -27,7 +27,8 @@ class SetChatPhoto(BaseClient): def set_chat_photo( self, chat_id: Union[int, str], - photo: str + photo: str, + file_ref: str = None ) -> bool: """Set a new profile photo for the chat. @@ -40,6 +41,10 @@ def set_chat_photo( photo (``str``): New chat photo. You can pass a :obj:`Photo` file_id or a file path to upload a new photo from your local machine. + + file_ref (``str``, *optional*): + A valid file reference obtained by a recently fetched media message. + To be used in combination with a file id in case a file reference is needed. Returns: ``bool``: True on success. @@ -54,14 +59,14 @@ def set_chat_photo( app.set_chat_photo(chat_id, "photo.jpg") # Set chat photo using an exiting Photo file_id - app.set_chat_photo(chat_id, photo.file_id) + app.set_chat_photo(chat_id, photo.file_id, photo.file_ref) """ peer = self.resolve_peer(chat_id) if os.path.exists(photo): photo = types.InputChatUploadedPhoto(file=self.save_file(photo)) else: - photo = utils.get_input_media_from_file_id(photo) + photo = utils.get_input_media_from_file_id(photo, file_ref, 2) photo = types.InputChatPhoto(id=photo.id) if isinstance(peer, types.InputPeerChat): From 42d1f70481e8bc87017e9636e3036e1c775e88da Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 28 Mar 2020 13:23:54 +0100 Subject: [PATCH 0153/1185] Update API schema to Layer 111 --- compiler/api/source/main_api.tl | 51 +++++++++++++++++++++++++++++---- 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl index 55028088e5..4a681f09c0 100644 --- a/compiler/api/source/main_api.tl +++ b/compiler/api/source/main_api.tl @@ -50,6 +50,7 @@ inputMediaGame#d33f43f3 id:InputGame = InputMedia; inputMediaInvoice#f4e096c3 flags:# title:string description:string photo:flags.0?InputWebDocument invoice:Invoice payload:bytes provider:string provider_data:DataJSON start_param:string = InputMedia; inputMediaGeoLive#ce4e82fd flags:# stopped:flags.0?true geo_point:InputGeoPoint period:flags.1?int = InputMedia; inputMediaPoll#abe9ca25 flags:# poll:Poll correct_answers:flags.0?Vector = InputMedia; +inputMediaDice#aeffa807 = InputMedia; inputChatPhotoEmpty#1ca48f57 = InputChatPhoto; inputChatUploadedPhoto#927c55b4 file:InputFile = InputChatPhoto; @@ -106,7 +107,7 @@ channel#d31a961e flags:# creator:flags.0?true left:flags.2?true broadcast:flags. channelForbidden#289da732 flags:# broadcast:flags.5?true megagroup:flags.8?true id:int access_hash:long title:string until_date:flags.16?int = Chat; chatFull#1b7c9db3 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:int about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int = ChatFull; -channelFull#2d895c74 flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_view_stats:flags.12?true can_set_location:flags.16?true has_scheduled:flags.19?true id:int about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?int migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?int location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int pts:int = ChatFull; +channelFull#f0e6672a flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_view_stats:flags.12?true can_set_location:flags.16?true has_scheduled:flags.19?true id:int about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?int migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?int location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int = ChatFull; chatParticipant#c8d7493e user_id:int inviter_id:int date:int = ChatParticipant; chatParticipantCreator#da13538a user_id:int = ChatParticipant; @@ -134,6 +135,7 @@ messageMediaGame#fdb19008 game:Game = MessageMedia; messageMediaInvoice#84551347 flags:# shipping_address_requested:flags.1?true test:flags.3?true title:string description:string photo:flags.0?WebDocument receipt_msg_id:flags.2?int currency:string total_amount:long start_param:string = MessageMedia; messageMediaGeoLive#7c3c2609 geo:GeoPoint period:int = MessageMedia; messageMediaPoll#4bd6e798 poll:Poll results:PollResults = MessageMedia; +messageMediaDice#638fe46b value:int = MessageMedia; messageActionEmpty#b6aef7b0 = MessageAction; messageActionChatCreate#a6638b9a title:string users:Vector = MessageAction; @@ -330,6 +332,9 @@ updateTheme#8216fba3 theme:Theme = Update; updateGeoLiveViewed#871fb939 peer:Peer msg_id:int = Update; updateLoginToken#564fe691 = Update; updateMessagePollVote#42f88f2c poll_id:long user_id:int options:Vector = Update; +updateDialogFilter#26ffde7d flags:# id:int filter:flags.0?DialogFilter = Update; +updateDialogFilterOrder#a5d72105 order:Vector = Update; +updateDialogFilters#3504914f = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -480,7 +485,7 @@ messages.affectedMessages#84d19185 pts:int pts_count:int = messages.AffectedMess webPageEmpty#eb1477e8 id:long = WebPage; webPagePending#c586da1c id:long date:int = WebPage; webPage#e89c45b2 flags:# id:long url:string display_url:string hash:int type:flags.0?string site_name:flags.1?string title:flags.2?string description:flags.3?string photo:flags.4?Photo embed_url:flags.5?string embed_type:flags.5?string embed_width:flags.6?int embed_height:flags.6?int duration:flags.7?int author:flags.8?string document:flags.9?Document cached_page:flags.10?Page attributes:flags.12?Vector = WebPage; -webPageNotModified#85849473 = WebPage; +webPageNotModified#7311ca11 flags:# cached_page_views:flags.0?int = WebPage; authorization#ad01d61d flags:# current:flags.0?true official_app:flags.1?true password_pending:flags.2?true hash:long device_model:string platform:string system_version:string api_id:int app_name:string app_version:string date_created:int date_active:int ip:string country:string region:string = Authorization; @@ -506,6 +511,7 @@ inputStickerSetEmpty#ffb62b95 = InputStickerSet; inputStickerSetID#9de7a269 id:long access_hash:long = InputStickerSet; inputStickerSetShortName#861cc8a0 short_name:string = InputStickerSet; inputStickerSetAnimatedEmoji#28703c8 = InputStickerSet; +inputStickerSetDice#79e21a53 = InputStickerSet; stickerSet#eeb46f27 flags:# archived:flags.1?true official:flags.2?true masks:flags.3?true animated:flags.5?true installed_date:flags.0?int id:long access_hash:long title:string short_name:string thumb:flags.4?PhotoSize thumb_dc_id:flags.4?int count:int hash:int = StickerSet; @@ -552,6 +558,7 @@ messageEntityCashtag#4c4e743f offset:int length:int = MessageEntity; messageEntityUnderline#9c4e7e8b offset:int length:int = MessageEntity; messageEntityStrike#bf0693d4 offset:int length:int = MessageEntity; messageEntityBlockquote#20df5d0 offset:int length:int = MessageEntity; +messageEntityBankCard#761e6af4 offset:int length:int = MessageEntity; inputChannelEmpty#ee8c1e86 = InputChannel; inputChannel#afeb712e channel_id:int access_hash:long = InputChannel; @@ -800,7 +807,7 @@ phoneCallDiscarded#50ca4de1 flags:# need_rating:flags.2?true need_debug:flags.3? phoneConnection#9d4c17c0 id:long ip:string ipv6:string port:int peer_tag:bytes = PhoneConnection; -phoneCallProtocol#a2bb35cb flags:# udp_p2p:flags.0?true udp_reflector:flags.1?true min_layer:int max_layer:int = PhoneCallProtocol; +phoneCallProtocol#fc878fc8 flags:# udp_p2p:flags.0?true udp_reflector:flags.1?true min_layer:int max_layer:int library_versions:Vector = PhoneCallProtocol; phone.phoneCall#ec82e140 phone_call:PhoneCall users:Vector = phone.PhoneCall; @@ -986,7 +993,7 @@ pageListOrderedItemBlocks#98dd8936 num:string blocks:Vector = PageLis pageRelatedArticle#b390dc08 flags:# url:string webpage_id:long title:flags.0?string description:flags.1?string photo_id:flags.2?long author:flags.3?string published_date:flags.4?int = PageRelatedArticle; -page#ae891bec flags:# part:flags.0?true rtl:flags.1?true v2:flags.2?true url:string blocks:Vector photos:Vector documents:Vector = Page; +page#98657f0d flags:# part:flags.0?true rtl:flags.1?true v2:flags.2?true url:string blocks:Vector photos:Vector documents:Vector views:flags.3?int = Page; help.supportName#8c05f1c9 name:string = help.SupportName; @@ -1051,6 +1058,7 @@ channelLocationEmpty#bfb5ad8b = ChannelLocation; channelLocation#209b82db geo_point:GeoPoint address:string = ChannelLocation; peerLocated#ca461b5d peer:Peer expires:int distance:int = PeerLocated; +peerSelfLocated#f8ec284b expires:int = PeerLocated; restrictionReason#d072acb4 platform:string reason:string text:string = RestrictionReason; @@ -1088,6 +1096,28 @@ messageUserVoteMultiple#e8fe0de user_id:int options:Vector date:int = Mes messages.votesList#823f649 flags:# count:int votes:Vector users:Vector next_offset:flags.0?string = messages.VotesList; +bankCardOpenUrl#f568028a url:string name:string = BankCardOpenUrl; + +payments.bankCardData#3e24e573 title:string open_urls:Vector = payments.BankCardData; + +dialogFilter#7438f7e8 flags:# contacts:flags.0?true non_contacts:flags.1?true groups:flags.2?true broadcasts:flags.3?true bots:flags.4?true exclude_muted:flags.11?true exclude_read:flags.12?true exclude_archived:flags.13?true id:int title:string emoticon:flags.25?string pinned_peers:Vector include_peers:Vector exclude_peers:Vector = DialogFilter; + +dialogFilterSuggested#77744d4a filter:DialogFilter description:string = DialogFilterSuggested; + +statsDateRangeDays#b637edaf min_date:int max_date:int = StatsDateRangeDays; + +statsAbsValueAndPrev#cb43acde current:double previous:double = StatsAbsValueAndPrev; + +statsPercentValue#cbce2fe0 part:double total:double = StatsPercentValue; + +statsGraphAsync#4a27eb2d token:string = StatsGraph; +statsGraphError#bedc9822 error:string = StatsGraph; +statsGraph#8ea464b6 flags:# json:DataJSON zoom_token:flags.0?string = StatsGraph; + +messageInteractionCounters#ad4fc9bd msg_id:int views:int forwards:int = MessageInteractionCounters; + +stats.broadcastStats#bdf78394 period:StatsDateRangeDays followers:StatsAbsValueAndPrev views_per_post:StatsAbsValueAndPrev shares_per_post:StatsAbsValueAndPrev enabled_notifications:StatsPercentValue growth_graph:StatsGraph followers_graph:StatsGraph mute_graph:StatsGraph top_hours_graph:StatsGraph interactions_graph:StatsGraph iv_interactions_graph:StatsGraph views_by_source_graph:StatsGraph new_followers_by_source_graph:StatsGraph languages_graph:StatsGraph recent_message_interactions:Vector = stats.BroadcastStats; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -1205,7 +1235,7 @@ contacts.getSaved#82f1e39f = Vector; contacts.toggleTopPeers#8514bdda enabled:Bool = Bool; contacts.addContact#e8f463d0 flags:# add_phone_privacy_exception:flags.0?true id:InputUser first_name:string last_name:string phone:string = Updates; contacts.acceptContact#f831a20f id:InputUser = Updates; -contacts.getLocated#a356056 geo_point:InputGeoPoint = Updates; +contacts.getLocated#d348bc44 flags:# background:flags.1?true geo_point:InputGeoPoint self_expires:flags.0?int = Updates; messages.getMessages#63c66506 id:Vector = messages.Messages; messages.getDialogs#a0ee3b73 flags:# exclude_pinned:flags.0?true folder_id:flags.1?int offset_date:int offset_id:int offset_peer:InputPeer limit:int hash:int = messages.Dialogs; @@ -1325,6 +1355,11 @@ messages.getScheduledMessages#bdbb0464 peer:InputPeer id:Vector = messages. messages.sendScheduledMessages#bd38850a peer:InputPeer id:Vector = Updates; messages.deleteScheduledMessages#59ae2b16 peer:InputPeer id:Vector = Updates; messages.getPollVotes#b86e380e flags:# peer:InputPeer id:int option:flags.0?bytes offset:flags.1?string limit:int = messages.VotesList; +messages.toggleStickerSets#b5052fea flags:# uninstall:flags.0?true archive:flags.1?true unarchive:flags.2?true stickersets:Vector = Bool; +messages.getDialogFilters#f19ed96d = Vector; +messages.getSuggestedDialogFilters#a29cd42c = Vector; +messages.updateDialogFilter#1ad4a04a flags:# id:int filter:flags.0?DialogFilter = Bool; +messages.updateDialogFiltersOrder#c563c1e4 order:Vector = Bool; updates.getState#edd4882a = updates.State; updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference; @@ -1409,6 +1444,7 @@ payments.validateRequestedInfo#770a8e74 flags:# save:flags.0?true msg_id:int inf payments.sendPaymentForm#2b8879b3 flags:# msg_id:int requested_info_id:flags.0?string shipping_option_id:flags.1?string credentials:InputPaymentCredentials = payments.PaymentResult; payments.getSavedInfo#227d824b = payments.SavedInfo; payments.clearSavedInfo#d83d70c1 flags:# credentials:flags.0?true info:flags.1?true = Bool; +payments.getBankCardData#2e79d779 number:string = payments.BankCardData; stickers.createStickerSet#9bd86e6a flags:# masks:flags.0?true user_id:InputUser title:string short_name:string stickers:Vector = messages.StickerSet; stickers.removeStickerFromSet#f7760f51 sticker:InputDocument = messages.StickerSet; @@ -1433,4 +1469,7 @@ langpack.getLanguage#6a596502 lang_pack:string lang_code:string = LangPackLangua folders.editPeerFolders#6847d0ab folder_peers:Vector = Updates; folders.deleteFolder#1c295881 folder_id:int = Updates; -// LAYER 109 +stats.getBroadcastStats#ab42441a flags:# dark:flags.0?true channel:InputChannel = stats.BroadcastStats; +stats.loadAsyncGraph#621d5fa0 flags:# token:string x:flags.0?long = StatsGraph; + +// LAYER 111 \ No newline at end of file From 1b15b1e3b8aabbef2c452e16b0b6af6cd44587fc Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 30 Mar 2020 11:19:58 +0200 Subject: [PATCH 0154/1185] Clarify docs --- 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 42a3f3304d..0a7827fb24 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -494,7 +494,7 @@ def sign_up(self, phone_number: str, phone_code_hash: str, first_name: str, last New user first name. last_name (``str``, *optional*): - New user last name. Defaults to "" (empty string). + New user last name. Defaults to "" (empty string, no last name). Returns: :obj:`User`: On success, the new registered user is returned. From f5c78ed435ae9351f7f5ec73848b637ddb15110e Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 30 Mar 2020 11:41:12 +0200 Subject: [PATCH 0155/1185] Update requirements.txt --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index a610478b21..9e6f759035 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ pyaes==1.6.1 -pysocks==1.7.0 \ No newline at end of file +pysocks==1.7.1 \ No newline at end of file From e741bcdb26cf22235e248040a7df25a1e505c5f5 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 30 Mar 2020 11:47:25 +0200 Subject: [PATCH 0156/1185] Drop [fast] setup directive --- docs/source/intro/install.rst | 2 +- setup.py | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/docs/source/intro/install.rst b/docs/source/intro/install.rst index 1749245fcf..edd4316102 100644 --- a/docs/source/intro/install.rst +++ b/docs/source/intro/install.rst @@ -24,7 +24,7 @@ Install Pyrogram .. code-block:: text - $ pip3 install -U pyrogram[fast] + $ pip3 install -U pyrogram tgcrypto Bleeding Edge ------------- diff --git a/setup.py b/setup.py index c516640d28..0e9bb2dbfa 100644 --- a/setup.py +++ b/setup.py @@ -175,9 +175,6 @@ def run(self): }, zip_safe=False, install_requires=requires, - extras_require={ - "fast": ["tgcrypto==1.2.0"] - }, cmdclass={ "clean": Clean, "generate": Generate From 0681ea72f7a3262ef9b2618ad362ceb38bc47bb3 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 30 Mar 2020 12:02:27 +0200 Subject: [PATCH 0157/1185] Add FAQ about webhooks --- docs/source/faq.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/source/faq.rst b/docs/source/faq.rst index b506514ca5..dddb10e465 100644 --- a/docs/source/faq.rst +++ b/docs/source/faq.rst @@ -91,6 +91,18 @@ 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. +Can I use Webhooks? +------------------- + +Lots of people ask this question because they are used to the bot API, but things are different in Pyrogram! + +There is no webhook in Pyrogram, simply because there is no HTTP involved, by default. However, a similar technique is +being used to make receiving updates efficient. + +Pyrogram uses persistent connections via TCP sockets to interact with the server and instead of actively asking for +updates every time (polling), Pyrogram will simply sit down and wait for the server to send updates by itself +the very moment they are available (server push). + Can I use the same file_id across different accounts? ----------------------------------------------------- From 441b89a8acc1ea550aaa80a357400d13009e56c3 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 30 Mar 2020 13:01:52 +0200 Subject: [PATCH 0158/1185] Add FAQs about Flood limits and sqlite3.OperationalError --- docs/source/faq.rst | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/docs/source/faq.rst b/docs/source/faq.rst index dddb10e465..80df70b519 100644 --- a/docs/source/faq.rst +++ b/docs/source/faq.rst @@ -334,6 +334,11 @@ your system or find and kill the process that is locking the database. On Unix b If you want to run multiple clients on the same account, you must authorize your account (either user or bot) from the beginning every time, and use different session names for each parallel client you are going to use. +sqlite3.OperationalError: unable to open database file +------------------------------------------------------ + +Stackoverflow to the rescue: https://stackoverflow.com/questions/4636970 + My verification code expires immediately! ----------------------------------------- @@ -343,6 +348,25 @@ messages you send and if an active verification code is found it will immediatel The reason behind this is to protect unaware users from giving their account access to any potential scammer, but if you legitimately want to share your account(s) verification codes, consider scrambling them, e.g. ``12345`` → ``1-2-3-4-5``. +How can avoid Flood Waits? +-------------------------- + +Long story short: make less requests, and remember that the API is designed to be used by official apps, by real people; +anything above normal usage could be limited. + +This question is being asked quite a lot of times, but the bottom line is that nobody knows the exact limits and it's +unlikely that such information will be ever disclosed, because otherwise people could easily circumvent them and defeat +their whole purpose. + +Do also note that Telegram wants to be a safe and reliable place and that limits exist to protect itself from abuses. +Having said that, here's some insights about limits: + +- They are tuned by Telegram based on real people usage and can change anytime. +- Some limits are be applied to single sessions, some others apply to the whole account. +- Limits vary based on methods and the arguments passed to methods. For example: log-ins are expensive and thus have + stricter limits; replying to a user command could cause a flood wait in case the user starts flooding, but + such limit will only be applied to that particular chat (i.e.: other users are not affected). + My account has been deactivated/limited! ---------------------------------------- From 77ecdd21fb8080643020e7969a8b0eccc61c52e2 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 30 Mar 2020 13:02:23 +0200 Subject: [PATCH 0159/1185] Move docs scripts into a dedicated folder --- docs/{ => scripts}/releases.py | 2 +- docs/{ => scripts}/sitemap.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename docs/{ => scripts}/releases.py (98%) rename docs/{ => scripts}/sitemap.py (99%) diff --git a/docs/releases.py b/docs/scripts/releases.py similarity index 98% rename from docs/releases.py rename to docs/scripts/releases.py index 9b7388f994..ce8ab385b2 100644 --- a/docs/releases.py +++ b/docs/scripts/releases.py @@ -24,7 +24,7 @@ import requests URL = "https://api.github.com/repos/pyrogram/pyrogram/releases" -DEST = Path("source/releases") +DEST = Path("../source/releases") INTRO = """ Release Notes ============= diff --git a/docs/sitemap.py b/docs/scripts/sitemap.py similarity index 99% rename from docs/sitemap.py rename to docs/scripts/sitemap.py index 4b08f218a5..cc3da354d7 100644 --- a/docs/sitemap.py +++ b/docs/scripts/sitemap.py @@ -66,7 +66,7 @@ def search(path): urls.append((path, now(), *dirs[folder])) - search("source") + search("../source") urls.sort(key=lambda x: x[3], reverse=True) From fd0908227efc7cbff126c265ce13ea1bb493bff6 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 30 Mar 2020 13:03:02 +0200 Subject: [PATCH 0160/1185] Use -U instead of --upgrade (more concise) --- docs/source/topics/tgcrypto.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/topics/tgcrypto.rst b/docs/source/topics/tgcrypto.rst index e01bb15c00..9dd4c8ac98 100644 --- a/docs/source/topics/tgcrypto.rst +++ b/docs/source/topics/tgcrypto.rst @@ -12,7 +12,7 @@ Installation .. code-block:: bash - $ pip3 install --upgrade tgcrypto + $ pip3 install -U tgcrypto .. note:: Being a C extension for Python, TgCrypto is an optional but *highly recommended* dependency; when TgCrypto is not detected in your system, Pyrogram will automatically fall back to PyAES and will show you a warning. From 75a39962f26eb618a3a52ce9d55f659ec2e2e114 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 30 Mar 2020 13:03:35 +0200 Subject: [PATCH 0161/1185] Update docs copyright year --- docs/source/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 40caccb0d3..519a96d972 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -28,7 +28,7 @@ FriendlyStyle.background_color = "#f3f2f1" project = "Pyrogram" -copyright = "2017-2019, Dan" +copyright = "2017-2020, Dan" author = "Dan" extensions = [ From 2f975c447ef3faa66ee957ea0b39087fc07b9b3b Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 30 Mar 2020 13:05:24 +0200 Subject: [PATCH 0162/1185] Add initial explanation on how to build docs --- docs/README.md | 8 ++++++++ docs/requirements.txt | 5 +++++ 2 files changed, 13 insertions(+) create mode 100644 docs/README.md create mode 100644 docs/requirements.txt diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000000..1c929447d7 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,8 @@ +# Pyrogram Docs + +- Install requirements. +- Install `pandoc` and `latexmk`. +- HTML: `make html` +- PDF: `make latexpdf` + +TODO: Explain better \ No newline at end of file diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000000..3ebe0d49d2 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1,5 @@ +sphinx +sphinx_rtd_theme +sphinx_copybutton +pypandoc +requests \ No newline at end of file From 746a6eb477c58ef7d643461970d45a497281d482 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 30 Mar 2020 14:38:57 +0200 Subject: [PATCH 0163/1185] Add support for Dice objects - add send_dice - add Dice class --- compiler/docs/compiler.py | 2 + pyrogram/client/methods/messages/__init__.py | 4 +- pyrogram/client/methods/messages/send_dice.py | 94 +++++++++++++++++++ .../types/messages_and_media/__init__.py | 3 +- .../client/types/messages_and_media/dice.py | 44 +++++++++ .../types/messages_and_media/message.py | 11 ++- 6 files changed, 154 insertions(+), 4 deletions(-) create mode 100644 pyrogram/client/methods/messages/send_dice.py create mode 100644 pyrogram/client/types/messages_and_media/dice.py diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index 346b47c739..20e0dd25c8 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -174,6 +174,7 @@ def get_title_list(s: str) -> list: vote_poll stop_poll retract_vote + send_dice download_media """, chats=""" @@ -334,6 +335,7 @@ def get_title_list(s: str) -> list: WebPage Poll PollOption + Dice """, bots_keyboard=""" Bots & Keyboards diff --git a/pyrogram/client/methods/messages/__init__.py b/pyrogram/client/methods/messages/__init__.py index 147e225a91..eaf6f7b0db 100644 --- a/pyrogram/client/methods/messages/__init__.py +++ b/pyrogram/client/methods/messages/__init__.py @@ -51,6 +51,7 @@ from .send_voice import SendVoice from .stop_poll import StopPoll from .vote_poll import VotePoll +from .send_dice import SendDice class Messages( @@ -88,6 +89,7 @@ class Messages( EditInlineText, EditInlineCaption, EditInlineMedia, - EditInlineReplyMarkup + EditInlineReplyMarkup, + SendDice ): pass diff --git a/pyrogram/client/methods/messages/send_dice.py b/pyrogram/client/methods/messages/send_dice.py new file mode 100644 index 0000000000..c5f95d4bb9 --- /dev/null +++ b/pyrogram/client/methods/messages/send_dice.py @@ -0,0 +1,94 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan +# +# 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 Union + +import pyrogram +from pyrogram.api import functions, types +from pyrogram.client.ext import BaseClient + + +class SendDice(BaseClient): + def send_dice( + self, + chat_id: Union[int, str], + disable_notification: bool = None, + reply_to_message_id: int = None, + schedule_date: int = None, + reply_markup: Union[ + "pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply" + ] = None + ) -> Union["pyrogram.Message", None]: + """Send a dice. + + Parameters: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + For your personal cloud (Saved Messages) you can simply use "me" or "self". + For a contact that exists in your Telegram address book you can use his phone number (str). + + disable_notification (``bool``, *optional*): + Sends the message silently. + Users will receive a notification with no sound. + + reply_to_message_id (``int``, *optional*): + If the message is a reply, ID of the original message. + + schedule_date (``int``, *optional*): + Date when the message will be automatically sent. Unix time. + + reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + Additional interface options. An object for an inline keyboard, custom reply keyboard, + instructions to remove reply keyboard or to force a reply from the user. + + Returns: + :obj:`Message`: On success, the sent dice message is returned. + + Example: + .. code-block:: python + + app.send_dice("pyrogramlounge") + """ + + r = self.send( + functions.messages.SendMedia( + peer=self.resolve_peer(chat_id), + media=types.InputMediaDice(), + silent=disable_notification or None, + reply_to_msg_id=reply_to_message_id, + random_id=self.rnd_id(), + schedule_date=schedule_date, + reply_markup=reply_markup.write() if reply_markup else None, + message="" + ) + ) + + for i in r.updates: + if isinstance( + i, + (types.UpdateNewMessage, types.UpdateNewChannelMessage, types.UpdateNewScheduledMessage) + ): + return pyrogram.Message._parse( + self, i.message, + {i.id: i for i in r.users}, + {i.id: i for i in r.chats}, + is_scheduled=isinstance(i, types.UpdateNewScheduledMessage) + ) diff --git a/pyrogram/client/types/messages_and_media/__init__.py b/pyrogram/client/types/messages_and_media/__init__.py index e514fb8d94..d24240436b 100644 --- a/pyrogram/client/types/messages_and_media/__init__.py +++ b/pyrogram/client/types/messages_and_media/__init__.py @@ -35,8 +35,9 @@ from .video_note import VideoNote from .voice import Voice from .webpage import WebPage +from .dice import Dice __all__ = [ "Animation", "Audio", "Contact", "Document", "Game", "Location", "Message", "MessageEntity", "Photo", "Thumbnail", - "StrippedThumbnail", "Poll", "PollOption", "Sticker", "Venue", "Video", "VideoNote", "Voice", "WebPage" + "StrippedThumbnail", "Poll", "PollOption", "Sticker", "Venue", "Video", "VideoNote", "Voice", "WebPage", "Dice" ] diff --git a/pyrogram/client/types/messages_and_media/dice.py b/pyrogram/client/types/messages_and_media/dice.py new file mode 100644 index 0000000000..c579a89041 --- /dev/null +++ b/pyrogram/client/types/messages_and_media/dice.py @@ -0,0 +1,44 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan +# +# 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 struct import pack +from typing import List + +import pyrogram +from pyrogram.api import types +from .thumbnail import Thumbnail +from ..object import Object +from ...ext.utils import encode_file_id, encode_file_ref + + +class Dice(Object): + """A dice containing a value that is randomly generated by Telegram. + + Parameters: + value (``int``): + Dice value, 1-6. + """ + + def __init__(self, *, client: "pyrogram.BaseClient" = None, value: int): + super().__init__(client) + + self.value = value + + @staticmethod + def _parse(client, dice: types.MessageMediaDice) -> "Dice": + return Dice(value=dice.value, client=client) diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index 897cf3455b..f566849b22 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -184,6 +184,9 @@ class Message(Object, Update): poll (:obj:`Poll`, *optional*): Message is a native poll, information about the poll. + dice (:obj:`Dice`, *optional*): + A dice containing a value that is randomly generated by Telegram. + new_chat_members (List of :obj:`User`, *optional*): New members that were added to the group or supergroup and information about them (the bot itself may be one of these members). @@ -306,6 +309,7 @@ def __init__( venue: "pyrogram.Venue" = None, web_page: bool = None, poll: "pyrogram.Poll" = None, + dice: "pyrogram.Dice" = None, new_chat_members: List[User] = None, left_chat_member: User = None, new_chat_title: str = None, @@ -370,6 +374,7 @@ def __init__( self.venue = venue self.web_page = web_page self.poll = poll + self.dice = dice self.new_chat_members = new_chat_members self.left_chat_member = left_chat_member self.new_chat_title = new_chat_title @@ -512,6 +517,7 @@ def _parse(client, message: types.Message or types.MessageService or types.Messa document = None web_page = None poll = None + dice = None media = message.media @@ -570,10 +576,10 @@ def _parse(client, message: types.Message or types.MessageService or types.Messa web_page = pyrogram.WebPage._parse(client, media.webpage) else: media = None - elif isinstance(media, types.MessageMediaPoll): poll = pyrogram.Poll._parse(client, media) - + elif isinstance(media, types.MessageMediaDice): + dice = pyrogram.Dice._parse(client, media) else: media = None @@ -643,6 +649,7 @@ def _parse(client, message: types.Message or types.MessageService or types.Messa document=document, web_page=web_page, poll=poll, + dice=dice, views=message.views, via_bot=User._parse(client, users.get(message.via_bot_id, None)), outgoing=message.out, From 42cd135009fc60717eafe5e58a116a5f30ca2f03 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 30 Mar 2020 14:39:16 +0200 Subject: [PATCH 0164/1185] Add missing download_media progress example --- pyrogram/client/methods/messages/download_media.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pyrogram/client/methods/messages/download_media.py b/pyrogram/client/methods/messages/download_media.py index 07f7768dea..220543977e 100644 --- a/pyrogram/client/methods/messages/download_media.py +++ b/pyrogram/client/methods/messages/download_media.py @@ -99,6 +99,12 @@ def download_media( # Download from file id app.download_media("CAADBAADyg4AAvLQYAEYD4F7vcZ43AI") + + # Keep track of the progress while downloading + def progress(current, total): + print("{:.1f}%".format(current * 100 / total)) + + app.download_media(message, progress=progress) """ error_message = "This message doesn't contain any downloadable media" available_media = ("audio", "document", "photo", "sticker", "animation", "video", "voice", "video_note") From 75ad20bc577a10db05c097bf238334feb9e1a81a Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 30 Mar 2020 14:39:36 +0200 Subject: [PATCH 0165/1185] Fix wrong lines emphasize --- 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 105796b9da..d8ee87afd5 100644 --- a/pyrogram/client/methods/messages/send_message.py +++ b/pyrogram/client/methods/messages/send_message.py @@ -80,7 +80,7 @@ def send_message( Example: .. code-block:: python - :emphasize-lines: 2,5,8,11,21-23,26-33 + :emphasize-lines: 2,5,8,11,21-23,26-32 # Simple example app.send_message("haskell", "Thanks for creating **Pyrogram**!") From b9c50b0ae0d4ea8785ed3be96d43cabc0d6fa9bc Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 30 Mar 2020 15:24:07 +0200 Subject: [PATCH 0166/1185] Add extended chat permissions --- .../types/user_and_chats/chat_member.py | 32 +++++++++++---- .../types/user_and_chats/chat_permissions.py | 41 +++++++++++++++---- 2 files changed, 55 insertions(+), 18 deletions(-) diff --git a/pyrogram/client/types/user_and_chats/chat_member.py b/pyrogram/client/types/user_and_chats/chat_member.py index a0157c2835..203be137bf 100644 --- a/pyrogram/client/types/user_and_chats/chat_member.py +++ b/pyrogram/client/types/user_and_chats/chat_member.py @@ -104,9 +104,17 @@ class ChatMember(Object): Restricted only. True, if the user is allowed to send audios, documents, photos, videos, video notes and voice notes. - can_send_other_messages (``bool``, *optional*): - Restricted only. - True, if the user is allowed to send animations, games, stickers and use inline bots. + can_send_stickers (``bool``, *optional*): + True, if the user is allowed to send stickers, implies can_send_media_messages. + + can_send_animations (``bool``, *optional*): + True, if the user is allowed to send animations (GIFs), implies can_send_media_messages. + + can_send_games (``bool``, *optional*): + True, if the user is allowed to send games, implies can_send_media_messages. + + can_use_inline_bots (``bool``, *optional*): + True, if the user is allowed to use inline bots, implies can_send_media_messages. can_add_web_page_previews (``bool``, *optional*): Restricted only. @@ -145,7 +153,10 @@ def __init__( # Restricted user permissions can_send_messages: bool = None, # Text, contacts, locations and venues can_send_media_messages: bool = None, # Audios, documents, photos, videos, video notes and voice notes - can_send_other_messages: bool = None, # Animations (GIFs), games, stickers, inline bot results + can_send_stickers: bool = None, + can_send_animations: bool = None, + can_send_games: bool = None, + can_use_inline_bots: bool = None, can_add_web_page_previews: bool = None, can_send_polls: bool = None ): @@ -173,7 +184,10 @@ def __init__( self.can_send_messages = can_send_messages self.can_send_media_messages = can_send_media_messages - self.can_send_other_messages = can_send_other_messages + self.can_send_stickers = can_send_stickers + self.can_send_animations = can_send_animations + self.can_send_games = can_send_games + self.can_use_inline_bots = can_use_inline_bots self.can_add_web_page_previews = can_add_web_page_previews self.can_send_polls = can_send_polls @@ -246,10 +260,10 @@ def _parse(client, member, users) -> "ChatMember": restricted_by=pyrogram.User._parse(client, users[member.kicked_by]), can_send_messages=not denied_permissions.send_messages, can_send_media_messages=not denied_permissions.send_media, - can_send_other_messages=( - not denied_permissions.send_stickers or not denied_permissions.send_gifs or - not denied_permissions.send_games or not denied_permissions.send_inline - ), + can_send_stickers=not denied_permissions.send_stickers, + can_send_animations=not denied_permissions.send_gifs, + can_send_games=not denied_permissions.send_games, + can_use_inline_bots=not denied_permissions.send_inline, can_add_web_page_previews=not denied_permissions.embed_links, can_send_polls=not denied_permissions.send_polls, can_change_info=not denied_permissions.change_info, diff --git a/pyrogram/client/types/user_and_chats/chat_permissions.py b/pyrogram/client/types/user_and_chats/chat_permissions.py index 1732692245..03d3e0721c 100644 --- a/pyrogram/client/types/user_and_chats/chat_permissions.py +++ b/pyrogram/client/types/user_and_chats/chat_permissions.py @@ -26,6 +26,15 @@ class ChatPermissions(Object): Some permissions make sense depending on the context: default chat permissions, restricted/kicked member or administrators in groups or channels. + .. note:: + + Pyrogram's chat permission are much more detailed. In particular, you can restrict sending stickers, animations, + games and inline bot results individually, allowing a finer control. + + If you wish to have the same permissions as seen in official apps or in bot API's *"can_send_other_messages"* + simply set these arguments to True: ``can_send_stickers``, ``can_send_animations``, ``can_send_games`` and + ``can_use_inline_bots``. + Parameters: can_send_messages (``bool``, *optional*): True, if the user is allowed to send text messages, contacts, locations and venues. @@ -34,9 +43,17 @@ class ChatPermissions(Object): True, if the user is allowed to send audios, documents, photos, videos, video notes and voice notes, implies can_send_messages. - can_send_other_messages (``bool``, *optional*): - True, if the user is allowed to send animations, games, stickers and use inline bots, implies - can_send_media_messages + can_send_stickers (``bool``, *optional*): + True, if the user is allowed to send stickers, implies can_send_media_messages. + + can_send_animations (``bool``, *optional*): + True, if the user is allowed to send animations (GIFs), implies can_send_media_messages. + + can_send_games (``bool``, *optional*): + True, if the user is allowed to send games, implies can_send_media_messages. + + can_use_inline_bots (``bool``, *optional*): + True, if the user is allowed to use inline bots, implies can_send_media_messages. can_add_web_page_previews (``bool``, *optional*): True, if the user is allowed to add web page previews to their messages, implies can_send_media_messages. @@ -61,7 +78,10 @@ def __init__( *, can_send_messages: bool = None, # Text, contacts, locations and venues can_send_media_messages: bool = None, # Audios, documents, photos, videos, video notes and voice notes - can_send_other_messages: bool = None, # Animations (GIFs), games, stickers, inline bot results + can_send_stickers: bool = None, + can_send_animations: bool = None, + can_send_games: bool = None, + can_use_inline_bots: bool = None, can_add_web_page_previews: bool = None, can_send_polls: bool = None, can_change_info: bool = None, @@ -72,7 +92,10 @@ def __init__( self.can_send_messages = can_send_messages self.can_send_media_messages = can_send_media_messages - self.can_send_other_messages = can_send_other_messages + self.can_send_stickers = can_send_stickers + self.can_send_animations = can_send_animations + self.can_send_games = can_send_games + self.can_use_inline_bots = can_use_inline_bots self.can_add_web_page_previews = can_add_web_page_previews self.can_send_polls = can_send_polls self.can_change_info = can_change_info @@ -85,10 +108,10 @@ def _parse(denied_permissions: types.ChatBannedRights) -> "ChatPermissions": return ChatPermissions( can_send_messages=not denied_permissions.send_messages, can_send_media_messages=not denied_permissions.send_media, - can_send_other_messages=( - not denied_permissions.send_stickers or not denied_permissions.send_gifs or - not denied_permissions.send_games or not denied_permissions.send_inline - ), + can_send_stickers=not denied_permissions.send_stickers, + can_send_animations=not denied_permissions.send_gifs, + can_send_games=not denied_permissions.send_games, + can_use_inline_bots=not denied_permissions.send_inline, can_add_web_page_previews=not denied_permissions.embed_links, can_send_polls=not denied_permissions.send_polls, can_change_info=not denied_permissions.change_info, From 2ba921c84d6f41861a019e91722aaea7953645a7 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 30 Mar 2020 16:59:22 +0200 Subject: [PATCH 0167/1185] Workaround the occasional delayed stop of a Client instance --- pyrogram/connection/transport/tcp/tcp.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyrogram/connection/transport/tcp/tcp.py b/pyrogram/connection/transport/tcp/tcp.py index dbb01946ff..db1c3ee7fd 100644 --- a/pyrogram/connection/transport/tcp/tcp.py +++ b/pyrogram/connection/transport/tcp/tcp.py @@ -19,6 +19,7 @@ import ipaddress import logging import socket +import time try: import socks @@ -72,6 +73,9 @@ def close(self): except OSError: pass finally: + # A tiny sleep placed here helps avoiding .recv(n) hanging until the timeout. + # This is a workaround that seems to fix the occasional delayed stop of a client. + time.sleep(0.001) super().close() def recvall(self, length: int) -> bytes or None: From 8681ca204372bfe70d069f4e6d76093309472c28 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 30 Mar 2020 17:33:28 +0200 Subject: [PATCH 0168/1185] Don't spawn unnecessary threads when no_updates=True --- pyrogram/client/client.py | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 0a7827fb24..7c2279b97a 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -216,7 +216,7 @@ def __init__( else: raise ValueError("Unknown storage engine") - self.dispatcher = Dispatcher(self, workers) + self.dispatcher = Dispatcher(self, 0 if no_updates else workers) def __enter__(self): return self.start() @@ -302,15 +302,16 @@ def initialize(self): self.load_plugins() - for i in range(self.UPDATES_WORKERS): - self.updates_workers_list.append( - Thread( - target=self.updates_worker, - name="UpdatesWorker#{}".format(i + 1) + if not self.no_updates: + 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() + self.updates_workers_list[-1].start() for i in range(self.DOWNLOAD_WORKERS): self.download_workers_list.append( @@ -355,13 +356,14 @@ def terminate(self): self.download_workers_list.clear() - for _ in range(self.UPDATES_WORKERS): - self.updates_queue.put(None) + if not self.no_updates: + for _ in range(self.UPDATES_WORKERS): + self.updates_queue.put(None) - for i in self.updates_workers_list: - i.join() + for i in self.updates_workers_list: + i.join() - self.updates_workers_list.clear() + self.updates_workers_list.clear() for i in self.media_sessions.values(): i.stop() From 3cf758433dfb5259c7460bf9e2e1caa5bc02fbaa Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 30 Mar 2020 18:35:50 +0200 Subject: [PATCH 0169/1185] Add missing await keywords --- pyrogram/client/methods/messages/send_dice.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/pyrogram/client/methods/messages/send_dice.py b/pyrogram/client/methods/messages/send_dice.py index c5f95d4bb9..389b6597d6 100644 --- a/pyrogram/client/methods/messages/send_dice.py +++ b/pyrogram/client/methods/messages/send_dice.py @@ -24,7 +24,7 @@ class SendDice(BaseClient): - def send_dice( + async def send_dice( self, chat_id: Union[int, str], disable_notification: bool = None, @@ -68,9 +68,9 @@ def send_dice( app.send_dice("pyrogramlounge") """ - r = self.send( + r = await self.send( functions.messages.SendMedia( - peer=self.resolve_peer(chat_id), + peer=await self.resolve_peer(chat_id), media=types.InputMediaDice(), silent=disable_notification or None, reply_to_msg_id=reply_to_message_id, @@ -82,11 +82,8 @@ def send_dice( ) for i in r.updates: - if isinstance( - i, - (types.UpdateNewMessage, types.UpdateNewChannelMessage, types.UpdateNewScheduledMessage) - ): - return pyrogram.Message._parse( + if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage, types.UpdateNewScheduledMessage)): + return await pyrogram.Message._parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats}, From 3f17e88836a34e3806950d60e24b91e515e86b42 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 1 Apr 2020 18:01:10 +0200 Subject: [PATCH 0170/1185] Add example on how to handle flood wait errors in the FAQ --- docs/source/faq.rst | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/source/faq.rst b/docs/source/faq.rst index 80df70b519..32827c764b 100644 --- a/docs/source/faq.rst +++ b/docs/source/faq.rst @@ -366,6 +366,20 @@ Having said that, here's some insights about limits: - Limits vary based on methods and the arguments passed to methods. For example: log-ins are expensive and thus have stricter limits; replying to a user command could cause a flood wait in case the user starts flooding, but such limit will only be applied to that particular chat (i.e.: other users are not affected). +- You can catch Flood Wait exceptions in your code and wait the required seconds before continuing, this way: + + .. code-block:: python + + import time + from pyrogram.errors import FloodWait + + try: + ... # Your code + except FloodWait as e: + time.sleep(e.x) # Wait "x" seconds before continuing + + + More info about error handling can be found `here `_. My account has been deactivated/limited! ---------------------------------------- From 2046768a5e7f2f6a1e983a044a2956ce18272b25 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 1 Apr 2020 20:07:22 +0200 Subject: [PATCH 0171/1185] Fix wrong reported type --- pyrogram/client/methods/messages/send_cached_media.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/methods/messages/send_cached_media.py b/pyrogram/client/methods/messages/send_cached_media.py index f50f27700c..463e9bef1f 100644 --- a/pyrogram/client/methods/messages/send_cached_media.py +++ b/pyrogram/client/methods/messages/send_cached_media.py @@ -61,7 +61,7 @@ def send_cached_media( A valid file reference obtained by a recently fetched media message. To be used in combination with a file id in case a file reference is needed. - caption (``bool``, *optional*): + caption (``str``, *optional*): Media caption, 0-1024 characters. parse_mode (``str``, *optional*): From 0b930498221e50669a328865c26a5c8afffb22e8 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 1 Apr 2020 20:08:46 +0200 Subject: [PATCH 0172/1185] Add content lists on relevant pages --- compiler/docs/template/bound-methods.rst | 6 ++++++ compiler/docs/template/methods.rst | 6 ++++++ compiler/docs/template/types.rst | 7 ++++++- docs/source/api/client.rst | 2 ++ docs/source/api/decorators.rst | 6 ++++++ docs/source/api/errors.rst | 6 ++++++ docs/source/api/handlers.rst | 6 ++++++ docs/source/faq.rst | 3 ++- docs/source/glossary.rst | 5 +++++ docs/source/intro/install.rst | 6 ++++++ docs/source/intro/setup.rst | 6 ++++++ docs/source/license.rst | 2 +- docs/source/powered-by.rst | 6 ++++++ docs/source/start/auth.rst | 6 ++++++ docs/source/start/errors.rst | 8 +++++++- docs/source/start/invoking.rst | 6 ++++++ docs/source/start/updates.rst | 6 ++++++ docs/source/support-pyrogram.rst | 2 ++ docs/source/topics/advanced-usage.rst | 6 ++++++ docs/source/topics/bots-interaction.rst | 6 ++++++ docs/source/topics/config-file.rst | 6 ++++++ docs/source/topics/create-filters.rst | 6 ++++++ docs/source/topics/debugging.rst | 6 ++++++ docs/source/topics/more-on-updates.rst | 6 ++++++ docs/source/topics/mtproto-vs-botapi.rst | 6 ++++++ docs/source/topics/proxy.rst | 6 ++++++ docs/source/topics/scheduling.rst | 6 ++++++ docs/source/topics/serializing.rst | 6 ++++++ docs/source/topics/session-settings.rst | 6 ++++++ docs/source/topics/smart-plugins.rst | 6 ++++++ docs/source/topics/storage-engines.rst | 6 ++++++ docs/source/topics/test-servers.rst | 6 ++++++ docs/source/topics/text-formatting.rst | 6 ++++++ docs/source/topics/tgcrypto.rst | 6 ++++++ docs/source/topics/use-filters.rst | 6 ++++++ 35 files changed, 193 insertions(+), 4 deletions(-) diff --git a/compiler/docs/template/bound-methods.rst b/compiler/docs/template/bound-methods.rst index 0057e071c4..4238df5296 100644 --- a/compiler/docs/template/bound-methods.rst +++ b/compiler/docs/template/bound-methods.rst @@ -20,6 +20,12 @@ some of the required arguments. app.run() +.. contents:: Contents + :backlinks: none + :local: + +----- + .. currentmodule:: pyrogram Message diff --git a/compiler/docs/template/methods.rst b/compiler/docs/template/methods.rst index a0c6df921b..5a287f87a7 100644 --- a/compiler/docs/template/methods.rst +++ b/compiler/docs/template/methods.rst @@ -13,6 +13,12 @@ This page is about Pyrogram methods. All the methods listed here are bound to a with app: app.send_message("haskell", "hi") +.. contents:: Contents + :backlinks: none + :local: + +----- + .. currentmodule:: pyrogram.Client Utilities diff --git a/compiler/docs/template/types.rst b/compiler/docs/template/types.rst index 5c91e68f26..2c79c11dee 100644 --- a/compiler/docs/template/types.rst +++ b/compiler/docs/template/types.rst @@ -13,8 +13,13 @@ This page is about Pyrogram types. All types listed here are accessible through **Optional** fields may not exist when irrelevant -- i.e.: they will contain the value of ``None`` and aren't shown when, for example, using ``print()``. -.. currentmodule:: pyrogram +.. contents:: Contents + :backlinks: none + :local: + +----- +.. currentmodule:: pyrogram Users & Chats ------------- diff --git a/docs/source/api/client.rst b/docs/source/api/client.rst index d28b7f6176..4190af5b1d 100644 --- a/docs/source/api/client.rst +++ b/docs/source/api/client.rst @@ -17,6 +17,8 @@ This page is about the Client class, which exposes high-level methods for an eas with app: app.send_message("me", "Hi!") +----- + Details ------- diff --git a/docs/source/api/decorators.rst b/docs/source/api/decorators.rst index fd397cc41a..2fc856bc10 100644 --- a/docs/source/api/decorators.rst +++ b/docs/source/api/decorators.rst @@ -24,6 +24,12 @@ functions. app.run() +.. contents:: Contents + :backlinks: none + :local: + +----- + .. currentmodule:: pyrogram Index diff --git a/docs/source/api/errors.rst b/docs/source/api/errors.rst index fad571e3e2..5c443cf314 100644 --- a/docs/source/api/errors.rst +++ b/docs/source/api/errors.rst @@ -15,6 +15,12 @@ follow the usual *PascalCase* convention. except FloodWait as e: ... +.. contents:: Contents + :backlinks: none + :local: + +----- + 303 - SeeOther -------------- diff --git a/docs/source/api/handlers.rst b/docs/source/api/handlers.rst index 1ae0961bd8..e66b66bdc7 100644 --- a/docs/source/api/handlers.rst +++ b/docs/source/api/handlers.rst @@ -20,6 +20,12 @@ For a much more convenient way of registering callback functions have a look at app.run() +.. contents:: Contents + :backlinks: none + :local: + +----- + .. currentmodule:: pyrogram Index diff --git a/docs/source/faq.rst b/docs/source/faq.rst index 32827c764b..b064e10b15 100644 --- a/docs/source/faq.rst +++ b/docs/source/faq.rst @@ -13,7 +13,8 @@ This FAQ page provides answers to common questions about Pyrogram and, to some e .. contents:: Contents :backlinks: none :local: - :depth: 1 + +----- What is Pyrogram? ----------------- diff --git a/docs/source/glossary.rst b/docs/source/glossary.rst index de9d354d45..a0e970ae95 100644 --- a/docs/source/glossary.rst +++ b/docs/source/glossary.rst @@ -8,6 +8,11 @@ general. Some words may as well link to dedicated articles in case the topic is If you think something interesting could be added here, feel free to propose it by opening a `Feature Request`_. +.. contents:: Contents + :backlinks: none + :local: + +----- Terms ----- diff --git a/docs/source/intro/install.rst b/docs/source/intro/install.rst index edd4316102..5312e44a84 100644 --- a/docs/source/intro/install.rst +++ b/docs/source/intro/install.rst @@ -11,6 +11,12 @@ We recommend using the latest versions of both Python 3 and pip. Pyrogram supports **Python 3** only, starting from version 3.5.3. **PyPy** is supported too. +.. contents:: Contents + :backlinks: none + :local: + +----- + Install Pyrogram ---------------- diff --git a/docs/source/intro/setup.rst b/docs/source/intro/setup.rst index b3aa1836de..f5bf607b3a 100644 --- a/docs/source/intro/setup.rst +++ b/docs/source/intro/setup.rst @@ -4,6 +4,12 @@ Project Setup We have just :doc:`installed Pyrogram `. In this page we'll discuss what you need to do in order to set up a project with the library. Let's see how it's done. +.. contents:: Contents + :backlinks: none + :local: + +----- + API Keys -------- diff --git a/docs/source/license.rst b/docs/source/license.rst index 43f59d7316..512f2c5b84 100644 --- a/docs/source/license.rst +++ b/docs/source/license.rst @@ -12,4 +12,4 @@ In other words: you can use and integrate Pyrogram into your own code --- either different license, 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 \ No newline at end of file +.. _GNU Lesser General Public License v3 or later (LGPLv3+): https://github.com/pyrogram/pyrogram/blob/develop/COPYING.lesser diff --git a/docs/source/powered-by.rst b/docs/source/powered-by.rst index 4af4fc2775..a696cebca6 100644 --- a/docs/source/powered-by.rst +++ b/docs/source/powered-by.rst @@ -9,6 +9,12 @@ This is a collection of remarkable projects made with Pyrogram. If you'd like to propose a project that's worth being listed here, feel free to open a `Feature Request`_. +.. contents:: Contents + :backlinks: none + :local: + +----- + Projects Showcase ----------------- diff --git a/docs/source/start/auth.rst b/docs/source/start/auth.rst index ca1ddd8fd4..0bbd4c5e4c 100644 --- a/docs/source/start/auth.rst +++ b/docs/source/start/auth.rst @@ -4,6 +4,12 @@ Authorization Once a :doc:`project is set up <../intro/setup>`, 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 bot. +.. contents:: Contents + :backlinks: none + :local: + +----- + User Authorization ------------------ diff --git a/docs/source/start/errors.rst b/docs/source/start/errors.rst index bd82bf7346..a9707d0895 100644 --- a/docs/source/start/errors.rst +++ b/docs/source/start/errors.rst @@ -8,6 +8,12 @@ to control the behaviour of your application. Pyrogram errors all live inside th from pyrogram import errors +.. contents:: Contents + :backlinks: none + :local: + +----- + RPCError -------- @@ -94,6 +100,6 @@ The value is stored in the ``x`` attribute of the exception object: from pyrogram.errors import FloodWait try: - ... + ... # Your code except FloodWait as e: time.sleep(e.x) # Wait "x" seconds before continuing diff --git a/docs/source/start/invoking.rst b/docs/source/start/invoking.rst index 5cb6817b23..74b313e42b 100644 --- a/docs/source/start/invoking.rst +++ b/docs/source/start/invoking.rst @@ -4,6 +4,12 @@ Calling Methods At this point, we have successfully :doc:`installed Pyrogram <../intro/install>` and :doc:`authorized ` our account; we are now aiming towards the core of the library. It's time to start playing with the API! +.. contents:: Contents + :backlinks: none + :local: + +----- + Basic Usage ----------- diff --git a/docs/source/start/updates.rst b/docs/source/start/updates.rst index 056fcb3d3c..4f010e27e8 100644 --- a/docs/source/start/updates.rst +++ b/docs/source/start/updates.rst @@ -4,6 +4,12 @@ Handling Updates Calling :doc:`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. +.. contents:: Contents + :backlinks: none + :local: + +----- + Defining Updates ---------------- diff --git a/docs/source/support-pyrogram.rst b/docs/source/support-pyrogram.rst index 81a0e533f3..2505585e96 100644 --- a/docs/source/support-pyrogram.rst +++ b/docs/source/support-pyrogram.rst @@ -9,6 +9,8 @@ found it to be useful, give Pyrogram a `Star on GitHub`_. Your appreciation mean Star

+----- + Donate ------ diff --git a/docs/source/topics/advanced-usage.rst b/docs/source/topics/advanced-usage.rst index 1460a3d8c3..a56e0da1dc 100644 --- a/docs/source/topics/advanced-usage.rst +++ b/docs/source/topics/advanced-usage.rst @@ -8,6 +8,12 @@ Telegram API. In this section, you'll be shown the alternative way of communicating with Telegram using Pyrogram: the main "raw" Telegram API with its functions and types. +.. contents:: Contents + :backlinks: none + :local: + +----- + Telegram Raw API ---------------- diff --git a/docs/source/topics/bots-interaction.rst b/docs/source/topics/bots-interaction.rst index ad99305028..97a72e05c0 100644 --- a/docs/source/topics/bots-interaction.rst +++ b/docs/source/topics/bots-interaction.rst @@ -3,6 +3,12 @@ Bots Interaction Users can interact with other bots via plain text messages as well as inline queries. +.. contents:: Contents + :backlinks: none + :local: + +----- + Inline Bots ----------- diff --git a/docs/source/topics/config-file.rst b/docs/source/topics/config-file.rst index a28025dbf9..b6faee058d 100644 --- a/docs/source/topics/config-file.rst +++ b/docs/source/topics/config-file.rst @@ -4,6 +4,12 @@ Configuration File As already mentioned in previous pages, Pyrogram can be configured by the use of an INI file. This page explains how this file is structured, how to use it and why. +.. contents:: Contents + :backlinks: none + :local: + +----- + Introduction ------------ diff --git a/docs/source/topics/create-filters.rst b/docs/source/topics/create-filters.rst index 6ae6e98cac..060357def3 100644 --- a/docs/source/topics/create-filters.rst +++ b/docs/source/topics/create-filters.rst @@ -9,6 +9,12 @@ for example) you can use :meth:`~pyrogram.Filters.create`. At the moment, the built-in filters are intended to be used with the :class:`~pyrogram.MessageHandler` only. +.. contents:: Contents + :backlinks: none + :local: + +----- + Custom Filters -------------- diff --git a/docs/source/topics/debugging.rst b/docs/source/topics/debugging.rst index 153c092786..7284884af2 100644 --- a/docs/source/topics/debugging.rst +++ b/docs/source/topics/debugging.rst @@ -4,6 +4,12 @@ Debugging When working with the API, chances are you'll stumble upon bugs, get stuck and start wondering how to continue. Nothing to actually worry about -- that's normal -- and luckily for you, Pyrogram provides some commodities to help you in this. +.. contents:: Contents + :backlinks: none + :local: + +----- + Caveman Debugging ----------------- diff --git a/docs/source/topics/more-on-updates.rst b/docs/source/topics/more-on-updates.rst index f23e692e2f..1bffaeef2b 100644 --- a/docs/source/topics/more-on-updates.rst +++ b/docs/source/topics/more-on-updates.rst @@ -4,6 +4,12 @@ More on Updates Here we'll show some advanced usages when working with :doc:`update handlers <../start/updates>` and :doc:`filters `. +.. contents:: Contents + :backlinks: none + :local: + +----- + Handler Groups -------------- diff --git a/docs/source/topics/mtproto-vs-botapi.rst b/docs/source/topics/mtproto-vs-botapi.rst index b0e46a7ff9..3668d0da48 100644 --- a/docs/source/topics/mtproto-vs-botapi.rst +++ b/docs/source/topics/mtproto-vs-botapi.rst @@ -6,6 +6,12 @@ already superior to, what is usually called, the official Bot API, in many respe why Pyrogram might be a better choice for your project by comparing the two APIs, but first, let's make it clear what actually is the MTProto and the Bot API. +.. contents:: Contents + :backlinks: none + :local: + +----- + What is the MTProto API? ------------------------ diff --git a/docs/source/topics/proxy.rst b/docs/source/topics/proxy.rst index 761899e621..cde55cc7da 100644 --- a/docs/source/topics/proxy.rst +++ b/docs/source/topics/proxy.rst @@ -4,6 +4,12 @@ SOCKS5 Proxy Pyrogram supports proxies with and without authentication. This feature allows Pyrogram to exchange data with Telegram through an intermediate SOCKS5 proxy server. +.. contents:: Contents + :backlinks: none + :local: + +----- + Usage ----- diff --git a/docs/source/topics/scheduling.rst b/docs/source/topics/scheduling.rst index 3cb95ec747..c5f410bb46 100644 --- a/docs/source/topics/scheduling.rst +++ b/docs/source/topics/scheduling.rst @@ -8,6 +8,12 @@ Since there's no built-in task scheduler in Pyrogram, this page will only show e with the main Python schedule libraries such as ``schedule`` and ``apscheduler``. For more detailed information, you can visit and learn from each library documentation. +.. contents:: Contents + :backlinks: none + :local: + +----- + Using ``schedule`` ------------------ diff --git a/docs/source/topics/serializing.rst b/docs/source/topics/serializing.rst index b4d66f4675..8e6f29de7b 100644 --- a/docs/source/topics/serializing.rst +++ b/docs/source/topics/serializing.rst @@ -5,6 +5,12 @@ Serializing means converting a Pyrogram object, which exists as Python class ins easily shared and stored anywhere. Pyrogram provides two formats for serializing its objects: one good looking for humans and another more compact for machines that is able to recover the original structures. +.. contents:: Contents + :backlinks: none + :local: + +----- + For Humans - str(obj) --------------------- diff --git a/docs/source/topics/session-settings.rst b/docs/source/topics/session-settings.rst index dd777bdae3..a97dbc063f 100644 --- a/docs/source/topics/session-settings.rst +++ b/docs/source/topics/session-settings.rst @@ -20,6 +20,12 @@ That's how a session looks like on the Android app, showing the three main piece - ``device_model``: **CPython 3.7.2** - ``system_version``: **Linux 4.15.0-23-generic** +.. contents:: Contents + :backlinks: none + :local: + +----- + Set Custom Values ----------------- diff --git a/docs/source/topics/smart-plugins.rst b/docs/source/topics/smart-plugins.rst index 7cfed47dd4..7ffe1cb2ee 100644 --- a/docs/source/topics/smart-plugins.rst +++ b/docs/source/topics/smart-plugins.rst @@ -9,6 +9,12 @@ different Pyrogram applications with **minimal boilerplate code**. Smart Plugins are completely optional and disabled by default. +.. contents:: Contents + :backlinks: none + :local: + +----- + Introduction ------------ diff --git a/docs/source/topics/storage-engines.rst b/docs/source/topics/storage-engines.rst index 44b4afa6c7..e7494e2bd0 100644 --- a/docs/source/topics/storage-engines.rst +++ b/docs/source/topics/storage-engines.rst @@ -5,6 +5,12 @@ Every time you login to Telegram, some personal piece of data are created and he and the server, Telegram). This session data is uniquely bound to your own account, indefinitely (until you logout or decide to manually terminate it) and is used to authorize a client to execute API calls on behalf of your identity. +.. contents:: Contents + :backlinks: none + :local: + +----- + Persisting Sessions ------------------- diff --git a/docs/source/topics/test-servers.rst b/docs/source/topics/test-servers.rst index 2f82f24c3a..451fc98a23 100644 --- a/docs/source/topics/test-servers.rst +++ b/docs/source/topics/test-servers.rst @@ -18,6 +18,12 @@ Telegram's test servers without hassle. All you need to do is start a new sessio Don't worry about your contacts and chats, they will be kept untouched inside the production environment; accounts authorized on test servers reside in a different, parallel instance of a Telegram database. +.. contents:: Contents + :backlinks: none + :local: + +----- + Test Mode in Official Apps -------------------------- diff --git a/docs/source/topics/text-formatting.rst b/docs/source/topics/text-formatting.rst index 0194dc58a7..f4ac23e1ef 100644 --- a/docs/source/topics/text-formatting.rst +++ b/docs/source/topics/text-formatting.rst @@ -17,6 +17,12 @@ Pyrogram uses a custom Markdown dialect for text formatting which adds some uniq texts easier in both Markdown and HTML. You can send sophisticated text messages and media captions using a great variety of decorations that can also be nested in order to combine multiple styles together. +.. contents:: Contents + :backlinks: none + :local: + +----- + Basic Styles ------------ diff --git a/docs/source/topics/tgcrypto.rst b/docs/source/topics/tgcrypto.rst index 9dd4c8ac98..a5d9f81521 100644 --- a/docs/source/topics/tgcrypto.rst +++ b/docs/source/topics/tgcrypto.rst @@ -7,6 +7,12 @@ Library specifically written in C for Pyrogram [1]_ as a Python extension. TgCrypto is a replacement for the much slower PyAES and implements the crypto algorithms Telegram requires, namely **AES-IGE 256 bit** (used in MTProto v2.0) and **AES-CTR 256 bit** (used for CDN encrypted files). +.. contents:: Contents + :backlinks: none + :local: + +----- + Installation ------------ diff --git a/docs/source/topics/use-filters.rst b/docs/source/topics/use-filters.rst index de7a35a830..8806052e94 100644 --- a/docs/source/topics/use-filters.rst +++ b/docs/source/topics/use-filters.rst @@ -7,6 +7,12 @@ comes from the server, but there's much more than that to come. Here we'll discuss about :class:`~pyrogram.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. +.. contents:: Contents + :backlinks: none + :local: + +----- + Single Filters -------------- From 945effc4a9b0ccbb687283bc220726ab65f98368 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 1 Apr 2020 22:39:39 +0200 Subject: [PATCH 0173/1185] Add tabs to docs (tgcrypto.rst) --- docs/requirements.txt | 1 + docs/source/conf.py | 3 ++- docs/source/topics/tgcrypto.rst | 27 +++++++++++++++++---------- 3 files changed, 20 insertions(+), 11 deletions(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 3ebe0d49d2..0e754f9f8a 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,5 +1,6 @@ sphinx sphinx_rtd_theme sphinx_copybutton +sphinx_tabs pypandoc requests \ No newline at end of file diff --git a/docs/source/conf.py b/docs/source/conf.py index 519a96d972..0302328deb 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -35,7 +35,8 @@ "sphinx.ext.autodoc", "sphinx.ext.napoleon", "sphinx.ext.autosummary", - "sphinx_copybutton" + "sphinx_copybutton", + "sphinx_tabs.tabs" ] master_doc = "index" diff --git a/docs/source/topics/tgcrypto.rst b/docs/source/topics/tgcrypto.rst index a5d9f81521..8feab86a64 100644 --- a/docs/source/topics/tgcrypto.rst +++ b/docs/source/topics/tgcrypto.rst @@ -7,12 +7,6 @@ Library specifically written in C for Pyrogram [1]_ as a Python extension. TgCrypto is a replacement for the much slower PyAES and implements the crypto algorithms Telegram requires, namely **AES-IGE 256 bit** (used in MTProto v2.0) and **AES-CTR 256 bit** (used for CDN encrypted files). -.. contents:: Contents - :backlinks: none - :local: - ------ - Installation ------------ @@ -27,10 +21,23 @@ The reason about being an optional package is that TgCrypto requires some extra The errors you receive when trying to install TgCrypto are system dependent, but also descriptive enough to understand what you should do next: -- **Windows**: Install `Visual C++ 2015 Build Tools `_. -- **macOS**: A pop-up will automatically ask you to install the command line developer tools. -- **Linux**: Install a proper C compiler (``gcc``, ``clang``) and the Python header files (``python3-dev``). -- **Termux (Android)**: Install ``clang`` package. +.. tabs:: + + .. tab:: Windows + + Install `Visual C++ 2015 Build Tools `_. + + .. tab:: macOS + + A pop-up will automatically ask you to install the command line developer tools. + + .. tab:: Linux + + Install a proper C compiler (``gcc``, ``clang``) and the Python header files (``python3-dev``). + + .. tab:: Termux + + Install ``clang`` package. .. _TgCrypto: https://github.com/pyrogram/tgcrypto From a54cd2e4fcf2ded7e09d8a7cdb234bfde1e5f346 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 2 Apr 2020 13:55:47 +0200 Subject: [PATCH 0174/1185] Add ttl_seconds to Photo objects --- pyrogram/client/types/messages_and_media/message.py | 2 +- pyrogram/client/types/messages_and_media/photo.py | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index f566849b22..451e8c25ef 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -523,7 +523,7 @@ def _parse(client, message: types.Message or types.MessageService or types.Messa if media: if isinstance(media, types.MessageMediaPhoto): - photo = Photo._parse(client, media.photo) + photo = Photo._parse(client, media) elif isinstance(media, types.MessageMediaGeo): location = Location._parse(client, media.geo) elif isinstance(media, types.MessageMediaContact): diff --git a/pyrogram/client/types/messages_and_media/photo.py b/pyrogram/client/types/messages_and_media/photo.py index 7a0230d684..1edfb4e2d9 100644 --- a/pyrogram/client/types/messages_and_media/photo.py +++ b/pyrogram/client/types/messages_and_media/photo.py @@ -42,6 +42,9 @@ class Photo(Object): height (``int``): Photo height. + ttl_seconds (``int``): + Time-to-live seconds, for secret photos. + file_size (``int``): File size. @@ -60,6 +63,7 @@ def __init__( file_ref: str, width: int, height: int, + ttl_seconds: int, file_size: int, date: int, thumbs: List[Thumbnail] @@ -70,12 +74,15 @@ def __init__( self.file_ref = file_ref self.width = width self.height = height + self.ttl_seconds = ttl_seconds self.file_size = file_size self.date = date self.thumbs = thumbs @staticmethod - def _parse(client, photo: types.Photo) -> "Photo": + def _parse(client, media_photo: types.MessageMediaPhoto) -> "Photo": + photo = media_photo.photo + if isinstance(photo, types.Photo): big = photo.sizes[-1] @@ -91,6 +98,7 @@ def _parse(client, photo: types.Photo) -> "Photo": file_ref=encode_file_ref(photo.file_reference), width=big.w, height=big.h, + ttl_seconds=media_photo.ttl_seconds, file_size=big.size, date=photo.date, thumbs=Thumbnail._parse(client, photo), From fa157b59f13ba69ed15053a70f6cd1c43e648daa Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 3 Apr 2020 17:15:28 +0200 Subject: [PATCH 0175/1185] Add support for ChosenInlineResult objects --- compiler/docs/compiler.py | 1 + docs/source/api/decorators.rst | 2 + docs/source/api/handlers.rst | 2 + pyrogram/client/ext/dispatcher.py | 12 +- pyrogram/client/handlers/__init__.py | 3 +- .../handlers/chosen_inline_result_handler.py | 48 +++++++ .../client/methods/decorators/__init__.py | 4 +- .../decorators/on_chosen_inline_result.py | 56 +++++++++ pyrogram/client/types/inline_mode/__init__.py | 3 +- .../types/inline_mode/chosen_inline_result.py | 117 ++++++++++++++++++ 10 files changed, 242 insertions(+), 6 deletions(-) create mode 100644 pyrogram/client/handlers/chosen_inline_result_handler.py create mode 100644 pyrogram/client/methods/decorators/on_chosen_inline_result.py create mode 100644 pyrogram/client/types/inline_mode/chosen_inline_result.py diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index 20e0dd25c8..c78a47ee2a 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -366,6 +366,7 @@ def get_title_list(s: str) -> list: InlineQueryResultArticle InlineQueryResultPhoto InlineQueryResultAnimation + ChosenInlineResult """, input_message_content=""" InputMessageContent diff --git a/docs/source/api/decorators.rst b/docs/source/api/decorators.rst index 2fc856bc10..cacb43b16f 100644 --- a/docs/source/api/decorators.rst +++ b/docs/source/api/decorators.rst @@ -41,6 +41,7 @@ Index - :meth:`~Client.on_message` - :meth:`~Client.on_callback_query` - :meth:`~Client.on_inline_query` + - :meth:`~Client.on_chosen_inline_result` - :meth:`~Client.on_deleted_messages` - :meth:`~Client.on_user_status` - :meth:`~Client.on_poll` @@ -56,6 +57,7 @@ Details .. autodecorator:: pyrogram.Client.on_message() .. autodecorator:: pyrogram.Client.on_callback_query() .. autodecorator:: pyrogram.Client.on_inline_query() +.. autodecorator:: pyrogram.Client.on_chosen_inline_result() .. autodecorator:: pyrogram.Client.on_deleted_messages() .. autodecorator:: pyrogram.Client.on_user_status() .. autodecorator:: pyrogram.Client.on_poll() diff --git a/docs/source/api/handlers.rst b/docs/source/api/handlers.rst index e66b66bdc7..4fdc511e54 100644 --- a/docs/source/api/handlers.rst +++ b/docs/source/api/handlers.rst @@ -38,6 +38,7 @@ Index - :class:`DeletedMessagesHandler` - :class:`CallbackQueryHandler` - :class:`InlineQueryHandler` + - :class:`ChosenInlineResultHandler` - :class:`UserStatusHandler` - :class:`PollHandler` - :class:`DisconnectHandler` @@ -53,6 +54,7 @@ Details .. autoclass:: DeletedMessagesHandler() .. autoclass:: CallbackQueryHandler() .. autoclass:: InlineQueryHandler() +.. autoclass:: ChosenInlineResultHandler() .. autoclass:: UserStatusHandler() .. autoclass:: PollHandler() .. autoclass:: DisconnectHandler() diff --git a/pyrogram/client/ext/dispatcher.py b/pyrogram/client/ext/dispatcher.py index 20be359eef..256dd5f25e 100644 --- a/pyrogram/client/ext/dispatcher.py +++ b/pyrogram/client/ext/dispatcher.py @@ -28,12 +28,14 @@ UpdateEditMessage, UpdateEditChannelMessage, UpdateDeleteMessages, UpdateDeleteChannelMessages, UpdateBotCallbackQuery, UpdateInlineBotCallbackQuery, - UpdateUserStatus, UpdateBotInlineQuery, UpdateMessagePoll + UpdateUserStatus, UpdateBotInlineQuery, UpdateMessagePoll, + UpdateBotInlineSend ) from . import utils from ..handlers import ( CallbackQueryHandler, MessageHandler, DeletedMessagesHandler, - UserStatusHandler, RawUpdateHandler, InlineQueryHandler, PollHandler + UserStatusHandler, RawUpdateHandler, InlineQueryHandler, PollHandler, + ChosenInlineResultHandler ) log = logging.getLogger(__name__) @@ -99,7 +101,11 @@ def __init__(self, client, workers: int): lambda upd, usr, cht: (pyrogram.InlineQuery._parse(self.client, upd, usr), InlineQueryHandler), (UpdateMessagePoll,): - lambda upd, usr, cht: (pyrogram.Poll._parse_update(self.client, upd), PollHandler) + lambda upd, usr, cht: (pyrogram.Poll._parse_update(self.client, upd), PollHandler), + + (UpdateBotInlineSend,): + lambda upd, usr, cht: (pyrogram.ChosenInlineResult._parse(self.client, upd, usr), + ChosenInlineResultHandler) } self.update_parsers = {key: value for key_tuple, value in self.update_parsers.items() for key in key_tuple} diff --git a/pyrogram/client/handlers/__init__.py b/pyrogram/client/handlers/__init__.py index 25acbedc9f..87ef10c5bd 100644 --- a/pyrogram/client/handlers/__init__.py +++ b/pyrogram/client/handlers/__init__.py @@ -24,8 +24,9 @@ from .poll_handler import PollHandler from .raw_update_handler import RawUpdateHandler from .user_status_handler import UserStatusHandler +from .chosen_inline_result_handler import ChosenInlineResultHandler __all__ = [ "MessageHandler", "DeletedMessagesHandler", "CallbackQueryHandler", "RawUpdateHandler", "DisconnectHandler", - "UserStatusHandler", "InlineQueryHandler", "PollHandler" + "UserStatusHandler", "InlineQueryHandler", "PollHandler", "ChosenInlineResultHandler" ] diff --git a/pyrogram/client/handlers/chosen_inline_result_handler.py b/pyrogram/client/handlers/chosen_inline_result_handler.py new file mode 100644 index 0000000000..f5014ab08e --- /dev/null +++ b/pyrogram/client/handlers/chosen_inline_result_handler.py @@ -0,0 +1,48 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan +# +# 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 .handler import Handler + + +class ChosenInlineResultHandler(Handler): + """The ChosenInlineResultHandler handler class. Used to handle chosen inline results coming from inline queries. + It is intended to be used with :meth:`~Client.add_handler` + + For a nicer way to register this handler, have a look at the + :meth:`~Client.on_chosen_inline_query` decorator. + + Parameters: + callback (``callable``): + Pass a function that will be called when a new chosen inline result arrives. + It takes *(client, chosen_inline_result)* as positional arguments (look at the section below for a + detailed description). + + filters (:obj:`Filters`): + Pass one or more filters to allow only a subset of chosen inline results to be passed + in your callback function. + + Other parameters: + client (:obj:`Client`): + The Client itself, useful when you want to call other API methods inside the message handler. + + chosen_inline_result (:obj:`ChosenInlineResult`): + The received chosen inline result. + """ + + def __init__(self, callback: callable, filters=None): + super().__init__(callback, filters) diff --git a/pyrogram/client/methods/decorators/__init__.py b/pyrogram/client/methods/decorators/__init__.py index 2c0a749a85..5a60ff9b20 100644 --- a/pyrogram/client/methods/decorators/__init__.py +++ b/pyrogram/client/methods/decorators/__init__.py @@ -24,6 +24,7 @@ from .on_poll import OnPoll from .on_raw_update import OnRawUpdate from .on_user_status import OnUserStatus +from .on_chosen_inline_result import OnChosenInlineResult class Decorators( @@ -34,6 +35,7 @@ class Decorators( OnDisconnect, OnUserStatus, OnInlineQuery, - OnPoll + OnPoll, + OnChosenInlineResult ): pass diff --git a/pyrogram/client/methods/decorators/on_chosen_inline_result.py b/pyrogram/client/methods/decorators/on_chosen_inline_result.py new file mode 100644 index 0000000000..40446eb2ff --- /dev/null +++ b/pyrogram/client/methods/decorators/on_chosen_inline_result.py @@ -0,0 +1,56 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan +# +# 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 Callable + +import pyrogram +from pyrogram.client.filters.filter import Filter +from ...ext import BaseClient + + +class OnChosenInlineResult(BaseClient): + def on_chosen_inline_result( + self=None, + filters=None, + group: int = 0 + ) -> callable: + """Decorator for handling chosen inline results. + + This does the same thing as :meth:`~pyrogram.Client.add_handler` using the :obj:`~pyrogram.ChosenInlineResult`. + + Parameters: + filters (:obj:`~pyrogram.Filters`, *optional*): + Pass one or more filters to allow only a subset of chosen inline results to be passed + in your function. + + group (``int``, *optional*): + The group identifier, defaults to 0. + """ + + def decorator(func: Callable) -> Callable: + if isinstance(self, pyrogram.Client): + self.add_handler(pyrogram.ChosenInlineResultHandler(func, filters), group) + elif isinstance(self, Filter) or self is None: + func.handler = ( + pyrogram.ChosenInlineResultHandler(func, self), + group if filters is None else filters + ) + + return func + + return decorator diff --git a/pyrogram/client/types/inline_mode/__init__.py b/pyrogram/client/types/inline_mode/__init__.py index 68da92d751..4980377d4f 100644 --- a/pyrogram/client/types/inline_mode/__init__.py +++ b/pyrogram/client/types/inline_mode/__init__.py @@ -21,8 +21,9 @@ from .inline_query_result_animation import InlineQueryResultAnimation from .inline_query_result_article import InlineQueryResultArticle from .inline_query_result_photo import InlineQueryResultPhoto +from .chosen_inline_result import ChosenInlineResult __all__ = [ "InlineQuery", "InlineQueryResult", "InlineQueryResultArticle", "InlineQueryResultPhoto", - "InlineQueryResultAnimation" + "InlineQueryResultAnimation", "ChosenInlineResult" ] diff --git a/pyrogram/client/types/inline_mode/chosen_inline_result.py b/pyrogram/client/types/inline_mode/chosen_inline_result.py new file mode 100644 index 0000000000..cdc5afc9bc --- /dev/null +++ b/pyrogram/client/types/inline_mode/chosen_inline_result.py @@ -0,0 +1,117 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan +# +# 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 . +# +# 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 base64 import b64encode +from struct import pack +from typing import Union + +import pyrogram +from pyrogram.api import types +from pyrogram.client.types.object import Object +from pyrogram.client.types.update import Update +from pyrogram.client.types.user_and_chats import User +from pyrogram.client.types.messages_and_media import Location +from pyrogram.client.ext import utils + + +class ChosenInlineResult(Object, Update): + """A :doc:`result ` of an inline query chosen by the user and sent to their chat partner. + + Parameters: + result_id (``str``): + The unique identifier for the result that was chosen. + + from_user (:obj:`User`): + The user that chose the result. + + query (``str``): + The query that was used to obtain the result. + + location (:obj:`Location`, *optional*): + Sender location, only for bots that require user location. + + inline_message_id (``str``, *optional*): + Identifier of the sent inline message. + Available only if there is an :doc:`inline keyboard ` attached to the message. + Will be also received in :doc:`callback queries ` and can be used to edit the message. + + .. note:: + + It is necessary to enable inline feedback via `@Botfather `_ in order to receive these + objects in updates. + """ + + def __init__( + self, + *, + client: "pyrogram.BaseClient" = None, + result_id: str, + from_user: User, + query: str, + location: "pyrogram.Location" = None, + inline_message_id: str = None, + ): + super().__init__(client) + + self.result_id = result_id + self.from_user = from_user + self.query = query + self.location = location + self.inline_message_id = inline_message_id + + @staticmethod + def _parse(client, chosen_inline_result: types.UpdateBotInlineSend, users) -> "ChosenInlineResult": + inline_message_id = None + + if isinstance(chosen_inline_result.msg_id, types.InputBotInlineMessageID): + inline_message_id = b64encode( + pack( + " "Photo": if isinstance(photo, types.Photo): big = photo.sizes[-1] @@ -98,7 +96,7 @@ def _parse(client, media_photo: types.MessageMediaPhoto) -> "Photo": file_ref=encode_file_ref(photo.file_reference), width=big.w, height=big.h, - ttl_seconds=media_photo.ttl_seconds, + ttl_seconds=ttl_seconds, file_size=big.size, date=photo.date, thumbs=Thumbnail._parse(client, photo), From c1a835b74ef3c5ddf79b18d764cb386687f6647c Mon Sep 17 00:00:00 2001 From: Real Phoenix <51527258+rsktg@users.noreply.github.com> Date: Mon, 6 Apr 2020 17:52:38 +0530 Subject: [PATCH 0177/1185] Add more Chat bound methods (#383) * Add more bound methods Bound methods for get_chat_member, get_chat_members, iter_chat_members, add_chat_members * Update compiler.py Co-authored-by: Dan <14043624+delivrance@users.noreply.github.com> --- compiler/docs/compiler.py | 4 + pyrogram/client/types/user_and_chats/chat.py | 123 ++++++++++++++++++- 2 files changed, 126 insertions(+), 1 deletion(-) diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index c78a47ee2a..a707af0731 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -450,6 +450,10 @@ def get_title_list(s: str) -> list: Chat.unban_member Chat.restrict_member Chat.promote_member + Chat.get_member + Chat.get_members + Chat.iter_members + Chat.add_members Chat.join Chat.leave """, diff --git a/pyrogram/client/types/user_and_chats/chat.py b/pyrogram/client/types/user_and_chats/chat.py index 46ff56621d..914159e35d 100644 --- a/pyrogram/client/types/user_and_chats/chat.py +++ b/pyrogram/client/types/user_and_chats/chat.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Union, List +from typing import Union, List, Generator, Optional import pyrogram from pyrogram.api import types @@ -725,3 +725,124 @@ async def export_invite_link(self): """ return await self._client.export_chat_invite_link(self.id) + + async def get_member( + self, + user_id: Union[int, str], + ) -> "pyrogram.ChatMember": + """Bound method *get_member* of :obj:`Chat`. + + Use as a shortcut for: + + .. code-block:: python + + client.get_chat_member( + chat_id=chat_id, + user_id=user_id + ) + + Example: + .. code-block:: python + + chat.get_member(user_id) + + Returns: + :obj:`ChatMember`: On success, a chat member is returned. + """ + + return await self._client.get_chat_member( + self.id, + user_id=user_id + ) + + async def get_members( + self, + offset: int = 0, + limit: int = 200, + query: str = "", + filter: str = Filters.ALL + ) -> List["pyrogram.ChatMember"]: + """Bound method *get_members* of :obj:`Chat`. + + Use as a shortcut for: + + .. code-block:: python + + client.get_chat_members(chat_id) + + Example: + .. code-block:: python + # Get first 200 recent members + chat.get_members() + + Returns: + List of :obj:`ChatMember`: On success, a list of chat members is returned. + """ + + return await self._client.get_chat_members( + self.id, + offset=offset, + limit=limit, + query=query, + filter=filter + ) + + async def iter_members( + self, + limit: int = 0, + query: str = "", + filter: str = Filters.ALL + ) -> Optional[Generator["pyrogram.ChatMember", None, None]]: + """Bound method *iter_members* of :obj:`Chat`. + + Use as a shortcut for: + + .. code-block:: python + + for member in client.iter_chat_members(chat_id): + print(member.user.first_name) + + Example: + .. code-block:: python + + for member in chat.iter_members(): + print(member.user.first_name) + + Returns: + ``Generator``: A generator yielding :obj:`ChatMember` objects. + """ + + return self._client.iter_chat_members( + self.id, + limit=limit, + query=query, + filter=filter + ) + + async def add_members( + self, + user_ids: Union[Union[int, str], List[Union[int, str]]], + forward_limit: int = 100 + ) -> bool: + """Bound method *add_members* of :obj:`Chat`. + + Use as a shortcut for: + + .. code-block:: python + + client.add_chat_members(chat_id, user_id) + + Example: + .. code-block:: python + + chat.add_members(user_id) + + Returns: + ``bool``: On success, True is returned. + """ + + return await self._client.add_chat_members( + self.id, + user_ids=user_ids, + forward_limit=forward_limit + ) \ No newline at end of file From 207d451a01ac59f24140add452915f478a307912 Mon Sep 17 00:00:00 2001 From: Real Phoenix <51527258+rsktg@users.noreply.github.com> Date: Mon, 6 Apr 2020 14:22:38 +0200 Subject: [PATCH 0178/1185] Add more Chat bound methods (#383) * Add more bound methods Bound methods for get_chat_member, get_chat_members, iter_chat_members, add_chat_members * Update compiler.py Co-authored-by: Dan <14043624+delivrance@users.noreply.github.com> --- compiler/docs/compiler.py | 4 + pyrogram/client/types/user_and_chats/chat.py | 133 +++++++++++++++++-- 2 files changed, 128 insertions(+), 9 deletions(-) diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index c78a47ee2a..a707af0731 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -450,6 +450,10 @@ def get_title_list(s: str) -> list: Chat.unban_member Chat.restrict_member Chat.promote_member + Chat.get_member + Chat.get_members + Chat.iter_members + Chat.add_members Chat.join Chat.leave """, diff --git a/pyrogram/client/types/user_and_chats/chat.py b/pyrogram/client/types/user_and_chats/chat.py index 3829e303f7..5e1f18fa25 100644 --- a/pyrogram/client/types/user_and_chats/chat.py +++ b/pyrogram/client/types/user_and_chats/chat.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Union, List +from typing import Union, List, Generator, Optional import pyrogram from pyrogram.api import types @@ -566,15 +566,8 @@ def restrict_member( return self._client.restrict_chat_member( chat_id=self.id, user_id=user_id, + permissions=permissions, until_date=until_date, - can_send_messages=permissions.can_send_messages, - can_send_media_messages=permissions.can_send_media_messages, - can_send_other_messages=permissions.can_send_other_messages, - can_add_web_page_previews=permissions.can_add_web_page_previews, - can_send_polls=permissions.can_send_polls, - can_change_info=permissions.can_change_info, - can_invite_users=permissions.can_invite_users, - can_pin_messages=permissions.can_pin_messages ) def promote_member( @@ -725,3 +718,125 @@ def export_invite_link(self): """ return self._client.export_chat_invite_link(self.id) + + def get_member( + self, + user_id: Union[int, str], + ) -> "pyrogram.ChatMember": + """Bound method *get_member* of :obj:`Chat`. + + Use as a shortcut for: + + .. code-block:: python + + client.get_chat_member( + chat_id=chat_id, + user_id=user_id + ) + + Example: + .. code-block:: python + + chat.get_member(user_id) + + Returns: + :obj:`ChatMember`: On success, a chat member is returned. + """ + + return self._client.get_chat_member( + self.id, + user_id=user_id + ) + + def get_members( + self, + offset: int = 0, + limit: int = 200, + query: str = "", + filter: str = "all" + ) -> List["pyrogram.ChatMember"]: + """Bound method *get_members* of :obj:`Chat`. + + Use as a shortcut for: + + .. code-block:: python + + client.get_chat_members(chat_id) + + Example: + .. code-block:: python + + # Get first 200 recent members + chat.get_members() + + Returns: + List of :obj:`ChatMember`: On success, a list of chat members is returned. + """ + + return self._client.get_chat_members( + self.id, + offset=offset, + limit=limit, + query=query, + filter=filter + ) + + def iter_members( + self, + limit: int = 0, + query: str = "", + filter: str = "all" + ) -> Optional[Generator["pyrogram.ChatMember", None, None]]: + """Bound method *iter_members* of :obj:`Chat`. + + Use as a shortcut for: + + .. code-block:: python + + for member in client.iter_chat_members(chat_id): + print(member.user.first_name) + + Example: + .. code-block:: python + + for member in chat.iter_members(): + print(member.user.first_name) + + Returns: + ``Generator``: A generator yielding :obj:`ChatMember` objects. + """ + + return self._client.iter_chat_members( + self.id, + limit=limit, + query=query, + filter=filter + ) + + def add_members( + self, + user_ids: Union[Union[int, str], List[Union[int, str]]], + forward_limit: int = 100 + ) -> bool: + """Bound method *add_members* of :obj:`Chat`. + + Use as a shortcut for: + + .. code-block:: python + + client.add_chat_members(chat_id, user_id) + + Example: + .. code-block:: python + + chat.add_members(user_id) + + Returns: + ``bool``: On success, True is returned. + """ + + return self._client.add_chat_members( + self.id, + user_ids=user_ids, + forward_limit=forward_limit + ) From 1b0b467d7b9df75b03629f751f8ed0021b6e9fda Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 6 Apr 2020 16:05:21 +0200 Subject: [PATCH 0179/1185] Fix iter_members not working properly as async generator --- pyrogram/client/types/user_and_chats/chat.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/types/user_and_chats/chat.py b/pyrogram/client/types/user_and_chats/chat.py index f1369a000b..4b3c1f9dc1 100644 --- a/pyrogram/client/types/user_and_chats/chat.py +++ b/pyrogram/client/types/user_and_chats/chat.py @@ -781,7 +781,7 @@ async def get_members( filter=filter ) - async def iter_members( + def iter_members( self, limit: int = 0, query: str = "", @@ -806,7 +806,7 @@ async def iter_members( ``Generator``: A generator yielding :obj:`ChatMember` objects. """ - return await self._client.iter_chat_members( + return self._client.iter_chat_members( self.id, limit=limit, query=query, From 3e900214f4fcd8c7440c904f6530e270bac7cd6a Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 6 Apr 2020 16:09:35 +0200 Subject: [PATCH 0180/1185] Pass the required file_reference when editing media messages --- pyrogram/client/methods/messages/edit_message_media.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pyrogram/client/methods/messages/edit_message_media.py b/pyrogram/client/methods/messages/edit_message_media.py index 3b573cffd3..b6fbf93313 100644 --- a/pyrogram/client/methods/messages/edit_message_media.py +++ b/pyrogram/client/methods/messages/edit_message_media.py @@ -92,7 +92,7 @@ def edit_message_media( id=types.InputPhoto( id=media.photo.id, access_hash=media.photo.access_hash, - file_reference=b"" + file_reference=media.photo.file_reference ) ) elif media.media.startswith("http"): @@ -129,7 +129,7 @@ def edit_message_media( id=types.InputDocument( id=media.document.id, access_hash=media.document.access_hash, - file_reference=b"" + file_reference=media.document.file_reference ) ) elif media.media.startswith("http"): @@ -165,7 +165,7 @@ def edit_message_media( id=types.InputDocument( id=media.document.id, access_hash=media.document.access_hash, - file_reference=b"" + file_reference=media.document.file_reference ) ) elif media.media.startswith("http"): @@ -203,7 +203,7 @@ def edit_message_media( id=types.InputDocument( id=media.document.id, access_hash=media.document.access_hash, - file_reference=b"" + file_reference=media.document.file_reference ) ) elif media.media.startswith("http"): @@ -234,7 +234,7 @@ def edit_message_media( id=types.InputDocument( id=media.document.id, access_hash=media.document.access_hash, - file_reference=b"" + file_reference=media.document.file_reference ) ) elif media.media.startswith("http"): From 91ebe5f2a8328d912afd50fb1a86fa99c6919482 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 8 Apr 2020 23:29:11 +0200 Subject: [PATCH 0181/1185] Fix object decoder breaking on re.Match objects --- pyrogram/client/types/object.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyrogram/client/types/object.py b/pyrogram/client/types/object.py index 87b32dfa9f..adafc8a064 100644 --- a/pyrogram/client/types/object.py +++ b/pyrogram/client/types/object.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 re from collections import OrderedDict from datetime import datetime from json import dumps @@ -47,6 +48,9 @@ def default(obj: "Object"): if isinstance(obj, bytes): return repr(obj) + if isinstance(obj, re.Match): + return repr(obj) + return OrderedDict( [("_", "pyrogram." + obj.__class__.__name__)] + [ From 6f638cd8bd1199eba93f48f6c26d0f18051f6212 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 10 Apr 2020 13:22:57 +0200 Subject: [PATCH 0182/1185] Add INPUT_FILTER_INVALID 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 32e306b4c9..38fd1582c3 100644 --- a/compiler/error/source/400_BAD_REQUEST.tsv +++ b/compiler/error/source/400_BAD_REQUEST.tsv @@ -141,4 +141,5 @@ OPTIONS_TOO_MUCH The poll options are too many POLL_ANSWERS_INVALID The poll answers are invalid POLL_QUESTION_INVALID The poll question is invalid FRESH_CHANGE_ADMINS_FORBIDDEN Recently logged-in users cannot change admins -BROADCAST_PUBLIC_VOTERS_FORBIDDEN Polls with public voters cannot be sent in channels \ No newline at end of file +BROADCAST_PUBLIC_VOTERS_FORBIDDEN Polls with public voters cannot be sent in channels +INPUT_FILTER_INVALID The filter is invalid for this query \ No newline at end of file From 374dc6d143f5f37d5cee57609b399c25062d7efa Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 10 Apr 2020 13:37:11 +0200 Subject: [PATCH 0183/1185] Add search_messages method --- compiler/docs/compiler.py | 1 + pyrogram/client/methods/messages/__init__.py | 4 +- .../methods/messages/search_messages.py | 184 ++++++++++++++++++ 3 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 pyrogram/client/methods/messages/search_messages.py diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index a707af0731..ff205f9b8b 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -175,6 +175,7 @@ def get_title_list(s: str) -> list: stop_poll retract_vote send_dice + search_messages download_media """, chats=""" diff --git a/pyrogram/client/methods/messages/__init__.py b/pyrogram/client/methods/messages/__init__.py index eaf6f7b0db..3df4e7cee7 100644 --- a/pyrogram/client/methods/messages/__init__.py +++ b/pyrogram/client/methods/messages/__init__.py @@ -52,6 +52,7 @@ from .stop_poll import StopPoll from .vote_poll import VotePoll from .send_dice import SendDice +from .search_messages import SearchMessages class Messages( @@ -90,6 +91,7 @@ class Messages( EditInlineCaption, EditInlineMedia, EditInlineReplyMarkup, - SendDice + SendDice, + SearchMessages ): pass diff --git a/pyrogram/client/methods/messages/search_messages.py b/pyrogram/client/methods/messages/search_messages.py new file mode 100644 index 0000000000..119c40e2ba --- /dev/null +++ b/pyrogram/client/methods/messages/search_messages.py @@ -0,0 +1,184 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan +# +# 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 Union, List, Generator + +import pyrogram +from pyrogram.client.ext import BaseClient, utils +from pyrogram.api import functions, types + + +class Filters: + EMPTY = types.InputMessagesFilterEmpty() + PHOTO = types.InputMessagesFilterPhotos() + VIDEO = types.InputMessagesFilterVideo() + PHOTO_VIDEO = types.InputMessagesFilterPhotoVideo() + DOCUMENT = types.InputMessagesFilterDocument() + URL = types.InputMessagesFilterUrl() + ANIMATION = types.InputMessagesFilterGif() + VOICE_NOTE = types.InputMessagesFilterVoice() + AUDIO = types.InputMessagesFilterMusic() + CHAT_PHOTO = types.InputMessagesFilterChatPhotos() + PHONE_CALL = types.InputMessagesFilterPhoneCalls() + AUDIO_VIDEO_NOTE = types.InputMessagesFilterRoundVideo() + VIDEO_NOTE = types.InputMessagesFilterRoundVideo() + MENTION = types.InputMessagesFilterMyMentions() + LOCATION = types.InputMessagesFilterGeo() + CONTACT = types.InputMessagesFilterContacts() + + +POSSIBLE_VALUES = list(map(lambda x: x.lower(), filter(lambda x: not x.startswith("__"), Filters.__dict__.keys()))) + + +# noinspection PyShadowingBuiltins +def get_chunk( + client: BaseClient, + chat_id: Union[int, str], + query: str = "", + filter: str = "empty", + offset: int = 0, + limit: int = 100, + from_user: Union[int, str] = None +) -> List["pyrogram.Message"]: + try: + filter = Filters.__dict__[filter.upper()] + except KeyError: + raise ValueError('Invalid filter "{}". Possible values are: {}'.format( + filter, ", ".join('"{}"'.format(v) for v in POSSIBLE_VALUES))) from None + + r = client.send( + functions.messages.Search( + peer=client.resolve_peer(chat_id), + q=query, + filter=filter, + min_date=0, + max_date=0, + offset_id=0, + add_offset=offset, + limit=limit, + min_id=0, + max_id=0, + from_id=( + client.resolve_peer(from_user) + if from_user + else None + ), + hash=0 + ) + ) + + return utils.parse_messages(client, r) + + +class SearchMessages(BaseClient): + # noinspection PyShadowingBuiltins + def search_messages( + self, + chat_id: Union[int, str], + query: str = "", + offset: int = 0, + filter: str = "empty", + limit: int = 0, + from_user: Union[int, str] = None + ) -> Generator["pyrogram.Message", None, None]: + """Search for text and media messages inside a specific chat. + + Parameters: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + For your personal cloud (Saved Messages) you can simply use "me" or "self". + For a contact that exists in your Telegram address book you can use his phone number (str). + + query (``str``, *optional*): + Text query string. + Required for text-only messages, optional for media messages (see the ``filter`` argument). + When passed while searching for media messages, the query will be applied to captions. + Defaults to "" (empty string). + + offset (``int``, *optional*): + Sequential number of the first message to be returned. + Defaults to 0. + + filter (``str``, *optional*): + Pass a filter in order to search for specific kind of messages only: + + - ``"empty"``: Search for all kind of messages (default). + - ``"photo"``: Search for photos. + - ``"video"``: Search for video. + - ``"photo_video"``: Search for either photo or video. + - ``"document"``: Search for documents (generic files). + - ``"url"``: Search for messages containing URLs (web links). + - ``"animation"``: Search for animations (GIFs). + - ``"voice_note"``: Search for voice notes. + - ``"audio"``: Search for audio files (music). + - ``"chat_photo"``: Search for chat photos. + - ``"phone_call"``: Search for phone calls. + - ``"audio_video_note"``: Search for either audio or video notes. + - ``"video_note"``: Search for video notes. + - ``"mention"``: Search for messages containing mentions to yourself. + - ``"location"``: Search for location messages. + - ``"contact"``: Search for contact messages. + + limit (``int``, *optional*): + Limits the number of messages to be retrieved. + By default, no limit is applied and all messages are returned. + + from_user (``int`` | ``str``): + Unique identifier (int) or username (str) of the target user you want to search for messages from. + + Returns: + ``Generator``: A generator yielding :obj:`Message` objects. + + Example: + .. code-block:: python + + # Search for text messages in @pyrogramchat. Get the last 333 results + for message in app.search_messages("pyrogramchat", query="dan", limit=333): + print(message.text) + + # Search for photos sent by @haskell in @pyrogramchat + for message in app.search_messages("pyrogramchat", "", filter="photo" limit=333, from_user="haskell"): + print(message.text) + """ + current = 0 + total = abs(limit) or (1 << 31) - 1 + limit = min(100, total) + + while True: + messages = get_chunk( + client=self, + chat_id=chat_id, + query=query, + filter=filter, + offset=offset, + limit=limit, + from_user=from_user + ) + + if not messages: + return + + offset += 100 + + for message in messages: + yield message + + current += 1 + + if current >= total: + return From ef5fa8f70b2857a8cd0859584f8d84f0e0825f94 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 12 Apr 2020 13:30:59 +0200 Subject: [PATCH 0184/1185] Fix message entities being kept unparsed for private messages Closes #386 --- pyrogram/client/methods/messages/send_message.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyrogram/client/methods/messages/send_message.py b/pyrogram/client/methods/messages/send_message.py index d8ee87afd5..f719031c45 100644 --- a/pyrogram/client/methods/messages/send_message.py +++ b/pyrogram/client/methods/messages/send_message.py @@ -151,7 +151,10 @@ def send_message( text=message, date=r.date, outgoing=r.out, - entities=entities, + entities=[ + pyrogram.MessageEntity._parse(None, entity, {}) + for entity in entities + ], client=self ) From 99604baaf4d883a7482a7a520900076050023f15 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 12 Apr 2020 13:42:00 +0200 Subject: [PATCH 0185/1185] Update restrict_chat_member and set_chat_permissions methods --- .../methods/chats/restrict_chat_member.py | 65 +++--------------- .../methods/chats/set_chat_permissions.py | 68 ++++--------------- 2 files changed, 24 insertions(+), 109 deletions(-) diff --git a/pyrogram/client/methods/chats/restrict_chat_member.py b/pyrogram/client/methods/chats/restrict_chat_member.py index 528ad1bca4..925526785c 100644 --- a/pyrogram/client/methods/chats/restrict_chat_member.py +++ b/pyrogram/client/methods/chats/restrict_chat_member.py @@ -71,66 +71,23 @@ def restrict_chat_member( # Chat member can only send text messages app.restrict_chat_member(chat_id, user_id, ChatPermissions(can_send_messages=True)) """ - send_messages = True - send_media = True - send_stickers = True - send_gifs = True - send_games = True - send_inline = True - embed_links = True - send_polls = True - change_info = True - invite_users = True - pin_messages = True - - if permissions.can_send_messages: - send_messages = None - - if permissions.can_send_media_messages: - send_messages = None - send_media = None - - if permissions.can_send_other_messages: - send_messages = None - send_stickers = None - send_gifs = None - send_games = None - send_inline = None - - if permissions.can_add_web_page_previews: - send_messages = None - embed_links = None - - if permissions.can_send_polls: - send_messages = None - send_polls = None - - if permissions.can_change_info: - change_info = None - - if permissions.can_invite_users: - invite_users = None - - if permissions.can_pin_messages: - pin_messages = None - r = self.send( functions.channels.EditBanned( channel=self.resolve_peer(chat_id), user_id=self.resolve_peer(user_id), banned_rights=types.ChatBannedRights( until_date=until_date, - send_messages=send_messages, - send_media=send_media, - send_stickers=send_stickers, - send_gifs=send_gifs, - send_games=send_games, - send_inline=send_inline, - embed_links=embed_links, - send_polls=send_polls, - change_info=change_info, - invite_users=invite_users, - pin_messages=pin_messages + send_messages=True if not permissions.can_send_messages else None, + send_media=True if not permissions.can_send_media_messages else None, + send_stickers=True if not permissions.can_send_stickers else None, + send_gifs=True if not permissions.can_send_animations else None, + send_games=True if not permissions.can_send_games else None, + send_inline=True if not permissions.can_use_inline_bots else None, + embed_links=True if not permissions.can_add_web_page_previews else None, + send_polls=True if not permissions.can_send_polls else None, + change_info=True if not permissions.can_change_info else None, + invite_users=True if not permissions.can_invite_users else None, + pin_messages=True if not permissions.can_pin_messages else None, ) ) ) diff --git a/pyrogram/client/methods/chats/set_chat_permissions.py b/pyrogram/client/methods/chats/set_chat_permissions.py index ce2851f8ed..afb8b6f166 100644 --- a/pyrogram/client/methods/chats/set_chat_permissions.py +++ b/pyrogram/client/methods/chats/set_chat_permissions.py @@ -58,69 +58,27 @@ def set_chat_permissions( ChatPermissions( can_send_messages=True, can_send_media_messages=True, - can_send_other_messages=True + can_send_stickers=True, + can_send_animations=True ) ) """ - send_messages = True - send_media = True - send_stickers = True - send_gifs = True - send_games = True - send_inline = True - embed_links = True - send_polls = True - change_info = True - invite_users = True - pin_messages = True - - if permissions.can_send_messages: - send_messages = None - - if permissions.can_send_media_messages: - send_messages = None - send_media = None - - if permissions.can_send_other_messages: - send_messages = None - send_stickers = None - send_gifs = None - send_games = None - send_inline = None - - if permissions.can_add_web_page_previews: - send_messages = None - embed_links = None - - if permissions.can_send_polls: - send_messages = None - send_polls = None - - if permissions.can_change_info: - change_info = None - - if permissions.can_invite_users: - invite_users = None - - if permissions.can_pin_messages: - pin_messages = None - r = self.send( functions.messages.EditChatDefaultBannedRights( peer=self.resolve_peer(chat_id), banned_rights=types.ChatBannedRights( until_date=0, - send_messages=send_messages, - send_media=send_media, - send_stickers=send_stickers, - send_gifs=send_gifs, - send_games=send_games, - send_inline=send_inline, - embed_links=embed_links, - send_polls=send_polls, - change_info=change_info, - invite_users=invite_users, - pin_messages=pin_messages + send_messages=True if not permissions.can_send_messages else None, + send_media=True if not permissions.can_send_media_messages else None, + send_stickers=True if not permissions.can_send_stickers else None, + send_gifs=True if not permissions.can_send_animations else None, + send_games=True if not permissions.can_send_games else None, + send_inline=True if not permissions.can_use_inline_bots else None, + embed_links=True if not permissions.can_add_web_page_previews else None, + send_polls=True if not permissions.can_send_polls else None, + change_info=True if not permissions.can_change_info else None, + invite_users=True if not permissions.can_invite_users else None, + pin_messages=True if not permissions.can_pin_messages else None, ) ) ) From 72e150ca13b82733bda4b79cb7fd2d4720d65b1e Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 20 Apr 2020 22:40:39 +0200 Subject: [PATCH 0186/1185] Add ttl_seconds field to Video objects --- pyrogram/client/types/messages_and_media/message.py | 3 ++- pyrogram/client/types/messages_and_media/video.py | 9 ++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index 7b99cbc500..b03f097bc4 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -561,7 +561,8 @@ def _parse(client, message: types.Message or types.MessageService or types.Messa if video_attributes.round_message: video_note = pyrogram.VideoNote._parse(client, doc, video_attributes) else: - video = pyrogram.Video._parse(client, doc, video_attributes, file_name) + video = pyrogram.Video._parse(client, doc, video_attributes, file_name, + media.ttl_seconds) elif types.DocumentAttributeSticker in attributes: sticker = pyrogram.Sticker._parse( client, doc, diff --git a/pyrogram/client/types/messages_and_media/video.py b/pyrogram/client/types/messages_and_media/video.py index 59ce08bcc3..92c8f46de6 100644 --- a/pyrogram/client/types/messages_and_media/video.py +++ b/pyrogram/client/types/messages_and_media/video.py @@ -51,6 +51,9 @@ class Video(Object): mime_type (``str``, *optional*): Mime type of a file as defined by sender. + ttl_seconds (``int``): + Time-to-live seconds, for secret photos. + supports_streaming (``bool``, *optional*): True, if the video was uploaded with streaming support. @@ -75,6 +78,7 @@ def __init__( duration: int, file_name: str = None, mime_type: str = None, + ttl_seconds: int = None, supports_streaming: bool = None, file_size: int = None, date: int = None, @@ -89,6 +93,7 @@ def __init__( self.duration = duration self.file_name = file_name self.mime_type = mime_type + self.ttl_seconds = ttl_seconds self.supports_streaming = supports_streaming self.file_size = file_size self.date = date @@ -99,7 +104,8 @@ def _parse( client, video: types.Document, video_attributes: types.DocumentAttributeVideo, - file_name: str + file_name: str, + ttl_seconds: int = None ) -> "Video": return Video( file_id=encode_file_id( @@ -117,6 +123,7 @@ def _parse( duration=video_attributes.duration, file_name=file_name, mime_type=video.mime_type, + ttl_seconds=ttl_seconds, supports_streaming=video_attributes.supports_streaming, file_size=video.size, date=video.date, From e42d6acc08095bb51b58874e2b86bfd564e4fe04 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 23 Apr 2020 12:54:52 +0200 Subject: [PATCH 0187/1185] Add support for scheduled forward messages --- .../methods/messages/forward_messages.py | 12 ++++++--- .../types/messages_and_media/message.py | 27 +++++++++++++------ 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/pyrogram/client/methods/messages/forward_messages.py b/pyrogram/client/methods/messages/forward_messages.py index b3931d963c..0a160857ab 100644 --- a/pyrogram/client/methods/messages/forward_messages.py +++ b/pyrogram/client/methods/messages/forward_messages.py @@ -31,7 +31,8 @@ def forward_messages( message_ids: Union[int, Iterable[int]], disable_notification: bool = None, as_copy: bool = False, - remove_caption: bool = False + remove_caption: bool = False, + schedule_date: int = None ) -> List["pyrogram.Message"]: """Forward messages of any kind. @@ -64,6 +65,9 @@ def forward_messages( message. Has no effect if *as_copy* is not enabled. Defaults to False. + schedule_date (``int``, *optional*): + Date when the message will be automatically sent. Unix time. + Returns: :obj:`Message` | List of :obj:`Message`: 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 a list of @@ -98,7 +102,8 @@ def forward_messages( chat_id, disable_notification=disable_notification, as_copy=True, - remove_caption=remove_caption + remove_caption=remove_caption, + schedule_date=schedule_date ) ) @@ -110,7 +115,8 @@ def forward_messages( from_peer=self.resolve_peer(from_chat_id), id=message_ids, silent=disable_notification or None, - random_id=[self.rnd_id() for _ in message_ids] + random_id=[self.rnd_id() for _ in message_ids], + schedule_date=schedule_date ) ) diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index b03f097bc4..abdf479246 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -2634,7 +2634,8 @@ def forward( chat_id: int or str, disable_notification: bool = None, as_copy: bool = False, - remove_caption: bool = False + remove_caption: bool = False, + schedule_date: int = None ) -> "Message": """Bound method *forward* of :obj:`Message`. @@ -2672,6 +2673,9 @@ def forward( message. Has no effect if *as_copy* is not enabled. Defaults to False. + schedule_date (``int``, *optional*): + Date when the message will be automatically sent. Unix time. + Returns: On success, the forwarded Message is returned. @@ -2691,7 +2695,8 @@ def forward( text=self.text.html, parse_mode="html", disable_web_page_preview=not self.web_page, - disable_notification=disable_notification + disable_notification=disable_notification, + schedule_date=schedule_date ) elif self.media: caption = self.caption.html if self.caption and not remove_caption else "" @@ -2699,7 +2704,8 @@ def forward( send_media = partial( self._client.send_cached_media, chat_id=chat_id, - disable_notification=disable_notification + disable_notification=disable_notification, + schedule_date=schedule_date ) if self.photo: @@ -2733,14 +2739,16 @@ def forward( first_name=self.contact.first_name, last_name=self.contact.last_name, vcard=self.contact.vcard, - disable_notification=disable_notification + disable_notification=disable_notification, + schedule_date=schedule_date ) elif self.location: return self._client.send_location( chat_id, latitude=self.location.latitude, longitude=self.location.longitude, - disable_notification=disable_notification + disable_notification=disable_notification, + schedule_date=schedule_date ) elif self.venue: return self._client.send_venue( @@ -2751,14 +2759,16 @@ def forward( address=self.venue.address, foursquare_id=self.venue.foursquare_id, foursquare_type=self.venue.foursquare_type, - disable_notification=disable_notification + disable_notification=disable_notification, + schedule_date=schedule_date ) elif self.poll: return self._client.send_poll( chat_id, question=self.poll.question, options=[opt.text for opt in self.poll.options], - disable_notification=disable_notification + disable_notification=disable_notification, + schedule_date=schedule_date ) elif self.game: return self._client.send_game( @@ -2780,7 +2790,8 @@ def forward( chat_id=chat_id, from_chat_id=self.chat.id, message_ids=self.message_id, - disable_notification=disable_notification + disable_notification=disable_notification, + schedule_date=schedule_date ) def delete(self, revoke: bool = True): From 4526a644dd0c9d9115bc28a8e30127f640fdfcca Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 23 Apr 2020 13:02:55 +0200 Subject: [PATCH 0188/1185] Fix scheduled forward messages not being parsed correctly --- pyrogram/client/methods/messages/forward_messages.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/methods/messages/forward_messages.py b/pyrogram/client/methods/messages/forward_messages.py index 0a160857ab..1e2b1702a5 100644 --- a/pyrogram/client/methods/messages/forward_messages.py +++ b/pyrogram/client/methods/messages/forward_messages.py @@ -126,7 +126,7 @@ def forward_messages( chats = {i.id: i for i in r.chats} for i in r.updates: - if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): + if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage, types.UpdateNewScheduledMessage)): forwarded_messages.append( pyrogram.Message._parse( self, i.message, From a4fcc758a6f67dccb94719908457b81b14db04d0 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 24 Apr 2020 15:47:15 +0200 Subject: [PATCH 0189/1185] Update API schema to Layer 112 --- compiler/api/source/main_api.tl | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl index 4a681f09c0..28918f19e7 100644 --- a/compiler/api/source/main_api.tl +++ b/compiler/api/source/main_api.tl @@ -49,8 +49,8 @@ inputMediaDocumentExternal#fb52dc99 flags:# url:string ttl_seconds:flags.0?int = inputMediaGame#d33f43f3 id:InputGame = InputMedia; inputMediaInvoice#f4e096c3 flags:# title:string description:string photo:flags.0?InputWebDocument invoice:Invoice payload:bytes provider:string provider_data:DataJSON start_param:string = InputMedia; inputMediaGeoLive#ce4e82fd flags:# stopped:flags.0?true geo_point:InputGeoPoint period:flags.1?int = InputMedia; -inputMediaPoll#abe9ca25 flags:# poll:Poll correct_answers:flags.0?Vector = InputMedia; -inputMediaDice#aeffa807 = InputMedia; +inputMediaPoll#f94e5f1 flags:# poll:Poll correct_answers:flags.0?Vector solution:flags.1?string solution_entities:flags.1?Vector = InputMedia; +inputMediaDice#e66fbf7b emoticon:string = InputMedia; inputChatPhotoEmpty#1ca48f57 = InputChatPhoto; inputChatUploadedPhoto#927c55b4 file:InputFile = InputChatPhoto; @@ -135,7 +135,7 @@ messageMediaGame#fdb19008 game:Game = MessageMedia; messageMediaInvoice#84551347 flags:# shipping_address_requested:flags.1?true test:flags.3?true title:string description:string photo:flags.0?WebDocument receipt_msg_id:flags.2?int currency:string total_amount:long start_param:string = MessageMedia; messageMediaGeoLive#7c3c2609 geo:GeoPoint period:int = MessageMedia; messageMediaPoll#4bd6e798 poll:Poll results:PollResults = MessageMedia; -messageMediaDice#638fe46b value:int = MessageMedia; +messageMediaDice#3f7ee58b value:int emoticon:string = MessageMedia; messageActionEmpty#b6aef7b0 = MessageAction; messageActionChatCreate#a6638b9a title:string users:Vector = MessageAction; @@ -511,7 +511,7 @@ inputStickerSetEmpty#ffb62b95 = InputStickerSet; inputStickerSetID#9de7a269 id:long access_hash:long = InputStickerSet; inputStickerSetShortName#861cc8a0 short_name:string = InputStickerSet; inputStickerSetAnimatedEmoji#28703c8 = InputStickerSet; -inputStickerSetDice#79e21a53 = InputStickerSet; +inputStickerSetDice#e67f520e emoticon:string = InputStickerSet; stickerSet#eeb46f27 flags:# archived:flags.1?true official:flags.2?true masks:flags.3?true animated:flags.5?true installed_date:flags.0?int id:long access_hash:long title:string short_name:string thumb:flags.4?PhotoSize thumb_dc_id:flags.4?int count:int hash:int = StickerSet; @@ -670,8 +670,8 @@ contacts.topPeersDisabled#b52c939d = contacts.TopPeers; draftMessageEmpty#1b0c841a flags:# date:flags.0?int = DraftMessage; draftMessage#fd8e711f flags:# no_webpage:flags.1?true reply_to_msg_id:flags.0?int message:string entities:flags.3?Vector date:int = DraftMessage; -messages.featuredStickersNotModified#4ede3cf = messages.FeaturedStickers; -messages.featuredStickers#f89d88e5 hash:int sets:Vector unread:Vector = messages.FeaturedStickers; +messages.featuredStickersNotModified#c6dc0c66 count:int = messages.FeaturedStickers; +messages.featuredStickers#b6abc341 hash:int count:int sets:Vector unread:Vector = messages.FeaturedStickers; messages.recentStickersNotModified#b17f890 = messages.RecentStickers; messages.recentStickers#22f3afb3 hash:int packs:Vector stickers:Vector dates:Vector = messages.RecentStickers; @@ -1002,11 +1002,11 @@ help.userInfo#1eb3758 message:string entities:Vector author:strin pollAnswer#6ca9c2e9 text:string option:bytes = PollAnswer; -poll#d5529d06 id:long flags:# closed:flags.0?true public_voters:flags.1?true multiple_choice:flags.2?true quiz:flags.3?true question:string answers:Vector = Poll; +poll#86e18161 id:long flags:# closed:flags.0?true public_voters:flags.1?true multiple_choice:flags.2?true quiz:flags.3?true question:string answers:Vector close_period:flags.4?int close_date:flags.5?int = Poll; pollAnswerVoters#3b6ddad2 flags:# chosen:flags.0?true correct:flags.1?true option:bytes voters:int = PollAnswerVoters; -pollResults#c87024a2 flags:# min:flags.0?true results:flags.1?Vector total_voters:flags.2?int recent_voters:flags.3?Vector = PollResults; +pollResults#badcc1a3 flags:# min:flags.0?true results:flags.1?Vector total_voters:flags.2?int recent_voters:flags.3?Vector solution:flags.4?string solution_entities:flags.4?Vector = PollResults; chatOnlines#f041e250 onlines:int = ChatOnlines; @@ -1122,7 +1122,7 @@ stats.broadcastStats#bdf78394 period:StatsDateRangeDays followers:StatsAbsValueA invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; invokeAfterMsgs#3dc4b4f0 {X:Type} msg_ids:Vector query:!X = X; -initConnection#785188b8 {X:Type} flags:# api_id:int device_model:string system_version:string app_version:string system_lang_code:string lang_pack:string lang_code:string proxy:flags.0?InputClientProxy query:!X = X; +initConnection#c1cd5ea9 {X:Type} flags:# api_id:int device_model:string system_version:string app_version:string system_lang_code:string lang_pack:string lang_code:string proxy:flags.0?InputClientProxy params:flags.1?JSONValue query:!X = X; invokeWithLayer#da9b0d0d {X:Type} layer:int query:!X = X; invokeWithoutUpdates#bf9459b7 {X:Type} query:!X = X; invokeWithMessagesRange#365275f2 {X:Type} range:MessageRange query:!X = X; @@ -1360,6 +1360,7 @@ messages.getDialogFilters#f19ed96d = Vector; messages.getSuggestedDialogFilters#a29cd42c = Vector; messages.updateDialogFilter#1ad4a04a flags:# id:int filter:flags.0?DialogFilter = Bool; messages.updateDialogFiltersOrder#c563c1e4 order:Vector = Bool; +messages.getOldFeaturedStickers#5fe7025b offset:int limit:int hash:int = messages.FeaturedStickers; updates.getState#edd4882a = updates.State; updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference; @@ -1437,6 +1438,7 @@ channels.getInactiveChannels#11e831ee = messages.InactiveChats; bots.sendCustomRequest#aa2769ed custom_method:string params:DataJSON = DataJSON; bots.answerWebhookJSONQuery#e6213f4d query_id:long data:DataJSON = Bool; +bots.setBotCommands#805d46f6 commands:Vector = Bool; payments.getPaymentForm#99f09745 msg_id:int = payments.PaymentForm; payments.getPaymentReceipt#a092a980 msg_id:int = payments.PaymentReceipt; @@ -1446,10 +1448,11 @@ payments.getSavedInfo#227d824b = payments.SavedInfo; payments.clearSavedInfo#d83d70c1 flags:# credentials:flags.0?true info:flags.1?true = Bool; payments.getBankCardData#2e79d779 number:string = payments.BankCardData; -stickers.createStickerSet#9bd86e6a flags:# masks:flags.0?true user_id:InputUser title:string short_name:string stickers:Vector = messages.StickerSet; +stickers.createStickerSet#f1036780 flags:# masks:flags.0?true animated:flags.1?true user_id:InputUser title:string short_name:string thumb:flags.2?InputDocument stickers:Vector = messages.StickerSet; stickers.removeStickerFromSet#f7760f51 sticker:InputDocument = messages.StickerSet; stickers.changeStickerPosition#ffb6d4ca sticker:InputDocument position:int = messages.StickerSet; stickers.addStickerToSet#8653febe stickerset:InputStickerSet sticker:InputStickerSetItem = messages.StickerSet; +stickers.setStickerSetThumb#9a364e30 stickerset:InputStickerSet thumb:InputDocument = messages.StickerSet; phone.getCallConfig#55451fa9 = DataJSON; phone.requestCall#42ff96ed flags:# video:flags.0?true user_id:InputUser random_id:int g_a_hash:bytes protocol:PhoneCallProtocol = phone.PhoneCall; @@ -1469,7 +1472,7 @@ langpack.getLanguage#6a596502 lang_pack:string lang_code:string = LangPackLangua folders.editPeerFolders#6847d0ab folder_peers:Vector = Updates; folders.deleteFolder#1c295881 folder_id:int = Updates; -stats.getBroadcastStats#ab42441a flags:# dark:flags.0?true channel:InputChannel = stats.BroadcastStats; +stats.getBroadcastStats#e6300dba flags:# dark:flags.0?true channel:InputChannel tz_offset:int = stats.BroadcastStats; stats.loadAsyncGraph#621d5fa0 flags:# token:string x:flags.0?long = StatsGraph; -// LAYER 111 \ No newline at end of file +// LAYER 112 \ No newline at end of file From 8fa1ca5d0b9bd4a5d45390bcacbfd472e9657422 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 24 Apr 2020 15:51:19 +0200 Subject: [PATCH 0190/1185] Add emoji related errors --- compiler/error/source/400_BAD_REQUEST.tsv | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/compiler/error/source/400_BAD_REQUEST.tsv b/compiler/error/source/400_BAD_REQUEST.tsv index 38fd1582c3..8b4d8709ce 100644 --- a/compiler/error/source/400_BAD_REQUEST.tsv +++ b/compiler/error/source/400_BAD_REQUEST.tsv @@ -142,4 +142,6 @@ POLL_ANSWERS_INVALID The poll answers are invalid POLL_QUESTION_INVALID The poll question is invalid FRESH_CHANGE_ADMINS_FORBIDDEN Recently logged-in users cannot change admins BROADCAST_PUBLIC_VOTERS_FORBIDDEN Polls with public voters cannot be sent in channels -INPUT_FILTER_INVALID The filter is invalid for this query \ No newline at end of file +INPUT_FILTER_INVALID The filter is invalid for this query +EMOTICON_EMPTY The emoticon parameter is empty +EMOTICON_INVALID The emoticon parameter is invalid \ No newline at end of file From 5b042a65467f83a11be595b08854353d6cfc1370 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 24 Apr 2020 15:51:40 +0200 Subject: [PATCH 0191/1185] Add support for darts mini-game with send_dice --- pyrogram/client/methods/messages/send_dice.py | 11 ++++++++++- pyrogram/client/types/messages_and_media/dice.py | 16 ++++++++++++---- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/pyrogram/client/methods/messages/send_dice.py b/pyrogram/client/methods/messages/send_dice.py index c5f95d4bb9..f90bc852b2 100644 --- a/pyrogram/client/methods/messages/send_dice.py +++ b/pyrogram/client/methods/messages/send_dice.py @@ -27,6 +27,7 @@ class SendDice(BaseClient): def send_dice( self, chat_id: Union[int, str], + emoji: str = "🎲", disable_notification: bool = None, reply_to_message_id: int = None, schedule_date: int = None, @@ -45,6 +46,10 @@ def send_dice( For your personal cloud (Saved Messages) you can simply use "me" or "self". For a contact that exists in your Telegram address book you can use his phone number (str). + emoji (``str``, *optional*): + Emoji on which the dice throw animation is based. Currently, must be one of "🎲" or "🎯". + Defauts to "🎲". + disable_notification (``bool``, *optional*): Sends the message silently. Users will receive a notification with no sound. @@ -65,13 +70,17 @@ def send_dice( Example: .. code-block:: python + # Send a dice app.send_dice("pyrogramlounge") + + # Send a dart + app.send_dice("pyrogramlounge", "🎯") """ r = self.send( functions.messages.SendMedia( peer=self.resolve_peer(chat_id), - media=types.InputMediaDice(), + media=types.InputMediaDice(emoticon=emoji), silent=disable_notification or None, reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), diff --git a/pyrogram/client/types/messages_and_media/dice.py b/pyrogram/client/types/messages_and_media/dice.py index c579a89041..7552f083d4 100644 --- a/pyrogram/client/types/messages_and_media/dice.py +++ b/pyrogram/client/types/messages_and_media/dice.py @@ -27,18 +27,26 @@ class Dice(Object): - """A dice containing a value that is randomly generated by Telegram. + """A dice with a random value from 1 to 6 for currently supported base emoji. Parameters: + emoji (``string``): + Emoji on which the dice throw animation is based. + value (``int``): - Dice value, 1-6. + Value of the dice, 1-6 for currently supported base emoji. """ - def __init__(self, *, client: "pyrogram.BaseClient" = None, value: int): + def __init__(self, *, client: "pyrogram.BaseClient" = None, emoji: str, value: int): super().__init__(client) + self.emoji = emoji self.value = value @staticmethod def _parse(client, dice: types.MessageMediaDice) -> "Dice": - return Dice(value=dice.value, client=client) + return Dice( + emoji=dice.emoticon, + value=dice.value, + client=client + ) From 8c2dd9d1c3ea219aa8d095e6b804cc1b13f25834 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 26 Apr 2020 12:21:10 +0200 Subject: [PATCH 0192/1185] Fix ttl_seconds and thumbs not being optional --- pyrogram/client/types/messages_and_media/photo.py | 14 +++++++------- pyrogram/client/types/messages_and_media/video.py | 12 ++++++------ 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/pyrogram/client/types/messages_and_media/photo.py b/pyrogram/client/types/messages_and_media/photo.py index 235f49310b..36d37cacea 100644 --- a/pyrogram/client/types/messages_and_media/photo.py +++ b/pyrogram/client/types/messages_and_media/photo.py @@ -42,15 +42,15 @@ class Photo(Object): height (``int``): Photo height. - ttl_seconds (``int``): - Time-to-live seconds, for secret photos. - file_size (``int``): File size. date (``int``): Date the photo was sent in Unix time. + ttl_seconds (``int``, *optional*): + Time-to-live seconds, for secret photos. + thumbs (List of :obj:`Thumbnail`, *optional*): Available thumbnails of this photo. """ @@ -63,10 +63,10 @@ def __init__( file_ref: str, width: int, height: int, - ttl_seconds: int, file_size: int, date: int, - thumbs: List[Thumbnail] + ttl_seconds: int = None, + thumbs: List[Thumbnail] = None ): super().__init__(client) @@ -74,9 +74,9 @@ def __init__( self.file_ref = file_ref self.width = width self.height = height - self.ttl_seconds = ttl_seconds self.file_size = file_size self.date = date + self.ttl_seconds = ttl_seconds self.thumbs = thumbs @staticmethod @@ -96,9 +96,9 @@ def _parse(client, photo: types.Photo, ttl_seconds: int = None) -> "Photo": file_ref=encode_file_ref(photo.file_reference), width=big.w, height=big.h, - ttl_seconds=ttl_seconds, file_size=big.size, date=photo.date, + ttl_seconds=ttl_seconds, thumbs=Thumbnail._parse(client, photo), client=client ) diff --git a/pyrogram/client/types/messages_and_media/video.py b/pyrogram/client/types/messages_and_media/video.py index 92c8f46de6..16388b181c 100644 --- a/pyrogram/client/types/messages_and_media/video.py +++ b/pyrogram/client/types/messages_and_media/video.py @@ -51,9 +51,6 @@ class Video(Object): mime_type (``str``, *optional*): Mime type of a file as defined by sender. - ttl_seconds (``int``): - Time-to-live seconds, for secret photos. - supports_streaming (``bool``, *optional*): True, if the video was uploaded with streaming support. @@ -63,6 +60,9 @@ class Video(Object): date (``int``, *optional*): Date the video was sent in Unix time. + ttl_seconds (``int``. *optional*): + Time-to-live seconds, for secret photos. + thumbs (List of :obj:`Thumbnail`, *optional*): Video thumbnails. """ @@ -78,10 +78,10 @@ def __init__( duration: int, file_name: str = None, mime_type: str = None, - ttl_seconds: int = None, supports_streaming: bool = None, file_size: int = None, date: int = None, + ttl_seconds: int = None, thumbs: List[Thumbnail] = None ): super().__init__(client) @@ -93,10 +93,10 @@ def __init__( self.duration = duration self.file_name = file_name self.mime_type = mime_type - self.ttl_seconds = ttl_seconds self.supports_streaming = supports_streaming self.file_size = file_size self.date = date + self.ttl_seconds = ttl_seconds self.thumbs = thumbs @staticmethod @@ -123,10 +123,10 @@ def _parse( duration=video_attributes.duration, file_name=file_name, mime_type=video.mime_type, - ttl_seconds=ttl_seconds, supports_streaming=video_attributes.supports_streaming, file_size=video.size, date=video.date, + ttl_seconds=ttl_seconds, thumbs=Thumbnail._parse(client, video), client=client ) From 22c29791c8972ebfef15f2594448c56e96e4c8aa Mon Sep 17 00:00:00 2001 From: MIRROR <48849009+SCP-079-MIRROR@users.noreply.github.com> Date: Sun, 26 Apr 2020 10:59:58 +0000 Subject: [PATCH 0193/1185] Fix the type hint of Message's web_page (#392) --- 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 abdf479246..575672e2ed 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -307,7 +307,7 @@ def __init__( contact: "pyrogram.Contact" = None, location: "pyrogram.Location" = None, venue: "pyrogram.Venue" = None, - web_page: bool = None, + web_page: "pyrogram.WebPage" = None, poll: "pyrogram.Poll" = None, dice: "pyrogram.Dice" = None, new_chat_members: List[User] = None, From 858f82fd88de51bac6993b4feadd59f563da3629 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 30 Apr 2020 02:43:23 +0200 Subject: [PATCH 0194/1185] Update Pyrogram to v0.17.0 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 60db23254e..13ea5ce519 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "0.17.0-dev" +__version__ = "0.17.0" __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)" __copyright__ = "Copyright (C) 2017-2020 Dan " From 23789393fe22dc45f1847f898a4b5390d7cf206c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 1 May 2020 16:37:03 +0200 Subject: [PATCH 0195/1185] Fix missing async/await for set_slow_mode --- pyrogram/client/methods/chats/set_slow_mode.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyrogram/client/methods/chats/set_slow_mode.py b/pyrogram/client/methods/chats/set_slow_mode.py index cf6c7096a3..445abd4b0a 100644 --- a/pyrogram/client/methods/chats/set_slow_mode.py +++ b/pyrogram/client/methods/chats/set_slow_mode.py @@ -23,7 +23,7 @@ class SetSlowMode(BaseClient): - def set_slow_mode( + async def set_slow_mode( self, chat_id: Union[int, str], seconds: int, @@ -47,9 +47,9 @@ def set_slow_mode( app.set_slow_mode("pyrogramchat", 60) """ - self.send( + await self.send( functions.channels.ToggleSlowMode( - channel=self.resolve_peer(chat_id), + channel=await self.resolve_peer(chat_id), seconds=seconds ) ) From 78ed83ebf7b0cf83c0ece4abaeaf3697754a6406 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 1 May 2020 18:45:52 +0200 Subject: [PATCH 0196/1185] Fix plugins not being properly unloaded Also, plugins' roots values will follow python notation: folder.plugins instead of folder/plugins, in case of roots inside another folder. --- 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 7c2279b97a..3ef82ada0a 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -872,6 +872,7 @@ def stop(self, block: bool = True): app.stop() """ + def do_it(): self.terminate() self.disconnect() @@ -918,6 +919,7 @@ def restart(self, block: bool = True): app.stop() """ + def do_it(): self.stop() self.start() @@ -1525,7 +1527,7 @@ def load_plugins(self): count = 0 if not include: - for path in sorted(Path(root).rglob("*.py")): + for path in sorted(Path(root.replace(".", "/")).rglob("*.py")): module_path = '.'.join(path.parent.parts + (path.stem,)) module = reload(import_module(module_path)) @@ -1587,7 +1589,7 @@ def load_plugins(self): warn_non_existent_functions = True try: - module = reload(import_module(module_path)) + module = import_module(module_path) except ImportError: log.warning('[{}] [UNLOAD] Ignoring non-existent module "{}"'.format( self.session_name, module_path)) From 20de3fccdf8048e53401777f7c7e07c83f0b4edb Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 2 May 2020 15:01:15 +0200 Subject: [PATCH 0197/1185] Fix Match objects not being properly printed in Python 3.6 --- pyrogram/client/types/object.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/types/object.py b/pyrogram/client/types/object.py index adafc8a064..750e0be765 100644 --- a/pyrogram/client/types/object.py +++ b/pyrogram/client/types/object.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -import re +import typing from collections import OrderedDict from datetime import datetime from json import dumps @@ -48,7 +48,9 @@ def default(obj: "Object"): if isinstance(obj, bytes): return repr(obj) - if isinstance(obj, re.Match): + # https://t.me/pyrogramchat/167281 + # Instead of re.Match, which breaks for python <=3.6 + if isinstance(obj, typing.Match): return repr(obj) return OrderedDict( From dd9423bbb1825770385d69e4a25cb9bae18b075f Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 2 May 2020 21:02:06 +0200 Subject: [PATCH 0198/1185] Update Pyrogram to v0.17.1 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 13ea5ce519..301e4e6452 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "0.17.0" +__version__ = "0.17.1" __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)" __copyright__ = "Copyright (C) 2017-2020 Dan " From fd4bd754cc6a710b2bfbf7f9918a5fb78e5be94d Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 2 May 2020 21:16:52 +0200 Subject: [PATCH 0199/1185] Add missing update_profile to docs and Client --- compiler/docs/compiler.py | 1 + pyrogram/client/methods/users/__init__.py | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index ff205f9b8b..6b596b2fce 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -226,6 +226,7 @@ def get_title_list(s: str) -> list: set_profile_photo delete_profile_photos update_username + update_profile block_user unblock_user """, diff --git a/pyrogram/client/methods/users/__init__.py b/pyrogram/client/methods/users/__init__.py index 1980303fef..65171af674 100644 --- a/pyrogram/client/methods/users/__init__.py +++ b/pyrogram/client/methods/users/__init__.py @@ -26,6 +26,7 @@ from .iter_profile_photos import IterProfilePhotos from .set_profile_photo import SetProfilePhoto from .unblock_user import UnblockUser +from .update_profile import UpdateProfile from .update_username import UpdateUsername @@ -40,6 +41,7 @@ class Users( UpdateUsername, GetProfilePhotosCount, IterProfilePhotos, - UnblockUser + UnblockUser, + UpdateProfile ): pass From 2563c1a6bedc0a71fe108b90675359649261390e Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 2 May 2020 21:17:05 +0200 Subject: [PATCH 0200/1185] Add copy button prompt text to ignore --- docs/source/conf.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/source/conf.py b/docs/source/conf.py index 0302328deb..a86895cee5 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -52,6 +52,8 @@ pygments_style = "friendly" +copybutton_prompt_text = "$ " + html_title = "Pyrogram Documentation" html_theme = "sphinx_rtd_theme" html_static_path = ["_static"] From 4ff924bcfb720f8ca1186a649d7d779613722618 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 2 May 2020 21:17:22 +0200 Subject: [PATCH 0201/1185] Update Pyrogram to v0.17.1 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 13ea5ce519..301e4e6452 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "0.17.0" +__version__ = "0.17.1" __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)" __copyright__ = "Copyright (C) 2017-2020 Dan " From 48e45fee9b3018fcd509362ea5775ed568379431 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 2 May 2020 21:16:52 +0200 Subject: [PATCH 0202/1185] Add missing update_profile to docs and Client --- compiler/docs/compiler.py | 1 + pyrogram/client/methods/users/__init__.py | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index ff205f9b8b..6b596b2fce 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -226,6 +226,7 @@ def get_title_list(s: str) -> list: set_profile_photo delete_profile_photos update_username + update_profile block_user unblock_user """, diff --git a/pyrogram/client/methods/users/__init__.py b/pyrogram/client/methods/users/__init__.py index 1980303fef..65171af674 100644 --- a/pyrogram/client/methods/users/__init__.py +++ b/pyrogram/client/methods/users/__init__.py @@ -26,6 +26,7 @@ from .iter_profile_photos import IterProfilePhotos from .set_profile_photo import SetProfilePhoto from .unblock_user import UnblockUser +from .update_profile import UpdateProfile from .update_username import UpdateUsername @@ -40,6 +41,7 @@ class Users( UpdateUsername, GetProfilePhotosCount, IterProfilePhotos, - UnblockUser + UnblockUser, + UpdateProfile ): pass From 3502153b709695a56eae2e7517237099c6925271 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 2 May 2020 21:17:05 +0200 Subject: [PATCH 0203/1185] Add copy button prompt text to ignore --- docs/source/conf.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/source/conf.py b/docs/source/conf.py index 0302328deb..a86895cee5 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -52,6 +52,8 @@ pygments_style = "friendly" +copybutton_prompt_text = "$ " + html_title = "Pyrogram Documentation" html_theme = "sphinx_rtd_theme" html_static_path = ["_static"] From 88e42ecc0d339ca9d9df8f50b305ec82a8567332 Mon Sep 17 00:00:00 2001 From: CyanBook Date: Mon, 4 May 2020 10:55:10 +0000 Subject: [PATCH 0204/1185] Update update_profile example (#395) --- pyrogram/client/methods/users/update_profile.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyrogram/client/methods/users/update_profile.py b/pyrogram/client/methods/users/update_profile.py index 19ec6d6606..91a7950ccd 100644 --- a/pyrogram/client/methods/users/update_profile.py +++ b/pyrogram/client/methods/users/update_profile.py @@ -50,13 +50,13 @@ def update_profile( .. code-block:: python # Update your first name only - app.update_bio(first_name="Pyrogram") + app.update_profile(first_name="Pyrogram") # Update first name and bio - app.update_bio(first_name="Pyrogram", bio="https://docs.pyrogram.org/") + app.update_profile(first_name="Pyrogram", bio="https://docs.pyrogram.org/") # Remove the last name - app.update_bio(last_name="") + app.update_profile(last_name="") """ return bool( From c05c5c44410cb2188f1189dfdd5e9fa53f7c4676 Mon Sep 17 00:00:00 2001 From: SuperCz1 <62919067+SuperCz1@users.noreply.github.com> Date: Thu, 7 May 2020 11:35:08 +0200 Subject: [PATCH 0205/1185] Add session name in "Sleeping for Xs" log lines (#401) * Update send_media_group.py * Update get_dialogs.py * Update get_dialogs.py * Update get_messages.py * Update get_history.py * Update get_chat_members.py --- pyrogram/client/methods/chats/get_chat_members.py | 2 +- pyrogram/client/methods/chats/get_dialogs.py | 2 +- pyrogram/client/methods/messages/get_history.py | 2 +- pyrogram/client/methods/messages/get_messages.py | 2 +- pyrogram/client/methods/messages/send_media_group.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pyrogram/client/methods/chats/get_chat_members.py b/pyrogram/client/methods/chats/get_chat_members.py index a8ae405487..6be3408b24 100644 --- a/pyrogram/client/methods/chats/get_chat_members.py +++ b/pyrogram/client/methods/chats/get_chat_members.py @@ -153,7 +153,7 @@ def get_chat_members( return pyrogram.List(pyrogram.ChatMember._parse(self, member, users) for member in members) except FloodWait as e: - log.warning("Sleeping for {}s".format(e.x)) + log.warning("[{}] Sleeping for {}s".format(self.session_name, e.x)) time.sleep(e.x) else: raise ValueError("The chat_id \"{}\" belongs to a user".format(chat_id)) diff --git a/pyrogram/client/methods/chats/get_dialogs.py b/pyrogram/client/methods/chats/get_dialogs.py index 6234538b72..ea2eec632c 100644 --- a/pyrogram/client/methods/chats/get_dialogs.py +++ b/pyrogram/client/methods/chats/get_dialogs.py @@ -82,7 +82,7 @@ def get_dialogs( ) ) except FloodWait as e: - log.warning("Sleeping {}s".format(e.x)) + log.warning("[{}] Sleeping for {}s".format(self.session_name, e.x)) time.sleep(e.x) else: break diff --git a/pyrogram/client/methods/messages/get_history.py b/pyrogram/client/methods/messages/get_history.py index bf348ac9a3..7741340c22 100644 --- a/pyrogram/client/methods/messages/get_history.py +++ b/pyrogram/client/methods/messages/get_history.py @@ -103,7 +103,7 @@ def get_history( ) ) except FloodWait as e: - log.warning("Sleeping for {}s".format(e.x)) + log.warning("[{}] Sleeping for {}s".format(self.session_name, e.x)) time.sleep(e.x) else: break diff --git a/pyrogram/client/methods/messages/get_messages.py b/pyrogram/client/methods/messages/get_messages.py index a18d84c2eb..f3e53b9419 100644 --- a/pyrogram/client/methods/messages/get_messages.py +++ b/pyrogram/client/methods/messages/get_messages.py @@ -116,7 +116,7 @@ def get_messages( try: r = self.send(rpc) except FloodWait as e: - log.warning("Sleeping for {}s".format(e.x)) + log.warning("[{}] Sleeping for {}s".format(self.session_name, e.x)) time.sleep(e.x) else: break diff --git a/pyrogram/client/methods/messages/send_media_group.py b/pyrogram/client/methods/messages/send_media_group.py index 055c7b7c7a..725a8a84a8 100644 --- a/pyrogram/client/methods/messages/send_media_group.py +++ b/pyrogram/client/methods/messages/send_media_group.py @@ -195,7 +195,7 @@ def send_media_group( ) ) except FloodWait as e: - log.warning("Sleeping for {}s".format(e.x)) + log.warning("[{}] Sleeping for {}s".format(self.session_name, e.x)) time.sleep(e.x) else: break From 99aee987bdc0154c875c3072cf368f4e9c31aee3 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 7 May 2020 12:53:45 +0200 Subject: [PATCH 0206/1185] Add an automatic sleep mechanism for flood wait exceptions --- pyrogram/client/client.py | 30 ++++++- .../client/methods/chats/get_chat_members.py | 33 +++---- pyrogram/client/methods/chats/get_dialogs.py | 33 +++---- .../client/methods/messages/get_history.py | 35 +++----- .../client/methods/messages/get_messages.py | 9 +- .../methods/messages/send_media_group.py | 87 +++++++------------ 6 files changed, 103 insertions(+), 124 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 3ef82ada0a..daaa86691c 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -154,6 +154,12 @@ class Client(Methods, BaseClient): download_media, ...) are less prone to throw FloodWait exceptions. Only available for users, bots will ignore this parameter. Defaults to False (normal session). + + sleep_threshold (``int``, *optional*): + Set a sleep threshold for flood wait exceptions happening globally in this client instance, below which any + request that raises a flood wait will be automatically invoked again after sleeping for the required amount + of time. Flood wait exceptions requiring higher waiting times will be raised. + Defaults to 60 (seconds). """ def __init__( @@ -178,7 +184,8 @@ def __init__( config_file: str = BaseClient.CONFIG_FILE, plugins: dict = None, no_updates: bool = None, - takeout: bool = None + takeout: bool = None, + sleep_threshold: int = 60 ): super().__init__() @@ -204,6 +211,7 @@ def __init__( self.plugins = plugins self.no_updates = no_updates self.takeout = takeout + self.sleep_threshold = sleep_threshold if isinstance(session_name, str): if session_name == ":memory:" or len(session_name) >= MemoryStorage.SESSION_STRING_SIZE: @@ -1401,13 +1409,31 @@ def send(self, data: TLObject, retries: int = Session.MAX_RETRIES, timeout: floa if not self.is_connected: raise ConnectionError("Client has not been started yet") + # Some raw methods that expect a query as argument are used here. + # Keep the original request query because is needed. + unwrapped_data = data + if self.no_updates: data = functions.InvokeWithoutUpdates(query=data) if self.takeout_id: data = functions.InvokeWithTakeout(takeout_id=self.takeout_id, query=data) - r = self.session.send(data, retries, timeout) + while True: + try: + r = self.session.send(data, retries, timeout) + except FloodWait as e: + amount = e.x + + if amount > self.sleep_threshold: + raise + + log.warning('[{}] Sleeping for {}s (required by "{}")'.format( + self.session_name, amount, ".".join(unwrapped_data.QUALNAME.split(".")[1:]))) + + time.sleep(amount) + else: + break self.fetch_peers(getattr(r, "users", [])) self.fetch_peers(getattr(r, "chats", [])) diff --git a/pyrogram/client/methods/chats/get_chat_members.py b/pyrogram/client/methods/chats/get_chat_members.py index 6be3408b24..bf20894433 100644 --- a/pyrogram/client/methods/chats/get_chat_members.py +++ b/pyrogram/client/methods/chats/get_chat_members.py @@ -136,24 +136,19 @@ def get_chat_members( else: raise ValueError("Invalid filter \"{}\"".format(filter)) - while True: - try: - r = self.send( - functions.channels.GetParticipants( - channel=peer, - filter=filter, - offset=offset, - limit=limit, - hash=0 - ) - ) - - members = r.participants - users = {i.id: i for i in r.users} - - return pyrogram.List(pyrogram.ChatMember._parse(self, member, users) for member in members) - except FloodWait as e: - log.warning("[{}] Sleeping for {}s".format(self.session_name, e.x)) - time.sleep(e.x) + r = self.send( + functions.channels.GetParticipants( + channel=peer, + filter=filter, + offset=offset, + limit=limit, + hash=0 + ) + ) + + members = r.participants + users = {i.id: i for i in r.users} + + return pyrogram.List(pyrogram.ChatMember._parse(self, member, users) for member in members) else: raise ValueError("The chat_id \"{}\" belongs to a user".format(chat_id)) diff --git a/pyrogram/client/methods/chats/get_dialogs.py b/pyrogram/client/methods/chats/get_dialogs.py index ea2eec632c..f5d5f44285 100644 --- a/pyrogram/client/methods/chats/get_dialogs.py +++ b/pyrogram/client/methods/chats/get_dialogs.py @@ -66,26 +66,19 @@ def get_dialogs( app.get_dialogs(pinned_only=True) """ - while True: - try: - if pinned_only: - r = self.send(functions.messages.GetPinnedDialogs(folder_id=0)) - else: - r = self.send( - functions.messages.GetDialogs( - offset_date=offset_date, - offset_id=0, - offset_peer=types.InputPeerEmpty(), - limit=limit, - hash=0, - exclude_pinned=True - ) - ) - except FloodWait as e: - log.warning("[{}] Sleeping for {}s".format(self.session_name, e.x)) - time.sleep(e.x) - else: - break + if pinned_only: + r = self.send(functions.messages.GetPinnedDialogs(folder_id=0)) + else: + r = self.send( + functions.messages.GetDialogs( + offset_date=offset_date, + offset_id=0, + offset_peer=types.InputPeerEmpty(), + limit=limit, + hash=0, + exclude_pinned=True + ) + ) users = {i.id: i for i in r.users} chats = {i.id: i for i in r.chats} diff --git a/pyrogram/client/methods/messages/get_history.py b/pyrogram/client/methods/messages/get_history.py index 7741340c22..79a1dec61b 100644 --- a/pyrogram/client/methods/messages/get_history.py +++ b/pyrogram/client/methods/messages/get_history.py @@ -85,28 +85,21 @@ def get_history( offset_id = offset_id or (1 if reverse else 0) - while True: - try: - messages = utils.parse_messages( - self, - self.send( - functions.messages.GetHistory( - peer=self.resolve_peer(chat_id), - offset_id=offset_id, - offset_date=offset_date, - add_offset=offset * (-1 if reverse else 1) - (limit if reverse else 0), - limit=limit, - max_id=0, - min_id=0, - hash=0 - ) - ) + messages = utils.parse_messages( + self, + self.send( + functions.messages.GetHistory( + peer=self.resolve_peer(chat_id), + offset_id=offset_id, + offset_date=offset_date, + add_offset=offset * (-1 if reverse else 1) - (limit if reverse else 0), + limit=limit, + max_id=0, + min_id=0, + hash=0 ) - except FloodWait as e: - log.warning("[{}] Sleeping for {}s".format(self.session_name, e.x)) - time.sleep(e.x) - else: - break + ) + ) if reverse: messages.reverse() diff --git a/pyrogram/client/methods/messages/get_messages.py b/pyrogram/client/methods/messages/get_messages.py index f3e53b9419..e505b566b3 100644 --- a/pyrogram/client/methods/messages/get_messages.py +++ b/pyrogram/client/methods/messages/get_messages.py @@ -112,14 +112,7 @@ def get_messages( else: rpc = functions.messages.GetMessages(id=ids) - while True: - try: - r = self.send(rpc) - except FloodWait as e: - log.warning("[{}] Sleeping for {}s".format(self.session_name, e.x)) - time.sleep(e.x) - else: - break + r = self.send(rpc) messages = utils.parse_messages(self, r, replies=replies) diff --git a/pyrogram/client/methods/messages/send_media_group.py b/pyrogram/client/methods/messages/send_media_group.py index 725a8a84a8..8571ef4fc5 100644 --- a/pyrogram/client/methods/messages/send_media_group.py +++ b/pyrogram/client/methods/messages/send_media_group.py @@ -78,21 +78,14 @@ def send_media_group( for i in media: if isinstance(i, pyrogram.InputMediaPhoto): if os.path.exists(i.media): - while True: - try: - media = self.send( - functions.messages.UploadMedia( - peer=self.resolve_peer(chat_id), - media=types.InputMediaUploadedPhoto( - file=self.save_file(i.media) - ) - ) + media = self.send( + functions.messages.UploadMedia( + peer=self.resolve_peer(chat_id), + media=types.InputMediaUploadedPhoto( + file=self.save_file(i.media) ) - except FloodWait as e: - log.warning("Sleeping for {}s".format(e.x)) - time.sleep(e.x) - else: - break + ) + ) media = types.InputMediaPhoto( id=types.InputPhoto( @@ -122,32 +115,25 @@ def send_media_group( media = utils.get_input_media_from_file_id(i.media, i.file_ref, 2) elif isinstance(i, pyrogram.InputMediaVideo): if os.path.exists(i.media): - while True: - try: - media = self.send( - functions.messages.UploadMedia( - peer=self.resolve_peer(chat_id), - media=types.InputMediaUploadedDocument( - file=self.save_file(i.media), - thumb=None if i.thumb is None else self.save_file(i.thumb), - mime_type=self.guess_mime_type(i.media) or "video/mp4", - attributes=[ - types.DocumentAttributeVideo( - supports_streaming=i.supports_streaming or None, - duration=i.duration, - w=i.width, - h=i.height - ), - types.DocumentAttributeFilename(file_name=os.path.basename(i.media)) - ] - ) - ) + media = self.send( + functions.messages.UploadMedia( + peer=self.resolve_peer(chat_id), + media=types.InputMediaUploadedDocument( + file=self.save_file(i.media), + thumb=None if i.thumb is None else self.save_file(i.thumb), + mime_type=self.guess_mime_type(i.media) or "video/mp4", + attributes=[ + types.DocumentAttributeVideo( + supports_streaming=i.supports_streaming or None, + duration=i.duration, + w=i.width, + h=i.height + ), + types.DocumentAttributeFilename(file_name=os.path.basename(i.media)) + ] ) - except FloodWait as e: - log.warning("Sleeping for {}s".format(e.x)) - time.sleep(e.x) - else: - break + ) + ) media = types.InputMediaDocument( id=types.InputDocument( @@ -184,21 +170,14 @@ def send_media_group( ) ) - while True: - try: - r = self.send( - functions.messages.SendMultiMedia( - peer=self.resolve_peer(chat_id), - multi_media=multi_media, - silent=disable_notification or None, - reply_to_msg_id=reply_to_message_id - ) - ) - except FloodWait as e: - log.warning("[{}] Sleeping for {}s".format(self.session_name, e.x)) - time.sleep(e.x) - else: - break + r = self.send( + functions.messages.SendMultiMedia( + peer=self.resolve_peer(chat_id), + multi_media=multi_media, + silent=disable_notification or None, + reply_to_msg_id=reply_to_message_id + ) + ) return utils.parse_messages( self, From 128ab4b0b94851720aa30782546d22195fc39f93 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 7 May 2020 13:38:22 +0200 Subject: [PATCH 0207/1185] Move the automatic sleep mechanism down to Session --- pyrogram/client/client.py | 22 ++------------- pyrogram/session/session.py | 56 +++++++++++++++++++++++++++---------- 2 files changed, 43 insertions(+), 35 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index daaa86691c..53bc3799da 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -185,7 +185,7 @@ def __init__( plugins: dict = None, no_updates: bool = None, takeout: bool = None, - sleep_threshold: int = 60 + sleep_threshold: int = Session.SLEEP_THRESHOLD ): super().__init__() @@ -1409,31 +1409,13 @@ def send(self, data: TLObject, retries: int = Session.MAX_RETRIES, timeout: floa if not self.is_connected: raise ConnectionError("Client has not been started yet") - # Some raw methods that expect a query as argument are used here. - # Keep the original request query because is needed. - unwrapped_data = data - if self.no_updates: data = functions.InvokeWithoutUpdates(query=data) if self.takeout_id: data = functions.InvokeWithTakeout(takeout_id=self.takeout_id, query=data) - while True: - try: - r = self.session.send(data, retries, timeout) - except FloodWait as e: - amount = e.x - - if amount > self.sleep_threshold: - raise - - log.warning('[{}] Sleeping for {}s (required by "{}")'.format( - self.session_name, amount, ".".join(unwrapped_data.QUALNAME.split(".")[1:]))) - - time.sleep(amount) - else: - break + r = self.session.send(data, retries, timeout, self.sleep_threshold) self.fetch_peers(getattr(r, "users", [])) self.fetch_peers(getattr(r, "chats", [])) diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index e2659871e2..3c34f57813 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -33,7 +33,7 @@ from pyrogram.api.core import Message, TLObject, MsgContainer, Long, FutureSalt, Int from pyrogram.connection import Connection from pyrogram.crypto import AES, KDF -from pyrogram.errors import RPCError, InternalServerError, AuthKeyDuplicated +from pyrogram.errors import RPCError, InternalServerError, AuthKeyDuplicated, FloodWait from .internals import MsgId, MsgFactory log = logging.getLogger(__name__) @@ -50,6 +50,7 @@ class Session: NET_WORKERS = 1 START_TIMEOUT = 1 WAIT_TIMEOUT = 15 + SLEEP_THRESHOLD = 60 MAX_RETRIES = 5 ACKS_THRESHOLD = 8 PING_INTERVAL = 5 @@ -432,19 +433,44 @@ def _send(self, data: TLObject, wait_response: bool = True, timeout: float = WAI else: return result - def send(self, data: TLObject, retries: int = MAX_RETRIES, timeout: float = WAIT_TIMEOUT): + def send( + self, + data: TLObject, + retries: int = MAX_RETRIES, + timeout: float = WAIT_TIMEOUT, + sleep_threshold: float = SLEEP_THRESHOLD + ): self.is_connected.wait(self.WAIT_TIMEOUT) - try: - return self._send(data, timeout=timeout) - except (OSError, TimeoutError, InternalServerError) as e: - if retries == 0: - raise e from None - - (log.warning if retries < 2 else log.info)( - "[{}] Retrying {} due to {}".format( - Session.MAX_RETRIES - retries + 1, - data.QUALNAME, e)) - - time.sleep(0.5) - return self.send(data, retries - 1, timeout) + if isinstance(data, (functions.InvokeWithoutUpdates, functions.InvokeWithTakeout)): + query = data.query + else: + query = data + + query = ".".join(query.QUALNAME.split(".")[1:]) + + while True: + try: + return self._send(data, timeout=timeout) + except FloodWait as e: + amount = e.x + + if amount > sleep_threshold: + raise + + log.warning('[{}] Sleeping for {}s (required by "{}")'.format( + self.client.session_name, amount, query)) + + time.sleep(amount) + except (OSError, TimeoutError, InternalServerError) as e: + if retries == 0: + raise e from None + + (log.warning if retries < 2 else log.info)( + '[{}] Retrying "{}" due to {}'.format( + Session.MAX_RETRIES - retries + 1, + query, e)) + + time.sleep(0.5) + + return self.send(data, retries - 1, timeout) From 12ce0a33c149faf3b903f19761aa742b9a04c659 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 7 May 2020 14:57:28 +0200 Subject: [PATCH 0208/1185] Add linked chats to Chat objects --- pyrogram/client/types/user_and_chats/chat.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/pyrogram/client/types/user_and_chats/chat.py b/pyrogram/client/types/user_and_chats/chat.py index 5e1f18fa25..03b7d76a91 100644 --- a/pyrogram/client/types/user_and_chats/chat.py +++ b/pyrogram/client/types/user_and_chats/chat.py @@ -101,6 +101,9 @@ class Chat(Object): distance (``int``, *optional*): Distance in meters of this group chat from your location. Returned only in :meth:`~Client.get_nearby_chats`. + + linked_chat (:obj:`Chat`, *optional*): + The linked discussion group (in case of channels) or the linked channel (in case of supergroups). """ def __init__( @@ -127,7 +130,8 @@ def __init__( members_count: int = None, restrictions: List[Restriction] = None, permissions: "pyrogram.ChatPermissions" = None, - distance: int = None + distance: int = None, + linked_chat: "pyrogram.Chat" = None ): super().__init__(client) @@ -152,6 +156,7 @@ def __init__( self.restrictions = restrictions self.permissions = permissions self.distance = distance + self.linked_chat = linked_chat @staticmethod def _parse_user_chat(client, user: types.User) -> "Chat": @@ -241,10 +246,14 @@ def _parse_full(client, chat_full: types.messages.ChatFull or types.UserFull) -> else: full_chat = chat_full.full_chat chat = None + linked_chat = None + + for c in chat_full.chats: + if full_chat.id == c.id: + chat = c - for i in chat_full.chats: - if full_chat.id == i.id: - chat = i + if full_chat.linked_chat_id == c.id: + linked_chat = c if isinstance(full_chat, types.ChatFull): parsed_chat = Chat._parse_chat_chat(client, chat) @@ -259,6 +268,7 @@ def _parse_full(client, chat_full: types.messages.ChatFull or types.UserFull) -> # TODO: Add StickerSet type parsed_chat.can_set_sticker_set = full_chat.can_set_stickers parsed_chat.sticker_set_name = getattr(full_chat.stickerset, "short_name", None) + parsed_chat.linked_chat = Chat._parse_channel_chat(client, linked_chat) if full_chat.pinned_msg_id: parsed_chat.pinned_message = client.get_messages( From 0556efa26bbf3f64f59716f77640429870dada8a Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 7 May 2020 15:05:31 +0200 Subject: [PATCH 0209/1185] Add support for joining linked chats with the .join() bound method --- pyrogram/client/methods/chats/join_chat.py | 15 ++++++++++----- pyrogram/client/types/user_and_chats/chat.py | 4 ++-- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/pyrogram/client/methods/chats/join_chat.py b/pyrogram/client/methods/chats/join_chat.py index c379bf03c5..666871153d 100644 --- a/pyrogram/client/methods/chats/join_chat.py +++ b/pyrogram/client/methods/chats/join_chat.py @@ -16,6 +16,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import Union + import pyrogram from pyrogram.api import functions, types from ...ext import BaseClient @@ -24,14 +26,14 @@ class JoinChat(BaseClient): def join_chat( self, - chat_id: str + chat_id: Union[int, str] ): """Join a group chat or channel. Parameters: - chat_id (``str``): - Unique identifier for the target chat in form of a *t.me/joinchat/* link or username of the target - channel/supergroup (in the format @username). + chat_id (``int`` | ``str``): + Unique identifier for the target chat in form of a *t.me/joinchat/* link, a username of the target + channel/supergroup (in the format @username) or a chat id of a linked chat (channel or supergroup). Returns: :obj:`Chat`: On success, a chat object is returned. @@ -44,8 +46,11 @@ def join_chat( # Join chat via invite link app.join_chat("https://t.me/joinchat/AAAAAE0QmSW3IUmm3UFR7A") + + # Join a linked chat + app.join_chat(app.get_chat("pyrogram").linked_chat.id) """ - match = self.INVITE_LINK_RE.match(chat_id) + match = self.INVITE_LINK_RE.match(str(chat_id)) if match: chat = self.send( diff --git a/pyrogram/client/types/user_and_chats/chat.py b/pyrogram/client/types/user_and_chats/chat.py index 03b7d76a91..db2b3ef624 100644 --- a/pyrogram/client/types/user_and_chats/chat.py +++ b/pyrogram/client/types/user_and_chats/chat.py @@ -675,7 +675,7 @@ def join(self): chat.join() Note: - This only works for public groups and channels that have set a username. + This only works for public groups, channels that have set a username or linked chats. Returns: :obj:`Chat`: On success, a chat object is returned. @@ -684,7 +684,7 @@ def join(self): RPCError: In case of a Telegram RPC error. """ - return self._client.join_chat(self.username) + return self._client.join_chat(self.username or self.id) def leave(self): """Bound method *leave* of :obj:`Chat`. From e4028fa6a7ec69ac3e372519ef70856c548cf448 Mon Sep 17 00:00:00 2001 From: Cezar H <29507335+usernein@users.noreply.github.com> Date: Thu, 14 May 2020 06:56:58 -0300 Subject: [PATCH 0210/1185] Add missing await (#403) await client.send_poll(...) was returning a coroutine instead of the Message object --- pyrogram/client/methods/messages/send_poll.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/methods/messages/send_poll.py b/pyrogram/client/methods/messages/send_poll.py index 0248d0b2b6..7607a54609 100644 --- a/pyrogram/client/methods/messages/send_poll.py +++ b/pyrogram/client/methods/messages/send_poll.py @@ -123,7 +123,7 @@ async def send_poll( for i in r.updates: if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage, types.UpdateNewScheduledMessage)): - return pyrogram.Message._parse( + return await pyrogram.Message._parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats}, From 27d5caf40e41fc3576518c4dc9469b5896fbe5fb Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 16 May 2020 00:35:05 +0200 Subject: [PATCH 0211/1185] Give Filters.regex superpowers Basically make it work on Message, CallbackQuery and InlineQuery updates --- pyrogram/client/filters/filters.py | 36 +++++++++++++------ .../bots_and_keyboards/callback_query.py | 9 +++-- .../client/types/inline_mode/inline_query.py | 10 ++++-- 3 files changed, 40 insertions(+), 15 deletions(-) diff --git a/pyrogram/client/filters/filters.py b/pyrogram/client/filters/filters.py index 2463764074..50543f6edd 100644 --- a/pyrogram/client/filters/filters.py +++ b/pyrogram/client/filters/filters.py @@ -20,6 +20,7 @@ from typing import Callable from .filter import Filter +from ..types import Message, CallbackQuery, InlineQuery from ..types.bots_and_keyboards import InlineKeyboardMarkup, ReplyKeyboardMarkup CUSTOM_FILTER_NAME = "CustomFilter" @@ -288,26 +289,39 @@ def func(flt, message): ) @staticmethod - def regex(pattern, flags: int = 0): - """Filter message texts or captions that match a given regular expression pattern. + def regex(pattern: str, flags: int = 0): + """Filter updates that match a given regular expression pattern. + + Can be applied to handlers that receive one of the following updates: + + - :obj:`Message`: The filter will match ``text`` or ``caption``. + - :obj:`CallbackQuery`: The filter will match ``data``. + - :obj:`InlineQuery`: The filter will match ``query``. + + When a pattern matches, all the `Match Objects `_ are + stored in the ``matches`` field of the update object itself. Parameters: pattern (``str``): - The RegEx pattern as string, it will be applied to the text or the caption of a message. When a pattern - matches, all the `Match Objects `_ are stored - in the *matches* field of the :obj:`Message` itself. + The regex pattern as string. flags (``int``, *optional*): - RegEx flags. + Regex flags. """ - def func(flt, message): - text = message.text or message.caption + def func(flt, update): + if isinstance(update, Message): + value = update.text or update.caption + elif isinstance(update, CallbackQuery): + value = update.data + elif isinstance(update, InlineQuery): + value = update.query + else: + raise ValueError("Regex filter doesn't work with {}".format(type(update))) - if text: - message.matches = list(flt.p.finditer(text)) or None + update.matches = list(flt.p.finditer(value)) or None - return bool(message.matches) + return bool(update.matches) return create(func, "RegexFilter", p=re.compile(pattern, flags)) diff --git a/pyrogram/client/types/bots_and_keyboards/callback_query.py b/pyrogram/client/types/bots_and_keyboards/callback_query.py index 34b8b52cdb..9820e5605a 100644 --- a/pyrogram/client/types/bots_and_keyboards/callback_query.py +++ b/pyrogram/client/types/bots_and_keyboards/callback_query.py @@ -18,7 +18,7 @@ from base64 import b64encode from struct import pack -from typing import Union +from typing import Union, List, Match import pyrogram from pyrogram.api import types @@ -59,6 +59,9 @@ class CallbackQuery(Object, Update): game_short_name (``str``, *optional*): Short name of a Game to be returned, serves as the unique identifier for the game. + matches (List of regex Matches, *optional*): + A list containing all `Match Objects `_ that match + the data of this callback query. Only applicable when using :obj:`Filters.regex `. """ def __init__( @@ -71,7 +74,8 @@ def __init__( message: "pyrogram.Message" = None, inline_message_id: str = None, data: Union[str, bytes] = None, - game_short_name: str = None + game_short_name: str = None, + matches: List[Match] = None ): super().__init__(client) @@ -82,6 +86,7 @@ def __init__( self.inline_message_id = inline_message_id self.data = data self.game_short_name = game_short_name + self.matches = matches @staticmethod def _parse(client, callback_query, users) -> "CallbackQuery": diff --git a/pyrogram/client/types/inline_mode/inline_query.py b/pyrogram/client/types/inline_mode/inline_query.py index 44fea75d66..eadc539c4f 100644 --- a/pyrogram/client/types/inline_mode/inline_query.py +++ b/pyrogram/client/types/inline_mode/inline_query.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import List +from typing import List, Match import pyrogram from pyrogram.api import types @@ -47,6 +47,10 @@ class InlineQuery(Object, Update): location (:obj:`Location`. *optional*): Sender location, only for bots that request user location. + + matches (List of regex Matches, *optional*): + A list containing all `Match Objects `_ that match + the query of this inline query. Only applicable when using :obj:`Filters.regex `. """ def __init__( @@ -57,7 +61,8 @@ def __init__( from_user: User, query: str, offset: str, - location: Location = None + location: Location = None, + matches: List[Match] = None ): super().__init__(client) @@ -66,6 +71,7 @@ def __init__( self.query = query self.offset = offset self.location = location + self.matches = matches @staticmethod def _parse(client, inline_query: types.UpdateBotInlineQuery, users: dict) -> "InlineQuery": From efc92715cc81ad2e6eba2bab3ca59af8776fc746 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 16 May 2020 00:40:05 +0200 Subject: [PATCH 0212/1185] Remove Filters.callback_data (superseded by Filters.regex) @ColinTheShark say goodbye, thanks. --- pyrogram/client/filters/filters.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/pyrogram/client/filters/filters.py b/pyrogram/client/filters/filters.py index 50543f6edd..83493965b2 100644 --- a/pyrogram/client/filters/filters.py +++ b/pyrogram/client/filters/filters.py @@ -389,15 +389,4 @@ def __call__(self, message): and message.from_user.is_self and not message.outgoing))) - @staticmethod - def callback_data(data: str or bytes): - """Filter callback queries for their data. - - Parameters: - data (``str`` | ``bytes``): - Pass the data you want to filter for. - """ - - return create(lambda flt, cb: cb.data == flt.data, "CallbackDataFilter", data=data) - dan = create(lambda _, m: bool(m.from_user and m.from_user.id == 23122162), "DanFilter") From 22eb42e1f362be05a2c9acb5aed3b01722f26145 Mon Sep 17 00:00:00 2001 From: Eric Solinas Date: Sat, 16 May 2020 12:24:14 +0200 Subject: [PATCH 0213/1185] Add Filter.linked_channel * filter messages coming from the channel connected to the chat * Update filters.py Co-authored-by: Dan <14043624+delivrance@users.noreply.github.com> --- pyrogram/client/filters/filters.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pyrogram/client/filters/filters.py b/pyrogram/client/filters/filters.py index 83493965b2..d740d3c7e9 100644 --- a/pyrogram/client/filters/filters.py +++ b/pyrogram/client/filters/filters.py @@ -214,6 +214,10 @@ class Filters: from_scheduled = create(lambda _, m: bool(m.from_scheduled), "FromScheduledFilter") """Filter new automatically sent messages that were previously scheduled.""" + + # Messages from linked channels are forwarded automatically by Telegram and have no sender (from_user is None). + linked_channel = create(lambda _, m: bool(m.forward_from_chat and not m.from_user), "LinkedChannelFilter") + """Filter messages that are automatically forwarded from the linked channel to the group chat.""" @staticmethod def command( From 0c83fa09a6a6f2f8ea9d34ba461e7bc15f26b8fb Mon Sep 17 00:00:00 2001 From: demget <30910794+demget@users.noreply.github.com> Date: Sat, 16 May 2020 13:33:21 +0300 Subject: [PATCH 0214/1185] Add file_name param to file-related functions (#308) --- pyrogram/client/methods/messages/send_animation.py | 7 ++++++- pyrogram/client/methods/messages/send_audio.py | 7 ++++++- pyrogram/client/methods/messages/send_document.py | 7 ++++++- pyrogram/client/methods/messages/send_video.py | 7 ++++++- 4 files changed, 24 insertions(+), 4 deletions(-) diff --git a/pyrogram/client/methods/messages/send_animation.py b/pyrogram/client/methods/messages/send_animation.py index 99e86fba6e..288ed04e69 100644 --- a/pyrogram/client/methods/messages/send_animation.py +++ b/pyrogram/client/methods/messages/send_animation.py @@ -38,6 +38,7 @@ def send_animation( width: int = 0, height: int = 0, thumb: str = None, + file_name: str = None, disable_notification: bool = None, reply_to_message_id: int = None, schedule_date: int = None, @@ -97,6 +98,10 @@ def send_animation( A thumbnail's width and height should not exceed 320 pixels. Thumbnails can't be reused and can be only uploaded as a new file. + file_name (``str``, *optional*): + File name of the animation sent. + Defaults to file's path basename. + disable_notification (``bool``, *optional*): Sends the message silently. Users will receive a notification with no sound. @@ -172,7 +177,7 @@ def progress(current, total): w=width, h=height ), - types.DocumentAttributeFilename(file_name=os.path.basename(animation)), + types.DocumentAttributeFilename(file_name=file_name or os.path.basename(animation)), types.DocumentAttributeAnimated() ] ) diff --git a/pyrogram/client/methods/messages/send_audio.py b/pyrogram/client/methods/messages/send_audio.py index 2d34f86176..e271d96cdc 100644 --- a/pyrogram/client/methods/messages/send_audio.py +++ b/pyrogram/client/methods/messages/send_audio.py @@ -37,6 +37,7 @@ def send_audio( performer: str = None, title: str = None, thumb: str = None, + file_name: str = None, disable_notification: bool = None, reply_to_message_id: int = None, schedule_date: int = None, @@ -94,6 +95,10 @@ def send_audio( A thumbnail's width and height should not exceed 320 pixels. Thumbnails can't be reused and can be only uploaded as a new file. + file_name (``str``, *optional*): + File name of the audio sent. + Defaults to file's path basename. + disable_notification (``bool``, *optional*): Sends the message silently. Users will receive a notification with no sound. @@ -171,7 +176,7 @@ def progress(current, total): performer=performer, title=title ), - types.DocumentAttributeFilename(file_name=os.path.basename(audio)) + types.DocumentAttributeFilename(file_name=file_name or os.path.basename(audio)) ] ) elif audio.startswith("http"): diff --git a/pyrogram/client/methods/messages/send_document.py b/pyrogram/client/methods/messages/send_document.py index 182d2985db..24a754f079 100644 --- a/pyrogram/client/methods/messages/send_document.py +++ b/pyrogram/client/methods/messages/send_document.py @@ -34,6 +34,7 @@ def send_document( thumb: str = None, caption: str = "", parse_mode: Union[str, None] = object, + file_name: str = None, disable_notification: bool = None, reply_to_message_id: int = None, schedule_date: int = None, @@ -80,6 +81,10 @@ def send_document( Pass "html" to enable HTML-style parsing only. Pass None to completely disable style parsing. + file_name (``str``, *optional*): + File name of the document sent. + Defaults to file's path basename. + disable_notification (``bool``, *optional*): Sends the message silently. Users will receive a notification with no sound. @@ -146,7 +151,7 @@ def progress(current, total): file=file, thumb=thumb, attributes=[ - types.DocumentAttributeFilename(file_name=os.path.basename(document)) + types.DocumentAttributeFilename(file_name=file_name or os.path.basename(document)) ] ) elif document.startswith("http"): diff --git a/pyrogram/client/methods/messages/send_video.py b/pyrogram/client/methods/messages/send_video.py index 8335b90279..fc58aa98aa 100644 --- a/pyrogram/client/methods/messages/send_video.py +++ b/pyrogram/client/methods/messages/send_video.py @@ -37,6 +37,7 @@ def send_video( width: int = 0, height: int = 0, thumb: str = None, + file_name: str = None, supports_streaming: bool = True, disable_notification: bool = None, reply_to_message_id: int = None, @@ -93,6 +94,10 @@ def send_video( A thumbnail's width and height should not exceed 320 pixels. Thumbnails can't be reused and can be only uploaded as a new file. + file_name (``str``, *optional*): + File name of the video sent. + Defaults to file's path basename. + supports_streaming (``bool``, *optional*): Pass True, if the uploaded video is suitable for streaming. Defaults to True. @@ -169,7 +174,7 @@ def progress(current, total): w=width, h=height ), - types.DocumentAttributeFilename(file_name=os.path.basename(video)) + types.DocumentAttributeFilename(file_name=file_name or os.path.basename(video)) ] ) elif video.startswith("http"): From 5b94c340c072382b686c1f90fdcc6bac4ce9d503 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 16 May 2020 14:40:22 +0200 Subject: [PATCH 0215/1185] Fix Filters.regex failing in case the value is None --- pyrogram/client/filters/filters.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyrogram/client/filters/filters.py b/pyrogram/client/filters/filters.py index d740d3c7e9..1921881361 100644 --- a/pyrogram/client/filters/filters.py +++ b/pyrogram/client/filters/filters.py @@ -323,7 +323,8 @@ def func(flt, update): else: raise ValueError("Regex filter doesn't work with {}".format(type(update))) - update.matches = list(flt.p.finditer(value)) or None + if value: + update.matches = list(flt.p.finditer(value)) or None return bool(update.matches) From bb89e949e88d5e2a0c2c56ebb27e990164fa96dc Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 23 May 2020 13:42:49 +0200 Subject: [PATCH 0216/1185] Update send_dice: add basketball "dice" --- pyrogram/client/methods/messages/send_dice.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/pyrogram/client/methods/messages/send_dice.py b/pyrogram/client/methods/messages/send_dice.py index f90bc852b2..4ed9b92630 100644 --- a/pyrogram/client/methods/messages/send_dice.py +++ b/pyrogram/client/methods/messages/send_dice.py @@ -38,7 +38,7 @@ def send_dice( "pyrogram.ForceReply" ] = None ) -> Union["pyrogram.Message", None]: - """Send a dice. + """Send a dice with a random value from 1 to 6. Parameters: chat_id (``int`` | ``str``): @@ -47,8 +47,8 @@ def send_dice( For a contact that exists in your Telegram address book you can use his phone number (str). emoji (``str``, *optional*): - Emoji on which the dice throw animation is based. Currently, must be one of "🎲" or "🎯". - Defauts to "🎲". + Emoji on which the dice throw animation is based. Currently, must be one of "🎲", "🎯" or "🏀". + Defaults to "🎲". disable_notification (``bool``, *optional*): Sends the message silently. @@ -75,6 +75,9 @@ def send_dice( # Send a dart app.send_dice("pyrogramlounge", "🎯") + + # Send a basketball + app.send_dice("pyrogramlounge", "🏀") """ r = self.send( From d5a18eb0633ea6f78d0c140e02924f9227cdf9de Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 23 May 2020 14:50:14 +0200 Subject: [PATCH 0217/1185] Add search_global method --- compiler/docs/compiler.py | 1 + pyrogram/client/methods/messages/__init__.py | 8 +- .../client/methods/messages/search_global.py | 96 +++++++++++++++++++ 3 files changed, 102 insertions(+), 3 deletions(-) create mode 100644 pyrogram/client/methods/messages/search_global.py diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index 6b596b2fce..8afd99f841 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -176,6 +176,7 @@ def get_title_list(s: str) -> list: retract_vote send_dice search_messages + search_global download_media """, chats=""" diff --git a/pyrogram/client/methods/messages/__init__.py b/pyrogram/client/methods/messages/__init__.py index 3df4e7cee7..e78d2bc52c 100644 --- a/pyrogram/client/methods/messages/__init__.py +++ b/pyrogram/client/methods/messages/__init__.py @@ -33,11 +33,14 @@ from .iter_history import IterHistory from .read_history import ReadHistory from .retract_vote import RetractVote +from .search_global import SearchGlobal +from .search_messages import SearchMessages from .send_animation import SendAnimation from .send_audio import SendAudio from .send_cached_media import SendCachedMedia from .send_chat_action import SendChatAction from .send_contact import SendContact +from .send_dice import SendDice from .send_document import SendDocument from .send_location import SendLocation from .send_media_group import SendMediaGroup @@ -51,8 +54,6 @@ from .send_voice import SendVoice from .stop_poll import StopPoll from .vote_poll import VotePoll -from .send_dice import SendDice -from .search_messages import SearchMessages class Messages( @@ -92,6 +93,7 @@ class Messages( EditInlineMedia, EditInlineReplyMarkup, SendDice, - SearchMessages + SearchMessages, + SearchGlobal ): pass diff --git a/pyrogram/client/methods/messages/search_global.py b/pyrogram/client/methods/messages/search_global.py new file mode 100644 index 0000000000..45262d7889 --- /dev/null +++ b/pyrogram/client/methods/messages/search_global.py @@ -0,0 +1,96 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan +# +# 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 Generator + +import pyrogram +from pyrogram.api import functions, types +from pyrogram.client.ext import BaseClient, utils + + +class SearchGlobal(BaseClient): + def search_global( + self, + query: str, + limit: int = 0, + ) -> Generator["pyrogram.Message", None, None]: + """Search messages globally from all of your chats. + + .. note:: + + Due to server-side limitations, you can only get up to around ~10,000 messages and each message + retrieved will not have any *reply_to_message* field. + + Parameters: + query (``str``): + Text query string. + + limit (``int``, *optional*): + Limits the number of messages to be retrieved. + By default, no limit is applied and all messages are returned. + + Returns: + ``Generator``: A generator yielding :obj:`Message` objects. + + Example: + .. code-block:: python + + # Search for "pyrogram". Get the first 420 results + for message in app.search_global("pyrogram", limit=420): + print(message.text) + """ + current = 0 + # There seems to be an hard limit of 10k, beyond which Telegram starts spitting one message at a time. + total = abs(limit) or (1 << 31) + limit = min(100, total) + + offset_date = 0 + offset_peer = types.InputPeerEmpty() + offset_id = 0 + + while True: + messages = utils.parse_messages( + self, + self.send( + functions.messages.SearchGlobal( + q=query, + offset_rate=offset_date, + offset_peer=offset_peer, + offset_id=offset_id, + limit=limit + ) + ), + replies=0 + ) + + if not messages: + return + + last = messages[-1] + + offset_date = last.date + offset_peer = self.resolve_peer(last.chat.id) + offset_id = last.message_id + + for message in messages: + yield message + + current += 1 + + if current >= total: + return From d93b9275f3e73082507d781a94e51095102857e8 Mon Sep 17 00:00:00 2001 From: SuperCz1 <62919067+SuperCz1@users.noreply.github.com> Date: Sat, 23 May 2020 14:52:14 +0200 Subject: [PATCH 0218/1185] Bugfixes in chat.py (#411) - Fixed this bug: https://t.me/pyrogramchat/169553 (which was caused because pyrogram trying to parse linked_chat even if it was None). - Fixed another related bug (which was caused because pyrogram trying to get linked_chat_id even with basic groups causing an AttributeError). --- pyrogram/client/types/user_and_chats/chat.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pyrogram/client/types/user_and_chats/chat.py b/pyrogram/client/types/user_and_chats/chat.py index db2b3ef624..4a7904cee7 100644 --- a/pyrogram/client/types/user_and_chats/chat.py +++ b/pyrogram/client/types/user_and_chats/chat.py @@ -251,9 +251,10 @@ def _parse_full(client, chat_full: types.messages.ChatFull or types.UserFull) -> for c in chat_full.chats: if full_chat.id == c.id: chat = c - - if full_chat.linked_chat_id == c.id: - linked_chat = c + + if isinstance(chat_full, types.ChannelFull): + if full_chat.linked_chat_id == c.id: + linked_chat = c if isinstance(full_chat, types.ChatFull): parsed_chat = Chat._parse_chat_chat(client, chat) @@ -268,7 +269,8 @@ def _parse_full(client, chat_full: types.messages.ChatFull or types.UserFull) -> # TODO: Add StickerSet type parsed_chat.can_set_sticker_set = full_chat.can_set_stickers parsed_chat.sticker_set_name = getattr(full_chat.stickerset, "short_name", None) - parsed_chat.linked_chat = Chat._parse_channel_chat(client, linked_chat) + if linked_chat: + parsed_chat.linked_chat = Chat._parse_channel_chat(client, linked_chat) if full_chat.pinned_msg_id: parsed_chat.pinned_message = client.get_messages( From 4a9cfa42ded8ae7f16392a3204a1569a7adf17c0 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 23 May 2020 15:01:29 +0200 Subject: [PATCH 0219/1185] Make CallbackQuery.answer optional arguments actually optional --- .../client/methods/bots/answer_callback_query.py | 15 +++++++++------ .../types/bots_and_keyboards/callback_query.py | 8 ++++---- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/pyrogram/client/methods/bots/answer_callback_query.py b/pyrogram/client/methods/bots/answer_callback_query.py index 53cd6c4991..2e00c07cfe 100644 --- a/pyrogram/client/methods/bots/answer_callback_query.py +++ b/pyrogram/client/methods/bots/answer_callback_query.py @@ -36,20 +36,20 @@ def answer_callback_query( callback_query_id (``str``): Unique identifier for the query to be answered. - text (``str``): + text (``str`` *optional*): Text of the notification. If not specified, nothing will be shown to the user, 0-200 characters. - show_alert (``bool``): + show_alert (``bool``, *optional*): If true, an alert will be shown by the client instead of a notification at the top of the chat screen. Defaults to False. - url (``str``): + url (``str``, *optional*): URL that will be opened by the user's client. If you have created a Game and accepted the conditions via @Botfather, specify the URL that opens your game – note that this will only work if the query comes from a callback_game button. Otherwise, you may use links like t.me/your_bot?start=XXXX that open your bot with a parameter. - cache_time (``int``): + cache_time (``int``, *optional*): 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. @@ -59,6 +59,9 @@ def answer_callback_query( Example: .. code-block:: python + # Answer only (remove the spinning circles) + app.answer_callback_query(query_id) + # Answer without alert app.answer_callback_query(query_id, text=text) @@ -70,7 +73,7 @@ def answer_callback_query( query_id=int(callback_query_id), cache_time=cache_time, alert=show_alert or None, - message=text, - url=url + message=text or None, + url=url or None ) ) diff --git a/pyrogram/client/types/bots_and_keyboards/callback_query.py b/pyrogram/client/types/bots_and_keyboards/callback_query.py index 9820e5605a..ec4048fcc3 100644 --- a/pyrogram/client/types/bots_and_keyboards/callback_query.py +++ b/pyrogram/client/types/bots_and_keyboards/callback_query.py @@ -143,20 +143,20 @@ def answer(self, text: str = None, show_alert: bool = None, url: str = None, cac callback_query.answer("Hello", show_alert=True) Parameters: - text (``str``): + text (``str``, *optional*): Text of the notification. If not specified, nothing will be shown to the user, 0-200 characters. - show_alert (``bool``): + show_alert (``bool`` *optional*): If true, an alert will be shown by the client instead of a notification at the top of the chat screen. Defaults to False. - url (``str``): + url (``str`` *optional*): URL that will be opened by the user's client. If you have created a Game and accepted the conditions via @Botfather, specify the URL that opens your game – note that this will only work if the query comes from a callback_game button. Otherwise, you may use links like t.me/your_bot?start=XXXX that open your bot with a parameter. - cache_time (``int``): + cache_time (``int`` *optional*): 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. """ From e30b9c52555f3ca66a64471a6e2862d216954320 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 23 May 2020 15:03:52 +0200 Subject: [PATCH 0220/1185] Fix small typos --- pyrogram/client/types/user_and_chats/chat.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyrogram/client/types/user_and_chats/chat.py b/pyrogram/client/types/user_and_chats/chat.py index 4a7904cee7..d95c5b3742 100644 --- a/pyrogram/client/types/user_and_chats/chat.py +++ b/pyrogram/client/types/user_and_chats/chat.py @@ -251,7 +251,7 @@ def _parse_full(client, chat_full: types.messages.ChatFull or types.UserFull) -> for c in chat_full.chats: if full_chat.id == c.id: chat = c - + if isinstance(chat_full, types.ChannelFull): if full_chat.linked_chat_id == c.id: linked_chat = c @@ -547,13 +547,13 @@ def restrict_member( client.restrict_chat_member( chat_id=chat_id, user_id=user_id, - permissions=ChatPermission() + permissions=ChatPermissions() ) Example: .. code-block:: python - chat.restrict_member(user_id, ChatPermission()) + chat.restrict_member(user_id, ChatPermissions()) Parameters: user_id (``int`` | ``str``): From b4467e82af1df67a025732a6b6c7f05a16d7ce9a Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 25 May 2020 14:31:31 +0200 Subject: [PATCH 0221/1185] Update Bot API diagram in docs --- docs/source/faq.rst | 2 +- docs/source/topics/mtproto-vs-botapi.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/faq.rst b/docs/source/faq.rst index b064e10b15..e56736d8f8 100644 --- a/docs/source/faq.rst +++ b/docs/source/faq.rst @@ -85,7 +85,7 @@ Requests against the official bot API endpoint are made via JSON/HTTP, but are h 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 +.. figure:: https://i.imgur.com/WvwBoZo.png :align: center Using MTProto is the only way to communicate with the actual Telegram servers, and the main API requires developers to diff --git a/docs/source/topics/mtproto-vs-botapi.rst b/docs/source/topics/mtproto-vs-botapi.rst index 3668d0da48..12b73b535e 100644 --- a/docs/source/topics/mtproto-vs-botapi.rst +++ b/docs/source/topics/mtproto-vs-botapi.rst @@ -35,7 +35,7 @@ accounts that are authorized via tokens instead of phone numbers. The Bot API is 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 +.. figure:: https://i.imgur.com/WvwBoZo.png :align: center .. _Bot API: https://core.telegram.org/bots/api From d82e9468f0f26991dcd67dbb8c3696d6c31bc31d Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 28 May 2020 15:43:42 +0200 Subject: [PATCH 0222/1185] Strip whitespace characters from the end of the message but preserve closing tags --- pyrogram/client/parser/html.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/parser/html.py b/pyrogram/client/parser/html.py index d614fb2f1f..35dd770c49 100644 --- a/pyrogram/client/parser/html.py +++ b/pyrogram/client/parser/html.py @@ -111,10 +111,11 @@ def __init__(self, client: Union["pyrogram.BaseClient", None]): self.client = client def parse(self, text: str): - text = utils.add_surrogates(text) + # Strip whitespace characters from the end of the message, but preserve closing tags + text = re.sub(r"\s*()\s*$", r"\1", text) parser = Parser(self.client) - parser.feed(text) + parser.feed(utils.add_surrogates(text)) parser.close() if parser.tag_entities: From f4d075597f1e5169debe81bd99434bc99eacea92 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 28 May 2020 22:19:15 +0200 Subject: [PATCH 0223/1185] Add missing async/await --- pyrogram/client/methods/users/update_profile.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/methods/users/update_profile.py b/pyrogram/client/methods/users/update_profile.py index 91a7950ccd..a968128dc7 100644 --- a/pyrogram/client/methods/users/update_profile.py +++ b/pyrogram/client/methods/users/update_profile.py @@ -21,7 +21,7 @@ class UpdateProfile(BaseClient): - def update_profile( + async def update_profile( self, first_name: str = None, last_name: str = None, @@ -60,7 +60,7 @@ def update_profile( """ return bool( - self.send( + await self.send( functions.account.UpdateProfile( first_name=first_name, last_name=last_name, From 7df85e2039e63938a820b0a5bd478067561cf0bf Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 2 Jun 2020 13:05:26 +0200 Subject: [PATCH 0224/1185] Show more relevant information when DEBUG logs are enabled Show exactly what is being sent and received --- pyrogram/session/session.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index 3c34f57813..3387b3d20d 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -286,7 +286,7 @@ def net_worker(self): else [data] ) - log.debug(data) + log.debug("Received:\n{}".format(data)) for msg in messages: if msg.seq_no % 2 != 0: @@ -406,6 +406,8 @@ def _send(self, data: TLObject, wait_response: bool = True, timeout: float = WAI if wait_response: self.results[msg_id] = Result() + log.debug("Sent:\n{}".format(message)) + payload = self.pack(message) try: From d65f773ed912cb537344120852f49dde395b8bea Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 5 Jun 2020 10:03:10 +0200 Subject: [PATCH 0225/1185] Update API schema to Layer 114 --- compiler/api/source/main_api.tl | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl index 28918f19e7..3d48a8cb8b 100644 --- a/compiler/api/source/main_api.tl +++ b/compiler/api/source/main_api.tl @@ -335,6 +335,7 @@ updateMessagePollVote#42f88f2c poll_id:long user_id:int options:Vector = updateDialogFilter#26ffde7d flags:# id:int filter:flags.0?DialogFilter = Update; updateDialogFilterOrder#a5d72105 order:Vector = Update; updateDialogFilters#3504914f = Update; +updatePhoneCallSignalingData#2661bf09 phone_call_id:long data:bytes = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -399,7 +400,7 @@ inputDocumentEmpty#72f0eaae = InputDocument; inputDocument#1abfb575 id:long access_hash:long file_reference:bytes = InputDocument; documentEmpty#36f8c871 id:long = Document; -document#9ba29cc1 flags:# id:long access_hash:long file_reference:bytes date:int mime_type:string size:int thumbs:flags.0?Vector dc_id:int attributes:Vector = Document; +document#1e87342b flags:# id:long access_hash:long file_reference:bytes date:int mime_type:string size:int thumbs:flags.0?Vector video_thumbs:flags.1?Vector dc_id:int attributes:Vector = Document; help.support#17c6b5f6 phone_number:string user:User = help.Support; @@ -629,7 +630,7 @@ messages.botResults#947ca848 flags:# gallery:flags.0?true query_id:long next_off exportedMessageLink#5dab1af4 link:string html:string = ExportedMessageLink; -messageFwdHeader#ec338270 flags:# from_id:flags.0?int from_name:flags.5?string date:int channel_id:flags.1?int channel_post:flags.2?int post_author:flags.3?string saved_from_peer:flags.4?Peer saved_from_msg_id:flags.4?int = MessageFwdHeader; +messageFwdHeader#353a686b flags:# from_id:flags.0?int from_name:flags.5?string date:int channel_id:flags.1?int channel_post:flags.2?int post_author:flags.3?string saved_from_peer:flags.4?Peer saved_from_msg_id:flags.4?int psa_type:flags.6?string = MessageFwdHeader; auth.codeTypeSms#72a3158c = auth.CodeType; auth.codeTypeCall#741cd3e3 = auth.CodeType; @@ -890,9 +891,6 @@ fileHash#6242c773 offset:int limit:int hash:bytes = FileHash; inputClientProxy#75588b3f address:string port:int = InputClientProxy; -help.proxyDataEmpty#e09e1fb8 expires:int = help.ProxyData; -help.proxyDataPromo#2bf7ee23 expires:int peer:Peer chats:Vector users:Vector = help.ProxyData; - help.termsOfServiceUpdateEmpty#e3309f7f expires:int = help.TermsOfServiceUpdate; help.termsOfServiceUpdate#28ecf961 expires:int terms_of_service:help.TermsOfService = help.TermsOfServiceUpdate; @@ -1118,6 +1116,11 @@ messageInteractionCounters#ad4fc9bd msg_id:int views:int forwards:int = MessageI stats.broadcastStats#bdf78394 period:StatsDateRangeDays followers:StatsAbsValueAndPrev views_per_post:StatsAbsValueAndPrev shares_per_post:StatsAbsValueAndPrev enabled_notifications:StatsPercentValue growth_graph:StatsGraph followers_graph:StatsGraph mute_graph:StatsGraph top_hours_graph:StatsGraph interactions_graph:StatsGraph iv_interactions_graph:StatsGraph views_by_source_graph:StatsGraph new_followers_by_source_graph:StatsGraph languages_graph:StatsGraph recent_message_interactions:Vector = stats.BroadcastStats; +help.promoDataEmpty#98f6ac75 expires:int = help.PromoData; +help.promoData#8c39793f flags:# proxy:flags.0?true expires:int peer:Peer chats:Vector users:Vector psa_type:flags.1?string psa_message:flags.2?string = help.PromoData; + +videoSize#435bb987 type:string location:FileLocation w:int h:int size:int = VideoSize; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -1389,7 +1392,6 @@ help.getAppChangelog#9010ef6f prev_app_version:string = Updates; help.setBotUpdatesStatus#ec22cfcd pending_updates_count:int message:string = Bool; help.getCdnConfig#52029342 = CdnConfig; help.getRecentMeUrls#3dc0f114 referer:string = help.RecentMeUrls; -help.getProxyData#3d7758e1 = help.ProxyData; help.getTermsOfServiceUpdate#2ca51fd1 = help.TermsOfServiceUpdate; help.acceptTermsOfService#ee72f79a id:DataJSON = Bool; help.getDeepLinkInfo#3fedc75f path:string = help.DeepLinkInfo; @@ -1399,6 +1401,8 @@ help.getPassportConfig#c661ad08 hash:int = help.PassportConfig; help.getSupportName#d360e72c = help.SupportName; help.getUserInfo#38a08d3 user_id:InputUser = help.UserInfo; help.editUserInfo#66b91b70 user_id:InputUser message:string entities:Vector = help.UserInfo; +help.getPromoData#c0977421 = help.PromoData; +help.hidePromoData#1e251c95 peer:InputPeer = Bool; channels.readHistory#cc104937 channel:InputChannel max_id:int = Bool; channels.deleteMessages#84c1fd4e channel:InputChannel id:Vector = messages.AffectedMessages; @@ -1462,6 +1466,7 @@ phone.receivedCall#17d54f61 peer:InputPhoneCall = Bool; phone.discardCall#b2cbc1c0 flags:# video:flags.0?true peer:InputPhoneCall duration:int reason:PhoneCallDiscardReason connection_id:long = Updates; phone.setCallRating#59ead627 flags:# user_initiative:flags.0?true peer:InputPhoneCall rating:int comment:string = Updates; phone.saveCallDebug#277add7e peer:InputPhoneCall debug:DataJSON = Bool; +phone.sendSignalingData#ff7a9383 peer:InputPhoneCall data:bytes = Bool; langpack.getLangPack#f2f2330a lang_pack:string lang_code:string = LangPackDifference; langpack.getStrings#efea3803 lang_pack:string lang_code:string keys:Vector = Vector; @@ -1472,7 +1477,7 @@ langpack.getLanguage#6a596502 lang_pack:string lang_code:string = LangPackLangua folders.editPeerFolders#6847d0ab folder_peers:Vector = Updates; folders.deleteFolder#1c295881 folder_id:int = Updates; -stats.getBroadcastStats#e6300dba flags:# dark:flags.0?true channel:InputChannel tz_offset:int = stats.BroadcastStats; +stats.getBroadcastStats#ab42441a flags:# dark:flags.0?true channel:InputChannel = stats.BroadcastStats; stats.loadAsyncGraph#621d5fa0 flags:# token:string x:flags.0?long = StatsGraph; -// LAYER 112 \ No newline at end of file +// LAYER 114 \ No newline at end of file From dd5bd800174d7a07315b1ce38fa1dda9d84bc321 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 6 Jun 2020 12:31:25 +0200 Subject: [PATCH 0226/1185] Remove unneeded code Closes #414 --- pyrogram/client/client.py | 47 ------------------- .../client/methods/contacts/get_contacts.py | 12 +---- 2 files changed, 2 insertions(+), 57 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 53bc3799da..5f52bfc0c4 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -685,9 +685,6 @@ def authorize(self) -> User: print(e.MESSAGE) self.phone_number = None self.bot_token = None - except FloodWait as e: - print(e.MESSAGE.format(x=e.x)) - time.sleep(e.x) else: break @@ -736,9 +733,6 @@ def authorize(self) -> User: return self.recover_password(recovery_code) except BadRequest as e: print(e.MESSAGE) - except FloodWait as e: - print(e.MESSAGE.format(x=e.x)) - time.sleep(e.x) except Exception as e: log.error(e, exc_info=True) raise @@ -749,12 +743,6 @@ def authorize(self) -> User: except BadRequest as e: print(e.MESSAGE) self.password = None - except FloodWait as e: - print(e.MESSAGE.format(x=e.x)) - time.sleep(e.x) - except FloodWait as e: - print(e.MESSAGE.format(x=e.x)) - time.sleep(e.x) else: break @@ -774,9 +762,6 @@ def authorize(self) -> User: ) except BadRequest as e: print(e.MESSAGE) - except FloodWait as e: - print(e.MESSAGE.format(x=e.x)) - time.sleep(e.x) else: break @@ -1636,38 +1621,6 @@ def load_plugins(self): log.warning('[{}] No plugin loaded from "{}"'.format( self.session_name, root)) - # def get_initial_dialogs_chunk(self, offset_date: int = 0): - # while True: - # try: - # r = self.send( - # functions.messages.GetDialogs( - # offset_date=offset_date, - # offset_id=0, - # offset_peer=types.InputPeerEmpty(), - # limit=self.DIALOGS_AT_ONCE, - # hash=0, - # exclude_pinned=True - # ) - # ) - # except FloodWait as e: - # log.warning("get_dialogs flood: waiting {} seconds".format(e.x)) - # time.sleep(e.x) - # else: - # log.info("Total peers: {}".format(self.storage.peers_count)) - # return r - # - # def get_initial_dialogs(self): - # self.send(functions.messages.GetPinnedDialogs(folder_id=0)) - # - # dialogs = self.get_initial_dialogs_chunk() - # offset_date = utils.get_offset_date(dialogs) - # - # while len(dialogs.dialogs) == self.DIALOGS_AT_ONCE: - # dialogs = self.get_initial_dialogs_chunk(offset_date) - # offset_date = utils.get_offset_date(dialogs) - # - # self.get_initial_dialogs_chunk() - def resolve_peer(self, peer_id: Union[int, str]): """Get the InputPeer of a known peer id. Useful whenever an InputPeer type is required. diff --git a/pyrogram/client/methods/contacts/get_contacts.py b/pyrogram/client/methods/contacts/get_contacts.py index 84d9c7d4ff..a0699e19ce 100644 --- a/pyrogram/client/methods/contacts/get_contacts.py +++ b/pyrogram/client/methods/contacts/get_contacts.py @@ -17,12 +17,10 @@ # along with Pyrogram. If not, see . import logging -import time from typing import List import pyrogram from pyrogram.api import functions -from pyrogram.errors import FloodWait from ...ext import BaseClient log = logging.getLogger(__name__) @@ -41,11 +39,5 @@ def get_contacts(self) -> List["pyrogram.User"]: contacts = app.get_contacts() print(contacts) """ - while True: - try: - contacts = self.send(functions.contacts.GetContacts(hash=0)) - except FloodWait as e: - log.warning("get_contacts flood: waiting {} seconds".format(e.x)) - time.sleep(e.x) - else: - return pyrogram.List(pyrogram.User._parse(self, user) for user in contacts.users) + contacts = self.send(functions.contacts.GetContacts(hash=0)) + return pyrogram.List(pyrogram.User._parse(self, user) for user in contacts.users) From 2adc01be7f03cc21c2ce3c7a96bab8a5a3dfaf47 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 11 Jun 2020 22:03:59 +0200 Subject: [PATCH 0227/1185] Remove unneeded file_ref arg from Message.download --- pyrogram/client/types/messages_and_media/message.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index 575672e2ed..215f86d006 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -2984,7 +2984,6 @@ def retract_vote( def download( self, - file_ref: str = None, file_name: str = "", block: bool = True, progress: callable = None, @@ -3004,10 +3003,6 @@ def download( message.download() Parameters: - file_ref (``str``, *optional*): - A valid file reference obtained by a recently fetched media message. - To be used in combination with a file id in case a file reference is needed. - file_name (``str``, *optional*): A custom *file_name* to be used instead of the one provided by Telegram. By default, all files are downloaded in the *downloads* folder in your working directory. @@ -3049,7 +3044,6 @@ def download( """ return self._client.download_media( message=self, - file_ref=file_ref, file_name=file_name, block=block, progress=progress, From 4aaa7160f06ef12a08e21d627512ce6446971a5f Mon Sep 17 00:00:00 2001 From: Florent Gallaire Date: Mon, 15 Jun 2020 05:19:45 +0000 Subject: [PATCH 0228/1185] Send a warning instead throwing an exeption when message type is not compatible with as_copy --- pyrogram/client/types/messages_and_media/message.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index 215f86d006..238736e653 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -18,6 +18,7 @@ from functools import partial from typing import List, Match, Union +import logging import pyrogram from pyrogram.api import types @@ -34,6 +35,8 @@ from ...ext import utils from ...parser import utils as parser_utils, Parser +log = logging.getLogger(__name__) + class Str(str): def __init__(self, *args): @@ -2684,12 +2687,10 @@ def forward( """ if as_copy: if self.service: - raise ValueError("Unable to copy service messages") - - if self.game and not self._client.is_bot: - raise ValueError("Users cannot send messages with Game media type") - - if self.text: + log.warning("Unable to copy service messages, message_id: {}".format(self.message_id)) + elif self.game and not self._client.is_bot: + log.warning("Users cannot send messages with Game media type, message_id: {}".format(self.message_id)) + elif self.text: return self._client.send_message( chat_id, text=self.text.html, From dd9b55f256c306bb26f7985e0cab07dd2be278a0 Mon Sep 17 00:00:00 2001 From: Ripe <42308266+Ripeey@users.noreply.github.com> Date: Thu, 2 Jul 2020 11:27:29 +0000 Subject: [PATCH 0229/1185] Update inline_query_result_animation.py (#435) add missing await --- .../client/types/inline_mode/inline_query_result_animation.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/types/inline_mode/inline_query_result_animation.py b/pyrogram/client/types/inline_mode/inline_query_result_animation.py index 756ee91aae..d53bbd594e 100644 --- a/pyrogram/client/types/inline_mode/inline_query_result_animation.py +++ b/pyrogram/client/types/inline_mode/inline_query_result_animation.py @@ -91,7 +91,7 @@ def __init__( self.reply_markup = reply_markup self.input_message_content = input_message_content - def write(self): + async def write(self): animation = types.InputWebDocument( url=self.animation_url, size=0, @@ -121,7 +121,7 @@ def write(self): if self.input_message_content else types.InputBotInlineMessageMediaAuto( reply_markup=self.reply_markup.write() if self.reply_markup else None, - **(Parser(None)).parse(self.caption, self.parse_mode) + **await(Parser(None)).parse(self.caption, self.parse_mode) ) ) ) From c196b90c9d1f73cbd72615131a97d0d703ef814b Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 6 Jul 2020 15:55:11 +0200 Subject: [PATCH 0230/1185] Allow uploading files up to 2000 MiB in size --- 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 5f52bfc0c4..0e186d85b5 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -1772,8 +1772,8 @@ def save_file( if file_size == 0: raise ValueError("File size equals to 0 B") - if file_size > 1500 * 1024 * 1024: - raise ValueError("Telegram doesn't support uploading files bigger than 1500 MiB") + if file_size > 2000 * 1024 * 1024: + raise ValueError("Telegram doesn't support uploading files bigger than 2000 MiB") file_total_parts = int(math.ceil(file_size / part_size)) is_big = True if file_size > 10 * 1024 * 1024 else False From db7fc68efe9c576b2f59a264d60496b1e4e7be28 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 6 Jul 2020 16:01:37 +0200 Subject: [PATCH 0231/1185] Update FILE_PART(S)_INVALID error message Telegram now allows uploads up to 2000 MiB in size (4000 parts) --- compiler/error/source/400_BAD_REQUEST.tsv | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/error/source/400_BAD_REQUEST.tsv b/compiler/error/source/400_BAD_REQUEST.tsv index 8b4d8709ce..ba6f68150f 100644 --- a/compiler/error/source/400_BAD_REQUEST.tsv +++ b/compiler/error/source/400_BAD_REQUEST.tsv @@ -12,8 +12,8 @@ PHONE_NUMBER_UNOCCUPIED The phone number is not yet being used USERS_TOO_FEW Not enough users (to create a chat, for example) USERS_TOO_MUCH The maximum number of users has been exceeded (to create a chat, for example) TYPE_CONSTRUCTOR_INVALID The type constructor is invalid -FILE_PART_INVALID The file part number is invalid. The value is not between 0 and 2999 -FILE_PARTS_INVALID Invalid number of parts. The value is not between 1 and 3000 +FILE_PART_INVALID The file part number is invalid. The value is not between 0 and 3999 +FILE_PARTS_INVALID Invalid number of parts. The value is not between 1 and 4000 FILE_PART_X_MISSING Part {x} of the file is missing from storage MD5_CHECKSUM_INVALID The file's checksum did not match the md5_checksum parameter PHOTO_INVALID_DIMENSIONS The photo dimensions are invalid @@ -144,4 +144,4 @@ FRESH_CHANGE_ADMINS_FORBIDDEN Recently logged-in users cannot change admins BROADCAST_PUBLIC_VOTERS_FORBIDDEN Polls with public voters cannot be sent in channels INPUT_FILTER_INVALID The filter is invalid for this query EMOTICON_EMPTY The emoticon parameter is empty -EMOTICON_INVALID The emoticon parameter is invalid \ No newline at end of file +EMOTICON_INVALID The emoticon parameter is invalid From 37d823f5792bec295536bc147642be70f18caa10 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 6 Jul 2020 16:07:06 +0200 Subject: [PATCH 0232/1185] Fix linked chat parsing There are two distinct ChatFull types using the same name (but different namespaces), their objects are kept in chat_full and full_chat. --- pyrogram/client/types/user_and_chats/chat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/types/user_and_chats/chat.py b/pyrogram/client/types/user_and_chats/chat.py index d95c5b3742..fe10357376 100644 --- a/pyrogram/client/types/user_and_chats/chat.py +++ b/pyrogram/client/types/user_and_chats/chat.py @@ -252,7 +252,7 @@ def _parse_full(client, chat_full: types.messages.ChatFull or types.UserFull) -> if full_chat.id == c.id: chat = c - if isinstance(chat_full, types.ChannelFull): + if isinstance(full_chat, types.ChannelFull): if full_chat.linked_chat_id == c.id: linked_chat = c From 3ec6b22d85da8ad905fe7de7e913f26dfe5b51bc Mon Sep 17 00:00:00 2001 From: Hearot Date: Wed, 8 Jul 2020 15:43:17 +0200 Subject: [PATCH 0233/1185] Add references to Pyrubrum (#429) --- docs/source/powered-by.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/source/powered-by.rst b/docs/source/powered-by.rst index a696cebca6..77e593e7ca 100644 --- a/docs/source/powered-by.rst +++ b/docs/source/powered-by.rst @@ -71,5 +71,15 @@ Projects Showcase ----- +`Pyrubrum `_ +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +| **An intuitive framework for creating Telegram bots** +| --- by `Hearot `_ + +- Source Code: https://github.com/hearot/pyrubrum + +----- + .. _Feature Request: https://github.com/pyrogram/pyrogram/issues/new?labels=enhancement&template=feature_request.md From 55d0b93cf079aa4622375b5d4e954ce6f89577e4 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 8 Jul 2020 17:16:06 +0200 Subject: [PATCH 0234/1185] Extend set_slow_mode to accept None --- pyrogram/client/methods/chats/set_slow_mode.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/pyrogram/client/methods/chats/set_slow_mode.py b/pyrogram/client/methods/chats/set_slow_mode.py index cf6c7096a3..8215c3b916 100644 --- a/pyrogram/client/methods/chats/set_slow_mode.py +++ b/pyrogram/client/methods/chats/set_slow_mode.py @@ -26,7 +26,7 @@ class SetSlowMode(BaseClient): def set_slow_mode( self, chat_id: Union[int, str], - seconds: int, + seconds: Union[int, None] ) -> bool: """Set the slow mode interval for a chat. @@ -34,9 +34,9 @@ def set_slow_mode( chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. - seconds (``int`` | ``str``): + seconds (``int`` | ``None``): Seconds in which members will be able to send only one message per this interval. - Valid values are: 0 (off), 10, 30, 60 (1m), 300 (5m), 900 (15m) or 3600 (1h). + Valid values are: 0 or None (off), 10, 30, 60 (1m), 300 (5m), 900 (15m) or 3600 (1h). Returns: ``bool``: True on success. @@ -44,13 +44,17 @@ def set_slow_mode( Example: .. code-block:: python + # Set slow mode to 60 seconds app.set_slow_mode("pyrogramchat", 60) + + # Disable slow mode + app.set_slow_mode("pyrogramchat", None) """ self.send( functions.channels.ToggleSlowMode( channel=self.resolve_peer(chat_id), - seconds=seconds + seconds=0 if seconds is None else seconds ) ) From 74e5a5a5e13c441f3531749934b2013f8bbf8cb6 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 8 Jul 2020 23:37:24 +0200 Subject: [PATCH 0235/1185] Add Mention link --- pyrogram/client/ext/__init__.py | 1 + pyrogram/client/ext/link.py | 52 ++++++++++++++++++++ pyrogram/client/types/user_and_chats/user.py | 10 ++-- 3 files changed, 57 insertions(+), 6 deletions(-) create mode 100644 pyrogram/client/ext/link.py diff --git a/pyrogram/client/ext/__init__.py b/pyrogram/client/ext/__init__.py index 8b44ca5805..e363d3d4db 100644 --- a/pyrogram/client/ext/__init__.py +++ b/pyrogram/client/ext/__init__.py @@ -20,4 +20,5 @@ from .dispatcher import Dispatcher from .emoji import Emoji from .file_data import FileData +from .link import Link from .syncer import Syncer diff --git a/pyrogram/client/ext/link.py b/pyrogram/client/ext/link.py new file mode 100644 index 0000000000..bd2d82cf9a --- /dev/null +++ b/pyrogram/client/ext/link.py @@ -0,0 +1,52 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan +# +# 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 html + + +class Link(str): + HTML = "{text}" + MD = "[{text}]({url})" + + def __init__(self, url: str, text: str, style: str): + super().__init__() + + self.url = url + self.text = text + self.style = style + + @staticmethod + def format(url: str, text: str, style: str): + if style in ["md", "markdown"]: + fmt = Link.MD + elif style in ["combined", "html", None]: + fmt = Link.HTML + else: + raise ValueError("{} is not a valid style/parse mode".format(style)) + + return fmt.format(url=url, text=html.escape(text)) + + # noinspection PyArgumentList + def __new__(cls, url, text, style): + return str.__new__(cls, Link.format(url, text, style)) + + def __call__(self, other: str = None, *, style: str = None): + return Link.format(self.url, other or self.text, style or self.style) + + def __str__(self): + return Link.format(self.url, self.text, self.style) diff --git a/pyrogram/client/types/user_and_chats/user.py b/pyrogram/client/types/user_and_chats/user.py index 6607aad7cb..b90db42af2 100644 --- a/pyrogram/client/types/user_and_chats/user.py +++ b/pyrogram/client/types/user_and_chats/user.py @@ -16,11 +16,11 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -import html from typing import List import pyrogram from pyrogram.api import types +from pyrogram.client.ext import Link from .chat_photo import ChatPhoto from .restriction import Restriction from ..object import Object @@ -158,11 +158,9 @@ def __init__( self.photo = photo self.restrictions = restrictions - def __format__(self, format_spec): - if format_spec == "mention": - return '{1}'.format(self.id, html.escape(self.first_name)) - - return html.escape(str(self)) + @property + def mention(self): + return Link("tg://user?id={}".format(self.id), self.first_name, self._client.parse_mode) @staticmethod def _parse(client, user: types.User) -> "User" or None: From 1e8c9812a110dd612d7035003ec0f18ff4e523ff Mon Sep 17 00:00:00 2001 From: Octo Date: Sat, 20 Jul 2019 09:20:57 +0300 Subject: [PATCH 0236/1185] Add support for downloading files to file pointer, fix for https://github.com/pyrogram/pyrogram/issues/284 --- pyrogram/client/client.py | 173 +++++++++--------- .../client/methods/messages/download_media.py | 13 ++ .../types/messages_and_media/message.py | 11 +- 3 files changed, 112 insertions(+), 85 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 0e186d85b5..b68bd89115 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . - +import io import logging import math import os @@ -1231,9 +1231,9 @@ def download_worker(self): temp_file_path = "" final_file_path = "" - + path = [None] try: - data, directory, file_name, done, progress, progress_args, path = packet + data, done, progress, progress_args, out, path, to_file = packet temp_file_path = self.get_file( media_type=data.media_type, @@ -1250,13 +1250,15 @@ def download_worker(self): file_size=data.file_size, is_big=data.is_big, progress=progress, - progress_args=progress_args + progress_args=progress_args, + out=out ) - - if temp_file_path: - final_file_path = os.path.abspath(re.sub("\\\\", "/", os.path.join(directory, file_name))) - os.makedirs(directory, exist_ok=True) - shutil.move(temp_file_path, final_file_path) + if to_file: + final_file_path = out.name + else: + final_file_path = '' + if to_file: + out.close() except Exception as e: log.error(e, exc_info=True) @@ -1864,7 +1866,8 @@ def get_file( file_size: int, is_big: bool, progress: callable, - progress_args: tuple = () + progress_args: tuple = (), + out: io.IOBase = None ) -> str: with self.media_sessions_lock: session = self.media_sessions.get(dc_id, None) @@ -1950,7 +1953,10 @@ def get_file( limit = 1024 * 1024 offset = 0 file_name = "" - + if not out: + f = tempfile.NamedTemporaryFile("wb", delete=False) + else: + f = out try: r = session.send( functions.upload.GetFile( @@ -1961,36 +1967,37 @@ def get_file( ) if isinstance(r, types.upload.File): - with tempfile.NamedTemporaryFile("wb", delete=False) as f: + if hasattr(f, "name"): file_name = f.name - while True: - chunk = r.bytes + while True: + chunk = r.bytes - if not chunk: - break + if not chunk: + break - f.write(chunk) + f.write(chunk) - offset += limit + offset += limit - if progress: - progress( - min(offset, file_size) - if file_size != 0 - else offset, - file_size, - *progress_args - ) + if progress: + progress( - r = session.send( - functions.upload.GetFile( - location=location, - offset=offset, - limit=limit - ) + min(offset, file_size) + if file_size != 0 + else offset, + file_size, + *progress_args ) + r = session.send( + functions.upload.GetFile( + location=location, + offset=offset, + limit=limit + ) + ) + elif isinstance(r, types.upload.FileCdnRedirect): with self.media_sessions_lock: cdn_session = self.media_sessions.get(r.dc_id, None) @@ -2003,70 +2010,71 @@ def get_file( self.media_sessions[r.dc_id] = cdn_session try: - with tempfile.NamedTemporaryFile("wb", delete=False) as f: + if hasattr(f, "name"): file_name = f.name - while True: - r2 = cdn_session.send( - functions.upload.GetCdnFile( - file_token=r.file_token, - offset=offset, - limit=limit - ) + while True: + r2 = cdn_session.send( + functions.upload.GetCdnFile( + file_token=r.file_token, + offset=offset, + limit=limit ) + ) - if isinstance(r2, types.upload.CdnFileReuploadNeeded): - try: - session.send( - functions.upload.ReuploadCdnFile( - file_token=r.file_token, - request_token=r2.request_token - ) + if isinstance(r2, types.upload.CdnFileReuploadNeeded): + try: + session.send( + functions.upload.ReuploadCdnFile( + file_token=r.file_token, + request_token=r2.request_token ) - except VolumeLocNotFound: - break - else: - continue + ) + except VolumeLocNotFound: + break + else: + continue - chunk = r2.bytes + chunk = r2.bytes - # https://core.telegram.org/cdn#decrypting-files - decrypted_chunk = AES.ctr256_decrypt( - chunk, - r.encryption_key, - bytearray( - r.encryption_iv[:-4] - + (offset // 16).to_bytes(4, "big") - ) + # https://core.telegram.org/cdn#decrypting-files + decrypted_chunk = AES.ctr256_decrypt( + chunk, + r.encryption_key, + bytearray( + r.encryption_iv[:-4] + + (offset // 16).to_bytes(4, "big") ) + ) - hashes = session.send( - functions.upload.GetCdnFileHashes( - file_token=r.file_token, - offset=offset - ) + hashes = session.send( + functions.upload.GetCdnFileHashes( + file_token=r.file_token, + offset=offset ) + ) - # https://core.telegram.org/cdn#verifying-files - for i, h in enumerate(hashes): - cdn_chunk = decrypted_chunk[h.limit * i: h.limit * (i + 1)] - assert h.hash == sha256(cdn_chunk).digest(), "Invalid CDN hash part {}".format(i) + # https://core.telegram.org/cdn#verifying-files + for i, h in enumerate(hashes): + cdn_chunk = decrypted_chunk[h.limit * i: h.limit * (i + 1)] + assert h.hash == sha256(cdn_chunk).digest(), "Invalid CDN hash part {}".format(i) - f.write(decrypted_chunk) + f.write(decrypted_chunk) - offset += limit + offset += limit - if progress: - progress( - min(offset, file_size) - if file_size != 0 - else offset, - file_size, - *progress_args - ) + if progress: + progress( - if len(chunk) < limit: - break + min(offset, file_size) + if file_size != 0 + else offset, + file_size, + *progress_args + ) + + if len(chunk) < limit: + break except Exception as e: raise e except Exception as e: @@ -2074,7 +2082,8 @@ def get_file( log.error(e, exc_info=True) try: - os.remove(file_name) + if out: + os.remove(file_name) except OSError: pass diff --git a/pyrogram/client/methods/messages/download_media.py b/pyrogram/client/methods/messages/download_media.py index 220543977e..2176e4aa98 100644 --- a/pyrogram/client/methods/messages/download_media.py +++ b/pyrogram/client/methods/messages/download_media.py @@ -17,7 +17,9 @@ # along with Pyrogram. If not, see . import binascii +import io import os +import re import struct import time from datetime import datetime @@ -37,6 +39,7 @@ def download_media( message: Union["pyrogram.Message", str], file_ref: str = None, file_name: str = DEFAULT_DOWNLOAD_DIR, + out: io.IOBase = None, block: bool = True, progress: callable = None, progress_args: tuple = () @@ -58,6 +61,9 @@ def download_media( You can also specify a path for downloading files in a custom location: paths that end with "/" are considered directories. All non-existent folders will be created automatically. + out (``io.IOBase``, *optional*): + A custom *file-like object* to be used when downloading file. Overrides file_name + block (``bool``, *optional*): Blocks the code execution until the file has been downloaded. Defaults to True. @@ -238,6 +244,13 @@ def get_existing_attributes() -> dict: extension ) + if not out: + out = open(os.path.abspath(re.sub("\\\\", "/", os.path.join(directory, file_name))), 'wb') + os.makedirs(directory, exist_ok=True) + to_file = True + else: + to_file = False + self.download_queue.put((data, done, progress, progress_args, out, path, to_file)) # Cast to string because Path objects aren't supported by Python 3.5 self.download_queue.put((data, str(directory), str(file_name), done, progress, progress_args, path)) diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index 215f86d006..cff8c5789d 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . - +import io from functools import partial from typing import List, Match, Union @@ -2964,7 +2964,7 @@ def retract_vote( chat_id=message.chat.id, message_id=message_id, ) - + Example: .. code-block:: python @@ -2985,6 +2985,7 @@ def retract_vote( def download( self, file_name: str = "", + out: io.IOBase = None, block: bool = True, progress: callable = None, progress_args: tuple = () @@ -3009,6 +3010,9 @@ def download( You can also specify a path for downloading files in a custom location: paths that end with "/" are considered directories. All non-existent folders will be created automatically. + out (``io.IOBase``, *optional*): + A custom *file-like object* to be used when downloading file. Overrides file_name + block (``bool``, *optional*): Blocks the code execution until the file has been downloaded. Defaults to True. @@ -3045,6 +3049,7 @@ def download( return self._client.download_media( message=self, file_name=file_name, + out=out, block=block, progress=progress, progress_args=progress_args, @@ -3074,7 +3079,7 @@ def vote( Parameters: option (``int``): Index of the poll option you want to vote for (0 to 9). - + Returns: :obj:`Poll`: On success, the poll with the chosen option is returned. From c13392d2ce59e80aab02689bc01866c789bb038f Mon Sep 17 00:00:00 2001 From: Octo Date: Sat, 20 Jul 2019 10:22:12 +0300 Subject: [PATCH 0237/1185] Add support for uploading from file pointers, fixes https://github.com/pyrogram/pyrogram/issues/261 --- pyrogram/client/client.py | 94 ++++++----- .../methods/messages/send_animated_sticker.py | 153 ++++++++++++++++++ .../client/methods/messages/send_animation.py | 45 ++++-- .../client/methods/messages/send_audio.py | 41 +++-- .../client/methods/messages/send_document.py | 35 ++-- .../client/methods/messages/send_photo.py | 29 ++-- .../client/methods/messages/send_sticker.py | 30 ++-- .../client/methods/messages/send_video.py | 40 +++-- .../methods/messages/send_video_note.py | 31 +++- .../client/methods/messages/send_voice.py | 37 +++-- 10 files changed, 418 insertions(+), 117 deletions(-) create mode 100644 pyrogram/client/methods/messages/send_animated_sticker.py diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index b68bd89115..1efb6f0675 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -1715,7 +1715,7 @@ def resolve_peer(self, peer_id: Union[int, str]): def save_file( self, - path: str, + path: Union[str, io.IOBase], file_id: int = None, file_part: int = 0, progress: callable = None, @@ -1767,9 +1767,20 @@ def save_file( Raises: RPCError: In case of a Telegram RPC error. + ValueError: if path is not str or file-like readable object """ part_size = 512 * 1024 - file_size = os.path.getsize(path) + if isinstance(path, str): + fp = open(path, 'rb') + filename = os.path.basename(path) + elif hasattr(path, 'write'): + fp = path + filename = fp.name + else: + raise ValueError("Invalid path passed! Pass file pointer or path to file") + fp.seek(0, os.SEEK_END) + file_size = fp.tell() + fp.seek(0) if file_size == 0: raise ValueError("File size equals to 0 B") @@ -1787,67 +1798,74 @@ def save_file( session.start() try: - with open(path, "rb") as f: - f.seek(part_size * file_part) - - while True: - chunk = f.read(part_size) - - if not chunk: - if not is_big: - md5_sum = "".join([hex(i)[2:].zfill(2) for i in md5_sum.digest()]) - break + fp.seek(part_size * file_part) - for _ in range(3): - if is_big: - rpc = functions.upload.SaveBigFilePart( - file_id=file_id, - file_part=file_part, - file_total_parts=file_total_parts, - bytes=chunk - ) - else: - rpc = functions.upload.SaveFilePart( - file_id=file_id, - file_part=file_part, - bytes=chunk - ) + while True: + chunk = fp.read(part_size) - if session.send(rpc): - break + if not chunk: + if not is_big: + md5_sum = "".join([hex(i)[2:].zfill(2) for i in md5_sum.digest()]) + break + + for _ in range(3): + if is_big: + rpc = functions.upload.SaveBigFilePart( + file_id=file_id, + file_part=file_part, + file_total_parts=file_total_parts, + bytes=chunk + ) else: - raise AssertionError("Telegram didn't accept chunk #{} of {}".format(file_part, path)) + rpc = functions.upload.SaveFilePart( + file_id=file_id, + file_part=file_part, + bytes=chunk + ) - if is_missing_part: - return + if session.send(rpc): + break + else: + raise AssertionError("Telegram didn't accept chunk #{} of {}".format(file_part, path)) - if not is_big: - md5_sum.update(chunk) + if is_missing_part: + return - file_part += 1 + if not is_big: + md5_sum.update(chunk) - if progress: - progress(min(file_part * part_size, file_size), file_size, *progress_args) + file_part += 1 + + if progress: + progress(min(file_part * part_size, file_size), file_size, *progress_args) except Client.StopTransmission: + if isinstance(path, str): + fp.close() raise except Exception as e: + if isinstance(path, str): + fp.close() log.error(e, exc_info=True) else: + if isinstance(path, str): + fp.close() if is_big: return types.InputFileBig( id=file_id, parts=file_total_parts, - name=os.path.basename(path), + name=filename, ) else: return types.InputFile( id=file_id, parts=file_total_parts, - name=os.path.basename(path), + name=filename, md5_checksum=md5_sum ) finally: + if isinstance(path, str): + fp.close() session.stop() def get_file( diff --git a/pyrogram/client/methods/messages/send_animated_sticker.py b/pyrogram/client/methods/messages/send_animated_sticker.py new file mode 100644 index 0000000000..b295939484 --- /dev/null +++ b/pyrogram/client/methods/messages/send_animated_sticker.py @@ -0,0 +1,153 @@ +# 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 io +import os +from typing import Union + +import pyrogram +from pyrogram.api import functions, types +from pyrogram.client.ext import BaseClient, utils +from pyrogram.errors import FilePartMissing + + +class SendAnimatedSticker(BaseClient): + def send_animated_sticker( + self, + chat_id: Union[int, str], + animated_sticker: Union[str, io.IOBase], + disable_notification: bool = None, + reply_to_message_id: int = None, + reply_markup: Union[ + "pyrogram.InlineKeyboardMarkup", + "pyrogram.ReplyKeyboardMarkup", + "pyrogram.ReplyKeyboardRemove", + "pyrogram.ForceReply" + ] = None, + progress: callable = None, + progress_args: tuple = () + ) -> Union["pyrogram.Message", None]: + """Send .tgs animated stickers. + + Parameters: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + For your personal cloud (Saved Messages) you can simply use "me" or "self". + For a contact that exists in your Telegram address book you can use his phone number (str). + + animated_sticker (``str`` | file-like object): + Animated sticker to send. + Pass a file_id as string to send a animated sticker that exists on the Telegram servers, + pass an HTTP URL as a string for Telegram to get a .webp animated sticker file from the Internet, or + pass a file path as string to upload a new animated sticker that exists on your local machine. + pass a readable file-like object with .name + + disable_notification (``bool``, *optional*): + Sends the message silently. + Users will receive a notification with no sound. + + reply_to_message_id (``int``, *optional*): + If the message is a reply, ID of the original message. + + reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + Additional interface options. An object for an inline keyboard, custom reply keyboard, + instructions to remove reply keyboard or to force a reply from the user. + + progress (``callable``, *optional*): + Pass a callback function to view the upload progress. + The function must take *(client, current, total, \*args)* as positional arguments (look at the section + below for a detailed description). + + progress_args (``tuple``, *optional*): + Extra custom arguments for the progress callback function. Useful, for example, if you want to pass + a chat_id and a message_id in order to edit a message with the updated progress. + + Other Parameters: + client (:obj:`Client`): + The Client itself, useful when you want to call other API methods inside the callback function. + + current (``int``): + The amount of bytes uploaded so far. + + total (``int``): + The size of the file. + + *args (``tuple``, *optional*): + Extra custom arguments as defined in the *progress_args* parameter. + You can either keep *\*args* or add every single extra argument in your function signature. + + Returns: + :obj:`Message` | ``None``: On success, the sent animated sticker message is returned, otherwise, in case the + upload is deliberately stopped with :meth:`~Client.stop_transmission`, None is returned. + Raises: + RPCError: In case of a Telegram RPC error. + """ + file = None + + try: + if isinstance(animated_sticker, str): + if os.path.exists(animated_sticker): + file = self.save_file(animated_sticker, progress=progress, progress_args=progress_args) + media = types.InputMediaUploadedDocument( + mime_type=self.guess_mime_type(animated_sticker) or "application/x-tgsticker", + file=file, + attributes=[ + types.DocumentAttributeFilename(file_name=os.path.basename(animated_sticker)) + ] + ) + elif animated_sticker.startswith("http"): + media = types.InputMediaDocumentExternal( + url=animated_sticker + ) + else: + media = utils.get_input_media_from_file_id(animated_sticker, 5) + elif hasattr(animated_sticker, "read"): + file = self.save_file(animated_sticker, progress=progress, progress_args=progress_args) + media = types.InputMediaUploadedDocument( + mime_type=self.guess_mime_type(animated_sticker.name) or "application/x-tgsticker", + file=file, + attributes=[ + types.DocumentAttributeFilename(file_name=animated_sticker.name) + ] + ) + + + while True: + try: + r = self.send( + functions.messages.SendMedia( + peer=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: + self.save_file(animated_sticker, file_id=file.id, file_part=e.x) + else: + for i in r.updates: + if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): + return 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_animation.py b/pyrogram/client/methods/messages/send_animation.py index 288ed04e69..f8078a7cf6 100644 --- a/pyrogram/client/methods/messages/send_animation.py +++ b/pyrogram/client/methods/messages/send_animation.py @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . - +import io import os from typing import Union @@ -29,6 +29,7 @@ class SendAnimation(BaseClient): def send_animation( self, chat_id: Union[int, str], + animation: Union[str, io.IOBase], animation: str, file_ref: str = None, caption: str = "", @@ -59,11 +60,13 @@ def send_animation( For your personal cloud (Saved Messages) you can simply use "me" or "self". For a contact that exists in your Telegram address book you can use his phone number (str). - animation (``str``): + animation (``str``| file-like object): Animation to send. Pass a file_id as string to send an animation that exists on the Telegram servers, pass an HTTP URL as a string for Telegram to get an animation from the Internet, or pass a file path as string to upload a new animation that exists on your local machine. + pass a readable file-like object with .name + file_ref (``str``, *optional*): A valid file reference obtained by a recently fetched media message. @@ -163,11 +166,36 @@ def progress(current, total): file = None try: - if os.path.exists(animation): + if isinstance(animation, str): + if os.path.exists(animation): + thumb = None if thumb is None else self.save_file(thumb) + file = self.save_file(animation, progress=progress, progress_args=progress_args) + media = types.InputMediaUploadedDocument( + mime_type=self.guess_mime_type(animation) or "video/mp4", + file=file, + thumb=thumb, + attributes=[ + types.DocumentAttributeVideo( + supports_streaming=True, + duration=duration, + w=width, + h=height + ), + types.DocumentAttributeFilename(file_name=file_name or os.path.basename(animation)), + types.DocumentAttributeAnimated() + ] + ) + elif animation.startswith("http"): + media = types.InputMediaDocumentExternal( + url=animation + ) + else: + media = utils.get_input_media_from_file_id(animation, file_ref, 10) + elif hasattr(animation, "read"): thumb = None if thumb is None else self.save_file(thumb) file = self.save_file(animation, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(animation) or "video/mp4", + mime_type=self.guess_mime_type(animation.name) or "video/mp4", file=file, thumb=thumb, attributes=[ @@ -177,17 +205,10 @@ def progress(current, total): w=width, h=height ), - types.DocumentAttributeFilename(file_name=file_name or os.path.basename(animation)), + types.DocumentAttributeFilename(file_name=animation.name), types.DocumentAttributeAnimated() ] ) - elif animation.startswith("http"): - media = types.InputMediaDocumentExternal( - url=animation - ) - else: - media = utils.get_input_media_from_file_id(animation, file_ref, 10) - while True: try: r = self.send( diff --git a/pyrogram/client/methods/messages/send_audio.py b/pyrogram/client/methods/messages/send_audio.py index e271d96cdc..0ff588d84f 100644 --- a/pyrogram/client/methods/messages/send_audio.py +++ b/pyrogram/client/methods/messages/send_audio.py @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . - +import io import os from typing import Union @@ -29,6 +29,7 @@ class SendAudio(BaseClient): def send_audio( self, chat_id: Union[int, str], + audio: Union[str, io.IOBase], audio: str, file_ref: str = None, caption: str = "", @@ -60,11 +61,12 @@ def send_audio( For your personal cloud (Saved Messages) you can simply use "me" or "self". For a contact that exists in your Telegram address book you can use his phone number (str). - audio (``str``): + audio (``str``, file-like object): Audio file to send. Pass a file_id as string to send an audio file that exists on the Telegram servers, pass an HTTP URL as a string for Telegram to get an audio file from the Internet, or pass a file path as string to upload a new audio file that exists on your local machine. + pass a readable file-like object with .name file_ref (``str``, *optional*): A valid file reference obtained by a recently fetched media message. @@ -163,11 +165,34 @@ def progress(current, total): file = None try: - if os.path.exists(audio): + if isinstance(audio, str): + if os.path.exists(audio): + thumb = None if thumb is None else self.save_file(thumb) + file = self.save_file(audio, progress=progress, progress_args=progress_args) + media = types.InputMediaUploadedDocument( + mime_type=self.guess_mime_type(audio) or "audio/mpeg", + file=file, + thumb=thumb, + attributes=[ + types.DocumentAttributeAudio( + duration=duration, + performer=performer, + title=title + ), + types.DocumentAttributeFilename(file_name=file_name or os.path.basename(audio)) + ] + ) + elif audio.startswith("http"): + media = types.InputMediaDocumentExternal( + url=audio + ) + else: + media = utils.get_input_media_from_file_id(audio, file_ref, 9) + elif hasattr(audio, "read"): thumb = None if thumb is None else self.save_file(thumb) file = self.save_file(audio, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(audio) or "audio/mpeg", + mime_type=self.guess_mime_type(audio.name) or "audio/mpeg", file=file, thumb=thumb, attributes=[ @@ -176,15 +201,9 @@ def progress(current, total): performer=performer, title=title ), - types.DocumentAttributeFilename(file_name=file_name or os.path.basename(audio)) + types.DocumentAttributeFilename(file_name=os.path.basename(audio.name)) ] ) - elif audio.startswith("http"): - media = types.InputMediaDocumentExternal( - url=audio - ) - else: - media = utils.get_input_media_from_file_id(audio, file_ref, 9) while True: try: diff --git a/pyrogram/client/methods/messages/send_document.py b/pyrogram/client/methods/messages/send_document.py index 24a754f079..c4a7252bfe 100644 --- a/pyrogram/client/methods/messages/send_document.py +++ b/pyrogram/client/methods/messages/send_document.py @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . - +import io import os from typing import Union @@ -29,6 +29,7 @@ class SendDocument(BaseClient): def send_document( self, chat_id: Union[int, str], + document: Union[str, io.IOBase], document: str, file_ref: str = None, thumb: str = None, @@ -60,6 +61,8 @@ def send_document( Pass a file_id as string to send a file that exists on the Telegram servers, pass an HTTP URL as a string for Telegram to get a file from the Internet, or pass a file path as string to upload a new file that exists on your local machine. + pass a readable file-like object with .name + file_ref (``str``, *optional*): A valid file reference obtained by a recently fetched media message. @@ -143,23 +146,35 @@ def progress(current, total): file = None try: - if os.path.exists(document): + if isinstance(document, str): + if os.path.exists(document): + thumb = None if thumb is None else self.save_file(thumb) + file = self.save_file(document, progress=progress, progress_args=progress_args) + media = types.InputMediaUploadedDocument( + mime_type=self.guess_mime_type(document) or "application/zip", + file=file, + thumb=thumb, + attributes=[ + types.DocumentAttributeFilename(file_name=file_name or os.path.basename(document)) + ] + ) + elif document.startswith("http"): + media = types.InputMediaDocumentExternal( + url=document + ) + else: + media = utils.get_input_media_from_file_id(document, file_ref, 5) + else: thumb = None if thumb is None else self.save_file(thumb) file = self.save_file(document, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(document) or "application/zip", + mime_type=self.guess_mime_type(document.name) or "application/zip", file=file, thumb=thumb, attributes=[ - types.DocumentAttributeFilename(file_name=file_name or os.path.basename(document)) + types.DocumentAttributeFilename(file_name=document.name) ] ) - elif document.startswith("http"): - media = types.InputMediaDocumentExternal( - url=document - ) - else: - media = utils.get_input_media_from_file_id(document, file_ref, 5) while True: try: diff --git a/pyrogram/client/methods/messages/send_photo.py b/pyrogram/client/methods/messages/send_photo.py index 4d6a18a3d0..7877c986ee 100644 --- a/pyrogram/client/methods/messages/send_photo.py +++ b/pyrogram/client/methods/messages/send_photo.py @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . - +import io import os from typing import Union @@ -29,6 +29,7 @@ class SendPhoto(BaseClient): def send_photo( self, chat_id: Union[int, str], + photo: Union[str, io.IOBase], photo: str, file_ref: str = None, caption: str = "", @@ -54,11 +55,12 @@ def send_photo( For your personal cloud (Saved Messages) you can simply use "me" or "self". For a contact that exists in your Telegram address book you can use his phone number (str). - photo (``str``): + photo (``str`` | file-like object): Photo to send. Pass a file_id as string to send a photo that exists on the Telegram servers, pass an HTTP URL as a string for Telegram to get a photo from the Internet, or pass a file path as string to upload a new photo that exists on your local machine. + pass a readable file-like object with .name file_ref (``str``, *optional*): A valid file reference obtained by a recently fetched media message. @@ -137,19 +139,26 @@ def send_photo( file = None try: - if os.path.exists(photo): + if isinstance(photo, str): + if os.path.exists(photo): + file = self.save_file(photo, progress=progress, progress_args=progress_args) + media = types.InputMediaUploadedPhoto( + file=file, + ttl_seconds=ttl_seconds + ) + elif photo.startswith("http"): + media = types.InputMediaPhotoExternal( + url=photo, + ttl_seconds=ttl_seconds + ) + else: + media = utils.get_input_media_from_file_id(photo, file_ref, 2) + elif hasattr(photo, "read"): file = self.save_file(photo, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedPhoto( file=file, ttl_seconds=ttl_seconds ) - elif photo.startswith("http"): - media = types.InputMediaPhotoExternal( - url=photo, - ttl_seconds=ttl_seconds - ) - else: - media = utils.get_input_media_from_file_id(photo, file_ref, 2) while True: try: diff --git a/pyrogram/client/methods/messages/send_sticker.py b/pyrogram/client/methods/messages/send_sticker.py index 76a42d3d8a..98cc44250a 100644 --- a/pyrogram/client/methods/messages/send_sticker.py +++ b/pyrogram/client/methods/messages/send_sticker.py @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . - +import io import os from typing import Union @@ -29,6 +29,7 @@ class SendSticker(BaseClient): def send_sticker( self, chat_id: Union[int, str], + sticker: Union[str, io.IOBase], sticker: str, file_ref: str = None, disable_notification: bool = None, @@ -56,6 +57,7 @@ def send_sticker( Pass a file_id as string to send a sticker that exists on the Telegram servers, pass an HTTP URL as a string for Telegram to get a .webp sticker file from the Internet, or pass a file path as string to upload a new sticker that exists on your local machine. + pass a readable file-like object with .name file_ref (``str``, *optional*): A valid file reference obtained by a recently fetched media message. @@ -113,21 +115,31 @@ def send_sticker( file = None try: - if os.path.exists(sticker): + if isinstance(sticker, str): + if os.path.exists(sticker): + file = self.save_file(sticker, progress=progress, progress_args=progress_args) + media = types.InputMediaUploadedDocument( + mime_type=self.guess_mime_type(sticker) or "image/webp", + file=file, + attributes=[ + types.DocumentAttributeFilename(file_name=os.path.basename(sticker)) + ] + ) + elif sticker.startswith("http"): + media = types.InputMediaDocumentExternal( + url=sticker + ) + else: + media = utils.get_input_media_from_file_id(sticker, file_ref, 8) + elif hasattr(sticker, "read"): file = self.save_file(sticker, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(sticker) or "image/webp", file=file, attributes=[ - types.DocumentAttributeFilename(file_name=os.path.basename(sticker)) + types.DocumentAttributeFilename(file_name=sticker.name) ] ) - elif sticker.startswith("http"): - media = types.InputMediaDocumentExternal( - url=sticker - ) - else: - media = utils.get_input_media_from_file_id(sticker, file_ref, 8) while True: try: diff --git a/pyrogram/client/methods/messages/send_video.py b/pyrogram/client/methods/messages/send_video.py index fc58aa98aa..6c1c2efe51 100644 --- a/pyrogram/client/methods/messages/send_video.py +++ b/pyrogram/client/methods/messages/send_video.py @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . - +import io import os from typing import Union @@ -29,6 +29,7 @@ class SendVideo(BaseClient): def send_video( self, chat_id: Union[int, str], + video: Union[str, io.IOBase], video: str, file_ref: str = None, caption: str = "", @@ -64,6 +65,7 @@ def send_video( Pass a file_id as string to send a video that exists on the Telegram servers, pass an HTTP URL as a string for Telegram to get a video from the Internet, or pass a file path as string to upload a new video that exists on your local machine. + pass a readable file-like object with .name file_ref (``str``, *optional*): A valid file reference obtained by a recently fetched media message. @@ -160,11 +162,35 @@ def progress(current, total): file = None try: - if os.path.exists(video): + if isinstance(video, str): + 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) + media = types.InputMediaUploadedDocument( + mime_type=self.guess_mime_type(video) or "video/mp4", + file=file, + thumb=thumb, + attributes=[ + types.DocumentAttributeVideo( + supports_streaming=supports_streaming or None, + duration=duration, + w=width, + h=height + ), + types.DocumentAttributeFilename(file_name=file_name or os.path.basename(video)) + ] + ) + elif video.startswith("http"): + media = types.InputMediaDocumentExternal( + url=video + ) + else: + media = utils.get_input_media_from_file_id(video, file_ref, 4) + elif hasattr(video, "read"): thumb = None if thumb is None else self.save_file(thumb) file = self.save_file(video, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(video) or "video/mp4", + mime_type=self.guess_mime_type(video.name) or "video/mp4", file=file, thumb=thumb, attributes=[ @@ -174,15 +200,9 @@ def progress(current, total): w=width, h=height ), - types.DocumentAttributeFilename(file_name=file_name or os.path.basename(video)) + types.DocumentAttributeFilename(file_name=video.name) ] ) - elif video.startswith("http"): - media = types.InputMediaDocumentExternal( - url=video - ) - else: - media = utils.get_input_media_from_file_id(video, file_ref, 4) while True: try: diff --git a/pyrogram/client/methods/messages/send_video_note.py b/pyrogram/client/methods/messages/send_video_note.py index 64bde11b54..a160567df9 100644 --- a/pyrogram/client/methods/messages/send_video_note.py +++ b/pyrogram/client/methods/messages/send_video_note.py @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . - +import io import os from typing import Union @@ -29,6 +29,7 @@ class SendVideoNote(BaseClient): def send_video_note( self, chat_id: Union[int, str], + video_note: Union[str, io.IOBase], video_note: str, file_ref: str = None, duration: int = 0, @@ -54,11 +55,12 @@ def send_video_note( For your personal cloud (Saved Messages) you can simply use "me" or "self". For a contact that exists in your Telegram address book you can use his phone number (str). - video_note (``str``): + video_note (``str``, file-like object): Video note to send. Pass a file_id as string to send a video note that exists on the Telegram servers, or pass a file path as string to upload a new video note that exists on your local machine. Sending video notes by a URL is currently unsupported. + pass a readable file-like object with .name file_ref (``str``, *optional*): A valid file reference obtained by a recently fetched media message. @@ -128,11 +130,30 @@ def send_video_note( file = None try: - if os.path.exists(video_note): + if isinstance(video_note, str): + if os.path.exists(video_note): + thumb = None if thumb is None else self.save_file(thumb) + file = self.save_file(video_note, progress=progress, progress_args=progress_args) + media = types.InputMediaUploadedDocument( + mime_type=self.guess_mime_type(video_note) or "video/mp4", + file=file, + thumb=thumb, + attributes=[ + types.DocumentAttributeVideo( + round_message=True, + duration=duration, + w=length, + h=length + ) + ] + ) + else: + media = utils.get_input_media_from_file_id(video_note, file_ref, 13) + elif hasattr(video_note, "read"): thumb = None if thumb is None else self.save_file(thumb) file = self.save_file(video_note, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(video_note) or "video/mp4", + mime_type=self.guess_mime_type(video_note.name) or "video/mp4", file=file, thumb=thumb, attributes=[ @@ -144,8 +165,6 @@ def send_video_note( ) ] ) - else: - media = utils.get_input_media_from_file_id(video_note, file_ref, 13) while True: try: diff --git a/pyrogram/client/methods/messages/send_voice.py b/pyrogram/client/methods/messages/send_voice.py index 753e380671..e57a652e6e 100644 --- a/pyrogram/client/methods/messages/send_voice.py +++ b/pyrogram/client/methods/messages/send_voice.py @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . - +import io import os from typing import Union @@ -29,6 +29,7 @@ class SendVoice(BaseClient): def send_voice( self, chat_id: Union[int, str], + voice: Union[str, io.IOBase], voice: str, file_ref=None, caption: str = "", @@ -54,11 +55,12 @@ def send_voice( For your personal cloud (Saved Messages) you can simply use "me" or "self". For a contact that exists in your Telegram address book you can use his phone number (str). - voice (``str``): + voice (``str``, file-like object): Audio file to send. Pass a file_id as string to send an audio that exists on the Telegram servers, pass an HTTP URL as a string for Telegram to get an audio from the Internet, or pass a file path as string to upload a new audio that exists on your local machine. + pass a readable file-like object with .name file_ref (``str``, *optional*): A valid file reference obtained by a recently fetched media message. @@ -132,11 +134,30 @@ def send_voice( file = None try: - if os.path.exists(voice): + if isinstance(voice, str): + if os.path.exists(voice): + file = self.save_file(voice, progress=progress, progress_args=progress_args) + media = types.InputMediaUploadedDocument( + mime_type=self.guess_mime_type(voice) or "audio/mpeg", + file=file, + attributes=[ + types.DocumentAttributeAudio( + voice=True, + duration=duration + ) + ] + ) + elif voice.startswith("http"): + media = types.InputMediaDocumentExternal( + url=voice + ) + else: + media = utils.get_input_media_from_file_id(voice, file_ref, 3) + elif hasattr(voice, "read"): file = self.save_file(voice, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(voice) or "audio/mpeg", - file=file, + mime_type=self.guess_mime_type(voice.name) or "audio/mpeg", + file=file.name, attributes=[ types.DocumentAttributeAudio( voice=True, @@ -144,12 +165,6 @@ def send_voice( ) ] ) - elif voice.startswith("http"): - media = types.InputMediaDocumentExternal( - url=voice - ) - else: - media = utils.get_input_media_from_file_id(voice, file_ref, 3) while True: try: From 173e08015a828a3fa50062a9747392832a1f3708 Mon Sep 17 00:00:00 2001 From: Yan Date: Sat, 20 Jul 2019 10:26:36 +0300 Subject: [PATCH 0238/1185] Annotate file-like objects in docstring of send_document --- pyrogram/client/methods/messages/send_document.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/methods/messages/send_document.py b/pyrogram/client/methods/messages/send_document.py index c4a7252bfe..be864b4b76 100644 --- a/pyrogram/client/methods/messages/send_document.py +++ b/pyrogram/client/methods/messages/send_document.py @@ -56,7 +56,7 @@ def send_document( For your personal cloud (Saved Messages) you can simply use "me" or "self". For a contact that exists in your Telegram address book you can use his phone number (str). - document (``str``): + document (``str`` | file-like object): File to send. Pass a file_id as string to send a file that exists on the Telegram servers, pass an HTTP URL as a string for Telegram to get a file from the Internet, or From 4c9fee525e6b622645ea6f362ff7fe3365f65229 Mon Sep 17 00:00:00 2001 From: Yan Date: Sat, 20 Jul 2019 10:28:07 +0300 Subject: [PATCH 0239/1185] Annotate file-like objects in docstring of send_sticker --- pyrogram/client/methods/messages/send_sticker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/methods/messages/send_sticker.py b/pyrogram/client/methods/messages/send_sticker.py index 98cc44250a..8cccfe01cd 100644 --- a/pyrogram/client/methods/messages/send_sticker.py +++ b/pyrogram/client/methods/messages/send_sticker.py @@ -52,7 +52,7 @@ def send_sticker( For your personal cloud (Saved Messages) you can simply use "me" or "self". For a contact that exists in your Telegram address book you can use his phone number (str). - sticker (``str``): + sticker (``str`` | file-like object): Sticker to send. Pass a file_id as string to send a sticker that exists on the Telegram servers, pass an HTTP URL as a string for Telegram to get a .webp sticker file from the Internet, or From 2e846f83ecef0f651ef47cd59efcd25e16fb0698 Mon Sep 17 00:00:00 2001 From: Yan Date: Sat, 20 Jul 2019 10:29:20 +0300 Subject: [PATCH 0240/1185] Annotate file-like objects in docstring of send_video --- 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 6c1c2efe51..458d5148b4 100644 --- a/pyrogram/client/methods/messages/send_video.py +++ b/pyrogram/client/methods/messages/send_video.py @@ -60,7 +60,7 @@ def send_video( For your personal cloud (Saved Messages) you can simply use "me" or "self". For a contact that exists in your Telegram address book you can use his phone number (str). - video (``str``): + video (``str`` | file-like object): Video to send. Pass a file_id as string to send a video that exists on the Telegram servers, pass an HTTP URL as a string for Telegram to get a video from the Internet, or From 3ec5f76b104b8cf14bf1f4d8104595d7d618e485 Mon Sep 17 00:00:00 2001 From: Yan Date: Sat, 20 Jul 2019 10:41:15 +0300 Subject: [PATCH 0241/1185] Fix TypeError in send_sticker --- pyrogram/client/methods/messages/send_sticker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/methods/messages/send_sticker.py b/pyrogram/client/methods/messages/send_sticker.py index 8cccfe01cd..60b8609eea 100644 --- a/pyrogram/client/methods/messages/send_sticker.py +++ b/pyrogram/client/methods/messages/send_sticker.py @@ -134,7 +134,7 @@ def send_sticker( elif hasattr(sticker, "read"): file = self.save_file(sticker, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(sticker) or "image/webp", + mime_type=self.guess_mime_type(sticker.name) or "image/webp", file=file, attributes=[ types.DocumentAttributeFilename(file_name=sticker.name) From 6b2d6ffacfdb3bd1e75d14cbb9c26df9950762d0 Mon Sep 17 00:00:00 2001 From: Octo Date: Sun, 4 Aug 2019 15:00:19 +0300 Subject: [PATCH 0242/1185] Fix send_voice --- pyrogram/client/methods/messages/send_voice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/methods/messages/send_voice.py b/pyrogram/client/methods/messages/send_voice.py index e57a652e6e..e4d0e352fe 100644 --- a/pyrogram/client/methods/messages/send_voice.py +++ b/pyrogram/client/methods/messages/send_voice.py @@ -157,7 +157,7 @@ def send_voice( file = self.save_file(voice, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(voice.name) or "audio/mpeg", - file=file.name, + file=file, attributes=[ types.DocumentAttributeAudio( voice=True, From 4a8e6fb855785588d79825f8ed6ade649fc1d6e8 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 9 Jul 2020 00:20:46 +0200 Subject: [PATCH 0243/1185] Cleanup --- pyrogram/client/client.py | 267 ++++++++---------- .../client/methods/messages/download_media.py | 13 - .../methods/messages/send_animated_sticker.py | 153 ---------- .../client/methods/messages/send_animation.py | 45 +-- .../client/methods/messages/send_audio.py | 41 +-- .../client/methods/messages/send_document.py | 37 +-- .../client/methods/messages/send_photo.py | 29 +- .../client/methods/messages/send_sticker.py | 34 +-- .../client/methods/messages/send_video.py | 42 +-- .../methods/messages/send_video_note.py | 31 +- .../client/methods/messages/send_voice.py | 35 +-- .../types/messages_and_media/message.py | 11 +- 12 files changed, 205 insertions(+), 533 deletions(-) delete mode 100644 pyrogram/client/methods/messages/send_animated_sticker.py diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 1efb6f0675..0e186d85b5 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -import io + import logging import math import os @@ -1231,9 +1231,9 @@ def download_worker(self): temp_file_path = "" final_file_path = "" - path = [None] + try: - data, done, progress, progress_args, out, path, to_file = packet + data, directory, file_name, done, progress, progress_args, path = packet temp_file_path = self.get_file( media_type=data.media_type, @@ -1250,15 +1250,13 @@ def download_worker(self): file_size=data.file_size, is_big=data.is_big, progress=progress, - progress_args=progress_args, - out=out + progress_args=progress_args ) - if to_file: - final_file_path = out.name - else: - final_file_path = '' - if to_file: - out.close() + + if temp_file_path: + final_file_path = os.path.abspath(re.sub("\\\\", "/", os.path.join(directory, file_name))) + os.makedirs(directory, exist_ok=True) + shutil.move(temp_file_path, final_file_path) except Exception as e: log.error(e, exc_info=True) @@ -1715,7 +1713,7 @@ def resolve_peer(self, peer_id: Union[int, str]): def save_file( self, - path: Union[str, io.IOBase], + path: str, file_id: int = None, file_part: int = 0, progress: callable = None, @@ -1767,20 +1765,9 @@ def save_file( Raises: RPCError: In case of a Telegram RPC error. - ValueError: if path is not str or file-like readable object """ part_size = 512 * 1024 - if isinstance(path, str): - fp = open(path, 'rb') - filename = os.path.basename(path) - elif hasattr(path, 'write'): - fp = path - filename = fp.name - else: - raise ValueError("Invalid path passed! Pass file pointer or path to file") - fp.seek(0, os.SEEK_END) - file_size = fp.tell() - fp.seek(0) + file_size = os.path.getsize(path) if file_size == 0: raise ValueError("File size equals to 0 B") @@ -1798,74 +1785,67 @@ def save_file( session.start() try: - fp.seek(part_size * file_part) + with open(path, "rb") as f: + f.seek(part_size * file_part) - while True: - chunk = fp.read(part_size) - - if not chunk: - if not is_big: - md5_sum = "".join([hex(i)[2:].zfill(2) for i in md5_sum.digest()]) - break - - for _ in range(3): - if is_big: - rpc = functions.upload.SaveBigFilePart( - file_id=file_id, - file_part=file_part, - file_total_parts=file_total_parts, - bytes=chunk - ) - else: - rpc = functions.upload.SaveFilePart( - file_id=file_id, - file_part=file_part, - bytes=chunk - ) + while True: + chunk = f.read(part_size) - if session.send(rpc): + if not chunk: + if not is_big: + md5_sum = "".join([hex(i)[2:].zfill(2) for i in md5_sum.digest()]) break - else: - raise AssertionError("Telegram didn't accept chunk #{} of {}".format(file_part, path)) - if is_missing_part: - return + for _ in range(3): + if is_big: + rpc = functions.upload.SaveBigFilePart( + file_id=file_id, + file_part=file_part, + file_total_parts=file_total_parts, + bytes=chunk + ) + else: + rpc = functions.upload.SaveFilePart( + file_id=file_id, + file_part=file_part, + bytes=chunk + ) + + if session.send(rpc): + break + else: + raise AssertionError("Telegram didn't accept chunk #{} of {}".format(file_part, path)) + + if is_missing_part: + return - if not is_big: - md5_sum.update(chunk) + if not is_big: + md5_sum.update(chunk) - file_part += 1 + file_part += 1 - if progress: - progress(min(file_part * part_size, file_size), file_size, *progress_args) + if progress: + progress(min(file_part * part_size, file_size), file_size, *progress_args) except Client.StopTransmission: - if isinstance(path, str): - fp.close() raise except Exception as e: - if isinstance(path, str): - fp.close() log.error(e, exc_info=True) else: - if isinstance(path, str): - fp.close() if is_big: return types.InputFileBig( id=file_id, parts=file_total_parts, - name=filename, + name=os.path.basename(path), ) else: return types.InputFile( id=file_id, parts=file_total_parts, - name=filename, + name=os.path.basename(path), md5_checksum=md5_sum ) finally: - if isinstance(path, str): - fp.close() session.stop() def get_file( @@ -1884,8 +1864,7 @@ def get_file( file_size: int, is_big: bool, progress: callable, - progress_args: tuple = (), - out: io.IOBase = None + progress_args: tuple = () ) -> str: with self.media_sessions_lock: session = self.media_sessions.get(dc_id, None) @@ -1971,10 +1950,7 @@ def get_file( limit = 1024 * 1024 offset = 0 file_name = "" - if not out: - f = tempfile.NamedTemporaryFile("wb", delete=False) - else: - f = out + try: r = session.send( functions.upload.GetFile( @@ -1985,36 +1961,35 @@ def get_file( ) if isinstance(r, types.upload.File): - if hasattr(f, "name"): + with tempfile.NamedTemporaryFile("wb", delete=False) as f: file_name = f.name - while True: - chunk = r.bytes - - if not chunk: - break + while True: + chunk = r.bytes - f.write(chunk) + if not chunk: + break - offset += limit + f.write(chunk) - if progress: - progress( + offset += limit - min(offset, file_size) - if file_size != 0 - else offset, - file_size, - *progress_args - ) + if progress: + progress( + min(offset, file_size) + if file_size != 0 + else offset, + file_size, + *progress_args + ) - r = session.send( - functions.upload.GetFile( - location=location, - offset=offset, - limit=limit + r = session.send( + functions.upload.GetFile( + location=location, + offset=offset, + limit=limit + ) ) - ) elif isinstance(r, types.upload.FileCdnRedirect): with self.media_sessions_lock: @@ -2028,71 +2003,70 @@ def get_file( self.media_sessions[r.dc_id] = cdn_session try: - if hasattr(f, "name"): + with tempfile.NamedTemporaryFile("wb", delete=False) as f: file_name = f.name - while True: - r2 = cdn_session.send( - functions.upload.GetCdnFile( - file_token=r.file_token, - offset=offset, - limit=limit + while True: + r2 = cdn_session.send( + functions.upload.GetCdnFile( + file_token=r.file_token, + offset=offset, + limit=limit + ) ) - ) - if isinstance(r2, types.upload.CdnFileReuploadNeeded): - try: - session.send( - functions.upload.ReuploadCdnFile( - file_token=r.file_token, - request_token=r2.request_token + if isinstance(r2, types.upload.CdnFileReuploadNeeded): + try: + session.send( + functions.upload.ReuploadCdnFile( + file_token=r.file_token, + request_token=r2.request_token + ) ) - ) - except VolumeLocNotFound: - break - else: - continue + except VolumeLocNotFound: + break + else: + continue - chunk = r2.bytes + chunk = r2.bytes - # https://core.telegram.org/cdn#decrypting-files - decrypted_chunk = AES.ctr256_decrypt( - chunk, - r.encryption_key, - bytearray( - r.encryption_iv[:-4] - + (offset // 16).to_bytes(4, "big") + # https://core.telegram.org/cdn#decrypting-files + decrypted_chunk = AES.ctr256_decrypt( + chunk, + r.encryption_key, + bytearray( + r.encryption_iv[:-4] + + (offset // 16).to_bytes(4, "big") + ) ) - ) - hashes = session.send( - functions.upload.GetCdnFileHashes( - file_token=r.file_token, - offset=offset + hashes = session.send( + functions.upload.GetCdnFileHashes( + file_token=r.file_token, + offset=offset + ) ) - ) - # https://core.telegram.org/cdn#verifying-files - for i, h in enumerate(hashes): - cdn_chunk = decrypted_chunk[h.limit * i: h.limit * (i + 1)] - assert h.hash == sha256(cdn_chunk).digest(), "Invalid CDN hash part {}".format(i) + # https://core.telegram.org/cdn#verifying-files + for i, h in enumerate(hashes): + cdn_chunk = decrypted_chunk[h.limit * i: h.limit * (i + 1)] + assert h.hash == sha256(cdn_chunk).digest(), "Invalid CDN hash part {}".format(i) - f.write(decrypted_chunk) + f.write(decrypted_chunk) - offset += limit - - if progress: - progress( + offset += limit - min(offset, file_size) - if file_size != 0 - else offset, - file_size, - *progress_args - ) + if progress: + progress( + min(offset, file_size) + if file_size != 0 + else offset, + file_size, + *progress_args + ) - if len(chunk) < limit: - break + if len(chunk) < limit: + break except Exception as e: raise e except Exception as e: @@ -2100,8 +2074,7 @@ def get_file( log.error(e, exc_info=True) try: - if out: - os.remove(file_name) + os.remove(file_name) except OSError: pass diff --git a/pyrogram/client/methods/messages/download_media.py b/pyrogram/client/methods/messages/download_media.py index 2176e4aa98..220543977e 100644 --- a/pyrogram/client/methods/messages/download_media.py +++ b/pyrogram/client/methods/messages/download_media.py @@ -17,9 +17,7 @@ # along with Pyrogram. If not, see . import binascii -import io import os -import re import struct import time from datetime import datetime @@ -39,7 +37,6 @@ def download_media( message: Union["pyrogram.Message", str], file_ref: str = None, file_name: str = DEFAULT_DOWNLOAD_DIR, - out: io.IOBase = None, block: bool = True, progress: callable = None, progress_args: tuple = () @@ -61,9 +58,6 @@ def download_media( You can also specify a path for downloading files in a custom location: paths that end with "/" are considered directories. All non-existent folders will be created automatically. - out (``io.IOBase``, *optional*): - A custom *file-like object* to be used when downloading file. Overrides file_name - block (``bool``, *optional*): Blocks the code execution until the file has been downloaded. Defaults to True. @@ -244,13 +238,6 @@ def get_existing_attributes() -> dict: extension ) - if not out: - out = open(os.path.abspath(re.sub("\\\\", "/", os.path.join(directory, file_name))), 'wb') - os.makedirs(directory, exist_ok=True) - to_file = True - else: - to_file = False - self.download_queue.put((data, done, progress, progress_args, out, path, to_file)) # Cast to string because Path objects aren't supported by Python 3.5 self.download_queue.put((data, str(directory), str(file_name), done, progress, progress_args, path)) diff --git a/pyrogram/client/methods/messages/send_animated_sticker.py b/pyrogram/client/methods/messages/send_animated_sticker.py deleted file mode 100644 index b295939484..0000000000 --- a/pyrogram/client/methods/messages/send_animated_sticker.py +++ /dev/null @@ -1,153 +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 io -import os -from typing import Union - -import pyrogram -from pyrogram.api import functions, types -from pyrogram.client.ext import BaseClient, utils -from pyrogram.errors import FilePartMissing - - -class SendAnimatedSticker(BaseClient): - def send_animated_sticker( - self, - chat_id: Union[int, str], - animated_sticker: Union[str, io.IOBase], - disable_notification: bool = None, - reply_to_message_id: int = None, - reply_markup: Union[ - "pyrogram.InlineKeyboardMarkup", - "pyrogram.ReplyKeyboardMarkup", - "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply" - ] = None, - progress: callable = None, - progress_args: tuple = () - ) -> Union["pyrogram.Message", None]: - """Send .tgs animated stickers. - - Parameters: - chat_id (``int`` | ``str``): - Unique identifier (int) or username (str) of the target chat. - For your personal cloud (Saved Messages) you can simply use "me" or "self". - For a contact that exists in your Telegram address book you can use his phone number (str). - - animated_sticker (``str`` | file-like object): - Animated sticker to send. - Pass a file_id as string to send a animated sticker that exists on the Telegram servers, - pass an HTTP URL as a string for Telegram to get a .webp animated sticker file from the Internet, or - pass a file path as string to upload a new animated sticker that exists on your local machine. - pass a readable file-like object with .name - - disable_notification (``bool``, *optional*): - Sends the message silently. - Users will receive a notification with no sound. - - reply_to_message_id (``int``, *optional*): - If the message is a reply, ID of the original message. - - reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): - Additional interface options. An object for an inline keyboard, custom reply keyboard, - instructions to remove reply keyboard or to force a reply from the user. - - progress (``callable``, *optional*): - Pass a callback function to view the upload progress. - The function must take *(client, current, total, \*args)* as positional arguments (look at the section - below for a detailed description). - - progress_args (``tuple``, *optional*): - Extra custom arguments for the progress callback function. Useful, for example, if you want to pass - a chat_id and a message_id in order to edit a message with the updated progress. - - Other Parameters: - client (:obj:`Client`): - The Client itself, useful when you want to call other API methods inside the callback function. - - current (``int``): - The amount of bytes uploaded so far. - - total (``int``): - The size of the file. - - *args (``tuple``, *optional*): - Extra custom arguments as defined in the *progress_args* parameter. - You can either keep *\*args* or add every single extra argument in your function signature. - - Returns: - :obj:`Message` | ``None``: On success, the sent animated sticker message is returned, otherwise, in case the - upload is deliberately stopped with :meth:`~Client.stop_transmission`, None is returned. - Raises: - RPCError: In case of a Telegram RPC error. - """ - file = None - - try: - if isinstance(animated_sticker, str): - if os.path.exists(animated_sticker): - file = self.save_file(animated_sticker, progress=progress, progress_args=progress_args) - media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(animated_sticker) or "application/x-tgsticker", - file=file, - attributes=[ - types.DocumentAttributeFilename(file_name=os.path.basename(animated_sticker)) - ] - ) - elif animated_sticker.startswith("http"): - media = types.InputMediaDocumentExternal( - url=animated_sticker - ) - else: - media = utils.get_input_media_from_file_id(animated_sticker, 5) - elif hasattr(animated_sticker, "read"): - file = self.save_file(animated_sticker, progress=progress, progress_args=progress_args) - media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(animated_sticker.name) or "application/x-tgsticker", - file=file, - attributes=[ - types.DocumentAttributeFilename(file_name=animated_sticker.name) - ] - ) - - - while True: - try: - r = self.send( - functions.messages.SendMedia( - peer=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: - self.save_file(animated_sticker, file_id=file.id, file_part=e.x) - else: - for i in r.updates: - if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - return 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_animation.py b/pyrogram/client/methods/messages/send_animation.py index f8078a7cf6..288ed04e69 100644 --- a/pyrogram/client/methods/messages/send_animation.py +++ b/pyrogram/client/methods/messages/send_animation.py @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -import io + import os from typing import Union @@ -29,7 +29,6 @@ class SendAnimation(BaseClient): def send_animation( self, chat_id: Union[int, str], - animation: Union[str, io.IOBase], animation: str, file_ref: str = None, caption: str = "", @@ -60,13 +59,11 @@ def send_animation( For your personal cloud (Saved Messages) you can simply use "me" or "self". For a contact that exists in your Telegram address book you can use his phone number (str). - animation (``str``| file-like object): + animation (``str``): Animation to send. Pass a file_id as string to send an animation that exists on the Telegram servers, pass an HTTP URL as a string for Telegram to get an animation from the Internet, or pass a file path as string to upload a new animation that exists on your local machine. - pass a readable file-like object with .name - file_ref (``str``, *optional*): A valid file reference obtained by a recently fetched media message. @@ -166,36 +163,11 @@ def progress(current, total): file = None try: - if isinstance(animation, str): - if os.path.exists(animation): - thumb = None if thumb is None else self.save_file(thumb) - file = self.save_file(animation, progress=progress, progress_args=progress_args) - media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(animation) or "video/mp4", - file=file, - thumb=thumb, - attributes=[ - types.DocumentAttributeVideo( - supports_streaming=True, - duration=duration, - w=width, - h=height - ), - types.DocumentAttributeFilename(file_name=file_name or os.path.basename(animation)), - types.DocumentAttributeAnimated() - ] - ) - elif animation.startswith("http"): - media = types.InputMediaDocumentExternal( - url=animation - ) - else: - media = utils.get_input_media_from_file_id(animation, file_ref, 10) - elif hasattr(animation, "read"): + if os.path.exists(animation): thumb = None if thumb is None else self.save_file(thumb) file = self.save_file(animation, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(animation.name) or "video/mp4", + mime_type=self.guess_mime_type(animation) or "video/mp4", file=file, thumb=thumb, attributes=[ @@ -205,10 +177,17 @@ def progress(current, total): w=width, h=height ), - types.DocumentAttributeFilename(file_name=animation.name), + types.DocumentAttributeFilename(file_name=file_name or os.path.basename(animation)), types.DocumentAttributeAnimated() ] ) + elif animation.startswith("http"): + media = types.InputMediaDocumentExternal( + url=animation + ) + else: + media = utils.get_input_media_from_file_id(animation, file_ref, 10) + while True: try: r = self.send( diff --git a/pyrogram/client/methods/messages/send_audio.py b/pyrogram/client/methods/messages/send_audio.py index 0ff588d84f..e271d96cdc 100644 --- a/pyrogram/client/methods/messages/send_audio.py +++ b/pyrogram/client/methods/messages/send_audio.py @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -import io + import os from typing import Union @@ -29,7 +29,6 @@ class SendAudio(BaseClient): def send_audio( self, chat_id: Union[int, str], - audio: Union[str, io.IOBase], audio: str, file_ref: str = None, caption: str = "", @@ -61,12 +60,11 @@ def send_audio( For your personal cloud (Saved Messages) you can simply use "me" or "self". For a contact that exists in your Telegram address book you can use his phone number (str). - audio (``str``, file-like object): + audio (``str``): Audio file to send. Pass a file_id as string to send an audio file that exists on the Telegram servers, pass an HTTP URL as a string for Telegram to get an audio file from the Internet, or pass a file path as string to upload a new audio file that exists on your local machine. - pass a readable file-like object with .name file_ref (``str``, *optional*): A valid file reference obtained by a recently fetched media message. @@ -165,34 +163,11 @@ def progress(current, total): file = None try: - if isinstance(audio, str): - if os.path.exists(audio): - thumb = None if thumb is None else self.save_file(thumb) - file = self.save_file(audio, progress=progress, progress_args=progress_args) - media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(audio) or "audio/mpeg", - file=file, - thumb=thumb, - attributes=[ - types.DocumentAttributeAudio( - duration=duration, - performer=performer, - title=title - ), - types.DocumentAttributeFilename(file_name=file_name or os.path.basename(audio)) - ] - ) - elif audio.startswith("http"): - media = types.InputMediaDocumentExternal( - url=audio - ) - else: - media = utils.get_input_media_from_file_id(audio, file_ref, 9) - elif hasattr(audio, "read"): + if os.path.exists(audio): thumb = None if thumb is None else self.save_file(thumb) file = self.save_file(audio, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(audio.name) or "audio/mpeg", + mime_type=self.guess_mime_type(audio) or "audio/mpeg", file=file, thumb=thumb, attributes=[ @@ -201,9 +176,15 @@ def progress(current, total): performer=performer, title=title ), - types.DocumentAttributeFilename(file_name=os.path.basename(audio.name)) + types.DocumentAttributeFilename(file_name=file_name or os.path.basename(audio)) ] ) + elif audio.startswith("http"): + media = types.InputMediaDocumentExternal( + url=audio + ) + else: + media = utils.get_input_media_from_file_id(audio, file_ref, 9) while True: try: diff --git a/pyrogram/client/methods/messages/send_document.py b/pyrogram/client/methods/messages/send_document.py index be864b4b76..24a754f079 100644 --- a/pyrogram/client/methods/messages/send_document.py +++ b/pyrogram/client/methods/messages/send_document.py @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -import io + import os from typing import Union @@ -29,7 +29,6 @@ class SendDocument(BaseClient): def send_document( self, chat_id: Union[int, str], - document: Union[str, io.IOBase], document: str, file_ref: str = None, thumb: str = None, @@ -56,13 +55,11 @@ def send_document( For your personal cloud (Saved Messages) you can simply use "me" or "self". For a contact that exists in your Telegram address book you can use his phone number (str). - document (``str`` | file-like object): + document (``str``): File to send. Pass a file_id as string to send a file that exists on the Telegram servers, pass an HTTP URL as a string for Telegram to get a file from the Internet, or pass a file path as string to upload a new file that exists on your local machine. - pass a readable file-like object with .name - file_ref (``str``, *optional*): A valid file reference obtained by a recently fetched media message. @@ -146,35 +143,23 @@ def progress(current, total): file = None try: - if isinstance(document, str): - if os.path.exists(document): - thumb = None if thumb is None else self.save_file(thumb) - file = self.save_file(document, progress=progress, progress_args=progress_args) - media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(document) or "application/zip", - file=file, - thumb=thumb, - attributes=[ - types.DocumentAttributeFilename(file_name=file_name or os.path.basename(document)) - ] - ) - elif document.startswith("http"): - media = types.InputMediaDocumentExternal( - url=document - ) - else: - media = utils.get_input_media_from_file_id(document, file_ref, 5) - else: + if os.path.exists(document): thumb = None if thumb is None else self.save_file(thumb) file = self.save_file(document, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(document.name) or "application/zip", + mime_type=self.guess_mime_type(document) or "application/zip", file=file, thumb=thumb, attributes=[ - types.DocumentAttributeFilename(file_name=document.name) + types.DocumentAttributeFilename(file_name=file_name or os.path.basename(document)) ] ) + elif document.startswith("http"): + media = types.InputMediaDocumentExternal( + url=document + ) + else: + media = utils.get_input_media_from_file_id(document, file_ref, 5) while True: try: diff --git a/pyrogram/client/methods/messages/send_photo.py b/pyrogram/client/methods/messages/send_photo.py index 7877c986ee..4d6a18a3d0 100644 --- a/pyrogram/client/methods/messages/send_photo.py +++ b/pyrogram/client/methods/messages/send_photo.py @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -import io + import os from typing import Union @@ -29,7 +29,6 @@ class SendPhoto(BaseClient): def send_photo( self, chat_id: Union[int, str], - photo: Union[str, io.IOBase], photo: str, file_ref: str = None, caption: str = "", @@ -55,12 +54,11 @@ def send_photo( For your personal cloud (Saved Messages) you can simply use "me" or "self". For a contact that exists in your Telegram address book you can use his phone number (str). - photo (``str`` | file-like object): + photo (``str``): Photo to send. Pass a file_id as string to send a photo that exists on the Telegram servers, pass an HTTP URL as a string for Telegram to get a photo from the Internet, or pass a file path as string to upload a new photo that exists on your local machine. - pass a readable file-like object with .name file_ref (``str``, *optional*): A valid file reference obtained by a recently fetched media message. @@ -139,26 +137,19 @@ def send_photo( file = None try: - if isinstance(photo, str): - if os.path.exists(photo): - file = self.save_file(photo, progress=progress, progress_args=progress_args) - media = types.InputMediaUploadedPhoto( - file=file, - ttl_seconds=ttl_seconds - ) - elif photo.startswith("http"): - media = types.InputMediaPhotoExternal( - url=photo, - ttl_seconds=ttl_seconds - ) - else: - media = utils.get_input_media_from_file_id(photo, file_ref, 2) - elif hasattr(photo, "read"): + if os.path.exists(photo): file = self.save_file(photo, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedPhoto( file=file, ttl_seconds=ttl_seconds ) + elif photo.startswith("http"): + media = types.InputMediaPhotoExternal( + url=photo, + ttl_seconds=ttl_seconds + ) + else: + media = utils.get_input_media_from_file_id(photo, file_ref, 2) while True: try: diff --git a/pyrogram/client/methods/messages/send_sticker.py b/pyrogram/client/methods/messages/send_sticker.py index 60b8609eea..76a42d3d8a 100644 --- a/pyrogram/client/methods/messages/send_sticker.py +++ b/pyrogram/client/methods/messages/send_sticker.py @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -import io + import os from typing import Union @@ -29,7 +29,6 @@ class SendSticker(BaseClient): def send_sticker( self, chat_id: Union[int, str], - sticker: Union[str, io.IOBase], sticker: str, file_ref: str = None, disable_notification: bool = None, @@ -52,12 +51,11 @@ def send_sticker( For your personal cloud (Saved Messages) you can simply use "me" or "self". For a contact that exists in your Telegram address book you can use his phone number (str). - sticker (``str`` | file-like object): + sticker (``str``): Sticker to send. Pass a file_id as string to send a sticker that exists on the Telegram servers, pass an HTTP URL as a string for Telegram to get a .webp sticker file from the Internet, or pass a file path as string to upload a new sticker that exists on your local machine. - pass a readable file-like object with .name file_ref (``str``, *optional*): A valid file reference obtained by a recently fetched media message. @@ -115,31 +113,21 @@ def send_sticker( file = None try: - if isinstance(sticker, str): - if os.path.exists(sticker): - file = self.save_file(sticker, progress=progress, progress_args=progress_args) - media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(sticker) or "image/webp", - file=file, - attributes=[ - types.DocumentAttributeFilename(file_name=os.path.basename(sticker)) - ] - ) - elif sticker.startswith("http"): - media = types.InputMediaDocumentExternal( - url=sticker - ) - else: - media = utils.get_input_media_from_file_id(sticker, file_ref, 8) - elif hasattr(sticker, "read"): + if os.path.exists(sticker): file = self.save_file(sticker, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(sticker.name) or "image/webp", + mime_type=self.guess_mime_type(sticker) or "image/webp", file=file, attributes=[ - types.DocumentAttributeFilename(file_name=sticker.name) + types.DocumentAttributeFilename(file_name=os.path.basename(sticker)) ] ) + elif sticker.startswith("http"): + media = types.InputMediaDocumentExternal( + url=sticker + ) + else: + media = utils.get_input_media_from_file_id(sticker, file_ref, 8) while True: try: diff --git a/pyrogram/client/methods/messages/send_video.py b/pyrogram/client/methods/messages/send_video.py index 458d5148b4..fc58aa98aa 100644 --- a/pyrogram/client/methods/messages/send_video.py +++ b/pyrogram/client/methods/messages/send_video.py @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -import io + import os from typing import Union @@ -29,7 +29,6 @@ class SendVideo(BaseClient): def send_video( self, chat_id: Union[int, str], - video: Union[str, io.IOBase], video: str, file_ref: str = None, caption: str = "", @@ -60,12 +59,11 @@ def send_video( For your personal cloud (Saved Messages) you can simply use "me" or "self". For a contact that exists in your Telegram address book you can use his phone number (str). - video (``str`` | file-like object): + video (``str``): Video to send. Pass a file_id as string to send a video that exists on the Telegram servers, pass an HTTP URL as a string for Telegram to get a video from the Internet, or pass a file path as string to upload a new video that exists on your local machine. - pass a readable file-like object with .name file_ref (``str``, *optional*): A valid file reference obtained by a recently fetched media message. @@ -162,35 +160,11 @@ def progress(current, total): file = None try: - if isinstance(video, str): - 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) - media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(video) or "video/mp4", - file=file, - thumb=thumb, - attributes=[ - types.DocumentAttributeVideo( - supports_streaming=supports_streaming or None, - duration=duration, - w=width, - h=height - ), - types.DocumentAttributeFilename(file_name=file_name or os.path.basename(video)) - ] - ) - elif video.startswith("http"): - media = types.InputMediaDocumentExternal( - url=video - ) - else: - media = utils.get_input_media_from_file_id(video, file_ref, 4) - elif hasattr(video, "read"): + 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) media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(video.name) or "video/mp4", + mime_type=self.guess_mime_type(video) or "video/mp4", file=file, thumb=thumb, attributes=[ @@ -200,9 +174,15 @@ def progress(current, total): w=width, h=height ), - types.DocumentAttributeFilename(file_name=video.name) + types.DocumentAttributeFilename(file_name=file_name or os.path.basename(video)) ] ) + elif video.startswith("http"): + media = types.InputMediaDocumentExternal( + url=video + ) + else: + media = utils.get_input_media_from_file_id(video, file_ref, 4) while True: try: diff --git a/pyrogram/client/methods/messages/send_video_note.py b/pyrogram/client/methods/messages/send_video_note.py index a160567df9..64bde11b54 100644 --- a/pyrogram/client/methods/messages/send_video_note.py +++ b/pyrogram/client/methods/messages/send_video_note.py @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -import io + import os from typing import Union @@ -29,7 +29,6 @@ class SendVideoNote(BaseClient): def send_video_note( self, chat_id: Union[int, str], - video_note: Union[str, io.IOBase], video_note: str, file_ref: str = None, duration: int = 0, @@ -55,12 +54,11 @@ def send_video_note( For your personal cloud (Saved Messages) you can simply use "me" or "self". For a contact that exists in your Telegram address book you can use his phone number (str). - video_note (``str``, file-like object): + video_note (``str``): Video note to send. Pass a file_id as string to send a video note that exists on the Telegram servers, or pass a file path as string to upload a new video note that exists on your local machine. Sending video notes by a URL is currently unsupported. - pass a readable file-like object with .name file_ref (``str``, *optional*): A valid file reference obtained by a recently fetched media message. @@ -130,30 +128,11 @@ def send_video_note( file = None try: - if isinstance(video_note, str): - if os.path.exists(video_note): - thumb = None if thumb is None else self.save_file(thumb) - file = self.save_file(video_note, progress=progress, progress_args=progress_args) - media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(video_note) or "video/mp4", - file=file, - thumb=thumb, - attributes=[ - types.DocumentAttributeVideo( - round_message=True, - duration=duration, - w=length, - h=length - ) - ] - ) - else: - media = utils.get_input_media_from_file_id(video_note, file_ref, 13) - elif hasattr(video_note, "read"): + if os.path.exists(video_note): thumb = None if thumb is None else self.save_file(thumb) file = self.save_file(video_note, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(video_note.name) or "video/mp4", + mime_type=self.guess_mime_type(video_note) or "video/mp4", file=file, thumb=thumb, attributes=[ @@ -165,6 +144,8 @@ def send_video_note( ) ] ) + else: + media = utils.get_input_media_from_file_id(video_note, file_ref, 13) while True: try: diff --git a/pyrogram/client/methods/messages/send_voice.py b/pyrogram/client/methods/messages/send_voice.py index e4d0e352fe..753e380671 100644 --- a/pyrogram/client/methods/messages/send_voice.py +++ b/pyrogram/client/methods/messages/send_voice.py @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -import io + import os from typing import Union @@ -29,7 +29,6 @@ class SendVoice(BaseClient): def send_voice( self, chat_id: Union[int, str], - voice: Union[str, io.IOBase], voice: str, file_ref=None, caption: str = "", @@ -55,12 +54,11 @@ def send_voice( For your personal cloud (Saved Messages) you can simply use "me" or "self". For a contact that exists in your Telegram address book you can use his phone number (str). - voice (``str``, file-like object): + voice (``str``): Audio file to send. Pass a file_id as string to send an audio that exists on the Telegram servers, pass an HTTP URL as a string for Telegram to get an audio from the Internet, or pass a file path as string to upload a new audio that exists on your local machine. - pass a readable file-like object with .name file_ref (``str``, *optional*): A valid file reference obtained by a recently fetched media message. @@ -134,29 +132,10 @@ def send_voice( file = None try: - if isinstance(voice, str): - if os.path.exists(voice): - file = self.save_file(voice, progress=progress, progress_args=progress_args) - media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(voice) or "audio/mpeg", - file=file, - attributes=[ - types.DocumentAttributeAudio( - voice=True, - duration=duration - ) - ] - ) - elif voice.startswith("http"): - media = types.InputMediaDocumentExternal( - url=voice - ) - else: - media = utils.get_input_media_from_file_id(voice, file_ref, 3) - elif hasattr(voice, "read"): + if os.path.exists(voice): file = self.save_file(voice, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(voice.name) or "audio/mpeg", + mime_type=self.guess_mime_type(voice) or "audio/mpeg", file=file, attributes=[ types.DocumentAttributeAudio( @@ -165,6 +144,12 @@ def send_voice( ) ] ) + elif voice.startswith("http"): + media = types.InputMediaDocumentExternal( + url=voice + ) + else: + media = utils.get_input_media_from_file_id(voice, file_ref, 3) while True: try: diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index cff8c5789d..215f86d006 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -15,7 +15,7 @@ # # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -import io + from functools import partial from typing import List, Match, Union @@ -2964,7 +2964,7 @@ def retract_vote( chat_id=message.chat.id, message_id=message_id, ) - + Example: .. code-block:: python @@ -2985,7 +2985,6 @@ def retract_vote( def download( self, file_name: str = "", - out: io.IOBase = None, block: bool = True, progress: callable = None, progress_args: tuple = () @@ -3010,9 +3009,6 @@ def download( You can also specify a path for downloading files in a custom location: paths that end with "/" are considered directories. All non-existent folders will be created automatically. - out (``io.IOBase``, *optional*): - A custom *file-like object* to be used when downloading file. Overrides file_name - block (``bool``, *optional*): Blocks the code execution until the file has been downloaded. Defaults to True. @@ -3049,7 +3045,6 @@ def download( return self._client.download_media( message=self, file_name=file_name, - out=out, block=block, progress=progress, progress_args=progress_args, @@ -3079,7 +3074,7 @@ def vote( Parameters: option (``int``): Index of the poll option you want to vote for (0 to 9). - + Returns: :obj:`Poll`: On success, the poll with the chosen option is returned. From de8f784f7847d435e539ab5208dfff4568ea51fd Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 9 Jul 2020 02:22:56 +0200 Subject: [PATCH 0244/1185] Use better checks for local and external files --- .../client/methods/chats/set_chat_photo.py | 2 +- .../methods/messages/edit_inline_media.py | 12 ++++++----- .../methods/messages/edit_message_media.py | 21 ++++++++++--------- .../client/methods/messages/send_animation.py | 5 +++-- .../client/methods/messages/send_audio.py | 5 +++-- .../client/methods/messages/send_document.py | 5 +++-- .../methods/messages/send_media_group.py | 11 +++++----- .../client/methods/messages/send_photo.py | 5 +++-- .../client/methods/messages/send_sticker.py | 5 +++-- .../client/methods/messages/send_video.py | 5 +++-- .../methods/messages/send_video_note.py | 2 +- .../client/methods/messages/send_voice.py | 5 +++-- 12 files changed, 46 insertions(+), 37 deletions(-) diff --git a/pyrogram/client/methods/chats/set_chat_photo.py b/pyrogram/client/methods/chats/set_chat_photo.py index 3a9967111b..d394322c32 100644 --- a/pyrogram/client/methods/chats/set_chat_photo.py +++ b/pyrogram/client/methods/chats/set_chat_photo.py @@ -63,7 +63,7 @@ def set_chat_photo( """ peer = self.resolve_peer(chat_id) - if os.path.exists(photo): + if os.path.isfile(photo): photo = types.InputChatUploadedPhoto(file=self.save_file(photo)) else: photo = utils.get_input_media_from_file_id(photo, file_ref, 2) diff --git a/pyrogram/client/methods/messages/edit_inline_media.py b/pyrogram/client/methods/messages/edit_inline_media.py index 700804d997..f409ae0603 100644 --- a/pyrogram/client/methods/messages/edit_inline_media.py +++ b/pyrogram/client/methods/messages/edit_inline_media.py @@ -16,6 +16,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import re + import pyrogram from pyrogram.api import functions, types from pyrogram.client.ext import BaseClient, utils @@ -72,35 +74,35 @@ def edit_inline_media( parse_mode = media.parse_mode if isinstance(media, InputMediaPhoto): - if media.media.startswith("http"): + if re.match("^https?://", media.media): media = types.InputMediaPhotoExternal( url=media.media ) else: media = utils.get_input_media_from_file_id(media.media, media.file_ref, 2) elif isinstance(media, InputMediaVideo): - if media.media.startswith("http"): + if re.match("^https?://", media.media): media = types.InputMediaDocumentExternal( url=media.media ) else: media = utils.get_input_media_from_file_id(media.media, media.file_ref, 4) elif isinstance(media, InputMediaAudio): - if media.media.startswith("http"): + if re.match("^https?://", media.media): media = types.InputMediaDocumentExternal( url=media.media ) else: media = utils.get_input_media_from_file_id(media.media, media.file_ref, 9) elif isinstance(media, InputMediaAnimation): - if media.media.startswith("http"): + if re.match("^https?://", media.media): media = types.InputMediaDocumentExternal( url=media.media ) else: media = utils.get_input_media_from_file_id(media.media, media.file_ref, 10) elif isinstance(media, InputMediaDocument): - if media.media.startswith("http"): + if re.match("^https?://", media.media): media = types.InputMediaDocumentExternal( url=media.media ) diff --git a/pyrogram/client/methods/messages/edit_message_media.py b/pyrogram/client/methods/messages/edit_message_media.py index b6fbf93313..3ab90744d7 100644 --- a/pyrogram/client/methods/messages/edit_message_media.py +++ b/pyrogram/client/methods/messages/edit_message_media.py @@ -17,6 +17,7 @@ # along with Pyrogram. If not, see . import os +import re from typing import Union import pyrogram @@ -78,7 +79,7 @@ def edit_message_media( parse_mode = media.parse_mode if isinstance(media, InputMediaPhoto): - if os.path.exists(media.media): + if os.path.isfile(media.media): media = self.send( functions.messages.UploadMedia( peer=self.resolve_peer(chat_id), @@ -95,14 +96,14 @@ def edit_message_media( file_reference=media.photo.file_reference ) ) - elif media.media.startswith("http"): + elif re.match("^https?://", media.media): media = types.InputMediaPhotoExternal( url=media.media ) else: media = utils.get_input_media_from_file_id(media.media, media.file_ref, 2) elif isinstance(media, InputMediaVideo): - if os.path.exists(media.media): + if os.path.isfile(media.media): media = self.send( functions.messages.UploadMedia( peer=self.resolve_peer(chat_id), @@ -132,14 +133,14 @@ def edit_message_media( file_reference=media.document.file_reference ) ) - elif media.media.startswith("http"): + elif re.match("^https?://", media.media): media = types.InputMediaDocumentExternal( url=media.media ) else: media = utils.get_input_media_from_file_id(media.media, media.file_ref, 4) elif isinstance(media, InputMediaAudio): - if os.path.exists(media.media): + if os.path.isfile(media.media): media = self.send( functions.messages.UploadMedia( peer=self.resolve_peer(chat_id), @@ -168,14 +169,14 @@ def edit_message_media( file_reference=media.document.file_reference ) ) - elif media.media.startswith("http"): + elif re.match("^https?://", media.media): media = types.InputMediaDocumentExternal( url=media.media ) else: media = utils.get_input_media_from_file_id(media.media, media.file_ref, 9) elif isinstance(media, InputMediaAnimation): - if os.path.exists(media.media): + if os.path.isfile(media.media): media = self.send( functions.messages.UploadMedia( peer=self.resolve_peer(chat_id), @@ -206,14 +207,14 @@ def edit_message_media( file_reference=media.document.file_reference ) ) - elif media.media.startswith("http"): + elif re.match("^https?://", media.media): media = types.InputMediaDocumentExternal( url=media.media ) else: media = utils.get_input_media_from_file_id(media.media, media.file_ref, 10) elif isinstance(media, InputMediaDocument): - if os.path.exists(media.media): + if os.path.isfile(media.media): media = self.send( functions.messages.UploadMedia( peer=self.resolve_peer(chat_id), @@ -237,7 +238,7 @@ def edit_message_media( file_reference=media.document.file_reference ) ) - elif media.media.startswith("http"): + elif re.match("^https?://", media.media): media = types.InputMediaDocumentExternal( url=media.media ) diff --git a/pyrogram/client/methods/messages/send_animation.py b/pyrogram/client/methods/messages/send_animation.py index 288ed04e69..c84e0503fb 100644 --- a/pyrogram/client/methods/messages/send_animation.py +++ b/pyrogram/client/methods/messages/send_animation.py @@ -17,6 +17,7 @@ # along with Pyrogram. If not, see . import os +import re from typing import Union import pyrogram @@ -163,7 +164,7 @@ def progress(current, total): file = None try: - if os.path.exists(animation): + if os.path.isfile(animation): thumb = None if thumb is None else self.save_file(thumb) file = self.save_file(animation, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( @@ -181,7 +182,7 @@ def progress(current, total): types.DocumentAttributeAnimated() ] ) - elif animation.startswith("http"): + elif re.match("^https?://", animation): media = types.InputMediaDocumentExternal( url=animation ) diff --git a/pyrogram/client/methods/messages/send_audio.py b/pyrogram/client/methods/messages/send_audio.py index e271d96cdc..49fd0e09f1 100644 --- a/pyrogram/client/methods/messages/send_audio.py +++ b/pyrogram/client/methods/messages/send_audio.py @@ -17,6 +17,7 @@ # along with Pyrogram. If not, see . import os +import re from typing import Union import pyrogram @@ -163,7 +164,7 @@ def progress(current, total): file = None try: - if os.path.exists(audio): + if os.path.isfile(audio): thumb = None if thumb is None else self.save_file(thumb) file = self.save_file(audio, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( @@ -179,7 +180,7 @@ def progress(current, total): types.DocumentAttributeFilename(file_name=file_name or os.path.basename(audio)) ] ) - elif audio.startswith("http"): + elif re.match("^https?://", audio): media = types.InputMediaDocumentExternal( url=audio ) diff --git a/pyrogram/client/methods/messages/send_document.py b/pyrogram/client/methods/messages/send_document.py index 24a754f079..8f15c5eeb6 100644 --- a/pyrogram/client/methods/messages/send_document.py +++ b/pyrogram/client/methods/messages/send_document.py @@ -17,6 +17,7 @@ # along with Pyrogram. If not, see . import os +import re from typing import Union import pyrogram @@ -143,7 +144,7 @@ def progress(current, total): file = None try: - if os.path.exists(document): + if os.path.isfile(document): thumb = None if thumb is None else self.save_file(thumb) file = self.save_file(document, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( @@ -154,7 +155,7 @@ def progress(current, total): types.DocumentAttributeFilename(file_name=file_name or os.path.basename(document)) ] ) - elif document.startswith("http"): + elif re.match("^https?://", document): media = types.InputMediaDocumentExternal( url=document ) diff --git a/pyrogram/client/methods/messages/send_media_group.py b/pyrogram/client/methods/messages/send_media_group.py index 8571ef4fc5..9ca4473b2a 100644 --- a/pyrogram/client/methods/messages/send_media_group.py +++ b/pyrogram/client/methods/messages/send_media_group.py @@ -18,13 +18,12 @@ import logging import os -import time +import re from typing import Union, List import pyrogram from pyrogram.api import functions, types from pyrogram.client.ext import BaseClient, utils -from pyrogram.errors import FloodWait log = logging.getLogger(__name__) @@ -77,7 +76,7 @@ def send_media_group( for i in media: if isinstance(i, pyrogram.InputMediaPhoto): - if os.path.exists(i.media): + if os.path.isfile(i.media): media = self.send( functions.messages.UploadMedia( peer=self.resolve_peer(chat_id), @@ -94,7 +93,7 @@ def send_media_group( file_reference=media.photo.file_reference ) ) - elif i.media.startswith("http"): + elif re.match("^https?://", i.media): media = self.send( functions.messages.UploadMedia( peer=self.resolve_peer(chat_id), @@ -114,7 +113,7 @@ def send_media_group( else: media = utils.get_input_media_from_file_id(i.media, i.file_ref, 2) elif isinstance(i, pyrogram.InputMediaVideo): - if os.path.exists(i.media): + if os.path.isfile(i.media): media = self.send( functions.messages.UploadMedia( peer=self.resolve_peer(chat_id), @@ -142,7 +141,7 @@ def send_media_group( file_reference=media.document.file_reference ) ) - elif i.media.startswith("http"): + elif re.match("^https?://", i.media): media = self.send( functions.messages.UploadMedia( peer=self.resolve_peer(chat_id), diff --git a/pyrogram/client/methods/messages/send_photo.py b/pyrogram/client/methods/messages/send_photo.py index 4d6a18a3d0..c21bb48742 100644 --- a/pyrogram/client/methods/messages/send_photo.py +++ b/pyrogram/client/methods/messages/send_photo.py @@ -17,6 +17,7 @@ # along with Pyrogram. If not, see . import os +import re from typing import Union import pyrogram @@ -137,13 +138,13 @@ def send_photo( file = None try: - if os.path.exists(photo): + if os.path.isfile(photo): file = self.save_file(photo, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedPhoto( file=file, ttl_seconds=ttl_seconds ) - elif photo.startswith("http"): + elif re.match("^https?://", photo): media = types.InputMediaPhotoExternal( url=photo, ttl_seconds=ttl_seconds diff --git a/pyrogram/client/methods/messages/send_sticker.py b/pyrogram/client/methods/messages/send_sticker.py index 76a42d3d8a..d95758854c 100644 --- a/pyrogram/client/methods/messages/send_sticker.py +++ b/pyrogram/client/methods/messages/send_sticker.py @@ -17,6 +17,7 @@ # along with Pyrogram. If not, see . import os +import re from typing import Union import pyrogram @@ -113,7 +114,7 @@ def send_sticker( file = None try: - if os.path.exists(sticker): + if os.path.isfile(sticker): file = self.save_file(sticker, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(sticker) or "image/webp", @@ -122,7 +123,7 @@ def send_sticker( types.DocumentAttributeFilename(file_name=os.path.basename(sticker)) ] ) - elif sticker.startswith("http"): + elif re.match("^https?://", sticker): media = types.InputMediaDocumentExternal( url=sticker ) diff --git a/pyrogram/client/methods/messages/send_video.py b/pyrogram/client/methods/messages/send_video.py index fc58aa98aa..9a67bbbb79 100644 --- a/pyrogram/client/methods/messages/send_video.py +++ b/pyrogram/client/methods/messages/send_video.py @@ -17,6 +17,7 @@ # along with Pyrogram. If not, see . import os +import re from typing import Union import pyrogram @@ -160,7 +161,7 @@ def progress(current, total): file = None try: - if os.path.exists(video): + if os.path.isfile(video): thumb = None if thumb is None else self.save_file(thumb) file = self.save_file(video, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( @@ -177,7 +178,7 @@ def progress(current, total): types.DocumentAttributeFilename(file_name=file_name or os.path.basename(video)) ] ) - elif video.startswith("http"): + elif re.match("^https?://", video): media = types.InputMediaDocumentExternal( url=video ) diff --git a/pyrogram/client/methods/messages/send_video_note.py b/pyrogram/client/methods/messages/send_video_note.py index 64bde11b54..b7acdc011e 100644 --- a/pyrogram/client/methods/messages/send_video_note.py +++ b/pyrogram/client/methods/messages/send_video_note.py @@ -128,7 +128,7 @@ def send_video_note( file = None try: - if os.path.exists(video_note): + if os.path.isfile(video_note): thumb = None if thumb is None else self.save_file(thumb) file = self.save_file(video_note, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( diff --git a/pyrogram/client/methods/messages/send_voice.py b/pyrogram/client/methods/messages/send_voice.py index 753e380671..23492f5393 100644 --- a/pyrogram/client/methods/messages/send_voice.py +++ b/pyrogram/client/methods/messages/send_voice.py @@ -17,6 +17,7 @@ # along with Pyrogram. If not, see . import os +import re from typing import Union import pyrogram @@ -132,7 +133,7 @@ def send_voice( file = None try: - if os.path.exists(voice): + if os.path.isfile(voice): file = self.save_file(voice, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(voice) or "audio/mpeg", @@ -144,7 +145,7 @@ def send_voice( ) ] ) - elif voice.startswith("http"): + elif re.match("^https?://", voice): media = types.InputMediaDocumentExternal( url=voice ) From b3faf21c95533232a71bbb7049d316ab18725c28 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 9 Jul 2020 02:56:09 +0200 Subject: [PATCH 0245/1185] Rework in-memory uploads --- pyrogram/client/client.py | 35 +++++++++---- .../client/methods/messages/send_animation.py | 52 +++++++++++++------ .../client/methods/messages/send_audio.py | 50 ++++++++++++------ .../client/methods/messages/send_document.py | 45 ++++++++++------ .../client/methods/messages/send_photo.py | 34 +++++++----- .../client/methods/messages/send_sticker.py | 39 +++++++++----- .../client/methods/messages/send_video.py | 51 ++++++++++++------ .../methods/messages/send_video_note.py | 40 ++++++++++---- .../client/methods/messages/send_voice.py | 40 +++++++++----- 9 files changed, 260 insertions(+), 126 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 0e186d85b5..c46f119af2 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 io import logging import math import os @@ -30,7 +31,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, BinaryIO from pyrogram.api import functions, types from pyrogram.api.core import TLObject @@ -39,9 +40,9 @@ from pyrogram.client.methods.password.utils import compute_check from pyrogram.crypto import AES from pyrogram.errors import ( - PhoneMigrate, NetworkMigrate, SessionPasswordNeeded, - FloodWait, PeerIdInvalid, VolumeLocNotFound, UserMigrate, ChannelPrivate, AuthBytesInvalid, - BadRequest) + PhoneMigrate, NetworkMigrate, SessionPasswordNeeded, PeerIdInvalid, VolumeLocNotFound, UserMigrate, ChannelPrivate, + AuthBytesInvalid, BadRequest +) from pyrogram.session import Auth, Session from .ext import utils, Syncer, BaseClient, Dispatcher from .methods import Methods @@ -1713,7 +1714,7 @@ def resolve_peer(self, peer_id: Union[int, str]): def save_file( self, - path: str, + path: Union[str, BinaryIO], file_id: int = None, file_part: int = 0, progress: callable = None, @@ -1767,7 +1768,19 @@ def save_file( RPCError: In case of a Telegram RPC error. """ part_size = 512 * 1024 - file_size = os.path.getsize(path) + + if isinstance(path, str): + fp = open(path, "rb") + elif isinstance(path, io.IOBase): + fp = path + else: + raise ValueError("Invalid file. Expected a file path as string or a binary (not text) file pointer") + + file_name = fp.name + + fp.seek(0, os.SEEK_END) + file_size = fp.tell() + fp.seek(0) if file_size == 0: raise ValueError("File size equals to 0 B") @@ -1785,11 +1798,11 @@ def save_file( session.start() try: - with open(path, "rb") as f: - f.seek(part_size * file_part) + with fp: + fp.seek(part_size * file_part) while True: - chunk = f.read(part_size) + chunk = fp.read(part_size) if not chunk: if not is_big: @@ -1835,14 +1848,14 @@ def save_file( return types.InputFileBig( id=file_id, parts=file_total_parts, - name=os.path.basename(path), + name=file_name, ) else: return types.InputFile( id=file_id, parts=file_total_parts, - name=os.path.basename(path), + name=file_name, md5_checksum=md5_sum ) finally: diff --git a/pyrogram/client/methods/messages/send_animation.py b/pyrogram/client/methods/messages/send_animation.py index c84e0503fb..a38856a00a 100644 --- a/pyrogram/client/methods/messages/send_animation.py +++ b/pyrogram/client/methods/messages/send_animation.py @@ -18,7 +18,7 @@ import os import re -from typing import Union +from typing import Union, BinaryIO import pyrogram from pyrogram.api import functions, types @@ -30,7 +30,7 @@ class SendAnimation(BaseClient): def send_animation( self, chat_id: Union[int, str], - animation: str, + animation: Union[str, BinaryIO], file_ref: str = None, caption: str = "", unsave: bool = False, @@ -38,7 +38,7 @@ def send_animation( duration: int = 0, width: int = 0, height: int = 0, - thumb: str = None, + thumb: Union[str, BinaryIO] = None, file_name: str = None, disable_notification: bool = None, reply_to_message_id: int = None, @@ -60,11 +60,12 @@ def send_animation( For your personal cloud (Saved Messages) you can simply use "me" or "self". For a contact that exists in your Telegram address book you can use his phone number (str). - animation (``str``): + animation (``str`` | ``BinaryIO``): Animation to send. Pass a file_id as string to send an animation that exists on the Telegram servers, - pass an HTTP URL as a string for Telegram to get an animation from the Internet, or - pass a file path as string to upload a new animation that exists on your local machine. + pass an HTTP URL as a string for Telegram to get an animation from the Internet, + pass a file path as string to upload a new animation that exists on your local machine, or + pass a binary file-like object with its attribute ".name" set for in-memory uploads. file_ref (``str``, *optional*): A valid file reference obtained by a recently fetched media message. @@ -93,7 +94,7 @@ def send_animation( height (``int``, *optional*): Animation height. - thumb (``str``, *optional*): + thumb (``str`` | ``BinaryIO``, *optional*): Thumbnail of the animation file sent. The thumbnail should be in JPEG format and less than 200 KB in size. A thumbnail's width and height should not exceed 320 pixels. @@ -164,11 +165,36 @@ def progress(current, total): file = None try: - if os.path.isfile(animation): + if isinstance(animation, str): + if os.path.isfile(animation): + thumb = None if thumb is None else self.save_file(thumb) + file = self.save_file(animation, progress=progress, progress_args=progress_args) + media = types.InputMediaUploadedDocument( + mime_type=self.guess_mime_type(animation) or "video/mp4", + file=file, + thumb=thumb, + attributes=[ + types.DocumentAttributeVideo( + supports_streaming=True, + duration=duration, + w=width, + h=height + ), + types.DocumentAttributeFilename(file_name=file_name or os.path.basename(animation)), + types.DocumentAttributeAnimated() + ] + ) + elif re.match("^https?://", animation): + media = types.InputMediaDocumentExternal( + url=animation + ) + else: + media = utils.get_input_media_from_file_id(animation, file_ref, 10) + else: thumb = None if thumb is None else self.save_file(thumb) file = self.save_file(animation, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(animation) or "video/mp4", + mime_type=self.guess_mime_type(animation.name) or "video/mp4", file=file, thumb=thumb, attributes=[ @@ -178,16 +204,10 @@ def progress(current, total): w=width, h=height ), - types.DocumentAttributeFilename(file_name=file_name or os.path.basename(animation)), + types.DocumentAttributeFilename(file_name=animation.name), types.DocumentAttributeAnimated() ] ) - elif re.match("^https?://", animation): - media = types.InputMediaDocumentExternal( - url=animation - ) - else: - media = utils.get_input_media_from_file_id(animation, file_ref, 10) while True: try: diff --git a/pyrogram/client/methods/messages/send_audio.py b/pyrogram/client/methods/messages/send_audio.py index 49fd0e09f1..08c03d073c 100644 --- a/pyrogram/client/methods/messages/send_audio.py +++ b/pyrogram/client/methods/messages/send_audio.py @@ -18,7 +18,7 @@ import os import re -from typing import Union +from typing import Union, BinaryIO import pyrogram from pyrogram.api import functions, types @@ -30,14 +30,14 @@ class SendAudio(BaseClient): def send_audio( self, chat_id: Union[int, str], - audio: str, + audio: Union[str, BinaryIO], file_ref: str = None, caption: str = "", parse_mode: Union[str, None] = object, duration: int = 0, performer: str = None, title: str = None, - thumb: str = None, + thumb: Union[str, BinaryIO] = None, file_name: str = None, disable_notification: bool = None, reply_to_message_id: int = None, @@ -61,11 +61,12 @@ def send_audio( For your personal cloud (Saved Messages) you can simply use "me" or "self". For a contact that exists in your Telegram address book you can use his phone number (str). - audio (``str``): + audio (``str`` | ``BinaryIO``): Audio file to send. Pass a file_id as string to send an audio file that exists on the Telegram servers, - pass an HTTP URL as a string for Telegram to get an audio file from the Internet, or - pass a file path as string to upload a new audio file that exists on your local machine. + pass an HTTP URL as a string for Telegram to get an audio file from the Internet, + pass a file path as string to upload a new audio file that exists on your local machine, or + pass a binary file-like object with its attribute ".name" set for in-memory uploads. file_ref (``str``, *optional*): A valid file reference obtained by a recently fetched media message. @@ -90,7 +91,7 @@ def send_audio( title (``str``, *optional*): Track name. - thumb (``str``, *optional*): + thumb (``str`` | ``BinaryIO``, *optional*): Thumbnail of the music file album cover. The thumbnail should be in JPEG format and less than 200 KB in size. A thumbnail's width and height should not exceed 320 pixels. @@ -164,11 +165,34 @@ def progress(current, total): file = None try: - if os.path.isfile(audio): + if isinstance(audio, str): + if os.path.isfile(audio): + thumb = None if thumb is None else self.save_file(thumb) + file = self.save_file(audio, progress=progress, progress_args=progress_args) + media = types.InputMediaUploadedDocument( + mime_type=self.guess_mime_type(audio) or "audio/mpeg", + file=file, + thumb=thumb, + attributes=[ + types.DocumentAttributeAudio( + duration=duration, + performer=performer, + title=title + ), + types.DocumentAttributeFilename(file_name=file_name or os.path.basename(audio)) + ] + ) + elif re.match("^https?://", audio): + media = types.InputMediaDocumentExternal( + url=audio + ) + else: + media = utils.get_input_media_from_file_id(audio, file_ref, 9) + else: thumb = None if thumb is None else self.save_file(thumb) file = self.save_file(audio, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(audio) or "audio/mpeg", + mime_type=self.guess_mime_type(audio.name) or "audio/mpeg", file=file, thumb=thumb, attributes=[ @@ -177,15 +201,9 @@ def progress(current, total): performer=performer, title=title ), - types.DocumentAttributeFilename(file_name=file_name or os.path.basename(audio)) + types.DocumentAttributeFilename(file_name=audio.name) ] ) - elif re.match("^https?://", audio): - media = types.InputMediaDocumentExternal( - url=audio - ) - else: - media = utils.get_input_media_from_file_id(audio, file_ref, 9) while True: try: diff --git a/pyrogram/client/methods/messages/send_document.py b/pyrogram/client/methods/messages/send_document.py index 8f15c5eeb6..2241754b8c 100644 --- a/pyrogram/client/methods/messages/send_document.py +++ b/pyrogram/client/methods/messages/send_document.py @@ -18,7 +18,7 @@ import os import re -from typing import Union +from typing import Union, BinaryIO import pyrogram from pyrogram.api import functions, types @@ -30,9 +30,9 @@ class SendDocument(BaseClient): def send_document( self, chat_id: Union[int, str], - document: str, + document: Union[str, BinaryIO], file_ref: str = None, - thumb: str = None, + thumb: Union[str, BinaryIO] = None, caption: str = "", parse_mode: Union[str, None] = object, file_name: str = None, @@ -56,17 +56,18 @@ def send_document( For your personal cloud (Saved Messages) you can simply use "me" or "self". For a contact that exists in your Telegram address book you can use his phone number (str). - document (``str``): + document (``str`` | ``BinaryIO``): File to send. Pass a file_id as string to send a file that exists on the Telegram servers, - pass an HTTP URL as a string for Telegram to get a file from the Internet, or - pass a file path as string to upload a new file that exists on your local machine. + pass an HTTP URL as a string for Telegram to get a file from the Internet, + pass a file path as string to upload a new file that exists on your local machine, or + pass a binary file-like object with its attribute ".name" set for in-memory uploads. file_ref (``str``, *optional*): A valid file reference obtained by a recently fetched media message. To be used in combination with a file id in case a file reference is needed. - thumb (``str``, *optional*): + thumb (``str`` | ``BinaryIO``, *optional*): Thumbnail of the file sent. The thumbnail should be in JPEG format and less than 200 KB in size. A thumbnail's width and height should not exceed 320 pixels. @@ -144,23 +145,35 @@ def progress(current, total): file = None try: - if os.path.isfile(document): + if isinstance(document, str): + if os.path.isfile(document): + thumb = None if thumb is None else self.save_file(thumb) + file = self.save_file(document, progress=progress, progress_args=progress_args) + media = types.InputMediaUploadedDocument( + mime_type=self.guess_mime_type(document) or "application/zip", + file=file, + thumb=thumb, + attributes=[ + types.DocumentAttributeFilename(file_name=file_name or os.path.basename(document)) + ] + ) + elif re.match("^https?://", document): + media = types.InputMediaDocumentExternal( + url=document + ) + else: + media = utils.get_input_media_from_file_id(document, file_ref, 5) + else: thumb = None if thumb is None else self.save_file(thumb) file = self.save_file(document, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(document) or "application/zip", + mime_type=self.guess_mime_type(document.name) or "application/zip", file=file, thumb=thumb, attributes=[ - types.DocumentAttributeFilename(file_name=file_name or os.path.basename(document)) + types.DocumentAttributeFilename(file_name=document.name) ] ) - elif re.match("^https?://", document): - media = types.InputMediaDocumentExternal( - url=document - ) - else: - media = utils.get_input_media_from_file_id(document, file_ref, 5) while True: try: diff --git a/pyrogram/client/methods/messages/send_photo.py b/pyrogram/client/methods/messages/send_photo.py index c21bb48742..6310168585 100644 --- a/pyrogram/client/methods/messages/send_photo.py +++ b/pyrogram/client/methods/messages/send_photo.py @@ -18,7 +18,7 @@ import os import re -from typing import Union +from typing import Union, BinaryIO import pyrogram from pyrogram.api import functions, types @@ -30,7 +30,7 @@ class SendPhoto(BaseClient): def send_photo( self, chat_id: Union[int, str], - photo: str, + photo: Union[str, BinaryIO], file_ref: str = None, caption: str = "", parse_mode: Union[str, None] = object, @@ -55,11 +55,12 @@ def send_photo( For your personal cloud (Saved Messages) you can simply use "me" or "self". For a contact that exists in your Telegram address book you can use his phone number (str). - photo (``str``): + photo (``str`` | ``BinaryIO``): Photo to send. Pass a file_id as string to send a photo that exists on the Telegram servers, - pass an HTTP URL as a string for Telegram to get a photo from the Internet, or - pass a file path as string to upload a new photo that exists on your local machine. + pass an HTTP URL as a string for Telegram to get a photo from the Internet, + pass a file path as string to upload a new photo that exists on your local machine, or + pass a binary file-like object with its attribute ".name" set for in-memory uploads. file_ref (``str``, *optional*): A valid file reference obtained by a recently fetched media message. @@ -138,19 +139,26 @@ def send_photo( file = None try: - if os.path.isfile(photo): + if isinstance(photo, str): + if os.path.isfile(photo): + file = self.save_file(photo, progress=progress, progress_args=progress_args) + media = types.InputMediaUploadedPhoto( + file=file, + ttl_seconds=ttl_seconds + ) + elif re.match("^https?://", photo): + media = types.InputMediaPhotoExternal( + url=photo, + ttl_seconds=ttl_seconds + ) + else: + media = utils.get_input_media_from_file_id(photo, file_ref, 2) + else: file = self.save_file(photo, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedPhoto( file=file, ttl_seconds=ttl_seconds ) - elif re.match("^https?://", photo): - media = types.InputMediaPhotoExternal( - url=photo, - ttl_seconds=ttl_seconds - ) - else: - media = utils.get_input_media_from_file_id(photo, file_ref, 2) while True: try: diff --git a/pyrogram/client/methods/messages/send_sticker.py b/pyrogram/client/methods/messages/send_sticker.py index d95758854c..0de47d640d 100644 --- a/pyrogram/client/methods/messages/send_sticker.py +++ b/pyrogram/client/methods/messages/send_sticker.py @@ -18,7 +18,7 @@ import os import re -from typing import Union +from typing import Union, BinaryIO import pyrogram from pyrogram.api import functions, types @@ -30,7 +30,7 @@ class SendSticker(BaseClient): def send_sticker( self, chat_id: Union[int, str], - sticker: str, + sticker: Union[str, BinaryIO], file_ref: str = None, disable_notification: bool = None, reply_to_message_id: int = None, @@ -52,11 +52,12 @@ def send_sticker( For your personal cloud (Saved Messages) you can simply use "me" or "self". For a contact that exists in your Telegram address book you can use his phone number (str). - sticker (``str``): + sticker (``str`` | ``BinaryIO``): Sticker to send. Pass a file_id as string to send a sticker that exists on the Telegram servers, - pass an HTTP URL as a string for Telegram to get a .webp sticker file from the Internet, or - pass a file path as string to upload a new sticker that exists on your local machine. + pass an HTTP URL as a string for Telegram to get a .webp sticker file from the Internet, + pass a file path as string to upload a new sticker that exists on your local machine, or + pass a binary file-like object with its attribute ".name" set for in-memory uploads. file_ref (``str``, *optional*): A valid file reference obtained by a recently fetched media message. @@ -114,21 +115,31 @@ def send_sticker( file = None try: - if os.path.isfile(sticker): + if isinstance(sticker, str): + if os.path.isfile(sticker): + file = self.save_file(sticker, progress=progress, progress_args=progress_args) + media = types.InputMediaUploadedDocument( + mime_type=self.guess_mime_type(sticker) or "image/webp", + file=file, + attributes=[ + types.DocumentAttributeFilename(file_name=os.path.basename(sticker)) + ] + ) + elif re.match("^https?://", sticker): + media = types.InputMediaDocumentExternal( + url=sticker + ) + else: + media = utils.get_input_media_from_file_id(sticker, file_ref, 8) + else: file = self.save_file(sticker, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(sticker) or "image/webp", + mime_type=self.guess_mime_type(sticker.name) or "image/webp", file=file, attributes=[ - types.DocumentAttributeFilename(file_name=os.path.basename(sticker)) + types.DocumentAttributeFilename(file_name=sticker.name) ] ) - elif re.match("^https?://", sticker): - media = types.InputMediaDocumentExternal( - url=sticker - ) - else: - media = utils.get_input_media_from_file_id(sticker, file_ref, 8) while True: try: diff --git a/pyrogram/client/methods/messages/send_video.py b/pyrogram/client/methods/messages/send_video.py index 9a67bbbb79..1f46252f67 100644 --- a/pyrogram/client/methods/messages/send_video.py +++ b/pyrogram/client/methods/messages/send_video.py @@ -18,7 +18,7 @@ import os import re -from typing import Union +from typing import Union, BinaryIO import pyrogram from pyrogram.api import functions, types @@ -30,14 +30,14 @@ class SendVideo(BaseClient): def send_video( self, chat_id: Union[int, str], - video: str, + video: Union[str, BinaryIO], file_ref: str = None, caption: str = "", parse_mode: Union[str, None] = object, duration: int = 0, width: int = 0, height: int = 0, - thumb: str = None, + thumb: Union[str, BinaryIO] = None, file_name: str = None, supports_streaming: bool = True, disable_notification: bool = None, @@ -60,11 +60,12 @@ def send_video( For your personal cloud (Saved Messages) you can simply use "me" or "self". For a contact that exists in your Telegram address book you can use his phone number (str). - video (``str``): + video (``str`` | ``BinaryIO``): Video to send. Pass a file_id as string to send a video that exists on the Telegram servers, - pass an HTTP URL as a string for Telegram to get a video from the Internet, or - pass a file path as string to upload a new video that exists on your local machine. + pass an HTTP URL as a string for Telegram to get a video from the Internet, + pass a file path as string to upload a new video that exists on your local machine, or + pass a binary file-like object with its attribute ".name" set for in-memory uploads. file_ref (``str``, *optional*): A valid file reference obtained by a recently fetched media message. @@ -89,7 +90,7 @@ def send_video( height (``int``, *optional*): Video height. - thumb (``str``, *optional*): + thumb (``str`` | ``BinaryIO``, *optional*): Thumbnail of the video sent. The thumbnail should be in JPEG format and less than 200 KB in size. A thumbnail's width and height should not exceed 320 pixels. @@ -161,11 +162,35 @@ def progress(current, total): file = None try: - if os.path.isfile(video): + if isinstance(video, str): + if os.path.isfile(video): + thumb = None if thumb is None else self.save_file(thumb) + file = self.save_file(video, progress=progress, progress_args=progress_args) + media = types.InputMediaUploadedDocument( + mime_type=self.guess_mime_type(video) or "video/mp4", + file=file, + thumb=thumb, + attributes=[ + types.DocumentAttributeVideo( + supports_streaming=supports_streaming or None, + duration=duration, + w=width, + h=height + ), + types.DocumentAttributeFilename(file_name=file_name or os.path.basename(video)) + ] + ) + elif re.match("^https?://", video): + media = types.InputMediaDocumentExternal( + url=video + ) + else: + media = utils.get_input_media_from_file_id(video, file_ref, 4) + else: thumb = None if thumb is None else self.save_file(thumb) file = self.save_file(video, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(video) or "video/mp4", + mime_type=self.guess_mime_type(video.name) or "video/mp4", file=file, thumb=thumb, attributes=[ @@ -175,15 +200,9 @@ def progress(current, total): w=width, h=height ), - types.DocumentAttributeFilename(file_name=file_name or os.path.basename(video)) + types.DocumentAttributeFilename(file_name=video.name) ] ) - elif re.match("^https?://", video): - media = types.InputMediaDocumentExternal( - url=video - ) - else: - media = utils.get_input_media_from_file_id(video, file_ref, 4) while True: try: diff --git a/pyrogram/client/methods/messages/send_video_note.py b/pyrogram/client/methods/messages/send_video_note.py index b7acdc011e..829f145942 100644 --- a/pyrogram/client/methods/messages/send_video_note.py +++ b/pyrogram/client/methods/messages/send_video_note.py @@ -17,7 +17,7 @@ # along with Pyrogram. If not, see . import os -from typing import Union +from typing import Union, BinaryIO import pyrogram from pyrogram.api import functions, types @@ -29,11 +29,11 @@ class SendVideoNote(BaseClient): def send_video_note( self, chat_id: Union[int, str], - video_note: str, + video_note: Union[str, BinaryIO], file_ref: str = None, duration: int = 0, length: int = 1, - thumb: str = None, + thumb: Union[str, BinaryIO] = None, disable_notification: bool = None, reply_to_message_id: int = None, schedule_date: int = None, @@ -54,10 +54,11 @@ def send_video_note( For your personal cloud (Saved Messages) you can simply use "me" or "self". For a contact that exists in your Telegram address book you can use his phone number (str). - video_note (``str``): + video_note (``str`` | ``BinaryIO``): Video note to send. - Pass a file_id as string to send a video note that exists on the Telegram servers, or - pass a file path as string to upload a new video note that exists on your local machine. + Pass a file_id as string to send a video note that exists on the Telegram servers, + pass a file path as string to upload a new video note that exists on your local machine, or + pass a binary file-like object with its attribute ".name" set for in-memory uploads. Sending video notes by a URL is currently unsupported. file_ref (``str``, *optional*): @@ -70,7 +71,7 @@ def send_video_note( length (``int``, *optional*): Video width and height. - thumb (``str``, *optional*): + thumb (``str`` | ``BinaryIO``, *optional*): Thumbnail of the video sent. The thumbnail should be in JPEG format and less than 200 KB in size. A thumbnail's width and height should not exceed 320 pixels. @@ -128,11 +129,30 @@ def send_video_note( file = None try: - if os.path.isfile(video_note): + if isinstance(video_note, str): + if os.path.isfile(video_note): + thumb = None if thumb is None else self.save_file(thumb) + file = self.save_file(video_note, progress=progress, progress_args=progress_args) + media = types.InputMediaUploadedDocument( + mime_type=self.guess_mime_type(video_note) or "video/mp4", + file=file, + thumb=thumb, + attributes=[ + types.DocumentAttributeVideo( + round_message=True, + duration=duration, + w=length, + h=length + ) + ] + ) + else: + media = utils.get_input_media_from_file_id(video_note, file_ref, 13) + else: thumb = None if thumb is None else self.save_file(thumb) file = self.save_file(video_note, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(video_note) or "video/mp4", + mime_type=self.guess_mime_type(video_note.name) or "video/mp4", file=file, thumb=thumb, attributes=[ @@ -144,8 +164,6 @@ def send_video_note( ) ] ) - else: - media = utils.get_input_media_from_file_id(video_note, file_ref, 13) while True: try: diff --git a/pyrogram/client/methods/messages/send_voice.py b/pyrogram/client/methods/messages/send_voice.py index 23492f5393..f99b423681 100644 --- a/pyrogram/client/methods/messages/send_voice.py +++ b/pyrogram/client/methods/messages/send_voice.py @@ -18,7 +18,7 @@ import os import re -from typing import Union +from typing import Union, BinaryIO import pyrogram from pyrogram.api import functions, types @@ -30,7 +30,7 @@ class SendVoice(BaseClient): def send_voice( self, chat_id: Union[int, str], - voice: str, + voice: Union[str, BinaryIO], file_ref=None, caption: str = "", parse_mode: Union[str, None] = object, @@ -55,11 +55,12 @@ def send_voice( For your personal cloud (Saved Messages) you can simply use "me" or "self". For a contact that exists in your Telegram address book you can use his phone number (str). - voice (``str``): + voice (``str`` | ``BinaryIO``): Audio file to send. Pass a file_id as string to send an audio that exists on the Telegram servers, - pass an HTTP URL as a string for Telegram to get an audio from the Internet, or - pass a file path as string to upload a new audio that exists on your local machine. + pass an HTTP URL as a string for Telegram to get an audio from the Internet, + pass a file path as string to upload a new audio that exists on your local machine, or + pass a binary file-like object with its attribute ".name" set for in-memory uploads. file_ref (``str``, *optional*): A valid file reference obtained by a recently fetched media message. @@ -133,10 +134,29 @@ def send_voice( file = None try: - if os.path.isfile(voice): + if isinstance(voice, str): + if os.path.isfile(voice): + file = self.save_file(voice, progress=progress, progress_args=progress_args) + media = types.InputMediaUploadedDocument( + mime_type=self.guess_mime_type(voice) or "audio/mpeg", + file=file, + attributes=[ + types.DocumentAttributeAudio( + voice=True, + duration=duration + ) + ] + ) + elif re.match("^https?://", voice): + media = types.InputMediaDocumentExternal( + url=voice + ) + else: + media = utils.get_input_media_from_file_id(voice, file_ref, 3) + else: file = self.save_file(voice, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(voice) or "audio/mpeg", + mime_type=self.guess_mime_type(voice.name) or "audio/mpeg", file=file, attributes=[ types.DocumentAttributeAudio( @@ -145,12 +165,6 @@ def send_voice( ) ] ) - elif re.match("^https?://", voice): - media = types.InputMediaDocumentExternal( - url=voice - ) - else: - media = utils.get_input_media_from_file_id(voice, file_ref, 3) while True: try: From fd944677c7268f58e881679ef77b93f10ae4de94 Mon Sep 17 00:00:00 2001 From: Florent Gallaire Date: Thu, 9 Jul 2020 01:49:38 +0000 Subject: [PATCH 0246/1185] Add chat.id --- pyrogram/client/types/messages_and_media/message.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index 238736e653..c6d58de39b 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -2687,9 +2687,9 @@ def forward( """ if as_copy: if self.service: - log.warning("Unable to copy service messages, message_id: {}".format(self.message_id)) + log.warning("Unable to copy service messages, message_id: {} from chat.id: {}".format(self.message_id, self.chat.id)) elif self.game and not self._client.is_bot: - log.warning("Users cannot send messages with Game media type, message_id: {}".format(self.message_id)) + log.warning("Users cannot send messages with Game media type, message_id: {} from chat.id: {}".format(self.message_id, self.chat.id)) elif self.text: return self._client.send_message( chat_id, From 531069b1e2a202f5e922705c6515fea7595edec9 Mon Sep 17 00:00:00 2001 From: Alisson Lauffer Date: Sun, 12 Jul 2020 01:43:30 -0300 Subject: [PATCH 0247/1185] Some fixes and speed improvments (#439) * Use raw string for re pattern * Trim trailing whitespaces from docstrings and code * Use isinstance() instead of type() for typechecking * Remove unused imports --- compiler/api/compiler.py | 2 +- docs/README.md | 2 +- pyrogram/client/client.py | 2 +- pyrogram/client/filters/filters.py | 14 +++++++------- .../client/methods/chats/delete_user_history.py | 2 +- .../client/methods/chats/get_chat_members.py | 2 -- pyrogram/client/methods/chats/get_dialogs.py | 2 -- pyrogram/client/methods/chats/set_chat_photo.py | 2 +- .../client/methods/chats/update_chat_username.py | 2 +- pyrogram/client/methods/messages/get_history.py | 2 -- pyrogram/client/methods/messages/get_messages.py | 2 -- .../client/methods/users/get_common_chats.py | 4 ++-- pyrogram/client/methods/users/update_profile.py | 16 ++++++++-------- pyrogram/client/methods/users/update_username.py | 2 +- .../types/inline_mode/chosen_inline_result.py | 2 -- pyrogram/client/types/messages_and_media/dice.py | 5 ----- .../client/types/messages_and_media/message.py | 4 ++-- pyrogram/session/internals/msg_factory.py | 4 ++-- 18 files changed, 28 insertions(+), 43 deletions(-) diff --git a/compiler/api/compiler.py b/compiler/api/compiler.py index f6cb57423b..3b0876f90e 100644 --- a/compiler/api/compiler.py +++ b/compiler/api/compiler.py @@ -26,7 +26,7 @@ SECTION_RE = re.compile(r"---(\w+)---") LAYER_RE = re.compile(r"//\sLAYER\s(\d+)") COMBINATOR_RE = re.compile(r"^([\w.]+)#([0-9a-f]+)\s(?:.*)=\s([\w<>.]+);(?: // Docs: (.+))?$", re.MULTILINE) -ARGS_RE = re.compile("[^{](\w+):([\w?!.<>#]+)") +ARGS_RE = re.compile(r"[^{](\w+):([\w?!.<>#]+)") FLAGS_RE = re.compile(r"flags\.(\d+)\?") FLAGS_RE_2 = re.compile(r"flags\.(\d+)\?([\w<>.]+)") FLAGS_RE_3 = re.compile(r"flags:#") diff --git a/docs/README.md b/docs/README.md index 1c929447d7..680e2cf488 100644 --- a/docs/README.md +++ b/docs/README.md @@ -2,7 +2,7 @@ - Install requirements. - Install `pandoc` and `latexmk`. -- HTML: `make html` +- HTML: `make html` - PDF: `make latexpdf` TODO: Explain better \ No newline at end of file diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index c46f119af2..8d53e354e3 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -1649,7 +1649,7 @@ def resolve_peer(self, peer_id: Union[int, str]): try: return self.storage.get_peer_by_id(peer_id) except KeyError: - if type(peer_id) is str: + if isinstance(peer_id, str): if peer_id in ("self", "me"): return types.InputPeerSelf() diff --git a/pyrogram/client/filters/filters.py b/pyrogram/client/filters/filters.py index 1921881361..e872d72431 100644 --- a/pyrogram/client/filters/filters.py +++ b/pyrogram/client/filters/filters.py @@ -214,7 +214,7 @@ class Filters: from_scheduled = create(lambda _, m: bool(m.from_scheduled), "FromScheduledFilter") """Filter new automatically sent messages that were previously scheduled.""" - + # Messages from linked channels are forwarded automatically by Telegram and have no sender (from_user is None). linked_channel = create(lambda _, m: bool(m.forward_from_chat and not m.from_user), "LinkedChannelFilter") """Filter messages that are automatically forwarded from the linked channel to the group chat.""" @@ -277,11 +277,11 @@ def func(flt, message): return False - commands = commands if type(commands) is list else [commands] + commands = commands if isinstance(commands, list) else [commands] commands = {c if case_sensitive else c.lower() for c in commands} prefixes = [] if prefixes is None else prefixes - prefixes = prefixes if type(prefixes) is list else [prefixes] + prefixes = prefixes if isinstance(prefixes, list) else [prefixes] prefixes = set(prefixes) if prefixes else {""} return create( @@ -345,11 +345,11 @@ class user(Filter, set): """ def __init__(self, users: int or str or list = None): - users = [] if users is None else users if type(users) is list else [users] + users = [] if users is None else users if isinstance(users, list) else [users] super().__init__( "me" if u in ["me", "self"] - else u.lower().strip("@") if type(u) is str + else u.lower().strip("@") if isinstance(u, str) else u for u in users ) @@ -376,11 +376,11 @@ class chat(Filter, set): """ def __init__(self, chats: int or str or list = None): - chats = [] if chats is None else chats if type(chats) is list else [chats] + chats = [] if chats is None else chats if isinstance(chats, list) else [chats] super().__init__( "me" if c in ["me", "self"] - else c.lower().strip("@") if type(c) is str + else c.lower().strip("@") if isinstance(c, str) else c for c in chats ) diff --git a/pyrogram/client/methods/chats/delete_user_history.py b/pyrogram/client/methods/chats/delete_user_history.py index 03d87ca43e..a35bf10c93 100644 --- a/pyrogram/client/methods/chats/delete_user_history.py +++ b/pyrogram/client/methods/chats/delete_user_history.py @@ -18,7 +18,7 @@ from typing import Union -from pyrogram.api import functions, types +from pyrogram.api import functions from pyrogram.client.ext import BaseClient diff --git a/pyrogram/client/methods/chats/get_chat_members.py b/pyrogram/client/methods/chats/get_chat_members.py index bf20894433..da1954a461 100644 --- a/pyrogram/client/methods/chats/get_chat_members.py +++ b/pyrogram/client/methods/chats/get_chat_members.py @@ -17,12 +17,10 @@ # along with Pyrogram. If not, see . import logging -import time from typing import Union, List import pyrogram from pyrogram.api import functions, types -from pyrogram.errors import FloodWait from ...ext import BaseClient log = logging.getLogger(__name__) diff --git a/pyrogram/client/methods/chats/get_dialogs.py b/pyrogram/client/methods/chats/get_dialogs.py index f5d5f44285..de03b04687 100644 --- a/pyrogram/client/methods/chats/get_dialogs.py +++ b/pyrogram/client/methods/chats/get_dialogs.py @@ -17,12 +17,10 @@ # along with Pyrogram. If not, see . import logging -import time from typing import List import pyrogram from pyrogram.api import functions, types -from pyrogram.errors import FloodWait from ...ext import BaseClient, utils log = logging.getLogger(__name__) diff --git a/pyrogram/client/methods/chats/set_chat_photo.py b/pyrogram/client/methods/chats/set_chat_photo.py index d394322c32..e22b6819b3 100644 --- a/pyrogram/client/methods/chats/set_chat_photo.py +++ b/pyrogram/client/methods/chats/set_chat_photo.py @@ -41,7 +41,7 @@ def set_chat_photo( photo (``str``): New chat photo. You can pass a :obj:`Photo` file_id or a file path to upload a new photo from your local machine. - + file_ref (``str``, *optional*): A valid file reference obtained by a recently fetched media message. To be used in combination with a file id in case a file reference is needed. diff --git a/pyrogram/client/methods/chats/update_chat_username.py b/pyrogram/client/methods/chats/update_chat_username.py index ff4db61b75..251d68329d 100644 --- a/pyrogram/client/methods/chats/update_chat_username.py +++ b/pyrogram/client/methods/chats/update_chat_username.py @@ -29,7 +29,7 @@ def update_chat_username( username: Union[str, None] ) -> bool: """Update a channel or a supergroup username. - + To update your own username (for users only, not bots) you can use :meth:`~Client.update_username`. Parameters: diff --git a/pyrogram/client/methods/messages/get_history.py b/pyrogram/client/methods/messages/get_history.py index 79a1dec61b..ea630aee12 100644 --- a/pyrogram/client/methods/messages/get_history.py +++ b/pyrogram/client/methods/messages/get_history.py @@ -17,13 +17,11 @@ # along with Pyrogram. If not, see . import logging -import time from typing import Union, List import pyrogram from pyrogram.api import functions from pyrogram.client.ext import utils -from pyrogram.errors import FloodWait from ...ext import BaseClient log = logging.getLogger(__name__) diff --git a/pyrogram/client/methods/messages/get_messages.py b/pyrogram/client/methods/messages/get_messages.py index e505b566b3..caf5bea019 100644 --- a/pyrogram/client/methods/messages/get_messages.py +++ b/pyrogram/client/methods/messages/get_messages.py @@ -17,12 +17,10 @@ # along with Pyrogram. If not, see . import logging -import time from typing import Union, Iterable, List import pyrogram from pyrogram.api import functions, types -from pyrogram.errors import FloodWait from ...ext import BaseClient, utils log = logging.getLogger(__name__) diff --git a/pyrogram/client/methods/users/get_common_chats.py b/pyrogram/client/methods/users/get_common_chats.py index 323c5e876f..35d037fbd6 100644 --- a/pyrogram/client/methods/users/get_common_chats.py +++ b/pyrogram/client/methods/users/get_common_chats.py @@ -35,7 +35,7 @@ def get_common_chats(self, user_id: Union[int, str]) -> list: Returns: List of :obj:`Chat`: On success, a list of the common chats is returned. - + Raises: ValueError: If the user_id doesn't belong to a user. @@ -58,5 +58,5 @@ def get_common_chats(self, user_id: Union[int, str]) -> list: ) return pyrogram.List([pyrogram.Chat._parse_chat(self, x) for x in r.chats]) - + raise ValueError('The user_id "{}" doesn\'t belong to a user'.format(user_id)) diff --git a/pyrogram/client/methods/users/update_profile.py b/pyrogram/client/methods/users/update_profile.py index 91a7950ccd..da7d62f4b3 100644 --- a/pyrogram/client/methods/users/update_profile.py +++ b/pyrogram/client/methods/users/update_profile.py @@ -28,13 +28,13 @@ def update_profile( bio: str = None ) -> bool: """Update your profile details such as first name, last name and bio. - + You can omit the parameters you don't want to change. - + Parameters: first_name (``str``, *optional*): The new first name. - + last_name (``str``, *optional*): The new last name. Pass "" (empty string) to remove it. @@ -42,19 +42,19 @@ def update_profile( bio (``str``, *optional*): The new bio, also known as "about". Max 70 characters. Pass "" (empty string) to remove it. - + Returns: ``bool``: True on success. - + Example: .. code-block:: python - + # Update your first name only app.update_profile(first_name="Pyrogram") - + # Update first name and bio app.update_profile(first_name="Pyrogram", bio="https://docs.pyrogram.org/") - + # Remove the last name app.update_profile(last_name="") """ diff --git a/pyrogram/client/methods/users/update_username.py b/pyrogram/client/methods/users/update_username.py index 88536842cc..24a12a8e45 100644 --- a/pyrogram/client/methods/users/update_username.py +++ b/pyrogram/client/methods/users/update_username.py @@ -28,7 +28,7 @@ def update_username( username: Union[str, None] ) -> bool: """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 :meth:`~Client.update_chat_username`. diff --git a/pyrogram/client/types/inline_mode/chosen_inline_result.py b/pyrogram/client/types/inline_mode/chosen_inline_result.py index cdc5afc9bc..55ef270dbd 100644 --- a/pyrogram/client/types/inline_mode/chosen_inline_result.py +++ b/pyrogram/client/types/inline_mode/chosen_inline_result.py @@ -33,7 +33,6 @@ from base64 import b64encode from struct import pack -from typing import Union import pyrogram from pyrogram.api import types @@ -41,7 +40,6 @@ from pyrogram.client.types.update import Update from pyrogram.client.types.user_and_chats import User from pyrogram.client.types.messages_and_media import Location -from pyrogram.client.ext import utils class ChosenInlineResult(Object, Update): diff --git a/pyrogram/client/types/messages_and_media/dice.py b/pyrogram/client/types/messages_and_media/dice.py index 7552f083d4..e76cad1efc 100644 --- a/pyrogram/client/types/messages_and_media/dice.py +++ b/pyrogram/client/types/messages_and_media/dice.py @@ -16,14 +16,9 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from struct import pack -from typing import List - import pyrogram from pyrogram.api import types -from .thumbnail import Thumbnail from ..object import Object -from ...ext.utils import encode_file_id, encode_file_ref class Dice(Object): diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index 215f86d006..476067152d 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -2964,7 +2964,7 @@ def retract_vote( chat_id=message.chat.id, message_id=message_id, ) - + Example: .. code-block:: python @@ -3074,7 +3074,7 @@ def vote( Parameters: option (``int``): Index of the poll option you want to vote for (0 to 9). - + Returns: :obj:`Poll`: On success, the poll with the chosen option is returned. diff --git a/pyrogram/session/internals/msg_factory.py b/pyrogram/session/internals/msg_factory.py index e5144fe402..3f4c302648 100644 --- a/pyrogram/session/internals/msg_factory.py +++ b/pyrogram/session/internals/msg_factory.py @@ -23,7 +23,7 @@ from .msg_id import MsgId from .seq_no import SeqNo -not_content_related = [Ping, HttpWait, MsgsAck, MsgContainer] +not_content_related = (Ping, HttpWait, MsgsAck, MsgContainer) class MsgFactory: @@ -34,6 +34,6 @@ def __call__(self, body: TLObject) -> Message: return Message( body, MsgId(), - self.seq_no(type(body) not in not_content_related), + self.seq_no(not isinstance(body, not_content_related)), len(body) ) From 5a0a0b67bfa5d4111d12bef19cb26feec93269bc Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 15 Jul 2020 10:58:15 +0200 Subject: [PATCH 0248/1185] Use f-string in the main example --- docs/source/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/index.rst b/docs/source/index.rst index 6a8ae4f595..fd227079e9 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -106,7 +106,7 @@ Welcome to Pyrogram @app.on_message(Filters.private) def hello(client, message): - message.reply_text("Hello {}".format(message.from_user.first_name)) + message.reply_text(f"Hello {message.from_user.first_name}") app.run() From 2504286365c602f893d9ac5d5137e6a145d5234a Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 17 Jul 2020 13:41:24 +0200 Subject: [PATCH 0249/1185] Do not reload plugins from disk Reloading from disk causes modules to be re-evaluated, and this is often not desirable. This will break the ability to hot reload plugins --- 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 8d53e354e3..af67cc1aa9 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -27,7 +27,7 @@ import time from configparser import ConfigParser from hashlib import sha256, md5 -from importlib import import_module, reload +from importlib import import_module from pathlib import Path from signal import signal, SIGINT, SIGTERM, SIGABRT from threading import Thread @@ -1523,7 +1523,7 @@ def load_plugins(self): if not include: for path in sorted(Path(root.replace(".", "/")).rglob("*.py")): module_path = '.'.join(path.parent.parts + (path.stem,)) - module = reload(import_module(module_path)) + module = import_module(module_path) for name in vars(module).keys(): # noinspection PyBroadException @@ -1545,7 +1545,7 @@ def load_plugins(self): warn_non_existent_functions = True try: - module = reload(import_module(module_path)) + module = import_module(module_path) except ImportError: log.warning('[{}] [LOAD] Ignoring non-existent module "{}"'.format( self.session_name, module_path)) From 589be971667267aaa59680912a2279dba9b27d45 Mon Sep 17 00:00:00 2001 From: Mendel E <31070530+mendelmaleh@users.noreply.github.com> Date: Mon, 20 Jul 2020 22:04:24 -0400 Subject: [PATCH 0250/1185] Add parse_mode property to Client (#443) * Add parse_mode property to Client This breaks set_parse_mode * Add back set_parse_mode for backwards compatibility --- pyrogram/client/client.py | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 3f60b2b06b..c0fbeb54d0 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -143,6 +143,11 @@ class Client(Methods, BaseClient): Your Smart Plugins settings as dict, e.g.: *dict(root="plugins")*. This is an alternative way to setup plugins if you don't want to use the *config.ini* file. + parse_mode (``str``, *optional*): + The parse mode, can be any of: *"combined"*, for the default combined mode. *"markdown"* or *"md"* + to force Markdown-only styles. *"html"* to force HTML-only styles. *None* to disable the parser + completely. + no_updates (``bool``, *optional*): Pass True to completely disable incoming updates for the current session. When updates are disabled your client can't receive any new message. @@ -184,6 +189,7 @@ def __init__( workdir: str = BaseClient.WORKDIR, config_file: str = BaseClient.CONFIG_FILE, plugins: dict = None, + parse_mode: str = BaseClient.PARSE_MODES[0], no_updates: bool = None, takeout: bool = None, sleep_threshold: int = Session.SLEEP_THRESHOLD @@ -210,6 +216,7 @@ def __init__( self.workdir = Path(workdir) self.config_file = Path(config_file) self.plugins = plugins + self.parse_mode = parse_mode self.no_updates = no_updates self.takeout = takeout self.sleep_threshold = sleep_threshold @@ -1145,6 +1152,21 @@ def export_session_string(self): """ return self.storage.export_session_string() + @property + def parse_mode(self): + return self._parse_mode + + @parse_mode.setter + def parse_mode(self, parse_mode: Union[str, None] = "combined"): + if parse_mode not in self.PARSE_MODES: + raise ValueError('parse_mode must be one of {} or None. Not "{}"'.format( + ", ".join('"{}"'.format(m) for m in self.PARSE_MODES[:-1]), + parse_mode + )) + + self._parse_mode = parse_mode + + # TODO: redundant, remove in next major version def set_parse_mode(self, parse_mode: Union[str, None] = "combined"): """Set the parse mode to be used globally by the client. @@ -1190,12 +1212,6 @@ def set_parse_mode(self, parse_mode: Union[str, None] = "combined"): app.send_message("haskell", "5. **markdown** and html") """ - if parse_mode not in self.PARSE_MODES: - raise ValueError('parse_mode must be one of {} or None. Not "{}"'.format( - ", ".join('"{}"'.format(m) for m in self.PARSE_MODES[:-1]), - parse_mode - )) - self.parse_mode = parse_mode def fetch_peers(self, peers: List[Union[types.User, types.Chat, types.Channel]]) -> bool: From c27f811620bd0fdd52b5111040ce9daa20822b96 Mon Sep 17 00:00:00 2001 From: Mendel E <31070530+mendelmaleh@users.noreply.github.com> Date: Mon, 20 Jul 2020 22:04:24 -0400 Subject: [PATCH 0251/1185] Add parse_mode property to Client (#443) * Add parse_mode property to Client This breaks set_parse_mode * Add back set_parse_mode for backwards compatibility --- pyrogram/client/client.py | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index af67cc1aa9..7927510357 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -143,6 +143,11 @@ class Client(Methods, BaseClient): Your Smart Plugins settings as dict, e.g.: *dict(root="plugins")*. This is an alternative way to setup plugins if you don't want to use the *config.ini* file. + parse_mode (``str``, *optional*): + The parse mode, can be any of: *"combined"*, for the default combined mode. *"markdown"* or *"md"* + to force Markdown-only styles. *"html"* to force HTML-only styles. *None* to disable the parser + completely. + no_updates (``bool``, *optional*): Pass True to completely disable incoming updates for the current session. When updates are disabled your client can't receive any new message. @@ -184,6 +189,7 @@ def __init__( workdir: str = BaseClient.WORKDIR, config_file: str = BaseClient.CONFIG_FILE, plugins: dict = None, + parse_mode: str = BaseClient.PARSE_MODES[0], no_updates: bool = None, takeout: bool = None, sleep_threshold: int = Session.SLEEP_THRESHOLD @@ -210,6 +216,7 @@ def __init__( self.workdir = Path(workdir) self.config_file = Path(config_file) self.plugins = plugins + self.parse_mode = parse_mode self.no_updates = no_updates self.takeout = takeout self.sleep_threshold = sleep_threshold @@ -1131,6 +1138,21 @@ def export_session_string(self): """ return self.storage.export_session_string() + @property + def parse_mode(self): + return self._parse_mode + + @parse_mode.setter + def parse_mode(self, parse_mode: Union[str, None] = "combined"): + if parse_mode not in self.PARSE_MODES: + raise ValueError('parse_mode must be one of {} or None. Not "{}"'.format( + ", ".join('"{}"'.format(m) for m in self.PARSE_MODES[:-1]), + parse_mode + )) + + self._parse_mode = parse_mode + + # TODO: redundant, remove in next major version def set_parse_mode(self, parse_mode: Union[str, None] = "combined"): """Set the parse mode to be used globally by the client. @@ -1176,12 +1198,6 @@ def set_parse_mode(self, parse_mode: Union[str, None] = "combined"): app.send_message("haskell", "5. **markdown** and html") """ - if parse_mode not in self.PARSE_MODES: - raise ValueError('parse_mode must be one of {} or None. Not "{}"'.format( - ", ".join('"{}"'.format(m) for m in self.PARSE_MODES[:-1]), - parse_mode - )) - self.parse_mode = parse_mode def fetch_peers(self, peers: List[Union[types.User, types.Chat, types.Channel]]) -> bool: From e5fda6f9a0833e99aa433c0f24b2e573df2a25f0 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 26 Jul 2020 12:28:15 +0200 Subject: [PATCH 0252/1185] Update API schema to Layer 116 --- compiler/api/source/main_api.tl | 43 ++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl index 3d48a8cb8b..3c073c436b 100644 --- a/compiler/api/source/main_api.tl +++ b/compiler/api/source/main_api.tl @@ -40,10 +40,9 @@ inputMediaUploadedPhoto#1e287d04 flags:# file:InputFile stickers:flags.0?Vector< inputMediaPhoto#b3ba0635 flags:# id:InputPhoto ttl_seconds:flags.0?int = InputMedia; inputMediaGeoPoint#f9c44144 geo_point:InputGeoPoint = InputMedia; inputMediaContact#f8ab7dfb phone_number:string first_name:string last_name:string vcard:string = InputMedia; -inputMediaUploadedDocument#5b38c6c1 flags:# nosound_video:flags.3?true file:InputFile thumb:flags.2?InputFile mime_type:string attributes:Vector stickers:flags.0?Vector ttl_seconds:flags.1?int = InputMedia; +inputMediaUploadedDocument#5b38c6c1 flags:# nosound_video:flags.3?true force_file:flags.4?true file:InputFile thumb:flags.2?InputFile mime_type:string attributes:Vector stickers:flags.0?Vector ttl_seconds:flags.1?int = InputMedia; inputMediaDocument#23ab23d2 flags:# id:InputDocument ttl_seconds:flags.0?int = InputMedia; inputMediaVenue#c13d1c11 geo_point:InputGeoPoint title:string address:string provider:string venue_id:string venue_type:string = InputMedia; -inputMediaGifExternal#4843b0fd url:string q:string = InputMedia; inputMediaPhotoExternal#e5bbfe1a flags:# url:string ttl_seconds:flags.0?int = InputMedia; inputMediaDocumentExternal#fb52dc99 flags:# url:string ttl_seconds:flags.0?int = InputMedia; inputMediaGame#d33f43f3 id:InputGame = InputMedia; @@ -53,7 +52,7 @@ inputMediaPoll#f94e5f1 flags:# poll:Poll correct_answers:flags.0?Vector s inputMediaDice#e66fbf7b emoticon:string = InputMedia; inputChatPhotoEmpty#1ca48f57 = InputChatPhoto; -inputChatUploadedPhoto#927c55b4 file:InputFile = InputChatPhoto; +inputChatUploadedPhoto#c642724e flags:# file:flags.0?InputFile video:flags.1?InputFile video_start_ts:flags.2?double = InputChatPhoto; inputChatPhoto#8953ad37 id:InputPhoto = InputChatPhoto; inputGeoPointEmpty#e4c123d6 = InputGeoPoint; @@ -91,7 +90,7 @@ userEmpty#200250ba id:int = User; user#938458c1 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true id:int access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector bot_inline_placeholder:flags.19?string lang_code:flags.22?string = User; userProfilePhotoEmpty#4f11bae1 = UserProfilePhoto; -userProfilePhoto#ecd75d8c photo_id:long photo_small:FileLocation photo_big:FileLocation dc_id:int = UserProfilePhoto; +userProfilePhoto#69d3ab26 flags:# has_video:flags.0?true photo_id:long photo_small:FileLocation photo_big:FileLocation dc_id:int = UserProfilePhoto; userStatusEmpty#9d05049 = UserStatus; userStatusOnline#edb93949 expires:int = UserStatus; @@ -117,7 +116,7 @@ chatParticipantsForbidden#fc900c2b flags:# chat_id:int self_participant:flags.0? chatParticipants#3f460fed chat_id:int participants:Vector version:int = ChatParticipants; chatPhotoEmpty#37c1011c = ChatPhoto; -chatPhoto#475cdbd5 photo_small:FileLocation photo_big:FileLocation dc_id:int = ChatPhoto; +chatPhoto#d20b9f3c flags:# has_video:flags.0?true photo_small:FileLocation photo_big:FileLocation dc_id:int = ChatPhoto; messageEmpty#83e5de54 id:int = Message; message#452c0e65 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true id:int from_id:flags.8?int to_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to_msg_id:flags.3?int date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector views:flags.10?int edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long restriction_reason:flags.22?Vector = Message; @@ -165,7 +164,7 @@ dialog#2c171f72 flags:# pinned:flags.2?true unread_mark:flags.3?true peer:Peer t dialogFolder#71bd134c flags:# pinned:flags.2?true folder:Folder peer:Peer top_message:int unread_muted_peers_count:int unread_unmuted_peers_count:int unread_muted_messages_count:int unread_unmuted_messages_count:int = Dialog; photoEmpty#2331b22d id:long = Photo; -photo#d07504a5 flags:# has_stickers:flags.0?true id:long access_hash:long file_reference:bytes date:int sizes:Vector dc_id:int = Photo; +photo#fb197a65 flags:# has_stickers:flags.0?true id:long access_hash:long file_reference:bytes date:int sizes:Vector video_sizes:flags.1?Vector dc_id:int = Photo; photoSizeEmpty#e17e23c type:string = PhotoSize; photoSize#77bfb61b type:string location:FileLocation w:int h:int size:int = PhotoSize; @@ -191,7 +190,7 @@ inputPeerNotifySettings#9c3d198e flags:# show_previews:flags.0?Bool silent:flags peerNotifySettings#af509d20 flags:# show_previews:flags.0?Bool silent:flags.1?Bool mute_until:flags.2?int sound:flags.3?string = PeerNotifySettings; -peerSettings#818426cd flags:# report_spam:flags.0?true add_contact:flags.1?true block_contact:flags.2?true share_contact:flags.3?true need_contacts_exception:flags.4?true report_geo:flags.5?true = PeerSettings; +peerSettings#733f2961 flags:# report_spam:flags.0?true add_contact:flags.1?true block_contact:flags.2?true share_contact:flags.3?true need_contacts_exception:flags.4?true report_geo:flags.5?true autoarchived:flags.7?true geo_distance:flags.6?int = PeerSettings; wallPaper#a437c3ed id:long flags:# creator:flags.0?true default:flags.1?true pattern:flags.3?true dark:flags.4?true access_hash:long slug:string document:Document settings:flags.2?WallPaperSettings = WallPaper; wallPaperNoFile#8af40b25 flags:# default:flags.1?true dark:flags.4?true settings:flags.2?WallPaperSettings = WallPaper; @@ -336,6 +335,7 @@ updateDialogFilter#26ffde7d flags:# id:int filter:flags.0?DialogFilter = Update; updateDialogFilterOrder#a5d72105 order:Vector = Update; updateDialogFilters#3504914f = Update; updatePhoneCallSignalingData#2661bf09 phone_call_id:long data:bytes = Update; +updateChannelParticipant#65d2b464 flags:# channel_id:int date:int user_id:int prev_participant:flags.0?ChannelParticipant new_participant:flags.1?ChannelParticipant qts:int = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -373,7 +373,7 @@ help.inviteText#18cb9f78 message:string = help.InviteText; encryptedChatEmpty#ab7ec0a0 id:int = EncryptedChat; encryptedChatWaiting#3bf703dc id:int access_hash:long date:int admin_id:int participant_id:int = EncryptedChat; -encryptedChatRequested#c878527e id:int access_hash:long date:int admin_id:int participant_id:int g_a:bytes = EncryptedChat; +encryptedChatRequested#62718a82 flags:# folder_id:flags.0?int id:int access_hash:long date:int admin_id:int participant_id:int g_a:bytes = EncryptedChat; encryptedChat#fa56ce36 id:int access_hash:long date:int admin_id:int participant_id:int g_a_or_b:bytes key_fingerprint:long = EncryptedChat; encryptedChatDiscarded#13d6dd27 id:int = EncryptedChat; @@ -507,6 +507,7 @@ chatInviteExported#fc2e05bc link:string = ExportedChatInvite; chatInviteAlready#5a686d7c chat:Chat = ChatInvite; chatInvite#dfc2f58e flags:# channel:flags.0?true broadcast:flags.1?true public:flags.2?true megagroup:flags.3?true title:string photo:Photo participants_count:int participants:flags.4?Vector = ChatInvite; +chatInvitePeek#61695cb0 chat:Chat expires:int = ChatInvite; inputStickerSetEmpty#ffb62b95 = InputStickerSet; inputStickerSetID#9de7a269 id:long access_hash:long = InputStickerSet; @@ -597,11 +598,6 @@ channels.channelParticipant#d0d9b163 participant:ChannelParticipant users:Vector help.termsOfService#780a0310 flags:# popup:flags.0?true id:DataJSON text:string entities:Vector min_age_confirm:flags.1?int = help.TermsOfService; -foundGif#162ecc1f url:string thumb_url:string content_url:string content_type:string w:int h:int = FoundGif; -foundGifCached#9c750409 url:string photo:Photo document:Document = FoundGif; - -messages.foundGifs#450a1c0a next_offset:int results:Vector = messages.FoundGifs; - messages.savedGifsNotModified#e8025ca2 = messages.SavedGifs; messages.savedGifs#2e0709a5 hash:int gifs:Vector = messages.SavedGifs; @@ -1119,7 +1115,17 @@ stats.broadcastStats#bdf78394 period:StatsDateRangeDays followers:StatsAbsValueA help.promoDataEmpty#98f6ac75 expires:int = help.PromoData; help.promoData#8c39793f flags:# proxy:flags.0?true expires:int peer:Peer chats:Vector users:Vector psa_type:flags.1?string psa_message:flags.2?string = help.PromoData; -videoSize#435bb987 type:string location:FileLocation w:int h:int size:int = VideoSize; +videoSize#e831c556 flags:# type:string location:FileLocation w:int h:int size:int video_start_ts:flags.0?double = VideoSize; + +statsGroupTopPoster#18f3d0f7 user_id:int messages:int avg_chars:int = StatsGroupTopPoster; + +statsGroupTopAdmin#6014f412 user_id:int deleted:int kicked:int banned:int = StatsGroupTopAdmin; + +statsGroupTopInviter#31962a4c user_id:int invitations:int = StatsGroupTopInviter; + +stats.megagroupStats#ef7ff916 period:StatsDateRangeDays members:StatsAbsValueAndPrev messages:StatsAbsValueAndPrev viewers:StatsAbsValueAndPrev posters:StatsAbsValueAndPrev growth_graph:StatsGraph members_graph:StatsGraph new_members_by_source_graph:StatsGraph languages_graph:StatsGraph messages_graph:StatsGraph actions_graph:StatsGraph top_hours_graph:StatsGraph weekdays_graph:StatsGraph top_posters:Vector top_admins:Vector top_inviters:Vector users:Vector = stats.MegagroupStats; + +globalPrivacySettings#bea2f424 flags:# archive_and_mute_new_noncontact_peers:flags.0?Bool = GlobalPrivacySettings; ---functions--- @@ -1215,6 +1221,8 @@ account.getThemes#285946f8 format:string hash:int = account.Themes; account.setContentSettings#b574b16b flags:# sensitive_enabled:flags.0?true = Bool; account.getContentSettings#8b9b4dae = account.ContentSettings; account.getMultiWallPapers#65ad71dc wallpapers:Vector = Vector; +account.getGlobalPrivacySettings#eb2b4cf6 = GlobalPrivacySettings; +account.setGlobalPrivacySettings#1edaaac2 settings:GlobalPrivacySettings = GlobalPrivacySettings; users.getUsers#d91a548 id:Vector = Vector; users.getFullUser#ca30a5b1 id:InputUser = UserFull; @@ -1290,7 +1298,6 @@ messages.migrateChat#15a3b8e3 chat_id:int = Updates; messages.searchGlobal#bf7225a4 flags:# folder_id:flags.0?int q:string offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; messages.reorderStickerSets#78337739 flags:# masks:flags.0?true order:Vector = Bool; messages.getDocumentByHash#338e2464 sha256:bytes size:int mime_type:string = Document; -messages.searchGifs#bf9a776b q:string offset:int = messages.FoundGifs; messages.getSavedGifs#83bf3d52 hash:int = messages.SavedGifs; messages.saveGif#327a30cb id:InputDocument unsave:Bool = Bool; messages.getInlineBotResults#514e999d flags:# bot:InputUser peer:InputPeer geo_point:flags.0?InputGeoPoint query:string offset:string = messages.BotResults; @@ -1370,7 +1377,7 @@ updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date: updates.getChannelDifference#3173d78 flags:# force:flags.0?true channel:InputChannel filter:ChannelMessagesFilter pts:int limit:int = updates.ChannelDifference; photos.updateProfilePhoto#f0bb5152 id:InputPhoto = UserProfilePhoto; -photos.uploadProfilePhoto#4f32c098 file:InputFile = photos.Photo; +photos.uploadProfilePhoto#89f30f69 flags:# file:flags.0?InputFile video:flags.1?InputFile video_start_ts:flags.2?double = photos.Photo; photos.deletePhotos#87cf7f2f id:Vector = Vector; photos.getUserPhotos#91cd32a8 user_id:InputUser offset:int max_id:long limit:int = photos.Photos; @@ -1403,6 +1410,7 @@ help.getUserInfo#38a08d3 user_id:InputUser = help.UserInfo; help.editUserInfo#66b91b70 user_id:InputUser message:string entities:Vector = help.UserInfo; help.getPromoData#c0977421 = help.PromoData; help.hidePromoData#1e251c95 peer:InputPeer = Bool; +help.dismissSuggestion#77fa99f suggestion:string = Bool; channels.readHistory#cc104937 channel:InputChannel max_id:int = Bool; channels.deleteMessages#84c1fd4e channel:InputChannel id:Vector = messages.AffectedMessages; @@ -1479,5 +1487,6 @@ folders.deleteFolder#1c295881 folder_id:int = Updates; stats.getBroadcastStats#ab42441a flags:# dark:flags.0?true channel:InputChannel = stats.BroadcastStats; stats.loadAsyncGraph#621d5fa0 flags:# token:string x:flags.0?long = StatsGraph; +stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel = stats.MegagroupStats; -// LAYER 114 \ No newline at end of file +// LAYER 116 \ No newline at end of file From b29c5fdc69e4d3af274761dac22acc962c6f6ff1 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 26 Jul 2020 20:23:58 +0200 Subject: [PATCH 0253/1185] Force document when using send_document --- pyrogram/client/methods/messages/send_document.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyrogram/client/methods/messages/send_document.py b/pyrogram/client/methods/messages/send_document.py index 2241754b8c..8faf837474 100644 --- a/pyrogram/client/methods/messages/send_document.py +++ b/pyrogram/client/methods/messages/send_document.py @@ -152,6 +152,7 @@ def progress(current, total): media = types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(document) or "application/zip", file=file, + force_file=True, thumb=thumb, attributes=[ types.DocumentAttributeFilename(file_name=file_name or os.path.basename(document)) From aaedeffff9cf489d46b397964d8d2120d2c9cb17 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 26 Jul 2020 23:17:53 +0200 Subject: [PATCH 0254/1185] Update instructions for accessing test servers in tdesktop --- docs/source/topics/test-servers.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/topics/test-servers.rst b/docs/source/topics/test-servers.rst index 451fc98a23..1906c2f159 100644 --- a/docs/source/topics/test-servers.rst +++ b/docs/source/topics/test-servers.rst @@ -30,7 +30,7 @@ Test Mode in Official Apps You can also login yourself into test servers using official desktop apps, such as Webogram and TDesktop: - **Webogram**: Login here: https://web.telegram.org/?test=1 -- **TDesktop**: Open settings and type ``testmode``. +- **TDesktop**: Hold ``Alt+Shift`` and right click on "Add account", then choose "Test server". Test Numbers ------------ From 9a8057074bacbd1983c379b30a447808f52ee3af Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 26 Jul 2020 23:18:26 +0200 Subject: [PATCH 0255/1185] Allow uploading profile photos using file-like objects --- pyrogram/client/methods/users/set_profile_photo.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/methods/users/set_profile_photo.py b/pyrogram/client/methods/users/set_profile_photo.py index ae627110b0..e7357030ea 100644 --- a/pyrogram/client/methods/users/set_profile_photo.py +++ b/pyrogram/client/methods/users/set_profile_photo.py @@ -18,22 +18,26 @@ from pyrogram.api import functions from ...ext import BaseClient +from typing import Union, BinaryIO class SetProfilePhoto(BaseClient): def set_profile_photo( self, - photo: str + photo: Union[str, BinaryIO] ) -> bool: """Set a new profile photo. + If you want to set a profile video instead, use :meth:`~Client.set_profile_video` + This method only works for Users. Bots profile photos must be set using BotFather. Parameters: photo (``str``): Profile photo to set. - Pass a file path as string to upload a new photo that exists on your local machine. + Pass a file path as string to upload a new photo that exists on your local machine or + pass a binary file-like object with its attribute ".name" set for in-memory uploads. Returns: ``bool``: True on success. From 2034a785f4492850850318e3b3368fe48cdd97a0 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 26 Jul 2020 23:18:52 +0200 Subject: [PATCH 0256/1185] Add set_profile_video method --- compiler/docs/compiler.py | 1 + pyrogram/client/methods/users/__init__.py | 4 +- .../client/methods/users/set_profile_video.py | 57 +++++++++++++++++++ 3 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 pyrogram/client/methods/users/set_profile_video.py diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index 8afd99f841..d0c1f61cde 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -225,6 +225,7 @@ def get_title_list(s: str) -> list: get_profile_photos_count iter_profile_photos set_profile_photo + set_profile_video delete_profile_photos update_username update_profile diff --git a/pyrogram/client/methods/users/__init__.py b/pyrogram/client/methods/users/__init__.py index 65171af674..e80a1c764f 100644 --- a/pyrogram/client/methods/users/__init__.py +++ b/pyrogram/client/methods/users/__init__.py @@ -25,6 +25,7 @@ from .get_users import GetUsers from .iter_profile_photos import IterProfilePhotos from .set_profile_photo import SetProfilePhoto +from .set_profile_video import SetProfileVideo from .unblock_user import UnblockUser from .update_profile import UpdateProfile from .update_username import UpdateUsername @@ -42,6 +43,7 @@ class Users( GetProfilePhotosCount, IterProfilePhotos, UnblockUser, - UpdateProfile + UpdateProfile, + SetProfileVideo ): pass diff --git a/pyrogram/client/methods/users/set_profile_video.py b/pyrogram/client/methods/users/set_profile_video.py new file mode 100644 index 0000000000..82213a6546 --- /dev/null +++ b/pyrogram/client/methods/users/set_profile_video.py @@ -0,0 +1,57 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan +# +# 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 Union, BinaryIO + +from pyrogram.api import functions +from ...ext import BaseClient + + +class SetProfileVideo(BaseClient): + def set_profile_video( + self, + video: Union[str, BinaryIO] + ) -> bool: + """Set a new profile video (H.264/MPEG-4 AVC video, max 5 seconds). + + If you want to set a profile photo instead, use :meth:`~Client.set_profile_photo` + + This method only works for Users. + + Parameters: + video (``str``): + Profile video to set. + Pass a file path as string to upload a new video that exists on your local machine or + pass a binary file-like object with its attribute ".name" set for in-memory uploads. + + Returns: + ``bool``: True on success. + + Example: + .. code-block:: python + + app.set_profile_video("new_video.mp4") + """ + + return bool( + self.send( + functions.photos.UploadProfilePhoto( + video=self.save_file(video) + ) + ) + ) From 8a14f5842123276aa3cd2434e3fbf0ba0c6aa3a7 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 27 Jul 2020 13:33:17 +0200 Subject: [PATCH 0257/1185] Allow uploading chat photos using BytesIO objects --- .../client/methods/chats/set_chat_photo.py | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/pyrogram/client/methods/chats/set_chat_photo.py b/pyrogram/client/methods/chats/set_chat_photo.py index e22b6819b3..593da47a34 100644 --- a/pyrogram/client/methods/chats/set_chat_photo.py +++ b/pyrogram/client/methods/chats/set_chat_photo.py @@ -17,7 +17,7 @@ # along with Pyrogram. If not, see . import os -from typing import Union +from typing import Union, BinaryIO from pyrogram.api import functions, types from ...ext import BaseClient, utils @@ -27,7 +27,7 @@ class SetChatPhoto(BaseClient): def set_chat_photo( self, chat_id: Union[int, str], - photo: str, + photo: Union[str, BinaryIO], file_ref: str = None ) -> bool: """Set a new profile photo for the chat. @@ -39,12 +39,13 @@ def set_chat_photo( Unique identifier (int) or username (str) of the target chat. photo (``str``): - New chat photo. You can pass a :obj:`Photo` file_id or a file path to upload a new photo from your local - machine. + New chat photo. You can pass a :obj:`Photo` file_id (in pair with a valid file_ref), a file path to + upload a new photo from your local machine or a binary file-like object with its attribute ".name" + set for in-memory uploads. file_ref (``str``, *optional*): A valid file reference obtained by a recently fetched media message. - To be used in combination with a file id in case a file reference is needed. + To be used in combination with a file_id in case a file reference is needed. Returns: ``bool``: True on success. @@ -63,11 +64,14 @@ def set_chat_photo( """ peer = self.resolve_peer(chat_id) - if os.path.isfile(photo): - photo = types.InputChatUploadedPhoto(file=self.save_file(photo)) + if isinstance(photo, str): + if os.path.isfile(photo): + photo = types.InputChatUploadedPhoto(file=self.save_file(photo)) + else: + photo = utils.get_input_media_from_file_id(photo, file_ref, 2) + photo = types.InputChatPhoto(id=photo.id) else: - photo = utils.get_input_media_from_file_id(photo, file_ref, 2) - photo = types.InputChatPhoto(id=photo.id) + photo = types.InputChatUploadedPhoto(file=self.save_file(photo)) if isinstance(peer, types.InputPeerChat): self.send( From 66e7573730dcc68452431d7d3e49ad2308228346 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 27 Jul 2020 13:42:05 +0200 Subject: [PATCH 0258/1185] Add new RPC errors --- compiler/error/source/400_BAD_REQUEST.tsv | 2 ++ 1 file changed, 2 insertions(+) diff --git a/compiler/error/source/400_BAD_REQUEST.tsv b/compiler/error/source/400_BAD_REQUEST.tsv index ba6f68150f..608bcc9d92 100644 --- a/compiler/error/source/400_BAD_REQUEST.tsv +++ b/compiler/error/source/400_BAD_REQUEST.tsv @@ -145,3 +145,5 @@ BROADCAST_PUBLIC_VOTERS_FORBIDDEN Polls with public voters cannot be sent in cha INPUT_FILTER_INVALID The filter is invalid for this query EMOTICON_EMPTY The emoticon parameter is empty EMOTICON_INVALID The emoticon parameter is invalid +VIDEO_FILE_INVALID The video file is invalid +PRIVACY_TOO_LONG Your privacy exception list has exceeded the maximum capacity \ No newline at end of file From 293e63008bd2637d0b021458e88dc6f07e2a210d Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 27 Jul 2020 13:42:27 +0200 Subject: [PATCH 0259/1185] Add set_chat_video method --- compiler/docs/compiler.py | 1 + pyrogram/client/methods/chats/__init__.py | 7 +- .../client/methods/chats/set_chat_video.py | 79 +++++++++++++++++++ 3 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 pyrogram/client/methods/chats/set_chat_video.py diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index d0c1f61cde..b26a4a8fde 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -190,6 +190,7 @@ def get_title_list(s: str) -> list: set_administrator_title export_chat_invite_link set_chat_photo + set_chat_video delete_chat_photo set_chat_title set_chat_description diff --git a/pyrogram/client/methods/chats/__init__.py b/pyrogram/client/methods/chats/__init__.py index 8c7fb3402c..2987133ab7 100644 --- a/pyrogram/client/methods/chats/__init__.py +++ b/pyrogram/client/methods/chats/__init__.py @@ -45,11 +45,13 @@ from .set_chat_permissions import SetChatPermissions from .set_chat_photo import SetChatPhoto from .set_chat_title import SetChatTitle +from .set_chat_video import SetChatVideo +from .set_slow_mode import SetSlowMode from .unarchive_chats import UnarchiveChats from .unban_chat_member import UnbanChatMember from .unpin_chat_message import UnpinChatMessage from .update_chat_username import UpdateChatUsername -from .set_slow_mode import SetSlowMode + class Chats( GetChat, @@ -85,6 +87,7 @@ class Chats( DeleteSupergroup, GetNearbyChats, SetAdministratorTitle, - SetSlowMode + SetSlowMode, + SetChatVideo ): pass diff --git a/pyrogram/client/methods/chats/set_chat_video.py b/pyrogram/client/methods/chats/set_chat_video.py new file mode 100644 index 0000000000..3f29948861 --- /dev/null +++ b/pyrogram/client/methods/chats/set_chat_video.py @@ -0,0 +1,79 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan +# +# 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 Union, BinaryIO + +from pyrogram.api import functions, types +from ...ext import BaseClient + + +class SetChatVideo(BaseClient): + def set_chat_video( + self, + chat_id: Union[int, str], + video: Union[str, BinaryIO], + ) -> bool: + """Set a new profile video for the chat (H.264/MPEG-4 AVC video, max 5 seconds). + + You must be an administrator in the chat for this to work and must have the appropriate admin rights. + + Parameters: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + + video (``str``): + New chat photo. You can pass a :obj:`Photo` file_id (in pair with a valid file_ref), a file path to + upload a new photo from your local machine or a binary file-like object with its attribute ".name" + set for in-memory uploads. + + Returns: + ``bool``: True on success. + + Raises: + ValueError: if a chat_id belongs to user. + + Example: + .. code-block:: python + + # Set chat using a local file + app.set_chat_video(chat_id, "video.mp4") + + # Set chat photo using an exiting Photo file_id + app.set_chat_photo(chat_id, photo.file_id, photo.file_ref) + """ + peer = self.resolve_peer(chat_id) + photo = types.InputChatUploadedPhoto(video=self.save_file(video)) + + if isinstance(peer, types.InputPeerChat): + self.send( + functions.messages.EditChatPhoto( + chat_id=peer.chat_id, + photo=photo + ) + ) + elif isinstance(peer, types.InputPeerChannel): + self.send( + functions.channels.EditPhoto( + channel=peer, + photo=photo + ) + ) + else: + raise ValueError("The chat_id \"{}\" belongs to a user".format(chat_id)) + + return True From d5fc21e6f4c9664dc0781b65c0257ab9ce2cf727 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 27 Jul 2020 14:27:09 +0200 Subject: [PATCH 0260/1185] Get the proper biggest photo size --- pyrogram/client/types/messages_and_media/photo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/types/messages_and_media/photo.py b/pyrogram/client/types/messages_and_media/photo.py index 36d37cacea..fe0678aba3 100644 --- a/pyrogram/client/types/messages_and_media/photo.py +++ b/pyrogram/client/types/messages_and_media/photo.py @@ -82,7 +82,7 @@ def __init__( @staticmethod def _parse(client, photo: types.Photo, ttl_seconds: int = None) -> "Photo": if isinstance(photo, types.Photo): - big = photo.sizes[-1] + big = list(filter(lambda p: isinstance(p, types.PhotoSize), photo.sizes))[-1] return Photo( file_id=encode_file_id( From b745ce95ed5d0a7ac596680218f22571364bd200 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 27 Jul 2020 15:03:23 +0200 Subject: [PATCH 0261/1185] Remove set_chat/profile_video --- compiler/docs/compiler.py | 2 - pyrogram/client/methods/chats/__init__.py | 4 +- .../client/methods/chats/set_chat_photo.py | 37 +++++++-- .../client/methods/chats/set_chat_video.py | 79 ------------------- pyrogram/client/methods/users/__init__.py | 2 - .../client/methods/users/set_profile_photo.py | 34 +++++--- .../client/methods/users/set_profile_video.py | 57 ------------- 7 files changed, 56 insertions(+), 159 deletions(-) delete mode 100644 pyrogram/client/methods/chats/set_chat_video.py delete mode 100644 pyrogram/client/methods/users/set_profile_video.py diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index b26a4a8fde..8afd99f841 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -190,7 +190,6 @@ def get_title_list(s: str) -> list: set_administrator_title export_chat_invite_link set_chat_photo - set_chat_video delete_chat_photo set_chat_title set_chat_description @@ -226,7 +225,6 @@ def get_title_list(s: str) -> list: get_profile_photos_count iter_profile_photos set_profile_photo - set_profile_video delete_profile_photos update_username update_profile diff --git a/pyrogram/client/methods/chats/__init__.py b/pyrogram/client/methods/chats/__init__.py index 2987133ab7..6e4a9228cf 100644 --- a/pyrogram/client/methods/chats/__init__.py +++ b/pyrogram/client/methods/chats/__init__.py @@ -45,7 +45,6 @@ from .set_chat_permissions import SetChatPermissions from .set_chat_photo import SetChatPhoto from .set_chat_title import SetChatTitle -from .set_chat_video import SetChatVideo from .set_slow_mode import SetSlowMode from .unarchive_chats import UnarchiveChats from .unban_chat_member import UnbanChatMember @@ -87,7 +86,6 @@ class Chats( DeleteSupergroup, GetNearbyChats, SetAdministratorTitle, - SetSlowMode, - SetChatVideo + SetSlowMode ): pass diff --git a/pyrogram/client/methods/chats/set_chat_photo.py b/pyrogram/client/methods/chats/set_chat_photo.py index 593da47a34..f5ef954f1e 100644 --- a/pyrogram/client/methods/chats/set_chat_photo.py +++ b/pyrogram/client/methods/chats/set_chat_photo.py @@ -27,10 +27,15 @@ class SetChatPhoto(BaseClient): def set_chat_photo( self, chat_id: Union[int, str], - photo: Union[str, BinaryIO], + *, + photo: Union[str, BinaryIO] = None, + video: Union[str, BinaryIO] = None, file_ref: str = None ) -> bool: - """Set a new profile photo for the chat. + """Set a new chat photo or video (H.264/MPEG-4 AVC video, max 5 seconds). + + The ``photo`` and ``video`` arguments are mutually exclusive. + Pass either one as named argument (see examples below). You must be an administrator in the chat for this to work and must have the appropriate admin rights. @@ -38,11 +43,16 @@ def set_chat_photo( chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. - photo (``str``): + photo (``str`` | ``BinaryIO``, *optional*): New chat photo. You can pass a :obj:`Photo` file_id (in pair with a valid file_ref), a file path to upload a new photo from your local machine or a binary file-like object with its attribute ".name" set for in-memory uploads. + video (``str`` | ``BinaryIO``, *optional*): + New chat video. You can pass a :obj:`Video` file_id (in pair with a valid file_ref), a file path to + upload a new video from your local machine or a binary file-like object with its attribute ".name" + set for in-memory uploads. + file_ref (``str``, *optional*): A valid file reference obtained by a recently fetched media message. To be used in combination with a file_id in case a file reference is needed. @@ -57,21 +67,34 @@ def set_chat_photo( .. code-block:: python # Set chat photo using a local file - app.set_chat_photo(chat_id, "photo.jpg") + app.set_chat_photo(chat_id, photo="photo.jpg") # Set chat photo using an exiting Photo file_id - app.set_chat_photo(chat_id, photo.file_id, photo.file_ref) + app.set_chat_photo(chat_id, photo=photo.file_id, file_ref=photo.file_ref) + + + # Set chat video using a local file + app.set_chat_photo(chat_id, video="video.mp4") + + # Set chat photo using an exiting Video file_id + app.set_chat_photo(chat_id, video=video.file_id, file_ref=video.file_ref) """ peer = self.resolve_peer(chat_id) if isinstance(photo, str): if os.path.isfile(photo): - photo = types.InputChatUploadedPhoto(file=self.save_file(photo)) + photo = types.InputChatUploadedPhoto( + file=self.save_file(photo), + video=self.save_file(video) + ) else: photo = utils.get_input_media_from_file_id(photo, file_ref, 2) photo = types.InputChatPhoto(id=photo.id) else: - photo = types.InputChatUploadedPhoto(file=self.save_file(photo)) + photo = types.InputChatUploadedPhoto( + file=self.save_file(photo), + video=self.save_file(video) + ) if isinstance(peer, types.InputPeerChat): self.send( diff --git a/pyrogram/client/methods/chats/set_chat_video.py b/pyrogram/client/methods/chats/set_chat_video.py deleted file mode 100644 index 3f29948861..0000000000 --- a/pyrogram/client/methods/chats/set_chat_video.py +++ /dev/null @@ -1,79 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan -# -# 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 Union, BinaryIO - -from pyrogram.api import functions, types -from ...ext import BaseClient - - -class SetChatVideo(BaseClient): - def set_chat_video( - self, - chat_id: Union[int, str], - video: Union[str, BinaryIO], - ) -> bool: - """Set a new profile video for the chat (H.264/MPEG-4 AVC video, max 5 seconds). - - You must be an administrator in the chat for this to work and must have the appropriate admin rights. - - Parameters: - chat_id (``int`` | ``str``): - Unique identifier (int) or username (str) of the target chat. - - video (``str``): - New chat photo. You can pass a :obj:`Photo` file_id (in pair with a valid file_ref), a file path to - upload a new photo from your local machine or a binary file-like object with its attribute ".name" - set for in-memory uploads. - - Returns: - ``bool``: True on success. - - Raises: - ValueError: if a chat_id belongs to user. - - Example: - .. code-block:: python - - # Set chat using a local file - app.set_chat_video(chat_id, "video.mp4") - - # Set chat photo using an exiting Photo file_id - app.set_chat_photo(chat_id, photo.file_id, photo.file_ref) - """ - peer = self.resolve_peer(chat_id) - photo = types.InputChatUploadedPhoto(video=self.save_file(video)) - - if isinstance(peer, types.InputPeerChat): - self.send( - functions.messages.EditChatPhoto( - chat_id=peer.chat_id, - photo=photo - ) - ) - elif isinstance(peer, types.InputPeerChannel): - self.send( - functions.channels.EditPhoto( - channel=peer, - photo=photo - ) - ) - else: - raise ValueError("The chat_id \"{}\" belongs to a user".format(chat_id)) - - return True diff --git a/pyrogram/client/methods/users/__init__.py b/pyrogram/client/methods/users/__init__.py index e80a1c764f..8ecf45f6f4 100644 --- a/pyrogram/client/methods/users/__init__.py +++ b/pyrogram/client/methods/users/__init__.py @@ -25,7 +25,6 @@ from .get_users import GetUsers from .iter_profile_photos import IterProfilePhotos from .set_profile_photo import SetProfilePhoto -from .set_profile_video import SetProfileVideo from .unblock_user import UnblockUser from .update_profile import UpdateProfile from .update_username import UpdateUsername @@ -44,6 +43,5 @@ class Users( IterProfilePhotos, UnblockUser, UpdateProfile, - SetProfileVideo ): pass diff --git a/pyrogram/client/methods/users/set_profile_photo.py b/pyrogram/client/methods/users/set_profile_photo.py index e7357030ea..01741df94c 100644 --- a/pyrogram/client/methods/users/set_profile_photo.py +++ b/pyrogram/client/methods/users/set_profile_photo.py @@ -16,42 +16,58 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import Union, BinaryIO + from pyrogram.api import functions from ...ext import BaseClient -from typing import Union, BinaryIO class SetProfilePhoto(BaseClient): def set_profile_photo( self, - photo: Union[str, BinaryIO] + *, + photo: Union[str, BinaryIO] = None, + video: Union[str, BinaryIO] = None ) -> bool: - """Set a new profile photo. + """Set a new profile photo or video (H.264/MPEG-4 AVC video, max 5 seconds). + + The ``photo`` and ``video`` arguments are mutually exclusive. + Pass either one as named argument (see examples below). - If you want to set a profile video instead, use :meth:`~Client.set_profile_video` + .. note:: - This method only works for Users. - Bots profile photos must be set using BotFather. + This method only works for Users. + Bots profile photos must be set using BotFather. Parameters: - photo (``str``): + photo (``str`` | ``BinaryIO``, *optional*): Profile photo to set. Pass a file path as string to upload a new photo that exists on your local machine or pass a binary file-like object with its attribute ".name" set for in-memory uploads. + video (``str`` | ``BinaryIO``, *optional*): + Profile video to set. + Pass a file path as string to upload a new video that exists on your local machine or + pass a binary file-like object with its attribute ".name" set for in-memory uploads. + Returns: ``bool``: True on success. Example: .. code-block:: python - app.set_profile_photo("new_photo.jpg") + # Set a new profile photo + app.set_profile_photo(photo="new_photo.jpg") + + # Set a new profile video + app.set_profile_photo(video="new_video.mp4") """ return bool( self.send( functions.photos.UploadProfilePhoto( - file=self.save_file(photo) + file=self.save_file(photo), + video=self.save_file(video) ) ) ) diff --git a/pyrogram/client/methods/users/set_profile_video.py b/pyrogram/client/methods/users/set_profile_video.py deleted file mode 100644 index 82213a6546..0000000000 --- a/pyrogram/client/methods/users/set_profile_video.py +++ /dev/null @@ -1,57 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan -# -# 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 Union, BinaryIO - -from pyrogram.api import functions -from ...ext import BaseClient - - -class SetProfileVideo(BaseClient): - def set_profile_video( - self, - video: Union[str, BinaryIO] - ) -> bool: - """Set a new profile video (H.264/MPEG-4 AVC video, max 5 seconds). - - If you want to set a profile photo instead, use :meth:`~Client.set_profile_photo` - - This method only works for Users. - - Parameters: - video (``str``): - Profile video to set. - Pass a file path as string to upload a new video that exists on your local machine or - pass a binary file-like object with its attribute ".name" set for in-memory uploads. - - Returns: - ``bool``: True on success. - - Example: - .. code-block:: python - - app.set_profile_video("new_video.mp4") - """ - - return bool( - self.send( - functions.photos.UploadProfilePhoto( - video=self.save_file(video) - ) - ) - ) From 516e0a13d59c5c3f72d9a22616bbf5121cd8a2b4 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 27 Jul 2020 15:06:18 +0200 Subject: [PATCH 0262/1185] Simplify None checks for file uploads --- pyrogram/client/client.py | 3 +++ pyrogram/client/methods/messages/edit_message_media.py | 8 ++++---- pyrogram/client/methods/messages/send_animation.py | 4 ++-- pyrogram/client/methods/messages/send_audio.py | 4 ++-- pyrogram/client/methods/messages/send_document.py | 4 ++-- pyrogram/client/methods/messages/send_media_group.py | 2 +- pyrogram/client/methods/messages/send_video.py | 4 ++-- pyrogram/client/methods/messages/send_video_note.py | 4 ++-- 8 files changed, 18 insertions(+), 15 deletions(-) diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index 7927510357..d2add2023a 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -1783,6 +1783,9 @@ def save_file( Raises: RPCError: In case of a Telegram RPC error. """ + if path is None: + return None + part_size = 512 * 1024 if isinstance(path, str): diff --git a/pyrogram/client/methods/messages/edit_message_media.py b/pyrogram/client/methods/messages/edit_message_media.py index 3ab90744d7..6950c25575 100644 --- a/pyrogram/client/methods/messages/edit_message_media.py +++ b/pyrogram/client/methods/messages/edit_message_media.py @@ -109,7 +109,7 @@ def edit_message_media( peer=self.resolve_peer(chat_id), media=types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(media.media) or "video/mp4", - thumb=None if media.thumb is None else self.save_file(media.thumb), + thumb=self.save_file(media.thumb), file=self.save_file(media.media), attributes=[ types.DocumentAttributeVideo( @@ -146,7 +146,7 @@ def edit_message_media( peer=self.resolve_peer(chat_id), media=types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(media.media) or "audio/mpeg", - thumb=None if media.thumb is None else self.save_file(media.thumb), + thumb=self.save_file(media.thumb), file=self.save_file(media.media), attributes=[ types.DocumentAttributeAudio( @@ -182,7 +182,7 @@ def edit_message_media( peer=self.resolve_peer(chat_id), media=types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(media.media) or "video/mp4", - thumb=None if media.thumb is None else self.save_file(media.thumb), + thumb=self.save_file(media.thumb), file=self.save_file(media.media), attributes=[ types.DocumentAttributeVideo( @@ -220,7 +220,7 @@ def edit_message_media( peer=self.resolve_peer(chat_id), media=types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(media.media) or "application/zip", - thumb=None if media.thumb is None else self.save_file(media.thumb), + thumb=self.save_file(media.thumb), file=self.save_file(media.media), attributes=[ types.DocumentAttributeFilename( diff --git a/pyrogram/client/methods/messages/send_animation.py b/pyrogram/client/methods/messages/send_animation.py index a38856a00a..e8d9285fb9 100644 --- a/pyrogram/client/methods/messages/send_animation.py +++ b/pyrogram/client/methods/messages/send_animation.py @@ -167,7 +167,7 @@ def progress(current, total): try: if isinstance(animation, str): if os.path.isfile(animation): - thumb = None if thumb is None else self.save_file(thumb) + thumb = self.save_file(thumb) file = self.save_file(animation, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(animation) or "video/mp4", @@ -191,7 +191,7 @@ def progress(current, total): else: media = utils.get_input_media_from_file_id(animation, file_ref, 10) else: - thumb = None if thumb is None else self.save_file(thumb) + thumb = self.save_file(thumb) file = self.save_file(animation, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(animation.name) or "video/mp4", diff --git a/pyrogram/client/methods/messages/send_audio.py b/pyrogram/client/methods/messages/send_audio.py index 08c03d073c..8dfabe8c5c 100644 --- a/pyrogram/client/methods/messages/send_audio.py +++ b/pyrogram/client/methods/messages/send_audio.py @@ -167,7 +167,7 @@ def progress(current, total): try: if isinstance(audio, str): if os.path.isfile(audio): - thumb = None if thumb is None else self.save_file(thumb) + thumb = self.save_file(thumb) file = self.save_file(audio, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(audio) or "audio/mpeg", @@ -189,7 +189,7 @@ def progress(current, total): else: media = utils.get_input_media_from_file_id(audio, file_ref, 9) else: - thumb = None if thumb is None else self.save_file(thumb) + thumb = self.save_file(thumb) file = self.save_file(audio, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(audio.name) or "audio/mpeg", diff --git a/pyrogram/client/methods/messages/send_document.py b/pyrogram/client/methods/messages/send_document.py index 8faf837474..8ca7fc4e8c 100644 --- a/pyrogram/client/methods/messages/send_document.py +++ b/pyrogram/client/methods/messages/send_document.py @@ -147,7 +147,7 @@ def progress(current, total): try: if isinstance(document, str): if os.path.isfile(document): - thumb = None if thumb is None else self.save_file(thumb) + thumb = self.save_file(thumb) file = self.save_file(document, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(document) or "application/zip", @@ -165,7 +165,7 @@ def progress(current, total): else: media = utils.get_input_media_from_file_id(document, file_ref, 5) else: - thumb = None if thumb is None else self.save_file(thumb) + thumb = self.save_file(thumb) file = self.save_file(document, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(document.name) or "application/zip", diff --git a/pyrogram/client/methods/messages/send_media_group.py b/pyrogram/client/methods/messages/send_media_group.py index 9ca4473b2a..2a2cca74a9 100644 --- a/pyrogram/client/methods/messages/send_media_group.py +++ b/pyrogram/client/methods/messages/send_media_group.py @@ -119,7 +119,7 @@ def send_media_group( peer=self.resolve_peer(chat_id), media=types.InputMediaUploadedDocument( file=self.save_file(i.media), - thumb=None if i.thumb is None else self.save_file(i.thumb), + thumb=self.save_file(i.thumb), mime_type=self.guess_mime_type(i.media) or "video/mp4", attributes=[ types.DocumentAttributeVideo( diff --git a/pyrogram/client/methods/messages/send_video.py b/pyrogram/client/methods/messages/send_video.py index 1f46252f67..40691771ef 100644 --- a/pyrogram/client/methods/messages/send_video.py +++ b/pyrogram/client/methods/messages/send_video.py @@ -164,7 +164,7 @@ def progress(current, total): try: if isinstance(video, str): if os.path.isfile(video): - thumb = None if thumb is None else self.save_file(thumb) + thumb = self.save_file(thumb) file = self.save_file(video, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(video) or "video/mp4", @@ -187,7 +187,7 @@ def progress(current, total): else: media = utils.get_input_media_from_file_id(video, file_ref, 4) else: - thumb = None if thumb is None else self.save_file(thumb) + thumb = self.save_file(thumb) file = self.save_file(video, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(video.name) or "video/mp4", diff --git a/pyrogram/client/methods/messages/send_video_note.py b/pyrogram/client/methods/messages/send_video_note.py index 829f145942..d3b32834ba 100644 --- a/pyrogram/client/methods/messages/send_video_note.py +++ b/pyrogram/client/methods/messages/send_video_note.py @@ -131,7 +131,7 @@ def send_video_note( try: if isinstance(video_note, str): if os.path.isfile(video_note): - thumb = None if thumb is None else self.save_file(thumb) + thumb = self.save_file(thumb) file = self.save_file(video_note, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(video_note) or "video/mp4", @@ -149,7 +149,7 @@ def send_video_note( else: media = utils.get_input_media_from_file_id(video_note, file_ref, 13) else: - thumb = None if thumb is None else self.save_file(thumb) + thumb = self.save_file(thumb) file = self.save_file(video_note, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(video_note.name) or "video/mp4", From b16c5d5fa3241c8f4ba7222ebfe361dcd3b0e92d Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 27 Jul 2020 15:12:24 +0200 Subject: [PATCH 0263/1185] Update Pyrogram's sticker file id --- examples/hello_world.py | 2 +- pyrogram/client/methods/messages/download_media.py | 2 +- pyrogram/client/methods/messages/send_cached_media.py | 2 +- pyrogram/client/methods/messages/send_sticker.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/hello_world.py b/examples/hello_world.py index 19d0ffe76f..5764e97972 100644 --- a/examples/hello_world.py +++ b/examples/hello_world.py @@ -13,4 +13,4 @@ app.send_location("me", 51.500729, -0.124583) # Send a sticker - app.send_sticker("me", "CAADBAADyg4AAvLQYAEYD4F7vcZ43AI") + app.send_sticker("me", "CAADBAADzg4AAvLQYAEz_x2EOgdRwBYE") diff --git a/pyrogram/client/methods/messages/download_media.py b/pyrogram/client/methods/messages/download_media.py index 220543977e..013971358f 100644 --- a/pyrogram/client/methods/messages/download_media.py +++ b/pyrogram/client/methods/messages/download_media.py @@ -98,7 +98,7 @@ def download_media( app.download_media(message) # Download from file id - app.download_media("CAADBAADyg4AAvLQYAEYD4F7vcZ43AI") + app.download_media("CAADBAADzg4AAvLQYAEz_x2EOgdRwBYE") # Keep track of the progress while downloading def progress(current, total): diff --git a/pyrogram/client/methods/messages/send_cached_media.py b/pyrogram/client/methods/messages/send_cached_media.py index 463e9bef1f..1ee139f535 100644 --- a/pyrogram/client/methods/messages/send_cached_media.py +++ b/pyrogram/client/methods/messages/send_cached_media.py @@ -91,7 +91,7 @@ def send_cached_media( Example: .. code-block:: python - app.send_cached_media("me", "CAADBAADyg4AAvLQYAEYD4F7vcZ43AI") + app.send_cached_media("me", "CAADBAADzg4AAvLQYAEz_x2EOgdRwBYE") """ r = self.send( diff --git a/pyrogram/client/methods/messages/send_sticker.py b/pyrogram/client/methods/messages/send_sticker.py index 0de47d640d..529a4f8d59 100644 --- a/pyrogram/client/methods/messages/send_sticker.py +++ b/pyrogram/client/methods/messages/send_sticker.py @@ -110,7 +110,7 @@ def send_sticker( app.send_sticker("me", "sticker.webp") # Send sticker using file_id - app.send_sticker("me", "CAADBAADyg4AAvLQYAEYD4F7vcZ43AI") + app.send_sticker("me", "CAADBAADzg4AAvLQYAEz_x2EOgdRwBYE") """ file = None From d3a34f1084ee561a252b6bb8298e51f04a468f44 Mon Sep 17 00:00:00 2001 From: Said Date: Mon, 27 Jul 2020 16:21:42 +0300 Subject: [PATCH 0264/1185] Add file_name parameter in edit_message_media method (#440) * ability to change file_name in editmessagemedia method * ability to change file_name in editmessagemedia method * ability to change file_name in editmessagemedia method * Update edit_message_media.py * Update input_media_document.py Co-authored-by: GadzhievSA Co-authored-by: Dan <14043624+delivrance@users.noreply.github.com> --- .../client/methods/messages/edit_message_media.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/pyrogram/client/methods/messages/edit_message_media.py b/pyrogram/client/methods/messages/edit_message_media.py index 6950c25575..9e91e945da 100644 --- a/pyrogram/client/methods/messages/edit_message_media.py +++ b/pyrogram/client/methods/messages/edit_message_media.py @@ -36,7 +36,8 @@ def edit_message_media( chat_id: Union[int, str], message_id: int, media: InputMedia, - reply_markup: "pyrogram.InlineKeyboardMarkup" = None + reply_markup: "pyrogram.InlineKeyboardMarkup" = None, + file_name: str = None ) -> "pyrogram.Message": """Edit animation, audio, document, photo or video messages. @@ -58,6 +59,10 @@ def edit_message_media( reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): An InlineKeyboardMarkup object. + file_name (``str``, *optional*): + File name of the media to be sent. Not applicable to photos. + Defaults to file's path basename. + Returns: :obj:`Message`: On success, the edited message is returned. @@ -119,7 +124,7 @@ def edit_message_media( h=media.height ), types.DocumentAttributeFilename( - file_name=os.path.basename(media.media) + file_name=file_name or os.path.basename(media.media) ) ] ) @@ -155,7 +160,7 @@ def edit_message_media( title=media.title ), types.DocumentAttributeFilename( - file_name=os.path.basename(media.media) + file_name=file_name or os.path.basename(media.media) ) ] ) @@ -192,7 +197,7 @@ def edit_message_media( h=media.height ), types.DocumentAttributeFilename( - file_name=os.path.basename(media.media) + file_name=file_name or os.path.basename(media.media) ), types.DocumentAttributeAnimated() ] @@ -224,7 +229,7 @@ def edit_message_media( file=self.save_file(media.media), attributes=[ types.DocumentAttributeFilename( - file_name=os.path.basename(media.media) + file_name=file_name or os.path.basename(media.media) ) ] ) From c5b7a365af6686479c682ed024c36d7dae7bcc6f Mon Sep 17 00:00:00 2001 From: ColinShark Date: Mon, 27 Jul 2020 15:30:49 +0200 Subject: [PATCH 0265/1185] Add message.link attribute/property (#442) * Add message.link attribute Adds the functionality to access the message link. Either as `t.me/c/` or `t.me/username` format. * Change message.link logic Property now has a returned type, list is a tuple and we use `utils.get_channel_id` to not rely on str.replace * Update message.py - Prepend https:// like official clients do - Use .format() Co-authored-by: Dan <14043624+delivrance@users.noreply.github.com> --- pyrogram/client/types/messages_and_media/message.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index 476067152d..f2f6abab7a 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -251,6 +251,9 @@ class Message(Object, Update): Messages sent from yourself to other chats are outgoing (*outgoing* is True). An exception is made for your own personal chat; messages sent there will be incoming. + link (``str``): + A link to the message, only for groups and channels. + matches (List of regex Matches, *optional*): A list containing all `Match Objects `_ that match the text of this message. Only applicable when using :obj:`Filters.regex `. @@ -670,6 +673,13 @@ def _parse(client, message: types.Message or types.MessageService or types.Messa return parsed_message + @property + def link(self) -> str: + if self.chat.type in ("group", "supergroup", "channel") and self.chat.username: + return "https://t.me/{}/{}".format(self.chat.username, self.message_id) + else: + return "https://t.me/c/{}/{}".format(utils.get_channel_id(self.chat.id), self.message_id) + def reply_text( self, text: str, From 0edf08245b800fdb8529c1286a1754067a9cf728 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 28 Jul 2020 17:38:58 +0200 Subject: [PATCH 0266/1185] Add mention to football dice --- pyrogram/client/methods/messages/send_dice.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/methods/messages/send_dice.py b/pyrogram/client/methods/messages/send_dice.py index 4ed9b92630..b426f93905 100644 --- a/pyrogram/client/methods/messages/send_dice.py +++ b/pyrogram/client/methods/messages/send_dice.py @@ -47,7 +47,7 @@ def send_dice( For a contact that exists in your Telegram address book you can use his phone number (str). emoji (``str``, *optional*): - Emoji on which the dice throw animation is based. Currently, must be one of "🎲", "🎯" or "🏀". + Emoji on which the dice throw animation is based. Currently, must be one of "🎲", "🎯", "🏀" or "⚽️". Defaults to "🎲". disable_notification (``bool``, *optional*): From 68a0119c09ee710a7105fd9695806e5ecb270bef Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 29 Jul 2020 22:46:12 +0200 Subject: [PATCH 0267/1185] Update Pyrogram to v0.18.0 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 301e4e6452..b6cc36dcc4 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "0.17.1" +__version__ = "0.18.0" __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)" __copyright__ = "Copyright (C) 2017-2020 Dan " From cefad3f89a162e0a902f47914d57335c2d13f6a4 Mon Sep 17 00:00:00 2001 From: CyanBook Date: Tue, 4 Aug 2020 01:22:48 +0200 Subject: [PATCH 0268/1185] Update download & upload limits with the newest one (#454) --- docs/source/topics/mtproto-vs-botapi.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/topics/mtproto-vs-botapi.rst b/docs/source/topics/mtproto-vs-botapi.rst index 12b73b535e..aab3525e44 100644 --- a/docs/source/topics/mtproto-vs-botapi.rst +++ b/docs/source/topics/mtproto-vs-botapi.rst @@ -55,7 +55,7 @@ HTTP Bot API. Using Pyrogram you can: .. hlist:: :columns: 1 - - :guilabel:`+` **Upload & download any file, up to 1500 MB each (~1.5 GB)** + - :guilabel:`+` **Upload & download any file, up to 2000 MiB each (~2 GB)** - :guilabel:`--` The Bot API allows uploads and downloads of files only up to 50 MB / 20 MB in size (respectively). .. hlist:: From 4df9357b48935133bb45750f30ffbad223a047dc Mon Sep 17 00:00:00 2001 From: Princic-1837592 <48788808+Princic-1837592@users.noreply.github.com> Date: Thu, 20 Aug 2020 16:38:54 +0200 Subject: [PATCH 0269/1185] Added Filters.all (#464) Useful filter to use as default value when extending Handler class --- pyrogram/client/filters/filters.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyrogram/client/filters/filters.py b/pyrogram/client/filters/filters.py index e872d72431..fd4a947c79 100644 --- a/pyrogram/client/filters/filters.py +++ b/pyrogram/client/filters/filters.py @@ -65,6 +65,9 @@ class Filters: create = create + all = create(lambda _, m: True, "AllFilter") + """Filter all messages.""" + me = create(lambda _, m: bool(m.from_user and m.from_user.is_self), "MeFilter") """Filter messages generated by you yourself.""" From 3bc96b41932c3182a84b05b9fbba688149b0e08d Mon Sep 17 00:00:00 2001 From: CyanBook Date: Fri, 21 Aug 2020 07:21:53 +0200 Subject: [PATCH 0270/1185] Add support for compiled patterns in Filters.regex (#468) * Add support for compiled patterns in Filters.regex and remove extra whitespaces * Update filters.py Co-authored-by: Dan <14043624+delivrance@users.noreply.github.com> --- pyrogram/client/filters/filters.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/pyrogram/client/filters/filters.py b/pyrogram/client/filters/filters.py index fd4a947c79..3a798874ea 100644 --- a/pyrogram/client/filters/filters.py +++ b/pyrogram/client/filters/filters.py @@ -17,7 +17,7 @@ # along with Pyrogram. If not, see . import re -from typing import Callable +from typing import Callable, Union from .filter import Filter from ..types import Message, CallbackQuery, InlineQuery @@ -67,7 +67,7 @@ class Filters: all = create(lambda _, m: True, "AllFilter") """Filter all messages.""" - + me = create(lambda _, m: bool(m.from_user and m.from_user.is_self), "MeFilter") """Filter messages generated by you yourself.""" @@ -296,7 +296,7 @@ def func(flt, message): ) @staticmethod - def regex(pattern: str, flags: int = 0): + def regex(pattern: Union[str, re.Pattern], flags: int = 0): """Filter updates that match a given regular expression pattern. Can be applied to handlers that receive one of the following updates: @@ -309,8 +309,8 @@ def regex(pattern: str, flags: int = 0): stored in the ``matches`` field of the update object itself. Parameters: - pattern (``str``): - The regex pattern as string. + pattern (``str`` | ``Pattern``): + The regex pattern as string or as pre-compiled pattern. flags (``int``, *optional*): Regex flags. @@ -331,7 +331,11 @@ def func(flt, update): return bool(update.matches) - return create(func, "RegexFilter", p=re.compile(pattern, flags)) + return create( + func, + "RegexFilter", + p=pattern if isinstance(pattern, re.Pattern) else re.compile(pattern, flags) + ) # noinspection PyPep8Naming class user(Filter, set): From 879e0bfc2deffafd416555a58017d4ff633c955a Mon Sep 17 00:00:00 2001 From: marcosrandulfegarrido Date: Fri, 21 Aug 2020 07:22:49 +0200 Subject: [PATCH 0271/1185] Fix some syntax errors that are causing errors on python pip package distribution (#467) Fix some syntax errors that are causing errors on python pip package distribution --- pyrogram/client/types/inline_mode/chosen_inline_result.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/types/inline_mode/chosen_inline_result.py b/pyrogram/client/types/inline_mode/chosen_inline_result.py index 55ef270dbd..b7180b0b59 100644 --- a/pyrogram/client/types/inline_mode/chosen_inline_result.py +++ b/pyrogram/client/types/inline_mode/chosen_inline_result.py @@ -77,7 +77,7 @@ def __init__( from_user: User, query: str, location: "pyrogram.Location" = None, - inline_message_id: str = None, + inline_message_id: str = None ): super().__init__(client) From b95587ed31ee5b5ff690360ecf5e2ddf180fd24e Mon Sep 17 00:00:00 2001 From: Yusuf_M_Thon_iD <32301831+Sunda001@users.noreply.github.com> Date: Fri, 21 Aug 2020 12:23:33 +0700 Subject: [PATCH 0272/1185] Small fixes on example docs (#460) --- pyrogram/client/methods/chats/restrict_chat_member.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client/methods/chats/restrict_chat_member.py b/pyrogram/client/methods/chats/restrict_chat_member.py index 925526785c..a070707854 100644 --- a/pyrogram/client/methods/chats/restrict_chat_member.py +++ b/pyrogram/client/methods/chats/restrict_chat_member.py @@ -66,7 +66,7 @@ def restrict_chat_member( app.restrict_chat_member(chat_id, user_id, ChatPermissions()) # Chat member muted for 24h - app.restrict_chat_member(chat_id, user_id, ChatPermissions(), int(time.time() + 86400)) + app.restrict_chat_member(chat_id, user_id, ChatPermissions(), int(time() + 86400)) # Chat member can only send text messages app.restrict_chat_member(chat_id, user_id, ChatPermissions(can_send_messages=True)) From 2e08266f5684a4e3191d879d066c4ca75ea0724e Mon Sep 17 00:00:00 2001 From: CyanBook Date: Fri, 21 Aug 2020 07:24:13 +0200 Subject: [PATCH 0273/1185] Update reply_* bound methods to support BinaryIO (#458) reply_* bound methods weren't added to handle BinaryIO object, now they are. --- .../client/types/messages_and_media/message.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index f2f6abab7a..0d1ee2b63d 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -17,7 +17,7 @@ # along with Pyrogram. If not, see . from functools import partial -from typing import List, Match, Union +from typing import List, Match, Union, BinaryIO import pyrogram from pyrogram.api import types @@ -763,7 +763,7 @@ def reply_text( def reply_animation( self, - animation: str, + animation: Union[str, BinaryIO], file_ref: str = None, quote: bool = None, caption: str = "", @@ -905,7 +905,7 @@ def reply_animation( def reply_audio( self, - audio: str, + audio: Union[str, BinaryIO], file_ref: str = None, quote: bool = None, caption: str = "", @@ -1260,7 +1260,7 @@ def reply_contact( def reply_document( self, - document: str, + document: Union[str, BinaryIO], file_ref: str = None, quote: bool = None, thumb: str = None, @@ -1661,7 +1661,7 @@ def reply_media_group( def reply_photo( self, - photo: str, + photo: Union[str, BinaryIO], file_ref: str = None, quote: bool = None, caption: str = "", @@ -1862,7 +1862,7 @@ def reply_poll( def reply_sticker( self, - sticker: str, + sticker: Union[str, BinaryIO], file_ref: str = None, quote: bool = None, disable_notification: bool = None, @@ -2065,7 +2065,7 @@ def reply_venue( def reply_video( self, - video: str, + video: Union[str, BinaryIO], file_ref: str = None, quote: bool = None, caption: str = "", @@ -2212,7 +2212,7 @@ def reply_video( def reply_video_note( self, - video_note: str, + video_note: Union[str, BinaryIO], file_ref: str = None, quote: bool = None, duration: int = 0, @@ -2335,7 +2335,7 @@ def reply_video_note( def reply_voice( self, - voice: str, + voice: Union[str, BinaryIO], file_ref: str = None, quote: bool = None, caption: str = "", From c8c6faa96e4bdde24e9c99035b7b035d972c21ae Mon Sep 17 00:00:00 2001 From: CyanBook Date: Fri, 21 Aug 2020 07:28:27 +0200 Subject: [PATCH 0274/1185] Change logging hierarchy for loading plugins (#451) Loading plugins shouldn't be considered a warning --- pyrogram/__init__.py | 2 +- pyrogram/client/client.py | 416 ++++++++++-------- pyrogram/client/ext/base_client.py | 53 ++- pyrogram/client/ext/dispatcher.py | 155 ++++--- pyrogram/client/ext/syncer.py | 40 +- pyrogram/client/ext/utils.py | 24 +- .../methods/bots/answer_callback_query.py | 4 +- .../methods/bots/answer_inline_query.py | 11 +- .../methods/bots/get_game_high_scores.py | 8 +- .../methods/bots/get_inline_bot_results.py | 6 +- .../methods/bots/request_callback_answer.py | 6 +- pyrogram/client/methods/bots/send_game.py | 8 +- .../methods/bots/send_inline_bot_result.py | 6 +- .../client/methods/bots/set_game_score.py | 10 +- .../client/methods/chats/add_chat_members.py | 12 +- .../client/methods/chats/archive_chats.py | 21 +- .../client/methods/chats/create_channel.py | 4 +- pyrogram/client/methods/chats/create_group.py | 6 +- .../client/methods/chats/create_supergroup.py | 4 +- .../client/methods/chats/delete_channel.py | 6 +- .../client/methods/chats/delete_chat_photo.py | 8 +- .../client/methods/chats/delete_supergroup.py | 6 +- .../methods/chats/export_chat_invite_link.py | 8 +- pyrogram/client/methods/chats/get_chat.py | 14 +- .../client/methods/chats/get_chat_member.py | 11 +- .../client/methods/chats/get_chat_members.py | 8 +- .../methods/chats/get_chat_members_count.py | 16 +- pyrogram/client/methods/chats/get_dialogs.py | 8 +- .../client/methods/chats/get_dialogs_count.py | 6 +- .../client/methods/chats/get_nearby_chats.py | 4 +- .../client/methods/chats/iter_chat_members.py | 15 +- pyrogram/client/methods/chats/iter_dialogs.py | 29 +- pyrogram/client/methods/chats/join_chat.py | 8 +- .../client/methods/chats/kick_chat_member.py | 12 +- pyrogram/client/methods/chats/leave_chat.py | 12 +- .../client/methods/chats/pin_chat_message.py | 6 +- .../methods/chats/promote_chat_member.py | 8 +- .../methods/chats/restrict_chat_member.py | 8 +- .../methods/chats/set_administrator_title.py | 12 +- .../methods/chats/set_chat_description.py | 6 +- .../methods/chats/set_chat_permissions.py | 6 +- .../client/methods/chats/set_chat_photo.py | 16 +- .../client/methods/chats/set_chat_title.py | 8 +- .../client/methods/chats/set_slow_mode.py | 6 +- .../client/methods/chats/unarchive_chats.py | 21 +- .../client/methods/chats/unban_chat_member.py | 8 +- .../methods/chats/unpin_chat_message.py | 6 +- .../methods/chats/update_chat_username.py | 6 +- .../client/methods/contacts/add_contacts.py | 4 +- .../methods/contacts/delete_contacts.py | 6 +- .../client/methods/contacts/get_contacts.py | 5 +- .../methods/contacts/get_contacts_count.py | 4 +- .../methods/messages/delete_messages.py | 8 +- .../client/methods/messages/download_media.py | 9 +- .../methods/messages/edit_inline_caption.py | 4 +- .../methods/messages/edit_inline_media.py | 6 +- .../messages/edit_inline_reply_markup.py | 4 +- .../methods/messages/edit_inline_text.py | 6 +- .../methods/messages/edit_message_caption.py | 4 +- .../methods/messages/edit_message_media.py | 46 +- .../messages/edit_message_reply_markup.py | 8 +- .../methods/messages/edit_message_text.py | 10 +- .../methods/messages/forward_messages.py | 15 +- .../client/methods/messages/get_history.py | 9 +- .../methods/messages/get_history_count.py | 6 +- .../client/methods/messages/get_messages.py | 9 +- .../client/methods/messages/iter_history.py | 13 +- .../client/methods/messages/read_history.py | 6 +- .../client/methods/messages/retract_vote.py | 6 +- .../client/methods/messages/search_global.py | 17 +- .../methods/messages/search_messages.py | 22 +- .../client/methods/messages/send_animation.py | 22 +- .../client/methods/messages/send_audio.py | 23 +- .../methods/messages/send_cached_media.py | 10 +- .../methods/messages/send_chat_action.py | 6 +- .../client/methods/messages/send_contact.py | 8 +- pyrogram/client/methods/messages/send_dice.py | 13 +- .../client/methods/messages/send_document.py | 20 +- .../client/methods/messages/send_location.py | 8 +- .../methods/messages/send_media_group.py | 30 +- .../client/methods/messages/send_message.py | 12 +- .../client/methods/messages/send_photo.py | 16 +- pyrogram/client/methods/messages/send_poll.py | 8 +- .../client/methods/messages/send_sticker.py | 14 +- .../client/methods/messages/send_venue.py | 8 +- .../client/methods/messages/send_video.py | 20 +- .../methods/messages/send_video_note.py | 18 +- .../client/methods/messages/send_voice.py | 16 +- pyrogram/client/methods/messages/stop_poll.py | 8 +- pyrogram/client/methods/messages/vote_poll.py | 8 +- .../methods/password/change_cloud_password.py | 6 +- .../methods/password/enable_cloud_password.py | 6 +- .../methods/password/remove_cloud_password.py | 6 +- pyrogram/client/methods/users/block_user.py | 6 +- .../methods/users/delete_profile_photos.py | 4 +- .../client/methods/users/get_common_chats.py | 6 +- pyrogram/client/methods/users/get_me.py | 6 +- .../methods/users/get_profile_photos.py | 10 +- .../methods/users/get_profile_photos_count.py | 8 +- pyrogram/client/methods/users/get_users.py | 7 +- .../methods/users/iter_profile_photos.py | 13 +- .../client/methods/users/set_profile_photo.py | 8 +- pyrogram/client/methods/users/unblock_user.py | 6 +- .../client/methods/users/update_profile.py | 4 +- .../client/methods/users/update_username.py | 4 +- pyrogram/client/parser/html.py | 4 +- pyrogram/client/parser/markdown.py | 4 +- pyrogram/client/parser/parser.py | 8 +- .../bots_and_keyboards/callback_query.py | 30 +- .../client/types/inline_mode/inline_query.py | 4 +- .../types/inline_mode/inline_query_result.py | 2 +- .../inline_query_result_animation.py | 4 +- .../inline_query_result_article.py | 4 +- .../inline_mode/inline_query_result_photo.py | 6 +- .../input_text_message_content.py | 4 +- .../types/messages_and_media/message.py | 146 +++--- .../types/messages_and_media/sticker.py | 17 +- pyrogram/client/types/update.py | 4 +- pyrogram/client/types/user_and_chats/chat.py | 66 +-- pyrogram/client/types/user_and_chats/user.py | 8 +- pyrogram/connection/connection.py | 30 +- pyrogram/connection/transport/tcp/__init__.py | 1 + pyrogram/connection/transport/tcp/tcp.py | 67 ++- .../connection/transport/tcp/tcp_abridged.py | 18 +- .../transport/tcp/tcp_abridged_o.py | 18 +- pyrogram/connection/transport/tcp/tcp_full.py | 23 +- .../transport/tcp/tcp_intermediate.py | 16 +- .../transport/tcp/tcp_intermediate_o.py | 16 +- pyrogram/crypto/__init__.py | 1 + pyrogram/crypto/mtproto.py | 62 +++ pyrogram/session/auth.py | 19 +- pyrogram/session/session.py | 265 +++++------ requirements.txt | 4 +- 133 files changed, 1336 insertions(+), 1194 deletions(-) create mode 100644 pyrogram/crypto/mtproto.py diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index b6cc36dcc4..053d33981a 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "0.18.0" +__version__ = "0.18.0-async" __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)" __copyright__ = "Copyright (C) 2017-2020 Dan " diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py index d2add2023a..4d0312f458 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 io import logging import math @@ -23,14 +24,11 @@ import re import shutil import tempfile -import threading -import time from configparser import ConfigParser from hashlib import sha256, md5 from importlib import import_module from pathlib import Path from signal import signal, SIGINT, SIGTERM, SIGABRT -from threading import Thread from typing import Union, List, BinaryIO from pyrogram.api import functions, types @@ -40,11 +38,13 @@ from pyrogram.client.methods.password.utils import compute_check from pyrogram.crypto import AES from pyrogram.errors import ( - PhoneMigrate, NetworkMigrate, SessionPasswordNeeded, PeerIdInvalid, VolumeLocNotFound, UserMigrate, ChannelPrivate, + PhoneMigrate, NetworkMigrate, SessionPasswordNeeded, + PeerIdInvalid, VolumeLocNotFound, UserMigrate, ChannelPrivate, AuthBytesInvalid, BadRequest ) from pyrogram.session import Auth, Session from .ext import utils, Syncer, BaseClient, Dispatcher +from .ext.utils import ainput from .methods import Methods from .storage import Storage, FileStorage, MemoryStorage from .types import User, SentCode, TermsOfService @@ -127,7 +127,7 @@ class Client(Methods, BaseClient): Defaults to False. workers (``int``, *optional*): - Thread pool size for handling incoming updates. + Number of maximum concurrent workers for handling incoming updates. Defaults to 4. workdir (``str``, *optional*): @@ -243,6 +243,12 @@ def __exit__(self, *args): except ConnectionError: pass + async def __aenter__(self): + return await self.start() + + async def __aexit__(self, *args): + await self.stop() + @property def proxy(self): return self._proxy @@ -259,7 +265,7 @@ def proxy(self, value): self._proxy["enabled"] = bool(value.get("enabled", True)) self._proxy.update(value) - def connect(self) -> bool: + async def connect(self) -> bool: """ Connect the client to Telegram servers. @@ -274,16 +280,17 @@ def connect(self) -> bool: raise ConnectionError("Client is already connected") self.load_config() - self.load_session() + await self.load_session() self.session = Session(self, self.storage.dc_id(), self.storage.auth_key()) - self.session.start() + + await self.session.start() self.is_connected = True return bool(self.storage.user_id()) - def disconnect(self): + async def disconnect(self): """Disconnect the client from Telegram servers. Raises: @@ -296,11 +303,11 @@ def disconnect(self): if self.is_initialized: raise ConnectionError("Can't disconnect an initialized client") - self.session.stop() + await self.session.stop() self.storage.close() self.is_connected = False - def initialize(self): + async def initialize(self): """Initialize the client by starting up workers. This method will start updates and download workers. @@ -319,33 +326,26 @@ def initialize(self): self.load_plugins() if not self.no_updates: - for i in range(self.UPDATES_WORKERS): - self.updates_workers_list.append( - Thread( - target=self.updates_worker, - name="UpdatesWorker#{}".format(i + 1) - ) + for _ in range(Client.UPDATES_WORKERS): + self.updates_worker_tasks.append( + asyncio.ensure_future(self.updates_worker()) ) - self.updates_workers_list[-1].start() + logging.info("Started {} UpdatesWorkerTasks".format(Client.UPDATES_WORKERS)) - for i in range(self.DOWNLOAD_WORKERS): - self.download_workers_list.append( - Thread( - target=self.download_worker, - name="DownloadWorker#{}".format(i + 1) - ) + for _ in range(Client.DOWNLOAD_WORKERS): + self.download_worker_tasks.append( + asyncio.ensure_future(self.download_worker()) ) - self.download_workers_list[-1].start() + logging.info("Started {} DownloadWorkerTasks".format(Client.DOWNLOAD_WORKERS)) - self.dispatcher.start() - - Syncer.add(self) + await self.dispatcher.start() + await Syncer.add(self) self.is_initialized = True - def terminate(self): + async def terminate(self): """Terminate the client by shutting down workers. This method does the opposite of :meth:`~Client.initialize`. @@ -358,37 +358,41 @@ def terminate(self): raise ConnectionError("Client is already terminated") if self.takeout_id: - self.send(functions.account.FinishTakeoutSession()) + await self.send(functions.account.FinishTakeoutSession()) log.warning("Takeout session {} finished".format(self.takeout_id)) - Syncer.remove(self) - self.dispatcher.stop() + await Syncer.remove(self) + await self.dispatcher.stop() + + for _ in range(Client.DOWNLOAD_WORKERS): + self.download_queue.put_nowait(None) - for _ in range(self.DOWNLOAD_WORKERS): - self.download_queue.put(None) + for task in self.download_worker_tasks: + await task - for i in self.download_workers_list: - i.join() + self.download_worker_tasks.clear() - self.download_workers_list.clear() + logging.info("Stopped {} DownloadWorkerTasks".format(Client.DOWNLOAD_WORKERS)) if not self.no_updates: - for _ in range(self.UPDATES_WORKERS): - self.updates_queue.put(None) + for _ in range(Client.UPDATES_WORKERS): + self.updates_queue.put_nowait(None) - for i in self.updates_workers_list: - i.join() + for task in self.updates_worker_tasks: + await task - self.updates_workers_list.clear() + self.updates_worker_tasks.clear() - for i in self.media_sessions.values(): - i.stop() + logging.info("Stopped {} UpdatesWorkerTasks".format(Client.UPDATES_WORKERS)) + + for media_session in self.media_sessions.values(): + await media_session.stop() self.media_sessions.clear() self.is_initialized = False - def send_code(self, phone_number: str) -> SentCode: + async def send_code(self, phone_number: str) -> SentCode: """Send the confirmation code to the given phone number. Parameters: @@ -405,7 +409,7 @@ def send_code(self, phone_number: str) -> SentCode: while True: try: - r = self.send( + r = await self.send( functions.auth.SendCode( phone_number=phone_number, api_id=self.api_id, @@ -414,17 +418,17 @@ def send_code(self, phone_number: str) -> SentCode: ) ) except (PhoneMigrate, NetworkMigrate) as e: - self.session.stop() + await self.session.stop() self.storage.dc_id(e.x) - self.storage.auth_key(Auth(self, self.storage.dc_id()).create()) + self.storage.auth_key(await Auth(self, self.storage.dc_id()).create()) self.session = Session(self, self.storage.dc_id(), self.storage.auth_key()) - self.session.start() + await self.session.start() else: return SentCode._parse(r) - def resend_code(self, phone_number: str, phone_code_hash: str) -> SentCode: + async def resend_code(self, phone_number: str, phone_code_hash: str) -> SentCode: """Re-send the confirmation code using a different type. The type of the code to be re-sent is specified in the *next_type* attribute of the :obj:`SentCode` object @@ -445,7 +449,7 @@ def resend_code(self, phone_number: str, phone_code_hash: str) -> SentCode: """ phone_number = phone_number.strip(" +") - r = self.send( + r = await self.send( functions.auth.ResendCode( phone_number=phone_number, phone_code_hash=phone_code_hash @@ -454,7 +458,8 @@ def resend_code(self, phone_number: str, phone_code_hash: str) -> SentCode: return SentCode._parse(r) - def sign_in(self, phone_number: str, phone_code_hash: str, phone_code: str) -> Union[User, TermsOfService, bool]: + async def sign_in(self, phone_number: str, phone_code_hash: str, phone_code: str) -> Union[ + User, TermsOfService, bool]: """Authorize a user in Telegram with a valid confirmation code. Parameters: @@ -479,7 +484,7 @@ def sign_in(self, phone_number: str, phone_code_hash: str, phone_code: str) -> U """ phone_number = phone_number.strip(" +") - r = self.send( + r = await self.send( functions.auth.SignIn( phone_number=phone_number, phone_code_hash=phone_code_hash, @@ -498,7 +503,7 @@ def sign_in(self, phone_number: str, phone_code_hash: str, phone_code: str) -> U return User._parse(self, r.user) - def sign_up(self, phone_number: str, phone_code_hash: str, first_name: str, last_name: str = "") -> User: + async def sign_up(self, phone_number: str, phone_code_hash: str, first_name: str, last_name: str = "") -> User: """Register a new user in Telegram. Parameters: @@ -522,7 +527,7 @@ def sign_up(self, phone_number: str, phone_code_hash: str, first_name: str, last """ phone_number = phone_number.strip(" +") - r = self.send( + r = await self.send( functions.auth.SignUp( phone_number=phone_number, first_name=first_name, @@ -536,7 +541,7 @@ def sign_up(self, phone_number: str, phone_code_hash: str, first_name: str, last return User._parse(self, r.user) - def sign_in_bot(self, bot_token: str) -> User: + async def sign_in_bot(self, bot_token: str) -> User: """Authorize a bot using its bot token generated by BotFather. Parameters: @@ -551,7 +556,7 @@ def sign_in_bot(self, bot_token: str) -> User: """ while True: try: - r = self.send( + r = await self.send( functions.auth.ImportBotAuthorization( flags=0, api_id=self.api_id, @@ -560,28 +565,28 @@ def sign_in_bot(self, bot_token: str) -> User: ) ) except UserMigrate as e: - self.session.stop() + await self.session.stop() self.storage.dc_id(e.x) - self.storage.auth_key(Auth(self, self.storage.dc_id()).create()) + self.storage.auth_key(await Auth(self, self.storage.dc_id()).create()) self.session = Session(self, self.storage.dc_id(), self.storage.auth_key()) - self.session.start() + await self.session.start() else: self.storage.user_id(r.user.id) self.storage.is_bot(True) return User._parse(self, r.user) - def get_password_hint(self) -> str: + async def get_password_hint(self) -> str: """Get your Two-Step Verification password hint. Returns: ``str``: On success, the password hint as string is returned. """ - return self.send(functions.account.GetPassword()).hint + return (await self.send(functions.account.GetPassword())).hint - def check_password(self, password: str) -> User: + async def check_password(self, password: str) -> User: """Check your Two-Step Verification password and log in. Parameters: @@ -594,10 +599,10 @@ def check_password(self, password: str) -> User: Raises: BadRequest: In case the password is invalid. """ - r = self.send( + r = await self.send( functions.auth.CheckPassword( password=compute_check( - self.send(functions.account.GetPassword()), + await self.send(functions.account.GetPassword()), password ) ) @@ -608,7 +613,7 @@ def check_password(self, password: str) -> User: return User._parse(self, r.user) - def send_recovery_code(self) -> str: + async def send_recovery_code(self) -> str: """Send a code to your email to recover your password. Returns: @@ -617,11 +622,11 @@ def send_recovery_code(self) -> str: Raises: BadRequest: In case no recovery email was set up. """ - return self.send( + return (await self.send( functions.auth.RequestPasswordRecovery() - ).email_pattern + )).email_pattern - def recover_password(self, recovery_code: str) -> User: + async def recover_password(self, recovery_code: str) -> User: """Recover your password with a recovery code and log in. Parameters: @@ -634,7 +639,7 @@ def recover_password(self, recovery_code: str) -> User: Raises: BadRequest: In case the recovery code is invalid. """ - r = self.send( + r = await self.send( functions.auth.RecoverPassword( code=recovery_code ) @@ -645,14 +650,14 @@ def recover_password(self, recovery_code: str) -> User: return User._parse(self, r.user) - def accept_terms_of_service(self, terms_of_service_id: str) -> bool: + async def accept_terms_of_service(self, terms_of_service_id: str) -> bool: """Accept the given terms of service. Parameters: terms_of_service_id (``str``): The terms of service identifier. """ - r = self.send( + r = await self.send( functions.help.AcceptTermsOfService( id=types.DataJSON( data=terms_of_service_id @@ -664,15 +669,15 @@ def accept_terms_of_service(self, terms_of_service_id: str) -> bool: return True - def authorize(self) -> User: + async def authorize(self) -> User: if self.bot_token: - return self.sign_in_bot(self.bot_token) + return await self.sign_in_bot(self.bot_token) while True: try: if not self.phone_number: while True: - value = input("Enter phone number or bot token: ") + value = await ainput("Enter phone number or bot token: ") if not value: continue @@ -684,11 +689,11 @@ def authorize(self) -> User: if ":" in value: self.bot_token = value - return self.sign_in_bot(value) + return await self.sign_in_bot(value) else: self.phone_number = value - sent_code = self.send_code(self.phone_number) + sent_code = await self.send_code(self.phone_number) except BadRequest as e: print(e.MESSAGE) self.phone_number = None @@ -697,7 +702,7 @@ def authorize(self) -> User: break if self.force_sms: - sent_code = self.resend_code(self.phone_number, sent_code.phone_code_hash) + sent_code = await self.resend_code(self.phone_number, sent_code.phone_code_hash) print("The confirmation code has been sent via {}".format( { @@ -710,10 +715,10 @@ def authorize(self) -> User: while True: if not self.phone_code: - self.phone_code = input("Enter confirmation code: ") + self.phone_code = await ainput("Enter confirmation code: ") try: - signed_in = self.sign_in(self.phone_number, sent_code.phone_code_hash, self.phone_code) + signed_in = await self.sign_in(self.phone_number, sent_code.phone_code_hash, self.phone_code) except BadRequest as e: print(e.MESSAGE) self.phone_code = None @@ -721,24 +726,24 @@ def authorize(self) -> User: print(e.MESSAGE) while True: - print("Password hint: {}".format(self.get_password_hint())) + print("Password hint: {}".format(await self.get_password_hint())) if not self.password: - self.password = input("Enter password (empty to recover): ") + self.password = await ainput("Enter password (empty to recover): ") try: if not self.password: - confirm = input("Confirm password recovery (y/n): ") + confirm = await ainput("Confirm password recovery (y/n): ") if confirm == "y": - email_pattern = self.send_recovery_code() + email_pattern = await self.send_recovery_code() print("The recovery code has been sent to {}".format(email_pattern)) while True: - recovery_code = input("Enter recovery code: ") + recovery_code = await ainput("Enter recovery code: ") try: - return self.recover_password(recovery_code) + return await self.recover_password(recovery_code) except BadRequest as e: print(e.MESSAGE) except Exception as e: @@ -747,7 +752,7 @@ def authorize(self) -> User: else: self.password = None else: - return self.check_password(self.password) + return await self.check_password(self.password) except BadRequest as e: print(e.MESSAGE) self.password = None @@ -758,11 +763,11 @@ def authorize(self) -> User: return signed_in while True: - first_name = input("Enter first name: ") - last_name = input("Enter last name (empty to skip): ") + first_name = await ainput("Enter first name: ") + last_name = await ainput("Enter last name (empty to skip): ") try: - signed_up = self.sign_up( + signed_up = await self.sign_up( self.phone_number, sent_code.phone_code_hash, first_name, @@ -775,11 +780,11 @@ def authorize(self) -> User: if isinstance(signed_in, TermsOfService): print("\n" + signed_in.text + "\n") - self.accept_terms_of_service(signed_in.id) + await self.accept_terms_of_service(signed_in.id) return signed_up - def log_out(self): + async def log_out(self): """Log out from Telegram and delete the *\\*.session* file. When you log out, the current client is stopped and the storage session deleted. @@ -794,13 +799,13 @@ def log_out(self): # Log out. app.log_out() """ - self.send(functions.auth.LogOut()) - self.stop() + await self.send(functions.auth.LogOut()) + await self.stop() self.storage.delete() return True - def start(self): + async def start(self): """Start the client. This method connects the client to Telegram and, in case of new sessions, automatically manages the full @@ -825,25 +830,25 @@ def start(self): app.stop() """ - is_authorized = self.connect() + is_authorized = await self.connect() try: if not is_authorized: - self.authorize() + await self.authorize() if not self.storage.is_bot() and self.takeout: - self.takeout_id = self.send(functions.account.InitTakeoutSession()).id + self.takeout_id = (await self.send(functions.account.InitTakeoutSession())).id log.warning("Takeout session {} initiated".format(self.takeout_id)) - self.send(functions.updates.GetState()) + await self.send(functions.updates.GetState()) except (Exception, KeyboardInterrupt): - self.disconnect() + await self.disconnect() raise else: - self.initialize() + await self.initialize() return self - def stop(self, block: bool = True): + async def stop(self, block: bool = True): """Stop the Client. This method disconnects the client from Telegram and stops the underlying tasks. @@ -874,18 +879,18 @@ def stop(self, block: bool = True): app.stop() """ - def do_it(): - self.terminate() - self.disconnect() + async def do_it(): + await self.terminate() + await self.disconnect() if block: - do_it() + await do_it() else: - Thread(target=do_it).start() + asyncio.ensure_future(do_it()) return self - def restart(self, block: bool = True): + async def restart(self, block: bool = True): """Restart the Client. This method will first call :meth:`~Client.stop` and then :meth:`~Client.start` in a row in order to restart @@ -921,19 +926,19 @@ def restart(self, block: bool = True): app.stop() """ - def do_it(): - self.stop() - self.start() + async def do_it(): + await self.stop() + await self.start() if block: - do_it() + await do_it() else: - Thread(target=do_it).start() + asyncio.ensure_future(do_it()) return self @staticmethod - def idle(stop_signals: tuple = (SIGINT, SIGTERM, SIGABRT)): + async def idle(stop_signals: tuple = (SIGINT, SIGTERM, SIGABRT)): """Block the main script execution until a signal is received. This static method will run an infinite loop in order to block the main script execution and prevent it from @@ -978,6 +983,7 @@ def idle(stop_signals: tuple = (SIGINT, SIGTERM, SIGABRT)): """ def signal_handler(_, __): + logging.info("Stop signal received ({}). Exiting...".format(_)) Client.is_idling = False for s in stop_signals: @@ -986,9 +992,9 @@ def signal_handler(_, __): Client.is_idling = True while Client.is_idling: - time.sleep(1) + await asyncio.sleep(1) - def run(self): + def run(self, coroutine=None): """Start the client, idle the main script and finally stop the client. This is a convenience method that calls :meth:`~Client.start`, :meth:`~Client.idle` and :meth:`~Client.stop` in @@ -1010,9 +1016,17 @@ def run(self): app.run() """ - self.start() - Client.idle() - self.stop() + loop = asyncio.get_event_loop() + run = loop.run_until_complete + + if coroutine is not None: + run(coroutine) + else: + run(self.start()) + run(Client.idle()) + run(self.stop()) + + loop.close() def add_handler(self, handler: Handler, group: int = 0): """Register an update handler. @@ -1236,12 +1250,9 @@ def fetch_peers(self, peers: List[Union[types.User, types.Chat, types.Channel]]) return is_min - def download_worker(self): - name = threading.current_thread().name - log.debug("{} started".format(name)) - + async def download_worker(self): while True: - packet = self.download_queue.get() + packet = await self.download_queue.get() if packet is None: break @@ -1252,7 +1263,7 @@ def download_worker(self): try: data, directory, file_name, done, progress, progress_args, path = packet - temp_file_path = self.get_file( + temp_file_path = await self.get_file( media_type=data.media_type, dc_id=data.dc_id, document_id=data.document_id, @@ -1289,14 +1300,9 @@ 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): while True: - updates = self.updates_queue.get() + updates = await self.updates_queue.get() if updates is None: break @@ -1328,9 +1334,9 @@ def updates_worker(self): if not isinstance(message, types.MessageEmpty): try: - diff = self.send( + diff = await self.send( functions.updates.GetChannelDifference( - channel=self.resolve_peer(utils.get_channel_id(channel_id)), + channel=await self.resolve_peer(utils.get_channel_id(channel_id)), filter=types.ChannelMessagesFilter( ranges=[types.MessageRange( min_id=update.message.id, @@ -1348,9 +1354,9 @@ def updates_worker(self): users.update({u.id: u for u in diff.users}) chats.update({c.id: c for c in diff.chats}) - self.dispatcher.updates_queue.put((update, users, chats)) + self.dispatcher.updates_queue.put_nowait((update, users, 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, @@ -1359,7 +1365,7 @@ def updates_worker(self): ) if diff.new_messages: - self.dispatcher.updates_queue.put(( + self.dispatcher.updates_queue.put_nowait(( types.UpdateNewMessage( message=diff.new_messages[0], pts=updates.pts, @@ -1369,17 +1375,15 @@ def updates_worker(self): {c.id: c for c in diff.chats} )) else: - self.dispatcher.updates_queue.put((diff.other_updates[0], {}, {})) + self.dispatcher.updates_queue.put_nowait((diff.other_updates[0], {}, {})) elif isinstance(updates, types.UpdateShort): - self.dispatcher.updates_queue.put((updates.update, {}, {})) + self.dispatcher.updates_queue.put_nowait((updates.update, {}, {})) elif isinstance(updates, types.UpdatesTooLong): log.info(updates) except Exception as e: log.error(e, exc_info=True) - log.debug("{} stopped".format(name)) - - def send(self, data: TLObject, retries: int = Session.MAX_RETRIES, timeout: float = Session.WAIT_TIMEOUT): + async def send(self, data: TLObject, retries: int = Session.MAX_RETRIES, timeout: float = Session.WAIT_TIMEOUT): """Send raw Telegram queries. This method makes it possible to manually call every single Telegram API method in a low-level manner. @@ -1417,7 +1421,7 @@ def send(self, data: TLObject, retries: int = Session.MAX_RETRIES, timeout: floa if self.takeout_id: data = functions.InvokeWithTakeout(takeout_id=self.takeout_id, query=data) - r = self.session.send(data, retries, timeout, self.sleep_threshold) + r = await self.session.send(data, retries, timeout, self.sleep_threshold) self.fetch_peers(getattr(r, "users", [])) self.fetch_peers(getattr(r, "chats", [])) @@ -1497,7 +1501,7 @@ def load_config(self): except KeyError: self.plugins = None - def load_session(self): + async def load_session(self): self.storage.open() session_empty = any([ @@ -1512,7 +1516,7 @@ def load_session(self): self.storage.date(0) self.storage.test_mode(self.test_mode) - self.storage.auth_key(Auth(self, self.storage.dc_id()).create()) + self.storage.auth_key(await Auth(self, self.storage.dc_id()).create()) self.storage.user_id(None) self.storage.is_bot(None) @@ -1632,13 +1636,13 @@ def load_plugins(self): self.session_name, name, module_path)) if count > 0: - log.warning('[{}] Successfully loaded {} plugin{} from "{}"'.format( + log.info('[{}] Successfully loaded {} plugin{} from "{}"'.format( self.session_name, count, "s" if count > 1 else "", root)) else: log.warning('[{}] No plugin loaded from "{}"'.format( self.session_name, root)) - def resolve_peer(self, peer_id: Union[int, str]): + async def resolve_peer(self, peer_id: Union[int, str]): """Get the InputPeer of a known peer id. Useful whenever an InputPeer type is required. @@ -1677,7 +1681,7 @@ def resolve_peer(self, peer_id: Union[int, str]): try: return self.storage.get_peer_by_username(peer_id) except KeyError: - self.send( + await self.send( functions.contacts.ResolveUsername( username=peer_id ) @@ -1694,7 +1698,7 @@ def resolve_peer(self, peer_id: Union[int, str]): if peer_type == "user": self.fetch_peers( - self.send( + await self.send( functions.users.GetUsers( id=[ types.InputUser( @@ -1706,13 +1710,13 @@ def resolve_peer(self, peer_id: Union[int, str]): ) ) elif peer_type == "chat": - self.send( + await self.send( functions.messages.GetChats( id=[-peer_id] ) ) else: - self.send( + await self.send( functions.channels.GetChannels( id=[ types.InputChannel( @@ -1728,7 +1732,7 @@ def resolve_peer(self, peer_id: Union[int, str]): except KeyError: raise PeerIdInvalid - def save_file( + async def save_file( self, path: Union[str, BinaryIO], file_id: int = None, @@ -1786,6 +1790,18 @@ def save_file( if path is None: return None + async def worker(session): + while True: + data = await queue.get() + + if data is None: + return + + try: + await asyncio.ensure_future(session.send(data)) + except Exception as e: + logging.error(e) + part_size = 512 * 1024 if isinstance(path, str): @@ -1808,15 +1824,20 @@ def save_file( raise ValueError("Telegram doesn't support uploading files bigger than 2000 MiB") 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 + 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 - - session = Session(self, self.storage.dc_id(), self.storage.auth_key(), is_media=True) - session.start() + pool = [Session(self, self.storage.dc_id(), self.storage.auth_key(), is_media=True) for _ in range(pool_size)] + workers = [asyncio.ensure_future(worker(session)) for session in pool for _ in range(workers_count)] + queue = asyncio.Queue(16) try: + for session in pool: + await session.start() + with fp: fp.seek(part_size * file_part) @@ -1828,25 +1849,21 @@ def save_file( md5_sum = "".join([hex(i)[2:].zfill(2) for i in md5_sum.digest()]) break - for _ in range(3): - if is_big: - rpc = functions.upload.SaveBigFilePart( - file_id=file_id, - file_part=file_part, - file_total_parts=file_total_parts, - bytes=chunk - ) - else: - rpc = functions.upload.SaveFilePart( - file_id=file_id, - file_part=file_part, - bytes=chunk - ) - - if session.send(rpc): - break + if is_big: + rpc = functions.upload.SaveBigFilePart( + file_id=file_id, + file_part=file_part, + file_total_parts=file_total_parts, + bytes=chunk + ) else: - raise AssertionError("Telegram didn't accept chunk #{} of {}".format(file_part, path)) + rpc = functions.upload.SaveFilePart( + file_id=file_id, + file_part=file_part, + bytes=chunk + ) + + await queue.put(rpc) if is_missing_part: return @@ -1857,7 +1874,7 @@ def save_file( file_part += 1 if progress: - progress(min(file_part * part_size, file_size), file_size, *progress_args) + await progress(min(file_part * part_size, file_size), file_size, *progress_args) except Client.StopTransmission: raise except Exception as e: @@ -1878,9 +1895,15 @@ def save_file( md5_checksum=md5_sum ) finally: - session.stop() + for _ in workers: + await queue.put(None) + + await asyncio.gather(*workers) + + for session in pool: + await session.stop() - def get_file( + async def get_file( self, media_type: int, dc_id: int, @@ -1898,23 +1921,23 @@ def get_file( progress: callable, progress_args: tuple = () ) -> str: - with self.media_sessions_lock: + async with self.media_sessions_lock: session = self.media_sessions.get(dc_id, None) if session is None: if dc_id != self.storage.dc_id(): - session = Session(self, dc_id, Auth(self, dc_id).create(), is_media=True) - session.start() + session = Session(self, dc_id, await Auth(self, dc_id).create(), is_media=True) + await session.start() for _ in range(3): - exported_auth = self.send( + exported_auth = await self.send( functions.auth.ExportAuthorization( dc_id=dc_id ) ) try: - session.send( + await session.send( functions.auth.ImportAuthorization( id=exported_auth.id, bytes=exported_auth.bytes @@ -1925,11 +1948,11 @@ def get_file( else: break else: - session.stop() + await session.stop() raise AuthBytesInvalid else: session = Session(self, dc_id, self.storage.auth_key(), is_media=True) - session.start() + await session.start() self.media_sessions[dc_id] = session @@ -1984,7 +2007,7 @@ def get_file( file_name = "" try: - r = session.send( + r = await session.send( functions.upload.GetFile( location=location, offset=offset, @@ -2007,7 +2030,7 @@ def get_file( offset += limit if progress: - progress( + await progress( min(offset, file_size) if file_size != 0 else offset, @@ -2015,7 +2038,7 @@ def get_file( *progress_args ) - r = session.send( + r = await session.send( functions.upload.GetFile( location=location, offset=offset, @@ -2024,13 +2047,16 @@ def get_file( ) elif isinstance(r, types.upload.FileCdnRedirect): - with self.media_sessions_lock: + async with self.media_sessions_lock: cdn_session = self.media_sessions.get(r.dc_id, None) if cdn_session is None: - cdn_session = Session(self, r.dc_id, Auth(self, r.dc_id).create(), is_media=True, is_cdn=True) + cdn_session = Session( + self, + r.dc_id, + await Auth(self, r.dc_id).create(), is_media=True, is_cdn=True) - cdn_session.start() + await cdn_session.start() self.media_sessions[r.dc_id] = cdn_session @@ -2039,7 +2065,7 @@ def get_file( 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, @@ -2049,7 +2075,7 @@ def get_file( 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 @@ -2072,7 +2098,7 @@ def get_file( ) ) - hashes = session.send( + hashes = await session.send( functions.upload.GetCdnFileHashes( file_token=r.file_token, offset=offset @@ -2089,7 +2115,7 @@ def get_file( offset += limit if progress: - progress( + await progress( min(offset, file_size) if file_size != 0 else offset, diff --git a/pyrogram/client/ext/base_client.py b/pyrogram/client/ext/base_client.py index 750dc3fc19..ca4e8f5ba1 100644 --- a/pyrogram/client/ext/base_client.py +++ b/pyrogram/client/ext/base_client.py @@ -16,13 +16,12 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import asyncio import os import platform import re import sys from pathlib import Path -from queue import Queue -from threading import Lock from pyrogram import __version__ from ..parser import Parser @@ -30,7 +29,7 @@ class BaseClient: - class StopTransmission(StopIteration): + class StopTransmission(StopAsyncIteration): pass APP_VERSION = "Pyrogram {}".format(__version__) @@ -52,7 +51,7 @@ class StopTransmission(StopIteration): INVITE_LINK_RE = re.compile(r"^(?:https?://)?(?:www\.)?(?:t(?:elegram)?\.(?:org|me|dog)/joinchat/)([\w-]+)$") DIALOGS_AT_ONCE = 100 UPDATES_WORKERS = 4 - DOWNLOAD_WORKERS = 1 + DOWNLOAD_WORKERS = 4 OFFLINE_SLEEP = 900 WORKERS = 4 WORKDIR = PARENT_DIR @@ -100,24 +99,24 @@ def __init__(self): self.session = None self.media_sessions = {} - self.media_sessions_lock = Lock() + self.media_sessions_lock = asyncio.Lock() self.is_connected = None self.is_initialized = None self.takeout_id = 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_tasks = [] + self.download_queue = asyncio.Queue() + self.download_worker_tasks = [] 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): @@ -126,50 +125,50 @@ def fetch_peers(self, *args, **kwargs): 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 - def get_chat_members_count(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 - def guess_mime_type(self, *args, **kwargs): + async def get_profile_photos(self, *args, **kwargs): pass - def guess_extension(self, *args, **kwargs): + async def edit_message_text(self, *args, **kwargs): pass - def get_profile_photos(self, *args, **kwargs): + async def edit_inline_text(self, *args, **kwargs): pass - def edit_message_text(self, *args, **kwargs): + async def edit_message_media(self, *args, **kwargs): pass - def edit_inline_text(self, *args, **kwargs): + async def edit_inline_media(self, *args, **kwargs): pass - def edit_message_media(self, *args, **kwargs): + async def edit_message_reply_markup(self, *args, **kwargs): pass - def edit_inline_media(self, *args, **kwargs): + async def edit_inline_reply_markup(self, *args, **kwargs): pass - def edit_message_reply_markup(self, *args, **kwargs): + def guess_mime_type(self, *args, **kwargs): pass - def edit_inline_reply_markup(self, *args, **kwargs): + def guess_extension(self, *args, **kwargs): pass diff --git a/pyrogram/client/ext/dispatcher.py b/pyrogram/client/ext/dispatcher.py index 256dd5f25e..818bc60c15 100644 --- a/pyrogram/client/ext/dispatcher.py +++ b/pyrogram/client/ext/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, Lock import pyrogram from pyrogram.api.types import ( @@ -69,105 +67,106 @@ def __init__(self, client, workers: int): self.client = client self.workers = workers - self.workers_list = [] + self.update_worker_tasks = [] self.locks_list = [] - self.updates_queue = Queue() + self.updates_queue = asyncio.Queue() self.groups = OrderedDict() + async def message_parser(update, users, chats): + return await pyrogram.Message._parse( + self.client, update.message, users, chats, + isinstance(update, UpdateNewScheduledMessage) + ), MessageHandler + + async def deleted_messages_parser(update, users, chats): + return utils.parse_deleted_messages(self.client, update), DeletedMessagesHandler + + async def callback_query_parser(update, users, chats): + return await pyrogram.CallbackQuery._parse(self.client, update, users), CallbackQueryHandler + + async def user_status_parser(update, users, chats): + return pyrogram.User._parse_user_status(self.client, update), UserStatusHandler + + async def inline_query_parser(update, users, chats): + return pyrogram.InlineQuery._parse(self.client, update, users), InlineQueryHandler + + async def poll_parser(update, users, chats): + return pyrogram.Poll._parse_update(self.client, update), PollHandler + + async def chosen_inline_result_parser(update, users, chats): + return pyrogram.ChosenInlineResult._parse(self.client, update, users), ChosenInlineResultHandler + self.update_parsers = { - Dispatcher.MESSAGE_UPDATES: - lambda upd, usr, cht: ( - pyrogram.Message._parse( - self.client, - upd.message, - usr, - cht, - isinstance(upd, UpdateNewScheduledMessage) - ), - MessageHandler - ), - - Dispatcher.DELETE_MESSAGES_UPDATES: - lambda upd, usr, cht: (utils.parse_deleted_messages(self.client, upd), DeletedMessagesHandler), - - Dispatcher.CALLBACK_QUERY_UPDATES: - lambda upd, usr, cht: (pyrogram.CallbackQuery._parse(self.client, upd, usr), CallbackQueryHandler), - - (UpdateUserStatus,): - lambda upd, usr, cht: (pyrogram.User._parse_user_status(self.client, upd), UserStatusHandler), - - (UpdateBotInlineQuery,): - lambda upd, usr, cht: (pyrogram.InlineQuery._parse(self.client, upd, usr), InlineQueryHandler), - - (UpdateMessagePoll,): - lambda upd, usr, cht: (pyrogram.Poll._parse_update(self.client, upd), PollHandler), - - (UpdateBotInlineSend,): - lambda upd, usr, cht: (pyrogram.ChosenInlineResult._parse(self.client, upd, usr), - ChosenInlineResultHandler) + Dispatcher.MESSAGE_UPDATES: message_parser, + Dispatcher.DELETE_MESSAGES_UPDATES: deleted_messages_parser, + Dispatcher.CALLBACK_QUERY_UPDATES: callback_query_parser, + (UpdateUserStatus,): user_status_parser, + (UpdateBotInlineQuery,): inline_query_parser, + (UpdateMessagePoll,): poll_parser, + (UpdateBotInlineSend,): chosen_inline_result_parser } self.update_parsers = {key: value for key_tuple, value in self.update_parsers.items() for key in key_tuple} - def start(self): + async def start(self): for i in range(self.workers): - self.locks_list.append(Lock()) + self.locks_list.append(asyncio.Lock()) - self.workers_list.append( - Thread( - target=self.update_worker, - name="UpdateWorker#{}".format(i + 1), - args=(self.locks_list[-1],) - ) + self.update_worker_tasks.append( + asyncio.ensure_future(self.update_worker(self.locks_list[-1])) ) - self.workers_list[-1].start() + logging.info("Started {} UpdateWorkerTasks".format(self.workers)) - def stop(self): - for _ in range(self.workers): - self.updates_queue.put(None) + async def stop(self): + for i in range(self.workers): + self.updates_queue.put_nowait(None) - for worker in self.workers_list: - worker.join() + for i in self.update_worker_tasks: + await i - self.workers_list.clear() - self.locks_list.clear() + self.update_worker_tasks.clear() self.groups.clear() + logging.info("Stopped {} UpdateWorkerTasks".format(self.workers)) + def add_handler(self, handler, group: int): - for lock in self.locks_list: - lock.acquire() + async def fn(): + for lock in self.locks_list: + await lock.acquire() - try: - if group not in self.groups: - self.groups[group] = [] - self.groups = OrderedDict(sorted(self.groups.items())) + try: + if group not in self.groups: + self.groups[group] = [] + self.groups = OrderedDict(sorted(self.groups.items())) - self.groups[group].append(handler) - finally: - for lock in self.locks_list: - lock.release() + self.groups[group].append(handler) + finally: + for lock in self.locks_list: + lock.release() + + asyncio.ensure_future(fn()) def remove_handler(self, handler, group: int): - for lock in self.locks_list: - lock.acquire() + async def fn(): + for lock in self.locks_list: + await lock.acquire() - try: - if group not in self.groups: - raise ValueError("Group {} does not exist. Handler was not removed.".format(group)) + try: + if group not in self.groups: + raise ValueError("Group {} does not exist. Handler was not removed.".format(group)) - self.groups[group].remove(handler) - finally: - for lock in self.locks_list: - lock.release() + self.groups[group].remove(handler) + finally: + for lock in self.locks_list: + lock.release() - def update_worker(self, lock): - name = threading.current_thread().name - log.debug("{} started".format(name)) + asyncio.ensure_future(fn()) + async def update_worker(self, lock): while True: - packet = self.updates_queue.get() + packet = await self.updates_queue.get() if packet is None: break @@ -177,12 +176,12 @@ def update_worker(self, lock): parser = self.update_parsers.get(type(update), None) parsed_update, handler_type = ( - parser(update, users, chats) + await parser(update, users, chats) if parser is not None else (None, type(None)) ) - with lock: + async with lock: for group in self.groups.values(): for handler in group: args = None @@ -202,7 +201,7 @@ def update_worker(self, lock): continue try: - handler.callback(self.client, *args) + await handler.callback(self.client, *args) except pyrogram.StopPropagation: raise except pyrogram.ContinuePropagation: @@ -215,5 +214,3 @@ def update_worker(self, lock): pass except Exception as e: log.error(e, exc_info=True) - - log.debug("{} stopped".format(name)) diff --git a/pyrogram/client/ext/syncer.py b/pyrogram/client/ext/syncer.py index bfe99c9814..65fb210453 100644 --- a/pyrogram/client/ext/syncer.py +++ b/pyrogram/client/ext/syncer.py @@ -16,9 +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 time -from threading import Thread, Event, Lock log = logging.getLogger(__name__) @@ -27,13 +27,18 @@ class Syncer: INTERVAL = 20 clients = {} - thread = None - event = Event() - lock = Lock() + event = None + lock = None @classmethod - def add(cls, client): - with cls.lock: + async def add(cls, client): + if cls.event is None: + cls.event = asyncio.Event() + + if cls.lock is None: + cls.lock = asyncio.Lock() + + async with cls.lock: cls.sync(client) cls.clients[id(client)] = client @@ -42,8 +47,8 @@ def add(cls, client): cls.start() @classmethod - def remove(cls, client): - with cls.lock: + async def remove(cls, client): + async with cls.lock: cls.sync(client) del cls.clients[id(client)] @@ -54,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: + async with 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): try: diff --git a/pyrogram/client/ext/utils.py b/pyrogram/client/ext/utils.py index b46a8038fb..9359ff0596 100644 --- a/pyrogram/client/ext/utils.py +++ b/pyrogram/client/ext/utils.py @@ -16,8 +16,11 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import asyncio import base64 import struct +import sys +from concurrent.futures.thread import ThreadPoolExecutor from typing import List from typing import Union @@ -80,6 +83,15 @@ def decode_file_ref(file_ref: str) -> bytes: return base64.urlsafe_b64decode(file_ref + "=" * (-len(file_ref) % 4)) +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_offset_date(dialogs): for m in reversed(dialogs.messages): if isinstance(m, types.MessageEmpty): @@ -141,24 +153,24 @@ def get_input_media_from_file_id( raise ValueError("Unknown media type: {}".format(file_id_str)) -def parse_messages(client, messages: types.messages.Messages, replies: int = 1) -> List["pyrogram.Message"]: +async def parse_messages(client, messages: types.messages.Messages, replies: int = 1) -> List["pyrogram.Message"]: users = {i.id: i for i in messages.users} chats = {i.id: i for i in messages.chats} if not messages.messages: return pyrogram.List() - parsed_messages = [ - pyrogram.Message._parse(client, message, users, chats, replies=0) - for message in messages.messages - ] + parsed_messages = [] + + for message in messages.messages: + parsed_messages.append(await pyrogram.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} reply_message_ids = [i[0] for i in filter(lambda x: x[1] is not None, messages_with_replies.items())] if reply_message_ids: - reply_messages = client.get_messages( + reply_messages = await client.get_messages( parsed_messages[0].chat.id, reply_to_message_ids=reply_message_ids, replies=replies - 1 diff --git a/pyrogram/client/methods/bots/answer_callback_query.py b/pyrogram/client/methods/bots/answer_callback_query.py index 2e00c07cfe..ff9a5b51fa 100644 --- a/pyrogram/client/methods/bots/answer_callback_query.py +++ b/pyrogram/client/methods/bots/answer_callback_query.py @@ -21,7 +21,7 @@ class AnswerCallbackQuery(BaseClient): - def answer_callback_query( + async def answer_callback_query( self, callback_query_id: str, text: str = None, @@ -68,7 +68,7 @@ def answer_callback_query( # Answer with alert app.answer_callback_query(query_id, text=text, show_alert=True) """ - 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/answer_inline_query.py b/pyrogram/client/methods/bots/answer_inline_query.py index 2f95c9b9f7..69b9184de6 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], @@ -93,10 +93,15 @@ def answer_inline_query( "Title", InputTextMessageContent("Message content"))]) """ - return self.send( + 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=is_gallery or None, private=is_personal or None, diff --git a/pyrogram/client/methods/bots/get_game_high_scores.py b/pyrogram/client/methods/bots/get_game_high_scores.py index 1cebc8a624..c40350ad58 100644 --- a/pyrogram/client/methods/bots/get_game_high_scores.py +++ b/pyrogram/client/methods/bots/get_game_high_scores.py @@ -24,7 +24,7 @@ class GetGameHighScores(BaseClient): - def get_game_high_scores( + async def get_game_high_scores( self, user_id: Union[int, str], chat_id: Union[int, str], @@ -59,11 +59,11 @@ def get_game_high_scores( """ # TODO: inline_message_id - r = self.send( + r = 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/get_inline_bot_results.py b/pyrogram/client/methods/bots/get_inline_bot_results.py index aa27b7c9f3..366594fcb0 100644 --- a/pyrogram/client/methods/bots/get_inline_bot_results.py +++ b/pyrogram/client/methods/bots/get_inline_bot_results.py @@ -24,7 +24,7 @@ class GetInlineBotResults(BaseClient): - def get_inline_bot_results( + async def get_inline_bot_results( self, bot: Union[int, str], query: str = "", @@ -70,9 +70,9 @@ def get_inline_bot_results( # 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/request_callback_answer.py b/pyrogram/client/methods/bots/request_callback_answer.py index 6178b94064..97eacf0dbb 100644 --- a/pyrogram/client/methods/bots/request_callback_answer.py +++ b/pyrogram/client/methods/bots/request_callback_answer.py @@ -23,7 +23,7 @@ class RequestCallbackAnswer(BaseClient): - def request_callback_answer( + async def request_callback_answer( self, chat_id: Union[int, str], message_id: int, @@ -64,9 +64,9 @@ def request_callback_answer( # 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( + return await self.send( functions.messages.GetBotCallbackAnswer( - peer=self.resolve_peer(chat_id), + peer=await self.resolve_peer(chat_id), msg_id=message_id, data=data ), diff --git a/pyrogram/client/methods/bots/send_game.py b/pyrogram/client/methods/bots/send_game.py index e9513ac8b8..4b4d2c02d2 100644 --- a/pyrogram/client/methods/bots/send_game.py +++ b/pyrogram/client/methods/bots/send_game.py @@ -24,7 +24,7 @@ class SendGame(BaseClient): - def send_game( + async def send_game( self, chat_id: Union[int, str], game_short_name: str, @@ -67,9 +67,9 @@ def send_game( app.send_game(chat_id, "gamename") """ - 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(), @@ -86,7 +86,7 @@ def send_game( 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/send_inline_bot_result.py b/pyrogram/client/methods/bots/send_inline_bot_result.py index 9b2cdf605e..8cc5bf115a 100644 --- a/pyrogram/client/methods/bots/send_inline_bot_result.py +++ b/pyrogram/client/methods/bots/send_inline_bot_result.py @@ -23,7 +23,7 @@ class SendInlineBotResult(BaseClient): - def send_inline_bot_result( + async def send_inline_bot_result( self, chat_id: Union[int, str], query_id: int, @@ -65,9 +65,9 @@ def send_inline_bot_result( app.send_inline_bot_result(chat_id, query_id, result_id) """ - 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/bots/set_game_score.py b/pyrogram/client/methods/bots/set_game_score.py index 25d8fc0b5a..3912d29492 100644 --- a/pyrogram/client/methods/bots/set_game_score.py +++ b/pyrogram/client/methods/bots/set_game_score.py @@ -24,7 +24,7 @@ class SetGameScore(BaseClient): - def set_game_score( + async def set_game_score( self, user_id: Union[int, str], score: int, @@ -75,12 +75,12 @@ def set_game_score( # Force set new score app.set_game_score(user_id, 25, force=True) """ - 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 ) @@ -88,7 +88,7 @@ def set_game_score( 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/methods/chats/add_chat_members.py b/pyrogram/client/methods/chats/add_chat_members.py index b04d5555b0..9a5f18eac8 100644 --- a/pyrogram/client/methods/chats/add_chat_members.py +++ b/pyrogram/client/methods/chats/add_chat_members.py @@ -23,7 +23,7 @@ class AddChatMembers(BaseClient): - def add_chat_members( + async def add_chat_members( self, chat_id: Union[int, str], user_ids: Union[Union[int, str], List[Union[int, str]]], @@ -60,26 +60,26 @@ def add_chat_members( # Change forward_limit (for basic groups only) app.add_chat_members(chat_id, user_id, forward_limit=25) """ - peer = self.resolve_peer(chat_id) + peer = await self.resolve_peer(chat_id) if not isinstance(user_ids, list): user_ids = [user_ids] if isinstance(peer, types.InputPeerChat): for user_id in user_ids: - self.send( + await self.send( functions.messages.AddChatUser( chat_id=peer.chat_id, - user_id=self.resolve_peer(user_id), + user_id=await self.resolve_peer(user_id), fwd_limit=forward_limit ) ) else: - self.send( + await self.send( functions.channels.InviteToChannel( channel=peer, users=[ - self.resolve_peer(user_id) + await self.resolve_peer(user_id) for user_id in user_ids ] ) diff --git a/pyrogram/client/methods/chats/archive_chats.py b/pyrogram/client/methods/chats/archive_chats.py index 3c1cabf74a..54c452a2df 100644 --- a/pyrogram/client/methods/chats/archive_chats.py +++ b/pyrogram/client/methods/chats/archive_chats.py @@ -23,7 +23,7 @@ class ArchiveChats(BaseClient): - def archive_chats( + async def archive_chats( self, chat_ids: Union[int, str, List[Union[int, str]]], ) -> bool: @@ -50,14 +50,19 @@ def archive_chats( if not isinstance(chat_ids, list): chat_ids = [chat_ids] - self.send( + folder_peers = [] + + for chat in chat_ids: + folder_peers.append( + types.InputFolderPeer( + peer=await self.resolve_peer(chat), + folder_id=1 + ) + ) + + await self.send( functions.folders.EditPeerFolders( - folder_peers=[ - types.InputFolderPeer( - peer=self.resolve_peer(chat), - folder_id=1 - ) for chat in chat_ids - ] + folder_peers=folder_peers ) ) diff --git a/pyrogram/client/methods/chats/create_channel.py b/pyrogram/client/methods/chats/create_channel.py index 5986f7035b..7885ed3ea9 100644 --- a/pyrogram/client/methods/chats/create_channel.py +++ b/pyrogram/client/methods/chats/create_channel.py @@ -22,7 +22,7 @@ class CreateChannel(BaseClient): - def create_channel( + async def create_channel( self, title: str, description: str = "" @@ -44,7 +44,7 @@ def create_channel( app.create_channel("Channel Title", "Channel Description") """ - r = self.send( + r = await self.send( functions.channels.CreateChannel( title=title, about=description, diff --git a/pyrogram/client/methods/chats/create_group.py b/pyrogram/client/methods/chats/create_group.py index 43ec6e7fd3..631aa75a71 100644 --- a/pyrogram/client/methods/chats/create_group.py +++ b/pyrogram/client/methods/chats/create_group.py @@ -24,7 +24,7 @@ class CreateGroup(BaseClient): - def create_group( + async def create_group( self, title: str, users: Union[Union[int, str], List[Union[int, str]]] @@ -55,10 +55,10 @@ def create_group( if not isinstance(users, list): users = [users] - r = self.send( + r = await self.send( functions.messages.CreateChat( title=title, - users=[self.resolve_peer(u) for u in users] + users=[await self.resolve_peer(u) for u in users] ) ) diff --git a/pyrogram/client/methods/chats/create_supergroup.py b/pyrogram/client/methods/chats/create_supergroup.py index 139064ec5b..1310d65ec2 100644 --- a/pyrogram/client/methods/chats/create_supergroup.py +++ b/pyrogram/client/methods/chats/create_supergroup.py @@ -22,7 +22,7 @@ class CreateSupergroup(BaseClient): - def create_supergroup( + async def create_supergroup( self, title: str, description: str = "" @@ -48,7 +48,7 @@ def create_supergroup( app.create_supergroup("Supergroup Title", "Supergroup Description") """ - r = self.send( + r = await self.send( functions.channels.CreateChannel( title=title, about=description, diff --git a/pyrogram/client/methods/chats/delete_channel.py b/pyrogram/client/methods/chats/delete_channel.py index fd07b0e6da..ff62e8d62f 100644 --- a/pyrogram/client/methods/chats/delete_channel.py +++ b/pyrogram/client/methods/chats/delete_channel.py @@ -23,7 +23,7 @@ class DeleteChannel(BaseClient): - def delete_channel(self, chat_id: Union[int, str]) -> bool: + async def delete_channel(self, chat_id: Union[int, str]) -> bool: """Delete a channel. Parameters: @@ -38,9 +38,9 @@ def delete_channel(self, chat_id: Union[int, str]) -> bool: app.delete_channel(channel_id) """ - self.send( + await self.send( functions.channels.DeleteChannel( - channel=self.resolve_peer(chat_id) + channel=await self.resolve_peer(chat_id) ) ) diff --git a/pyrogram/client/methods/chats/delete_chat_photo.py b/pyrogram/client/methods/chats/delete_chat_photo.py index 655d6fd6a9..cc2e06dd37 100644 --- a/pyrogram/client/methods/chats/delete_chat_photo.py +++ b/pyrogram/client/methods/chats/delete_chat_photo.py @@ -23,7 +23,7 @@ class DeleteChatPhoto(BaseClient): - def delete_chat_photo( + async def delete_chat_photo( self, chat_id: Union[int, str] ) -> bool: @@ -46,17 +46,17 @@ def delete_chat_photo( app.delete_chat_photo(chat_id) """ - 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/delete_supergroup.py b/pyrogram/client/methods/chats/delete_supergroup.py index df4649e561..f24c55a131 100644 --- a/pyrogram/client/methods/chats/delete_supergroup.py +++ b/pyrogram/client/methods/chats/delete_supergroup.py @@ -23,7 +23,7 @@ class DeleteSupergroup(BaseClient): - def delete_supergroup(self, chat_id: Union[int, str]) -> bool: + async def delete_supergroup(self, chat_id: Union[int, str]) -> bool: """Delete a supergroup. Parameters: @@ -38,9 +38,9 @@ def delete_supergroup(self, chat_id: Union[int, str]) -> bool: app.delete_supergroup(supergroup_id) """ - self.send( + await self.send( functions.channels.DeleteChannel( - channel=self.resolve_peer(chat_id) + channel=await self.resolve_peer(chat_id) ) ) diff --git a/pyrogram/client/methods/chats/export_chat_invite_link.py b/pyrogram/client/methods/chats/export_chat_invite_link.py index 671c1ade9b..ac0d3b9102 100644 --- a/pyrogram/client/methods/chats/export_chat_invite_link.py +++ b/pyrogram/client/methods/chats/export_chat_invite_link.py @@ -23,7 +23,7 @@ class ExportChatInviteLink(BaseClient): - def export_chat_invite_link( + async def export_chat_invite_link( self, chat_id: Union[int, str] ) -> str: @@ -55,13 +55,13 @@ def export_chat_invite_link( link = app.export_chat_invite_link(chat_id) print(link) """ - peer = self.resolve_peer(chat_id) + peer = await self.resolve_peer(chat_id) if isinstance(peer, (types.InputPeerChat, types.InputPeerChannel)): - return self.send( + return (await self.send( functions.messages.ExportChatInvite( peer=peer ) - ).link + )).link else: raise ValueError('The chat_id "{}" belongs to a user'.format(chat_id)) diff --git a/pyrogram/client/methods/chats/get_chat.py b/pyrogram/client/methods/chats/get_chat.py index 14adc1a78c..3c94507073 100644 --- a/pyrogram/client/methods/chats/get_chat.py +++ b/pyrogram/client/methods/chats/get_chat.py @@ -24,7 +24,7 @@ class GetChat(BaseClient): - def get_chat( + async def get_chat( self, chat_id: Union[int, str] ) -> Union["pyrogram.Chat", "pyrogram.ChatPreview"]: @@ -55,7 +55,7 @@ def get_chat( match = self.INVITE_LINK_RE.match(str(chat_id)) if match: - r = self.send( + r = await self.send( functions.messages.CheckChatInvite( hash=match.group(1) ) @@ -72,13 +72,13 @@ def get_chat( if isinstance(r.chat, types.Channel): chat_id = utils.get_channel_id(r.chat.id) - peer = self.resolve_peer(chat_id) + peer = await self.resolve_peer(chat_id) if isinstance(peer, types.InputPeerChannel): - r = self.send(functions.channels.GetFullChannel(channel=peer)) + r = await self.send(functions.channels.GetFullChannel(channel=peer)) elif isinstance(peer, (types.InputPeerUser, types.InputPeerSelf)): - r = self.send(functions.users.GetFullUser(id=peer)) + r = await self.send(functions.users.GetFullUser(id=peer)) else: - r = self.send(functions.messages.GetFullChat(chat_id=peer.chat_id)) + r = await self.send(functions.messages.GetFullChat(chat_id=peer.chat_id)) - return pyrogram.Chat._parse_full(self, r) + return await pyrogram.Chat._parse_full(self, r) diff --git a/pyrogram/client/methods/chats/get_chat_member.py b/pyrogram/client/methods/chats/get_chat_member.py index 9a7bdeff33..b77bca8553 100644 --- a/pyrogram/client/methods/chats/get_chat_member.py +++ b/pyrogram/client/methods/chats/get_chat_member.py @@ -21,11 +21,12 @@ import pyrogram from pyrogram.api import functions, types from pyrogram.errors import UserNotParticipant + from ...ext import BaseClient class GetChatMember(BaseClient): - def get_chat_member( + async def get_chat_member( self, chat_id: Union[int, str], user_id: Union[int, str] @@ -50,11 +51,11 @@ def get_chat_member( dan = app.get_chat_member("pyrogramchat", "haskell") print(dan) """ - chat = self.resolve_peer(chat_id) - user = self.resolve_peer(user_id) + chat = await self.resolve_peer(chat_id) + user = await self.resolve_peer(user_id) if isinstance(chat, types.InputPeerChat): - r = self.send( + r = await self.send( functions.messages.GetFullChat( chat_id=chat.chat_id ) @@ -75,7 +76,7 @@ def get_chat_member( else: raise UserNotParticipant elif isinstance(chat, types.InputPeerChannel): - r = self.send( + r = await self.send( functions.channels.GetParticipant( channel=chat, user_id=user diff --git a/pyrogram/client/methods/chats/get_chat_members.py b/pyrogram/client/methods/chats/get_chat_members.py index da1954a461..4f7613ce35 100644 --- a/pyrogram/client/methods/chats/get_chat_members.py +++ b/pyrogram/client/methods/chats/get_chat_members.py @@ -36,7 +36,7 @@ class Filters: class GetChatMembers(BaseClient): - def get_chat_members( + async def get_chat_members( self, chat_id: Union[int, str], offset: int = 0, @@ -103,10 +103,10 @@ def get_chat_members( # Get all bots app.get_chat_members("pyrogramchat", filter="bots") """ - peer = self.resolve_peer(chat_id) + peer = await self.resolve_peer(chat_id) if isinstance(peer, types.InputPeerChat): - r = self.send( + r = await self.send( functions.messages.GetFullChat( chat_id=peer.chat_id ) @@ -134,7 +134,7 @@ def get_chat_members( else: raise ValueError("Invalid filter \"{}\"".format(filter)) - r = self.send( + r = await self.send( functions.channels.GetParticipants( channel=peer, filter=filter, diff --git a/pyrogram/client/methods/chats/get_chat_members_count.py b/pyrogram/client/methods/chats/get_chat_members_count.py index ad77acc1bf..88c126690f 100644 --- a/pyrogram/client/methods/chats/get_chat_members_count.py +++ b/pyrogram/client/methods/chats/get_chat_members_count.py @@ -23,7 +23,7 @@ class GetChatMembersCount(BaseClient): - def get_chat_members_count( + async def get_chat_members_count( self, chat_id: Union[int, str] ) -> int: @@ -45,19 +45,23 @@ def get_chat_members_count( count = app.get_chat_members_count("pyrogramchat") print(count) """ - 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)) diff --git a/pyrogram/client/methods/chats/get_dialogs.py b/pyrogram/client/methods/chats/get_dialogs.py index de03b04687..d52bd2cedd 100644 --- a/pyrogram/client/methods/chats/get_dialogs.py +++ b/pyrogram/client/methods/chats/get_dialogs.py @@ -27,7 +27,7 @@ class GetDialogs(BaseClient): - def get_dialogs( + async def get_dialogs( self, offset_date: int = 0, limit: int = 100, @@ -65,9 +65,9 @@ def get_dialogs( """ if pinned_only: - r = self.send(functions.messages.GetPinnedDialogs(folder_id=0)) + r = await self.send(functions.messages.GetPinnedDialogs(folder_id=0)) else: - r = self.send( + r = await self.send( functions.messages.GetDialogs( offset_date=offset_date, offset_id=0, @@ -94,7 +94,7 @@ def get_dialogs( else: chat_id = utils.get_peer_id(to_id) - messages[chat_id] = pyrogram.Message._parse(self, message, users, chats) + messages[chat_id] = await pyrogram.Message._parse(self, message, users, chats) parsed_dialogs = [] diff --git a/pyrogram/client/methods/chats/get_dialogs_count.py b/pyrogram/client/methods/chats/get_dialogs_count.py index 7b81182eda..da4c60cee7 100644 --- a/pyrogram/client/methods/chats/get_dialogs_count.py +++ b/pyrogram/client/methods/chats/get_dialogs_count.py @@ -21,7 +21,7 @@ class GetDialogsCount(BaseClient): - def get_dialogs_count(self, pinned_only: bool = False) -> int: + async def get_dialogs_count(self, pinned_only: bool = False) -> int: """Get the total count of your dialogs. pinned_only (``bool``, *optional*): @@ -39,9 +39,9 @@ def get_dialogs_count(self, pinned_only: bool = False) -> int: """ if pinned_only: - return len(self.send(functions.messages.GetPinnedDialogs(folder_id=0)).dialogs) + return len((await self.send(functions.messages.GetPinnedDialogs(folder_id=0))).dialogs) else: - r = self.send( + r = await self.send( functions.messages.GetDialogs( offset_date=0, offset_id=0, diff --git a/pyrogram/client/methods/chats/get_nearby_chats.py b/pyrogram/client/methods/chats/get_nearby_chats.py index 1ccab72976..6b4ab56ddd 100644 --- a/pyrogram/client/methods/chats/get_nearby_chats.py +++ b/pyrogram/client/methods/chats/get_nearby_chats.py @@ -24,7 +24,7 @@ class GetNearbyChats(BaseClient): - def get_nearby_chats( + async def get_nearby_chats( self, latitude: float, longitude: float @@ -48,7 +48,7 @@ def get_nearby_chats( print(chats) """ - r = self.send( + r = await self.send( functions.contacts.GetLocated( geo_point=types.InputGeoPoint( lat=latitude, diff --git a/pyrogram/client/methods/chats/iter_chat_members.py b/pyrogram/client/methods/chats/iter_chat_members.py index 0bc903052b..b5ded4dce4 100644 --- a/pyrogram/client/methods/chats/iter_chat_members.py +++ b/pyrogram/client/methods/chats/iter_chat_members.py @@ -17,10 +17,12 @@ # along with Pyrogram. If not, see . from string import ascii_lowercase -from typing import Union, Generator +from typing import Union, Generator, Optional import pyrogram +from async_generator import async_generator, yield_ from pyrogram.api import types + from ...ext import BaseClient @@ -38,13 +40,14 @@ class Filters: class IterChatMembers(BaseClient): - def iter_chat_members( + @async_generator + async def iter_chat_members( self, chat_id: Union[int, str], limit: int = 0, query: str = "", filter: str = Filters.ALL - ) -> Generator["pyrogram.ChatMember", None, None]: + ) -> Optional[Generator["pyrogram.ChatMember", None, None]]: """Iterate through the members of a chat sequentially. This convenience method does the same as repeatedly calling :meth:`~Client.get_chat_members` in a loop, thus saving you @@ -97,7 +100,7 @@ def iter_chat_members( 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) if filter not in QUERYABLE_FILTERS: queries = [""] @@ -106,7 +109,7 @@ def iter_chat_members( 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, @@ -128,7 +131,7 @@ def iter_chat_members( 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 a2eddcb9d8..a1933a7e20 100644 --- a/pyrogram/client/methods/chats/iter_dialogs.py +++ b/pyrogram/client/methods/chats/iter_dialogs.py @@ -16,18 +16,21 @@ # 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 Generator, Optional + +from async_generator import async_generator, yield_ import pyrogram from ...ext import BaseClient class IterDialogs(BaseClient): - def iter_dialogs( + @async_generator + async def iter_dialogs( self, - offset_date: int = 0, - limit: int = None - ) -> Generator["pyrogram.Dialog", None, None]: + limit: int = 0, + offset_date: int = 0 + ) -> Optional[Generator["pyrogram.Dialog", None, None]]: """Iterate through a user's dialogs sequentially. This convenience method does the same as repeatedly calling :meth:`~Client.get_dialogs` in a loop, thus saving @@ -35,14 +38,14 @@ def iter_dialogs( single call. Parameters: - 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 (``int``, *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: ``Generator``: A generator yielding :obj:`Dialog` objects. @@ -57,12 +60,12 @@ def iter_dialogs( total = limit or (1 << 31) - 1 limit = min(100, total) - pinned_dialogs = self.get_dialogs( + pinned_dialogs = await self.get_dialogs( pinned_only=True ) for dialog in pinned_dialogs: - yield dialog + await yield_(dialog) current += 1 @@ -70,7 +73,7 @@ def iter_dialogs( return while True: - dialogs = self.get_dialogs( + dialogs = await self.get_dialogs( offset_date=offset_date, limit=limit ) @@ -81,7 +84,7 @@ def iter_dialogs( offset_date = dialogs[-1].top_message.date for dialog in dialogs: - yield dialog + await yield_(dialog) current += 1 diff --git a/pyrogram/client/methods/chats/join_chat.py b/pyrogram/client/methods/chats/join_chat.py index 666871153d..05bde18643 100644 --- a/pyrogram/client/methods/chats/join_chat.py +++ b/pyrogram/client/methods/chats/join_chat.py @@ -24,7 +24,7 @@ class JoinChat(BaseClient): - def join_chat( + async def join_chat( self, chat_id: Union[int, str] ): @@ -53,7 +53,7 @@ def join_chat( match = self.INVITE_LINK_RE.match(str(chat_id)) if match: - chat = self.send( + chat = await self.send( functions.messages.ImportChatInvite( hash=match.group(1) ) @@ -63,9 +63,9 @@ def join_chat( elif isinstance(chat.chats[0], types.Channel): return pyrogram.Chat._parse_channel_chat(self, chat.chats[0]) else: - chat = self.send( + chat = await self.send( functions.channels.JoinChannel( - channel=self.resolve_peer(chat_id) + channel=await self.resolve_peer(chat_id) ) ) diff --git a/pyrogram/client/methods/chats/kick_chat_member.py b/pyrogram/client/methods/chats/kick_chat_member.py index 55a177f466..d72da5ab22 100644 --- a/pyrogram/client/methods/chats/kick_chat_member.py +++ b/pyrogram/client/methods/chats/kick_chat_member.py @@ -24,7 +24,7 @@ class KickChatMember(BaseClient): - def kick_chat_member( + async def kick_chat_member( self, chat_id: Union[int, str], user_id: Union[int, str], @@ -68,11 +68,11 @@ def kick_chat_member( # Kick chat member and automatically unban after 24h app.kick_chat_member(chat_id, user_id, int(time.time() + 86400)) """ - 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): - r = self.send( + r = await self.send( functions.channels.EditBanned( channel=chat_peer, user_id=user_peer, @@ -90,7 +90,7 @@ def kick_chat_member( ) ) else: - r = self.send( + r = await self.send( functions.messages.DeleteChatUser( chat_id=abs(chat_id), user_id=user_peer @@ -99,7 +99,7 @@ def kick_chat_member( 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/chats/leave_chat.py b/pyrogram/client/methods/chats/leave_chat.py index 2cc1c05755..31b7cc78cd 100644 --- a/pyrogram/client/methods/chats/leave_chat.py +++ b/pyrogram/client/methods/chats/leave_chat.py @@ -23,7 +23,7 @@ class LeaveChat(BaseClient): - def leave_chat( + async def leave_chat( self, chat_id: Union[int, str], delete: bool = False @@ -48,16 +48,16 @@ def leave_chat( # Leave basic chat and also delete the dialog app.leave_chat(chat_id, delete=True) """ - 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() @@ -65,7 +65,7 @@ def leave_chat( ) if delete: - self.send( + await self.send( functions.messages.DeleteHistory( peer=peer, max_id=0 diff --git a/pyrogram/client/methods/chats/pin_chat_message.py b/pyrogram/client/methods/chats/pin_chat_message.py index 44191a2d8b..6adaa2e84c 100644 --- a/pyrogram/client/methods/chats/pin_chat_message.py +++ b/pyrogram/client/methods/chats/pin_chat_message.py @@ -23,7 +23,7 @@ class PinChatMessage(BaseClient): - def pin_chat_message( + async def pin_chat_message( self, chat_id: Union[int, str], message_id: int, @@ -56,9 +56,9 @@ def pin_chat_message( # Pin without notification app.pin_chat_message(chat_id, message_id, disable_notification=True) """ - self.send( + await self.send( functions.messages.UpdatePinnedMessage( - peer=self.resolve_peer(chat_id), + peer=await self.resolve_peer(chat_id), id=message_id, silent=disable_notification or None ) diff --git a/pyrogram/client/methods/chats/promote_chat_member.py b/pyrogram/client/methods/chats/promote_chat_member.py index 70b4f4e26c..c6912031f7 100644 --- a/pyrogram/client/methods/chats/promote_chat_member.py +++ b/pyrogram/client/methods/chats/promote_chat_member.py @@ -23,7 +23,7 @@ class PromoteChatMember(BaseClient): - def promote_chat_member( + async def promote_chat_member( self, chat_id: Union[int, str], user_id: Union[int, str], @@ -84,10 +84,10 @@ def promote_chat_member( # Promote chat member to supergroup admin app.promote_chat_member(chat_id, user_id) """ - 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.ChatAdminRights( 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 a070707854..500482a1fd 100644 --- a/pyrogram/client/methods/chats/restrict_chat_member.py +++ b/pyrogram/client/methods/chats/restrict_chat_member.py @@ -24,7 +24,7 @@ class RestrictChatMember(BaseClient): - def restrict_chat_member( + async def restrict_chat_member( self, chat_id: Union[int, str], user_id: Union[int, str], @@ -71,10 +71,10 @@ def restrict_chat_member( # Chat member can only send text messages app.restrict_chat_member(chat_id, user_id, ChatPermissions(can_send_messages=True)) """ - r = self.send( + r = 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.ChatBannedRights( until_date=until_date, send_messages=True if not permissions.can_send_messages else None, diff --git a/pyrogram/client/methods/chats/set_administrator_title.py b/pyrogram/client/methods/chats/set_administrator_title.py index 361a4e1c2f..0cbc937a33 100644 --- a/pyrogram/client/methods/chats/set_administrator_title.py +++ b/pyrogram/client/methods/chats/set_administrator_title.py @@ -23,7 +23,7 @@ class SetAdministratorTitle(BaseClient): - def set_administrator_title( + async def set_administrator_title( self, chat_id: Union[int, str], user_id: Union[int, str], @@ -54,15 +54,15 @@ def set_administrator_title( app.set_administrator_title(chat_id, user_id, "ฅ^•ﻌ•^ฅ") """ - 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) - r = self.send( + r = (await self.send( functions.channels.GetParticipant( channel=chat_id, user_id=user_id ) - ).participant + )).participant if isinstance(r, types.ChannelParticipantCreator): admin_rights = types.ChatAdminRights( @@ -104,7 +104,7 @@ def set_administrator_title( if not admin_rights.add_admins: admin_rights.add_admins = None - self.send( + await self.send( functions.channels.EditAdmin( channel=chat_id, user_id=user_id, diff --git a/pyrogram/client/methods/chats/set_chat_description.py b/pyrogram/client/methods/chats/set_chat_description.py index 312b63eb86..e296040894 100644 --- a/pyrogram/client/methods/chats/set_chat_description.py +++ b/pyrogram/client/methods/chats/set_chat_description.py @@ -23,7 +23,7 @@ class SetChatDescription(BaseClient): - def set_chat_description( + async def set_chat_description( self, chat_id: Union[int, str], description: str @@ -49,10 +49,10 @@ def set_chat_description( app.set_chat_description(chat_id, "New Description") """ - peer = self.resolve_peer(chat_id) + peer = await self.resolve_peer(chat_id) if isinstance(peer, (types.InputPeerChannel, types.InputPeerChat)): - self.send( + await self.send( functions.messages.EditChatAbout( peer=peer, about=description diff --git a/pyrogram/client/methods/chats/set_chat_permissions.py b/pyrogram/client/methods/chats/set_chat_permissions.py index afb8b6f166..3509baf4ac 100644 --- a/pyrogram/client/methods/chats/set_chat_permissions.py +++ b/pyrogram/client/methods/chats/set_chat_permissions.py @@ -24,7 +24,7 @@ class SetChatPermissions(BaseClient): - def set_chat_permissions( + async def set_chat_permissions( self, chat_id: Union[int, str], permissions: ChatPermissions, @@ -63,9 +63,9 @@ def set_chat_permissions( ) ) """ - r = self.send( + r = await self.send( functions.messages.EditChatDefaultBannedRights( - peer=self.resolve_peer(chat_id), + peer=await self.resolve_peer(chat_id), banned_rights=types.ChatBannedRights( until_date=0, send_messages=True if not permissions.can_send_messages else None, diff --git a/pyrogram/client/methods/chats/set_chat_photo.py b/pyrogram/client/methods/chats/set_chat_photo.py index f5ef954f1e..e6d3544871 100644 --- a/pyrogram/client/methods/chats/set_chat_photo.py +++ b/pyrogram/client/methods/chats/set_chat_photo.py @@ -24,7 +24,7 @@ class SetChatPhoto(BaseClient): - def set_chat_photo( + async def set_chat_photo( self, chat_id: Union[int, str], *, @@ -79,32 +79,32 @@ def set_chat_photo( # Set chat photo using an exiting Video file_id app.set_chat_photo(chat_id, video=video.file_id, file_ref=video.file_ref) """ - peer = self.resolve_peer(chat_id) + peer = await self.resolve_peer(chat_id) if isinstance(photo, str): if os.path.isfile(photo): photo = types.InputChatUploadedPhoto( - file=self.save_file(photo), - video=self.save_file(video) + file=await self.save_file(photo), + video=await self.save_file(video) ) else: photo = utils.get_input_media_from_file_id(photo, file_ref, 2) photo = types.InputChatPhoto(id=photo.id) else: photo = types.InputChatUploadedPhoto( - file=self.save_file(photo), - video=self.save_file(video) + file=await self.save_file(photo), + video=await self.save_file(video) ) 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 9d6a2d246a..a6b9bc71e3 100644 --- a/pyrogram/client/methods/chats/set_chat_title.py +++ b/pyrogram/client/methods/chats/set_chat_title.py @@ -23,7 +23,7 @@ class SetChatTitle(BaseClient): - def set_chat_title( + async def set_chat_title( self, chat_id: Union[int, str], title: str @@ -54,17 +54,17 @@ def set_chat_title( app.set_chat_title(chat_id, "New Title") """ - 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/set_slow_mode.py b/pyrogram/client/methods/chats/set_slow_mode.py index 8215c3b916..185a3824eb 100644 --- a/pyrogram/client/methods/chats/set_slow_mode.py +++ b/pyrogram/client/methods/chats/set_slow_mode.py @@ -23,7 +23,7 @@ class SetSlowMode(BaseClient): - def set_slow_mode( + async def set_slow_mode( self, chat_id: Union[int, str], seconds: Union[int, None] @@ -51,9 +51,9 @@ def set_slow_mode( app.set_slow_mode("pyrogramchat", None) """ - self.send( + await self.send( functions.channels.ToggleSlowMode( - channel=self.resolve_peer(chat_id), + channel=await self.resolve_peer(chat_id), seconds=0 if seconds is None else seconds ) ) diff --git a/pyrogram/client/methods/chats/unarchive_chats.py b/pyrogram/client/methods/chats/unarchive_chats.py index b004e4bbb9..dfba70a7fa 100644 --- a/pyrogram/client/methods/chats/unarchive_chats.py +++ b/pyrogram/client/methods/chats/unarchive_chats.py @@ -23,7 +23,7 @@ class UnarchiveChats(BaseClient): - def unarchive_chats( + async def unarchive_chats( self, chat_ids: Union[int, str, List[Union[int, str]]], ) -> bool: @@ -50,14 +50,19 @@ def unarchive_chats( if not isinstance(chat_ids, list): chat_ids = [chat_ids] - self.send( + folder_peers = [] + + for chat in chat_ids: + folder_peers.append( + types.InputFolderPeer( + peer=await self.resolve_peer(chat), + folder_id=0 + ) + ) + + await self.send( functions.folders.EditPeerFolders( - folder_peers=[ - types.InputFolderPeer( - peer=self.resolve_peer(chat), - folder_id=0 - ) for chat in chat_ids - ] + folder_peers=folder_peers ) ) diff --git a/pyrogram/client/methods/chats/unban_chat_member.py b/pyrogram/client/methods/chats/unban_chat_member.py index fc0c975116..4a7b3940cf 100644 --- a/pyrogram/client/methods/chats/unban_chat_member.py +++ b/pyrogram/client/methods/chats/unban_chat_member.py @@ -23,7 +23,7 @@ class UnbanChatMember(BaseClient): - def unban_chat_member( + async def unban_chat_member( self, chat_id: Union[int, str], user_id: Union[int, str] @@ -49,10 +49,10 @@ def unban_chat_member( # Unban chat member right now app.unban_chat_member(chat_id, user_id) """ - 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.ChatBannedRights( until_date=0 ) diff --git a/pyrogram/client/methods/chats/unpin_chat_message.py b/pyrogram/client/methods/chats/unpin_chat_message.py index 6defd99f9d..0ca8254aec 100644 --- a/pyrogram/client/methods/chats/unpin_chat_message.py +++ b/pyrogram/client/methods/chats/unpin_chat_message.py @@ -23,7 +23,7 @@ class UnpinChatMessage(BaseClient): - def unpin_chat_message( + async def unpin_chat_message( self, chat_id: Union[int, str] ) -> bool: @@ -43,9 +43,9 @@ def unpin_chat_message( app.unpin_chat_message(chat_id) """ - self.send( + await self.send( functions.messages.UpdatePinnedMessage( - peer=self.resolve_peer(chat_id), + peer=await self.resolve_peer(chat_id), id=0 ) ) diff --git a/pyrogram/client/methods/chats/update_chat_username.py b/pyrogram/client/methods/chats/update_chat_username.py index 251d68329d..b1c57f1ed3 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] @@ -50,11 +50,11 @@ def update_chat_username( app.update_chat_username(chat_id, "new_username") """ - 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/contacts/add_contacts.py b/pyrogram/client/methods/contacts/add_contacts.py index a5bd4a933b..7226d60b19 100644 --- a/pyrogram/client/methods/contacts/add_contacts.py +++ b/pyrogram/client/methods/contacts/add_contacts.py @@ -24,7 +24,7 @@ class AddContacts(BaseClient): - def add_contacts( + async def add_contacts( self, contacts: List["pyrogram.InputPhoneContact"] ): @@ -47,7 +47,7 @@ def add_contacts( InputPhoneContact("38987654321", "Bar"), InputPhoneContact("01234567891", "Baz")]) """ - 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 27e6cfff76..777d8b39d0 100644 --- a/pyrogram/client/methods/contacts/delete_contacts.py +++ b/pyrogram/client/methods/contacts/delete_contacts.py @@ -24,7 +24,7 @@ class DeleteContacts(BaseClient): - def delete_contacts( + async def delete_contacts( self, ids: List[int] ): @@ -47,14 +47,14 @@ def delete_contacts( 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 a0699e19ce..8f8392d68a 100644 --- a/pyrogram/client/methods/contacts/get_contacts.py +++ b/pyrogram/client/methods/contacts/get_contacts.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 from typing import List @@ -27,7 +28,7 @@ class GetContacts(BaseClient): - def get_contacts(self) -> List["pyrogram.User"]: + async def get_contacts(self) -> List["pyrogram.User"]: """Get contacts from your Telegram address book. Returns: @@ -39,5 +40,5 @@ def get_contacts(self) -> List["pyrogram.User"]: contacts = app.get_contacts() print(contacts) """ - contacts = self.send(functions.contacts.GetContacts(hash=0)) + contacts = await self.send(functions.contacts.GetContacts(hash=0)) return pyrogram.List(pyrogram.User._parse(self, user) for user in contacts.users) diff --git a/pyrogram/client/methods/contacts/get_contacts_count.py b/pyrogram/client/methods/contacts/get_contacts_count.py index b7871fde6a..8435557a9e 100644 --- a/pyrogram/client/methods/contacts/get_contacts_count.py +++ b/pyrogram/client/methods/contacts/get_contacts_count.py @@ -21,7 +21,7 @@ class GetContactsCount(BaseClient): - def get_contacts_count(self) -> int: + async def get_contacts_count(self) -> int: """Get the total count of contacts from your Telegram address book. Returns: @@ -34,4 +34,4 @@ def get_contacts_count(self) -> int: print(count) """ - return len(self.send(functions.contacts.GetContacts(hash=0)).contacts) + return len((await self.send(functions.contacts.GetContacts(hash=0))).contacts) diff --git a/pyrogram/client/methods/messages/delete_messages.py b/pyrogram/client/methods/messages/delete_messages.py index a6af6a694f..5deb6d5aa8 100644 --- a/pyrogram/client/methods/messages/delete_messages.py +++ b/pyrogram/client/methods/messages/delete_messages.py @@ -23,7 +23,7 @@ class DeleteMessages(BaseClient): - def delete_messages( + async def delete_messages( self, chat_id: Union[int, str], message_ids: Iterable[int], @@ -62,18 +62,18 @@ def delete_messages( # Delete messages only on your side (without revoking) app.delete_messages(chat_id, message_id, revoke=False) """ - 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): - r = self.send( + r = await self.send( functions.channels.DeleteMessages( channel=peer, id=message_ids ) ) else: - r = self.send( + r = await self.send( functions.messages.DeleteMessages( id=message_ids, revoke=revoke or None diff --git a/pyrogram/client/methods/messages/download_media.py b/pyrogram/client/methods/messages/download_media.py index 013971358f..3c1a8cbeb3 100644 --- a/pyrogram/client/methods/messages/download_media.py +++ b/pyrogram/client/methods/messages/download_media.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 binascii import os import struct @@ -32,7 +33,7 @@ class DownloadMedia(BaseClient): - def download_media( + async def download_media( self, message: Union["pyrogram.Message", str], file_ref: str = None, @@ -202,7 +203,7 @@ def get_existing_attributes() -> dict: except (AssertionError, binascii.Error, struct.error): raise FileIdInvalid from None - done = Event() + done = asyncio.Event() path = [None] directory, file_name = os.path.split(file_name) @@ -239,9 +240,9 @@ def get_existing_attributes() -> dict: ) # Cast to string because Path objects aren't supported by Python 3.5 - self.download_queue.put((data, str(directory), str(file_name), done, progress, progress_args, path)) + self.download_queue.put_nowait((data, str(directory), str(file_name), done, progress, progress_args, path)) if block: - done.wait() + await done.wait() return path[0] diff --git a/pyrogram/client/methods/messages/edit_inline_caption.py b/pyrogram/client/methods/messages/edit_inline_caption.py index 58cd05b252..335f878eb7 100644 --- a/pyrogram/client/methods/messages/edit_inline_caption.py +++ b/pyrogram/client/methods/messages/edit_inline_caption.py @@ -23,7 +23,7 @@ class EditInlineCaption(BaseClient): - def edit_inline_caption( + async def edit_inline_caption( self, inline_message_id: str, caption: str, @@ -58,7 +58,7 @@ def edit_inline_caption( # Bots only app.edit_inline_caption(inline_message_id, "new media caption") """ - return self.edit_inline_text( + return await self.edit_inline_text( inline_message_id=inline_message_id, text=caption, parse_mode=parse_mode, diff --git a/pyrogram/client/methods/messages/edit_inline_media.py b/pyrogram/client/methods/messages/edit_inline_media.py index f409ae0603..74cb29108b 100644 --- a/pyrogram/client/methods/messages/edit_inline_media.py +++ b/pyrogram/client/methods/messages/edit_inline_media.py @@ -29,7 +29,7 @@ class EditInlineMedia(BaseClient): - def edit_inline_media( + async def edit_inline_media( self, inline_message_id: str, media: InputMedia, @@ -109,11 +109,11 @@ def edit_inline_media( else: media = utils.get_input_media_from_file_id(media.media, media.file_ref, 5) - return self.send( + return await self.send( functions.messages.EditInlineBotMessage( id=utils.unpack_inline_message_id(inline_message_id), media=media, reply_markup=reply_markup.write() if reply_markup else None, - **self.parser.parse(caption, parse_mode) + **await self.parser.parse(caption, parse_mode) ) ) diff --git a/pyrogram/client/methods/messages/edit_inline_reply_markup.py b/pyrogram/client/methods/messages/edit_inline_reply_markup.py index 44e6197ccd..f538180641 100644 --- a/pyrogram/client/methods/messages/edit_inline_reply_markup.py +++ b/pyrogram/client/methods/messages/edit_inline_reply_markup.py @@ -22,7 +22,7 @@ class EditInlineReplyMarkup(BaseClient): - def edit_inline_reply_markup( + async def edit_inline_reply_markup( self, inline_message_id: str, reply_markup: "pyrogram.InlineKeyboardMarkup" = None @@ -50,7 +50,7 @@ def edit_inline_reply_markup( InlineKeyboardMarkup([[ InlineKeyboardButton("New button", callback_data="new_data")]])) """ - return self.send( + return await self.send( functions.messages.EditInlineBotMessage( id=utils.unpack_inline_message_id(inline_message_id), reply_markup=reply_markup.write() if reply_markup else None, diff --git a/pyrogram/client/methods/messages/edit_inline_text.py b/pyrogram/client/methods/messages/edit_inline_text.py index 59b5ab7383..cfdd232ba9 100644 --- a/pyrogram/client/methods/messages/edit_inline_text.py +++ b/pyrogram/client/methods/messages/edit_inline_text.py @@ -24,7 +24,7 @@ class EditInlineText(BaseClient): - def edit_inline_text( + async def edit_inline_text( self, inline_message_id: str, text: str, @@ -71,11 +71,11 @@ def edit_inline_text( disable_web_page_preview=True) """ - return self.send( + return await self.send( functions.messages.EditInlineBotMessage( id=utils.unpack_inline_message_id(inline_message_id), no_webpage=disable_web_page_preview or None, reply_markup=reply_markup.write() if reply_markup else None, - **self.parser.parse(text, parse_mode) + **await self.parser.parse(text, parse_mode) ) ) diff --git a/pyrogram/client/methods/messages/edit_message_caption.py b/pyrogram/client/methods/messages/edit_message_caption.py index a7c5b94c6b..01bd81474a 100644 --- a/pyrogram/client/methods/messages/edit_message_caption.py +++ b/pyrogram/client/methods/messages/edit_message_caption.py @@ -23,7 +23,7 @@ class EditMessageCaption(BaseClient): - def edit_message_caption( + async def edit_message_caption( self, chat_id: Union[int, str], message_id: int, @@ -63,7 +63,7 @@ def edit_message_caption( app.edit_message_caption(chat_id, message_id, "new media caption") """ - return self.edit_message_text( + return await self.edit_message_text( chat_id=chat_id, message_id=message_id, text=caption, diff --git a/pyrogram/client/methods/messages/edit_message_media.py b/pyrogram/client/methods/messages/edit_message_media.py index 9e91e945da..765c159842 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( + async def edit_message_media( self, chat_id: Union[int, str], message_id: int, @@ -85,11 +85,11 @@ def edit_message_media( if isinstance(media, InputMediaPhoto): if os.path.isfile(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) ) ) ) @@ -109,13 +109,13 @@ def edit_message_media( media = utils.get_input_media_from_file_id(media.media, media.file_ref, 2) elif isinstance(media, InputMediaVideo): if os.path.isfile(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=self.guess_mime_type(media.media) or "video/mp4", - thumb=self.save_file(media.thumb), - file=self.save_file(media.media), + thumb=await self.save_file(media.thumb), + file=await self.save_file(media.media), attributes=[ types.DocumentAttributeVideo( supports_streaming=media.supports_streaming or None, @@ -146,13 +146,13 @@ def edit_message_media( media = utils.get_input_media_from_file_id(media.media, media.file_ref, 4) elif isinstance(media, InputMediaAudio): if os.path.isfile(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=self.guess_mime_type(media.media) or "audio/mpeg", - thumb=self.save_file(media.thumb), - file=self.save_file(media.media), + thumb=await self.save_file(media.thumb), + file=await self.save_file(media.media), attributes=[ types.DocumentAttributeAudio( duration=media.duration, @@ -182,13 +182,13 @@ def edit_message_media( media = utils.get_input_media_from_file_id(media.media, media.file_ref, 9) elif isinstance(media, InputMediaAnimation): if os.path.isfile(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=self.guess_mime_type(media.media) or "video/mp4", thumb=self.save_file(media.thumb), - file=self.save_file(media.media), + file=await self.save_file(media.media), attributes=[ types.DocumentAttributeVideo( supports_streaming=True, @@ -220,13 +220,13 @@ def edit_message_media( media = utils.get_input_media_from_file_id(media.media, media.file_ref, 10) elif isinstance(media, InputMediaDocument): if os.path.isfile(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=self.guess_mime_type(media.media) or "application/zip", - thumb=self.save_file(media.thumb), - file=self.save_file(media.media), + thumb=await self.save_file(media.thumb), + file=await self.save_file(media.media), attributes=[ types.DocumentAttributeFilename( file_name=file_name or os.path.basename(media.media) @@ -250,19 +250,19 @@ def edit_message_media( else: media = utils.get_input_media_from_file_id(media.media, media.file_ref, 5) - 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, media=media, reply_markup=reply_markup.write() if reply_markup else None, - **self.parser.parse(caption, parse_mode) + **await self.parser.parse(caption, parse_mode) ) ) 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/methods/messages/edit_message_reply_markup.py b/pyrogram/client/methods/messages/edit_message_reply_markup.py index 35c6cb3e6b..65fa26e221 100644 --- a/pyrogram/client/methods/messages/edit_message_reply_markup.py +++ b/pyrogram/client/methods/messages/edit_message_reply_markup.py @@ -24,7 +24,7 @@ class EditMessageReplyMarkup(BaseClient): - def edit_message_reply_markup( + async def edit_message_reply_markup( self, chat_id: Union[int, str], message_id: int, @@ -58,9 +58,9 @@ def edit_message_reply_markup( InlineKeyboardMarkup([[ InlineKeyboardButton("New button", callback_data="new_data")]])) """ - 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, ) @@ -68,7 +68,7 @@ def edit_message_reply_markup( 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/methods/messages/edit_message_text.py b/pyrogram/client/methods/messages/edit_message_text.py index df5ebace1b..a2b43f1681 100644 --- a/pyrogram/client/methods/messages/edit_message_text.py +++ b/pyrogram/client/methods/messages/edit_message_text.py @@ -24,7 +24,7 @@ class EditMessageText(BaseClient): - def edit_message_text( + async def edit_message_text( self, chat_id: Union[int, str], message_id: int, @@ -75,19 +75,19 @@ def edit_message_text( disable_web_page_preview=True) """ - 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, - **self.parser.parse(text, parse_mode) + **await self.parser.parse(text, parse_mode) ) ) 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/methods/messages/forward_messages.py b/pyrogram/client/methods/messages/forward_messages.py index 1e2b1702a5..c7f47a5248 100644 --- a/pyrogram/client/methods/messages/forward_messages.py +++ b/pyrogram/client/methods/messages/forward_messages.py @@ -20,11 +20,12 @@ import pyrogram from pyrogram.api import functions, types + from ...ext import BaseClient class ForwardMessages(BaseClient): - def forward_messages( + async def forward_messages( self, chat_id: Union[int, str], from_chat_id: Union[int, str], @@ -94,11 +95,11 @@ def forward_messages( forwarded_messages = [] for chunk in [message_ids[i:i + 200] for i in range(0, len(message_ids), 200)]: - messages = self.get_messages(chat_id=from_chat_id, message_ids=chunk) + messages = await self.get_messages(chat_id=from_chat_id, message_ids=chunk) for message in messages: forwarded_messages.append( - message.forward( + await message.forward( chat_id, disable_notification=disable_notification, as_copy=True, @@ -109,10 +110,10 @@ def forward_messages( return pyrogram.List(forwarded_messages) if is_iterable else forwarded_messages[0] else: - 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], @@ -128,7 +129,7 @@ def forward_messages( for i in r.updates: if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage, types.UpdateNewScheduledMessage)): forwarded_messages.append( - pyrogram.Message._parse( + await pyrogram.Message._parse( self, i.message, users, chats ) diff --git a/pyrogram/client/methods/messages/get_history.py b/pyrogram/client/methods/messages/get_history.py index ea630aee12..92a84d9bf5 100644 --- a/pyrogram/client/methods/messages/get_history.py +++ b/pyrogram/client/methods/messages/get_history.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 from typing import Union, List @@ -28,7 +29,7 @@ class GetHistory(BaseClient): - def get_history( + async def get_history( self, chat_id: Union[int, str], limit: int = 100, @@ -83,11 +84,11 @@ def get_history( offset_id = offset_id or (1 if reverse else 0) - messages = utils.parse_messages( + messages = await utils.parse_messages( self, - self.send( + 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 * (-1 if reverse else 1) - (limit if reverse else 0), diff --git a/pyrogram/client/methods/messages/get_history_count.py b/pyrogram/client/methods/messages/get_history_count.py index cbdb136569..d7476f95c2 100644 --- a/pyrogram/client/methods/messages/get_history_count.py +++ b/pyrogram/client/methods/messages/get_history_count.py @@ -26,7 +26,7 @@ class GetHistoryCount(BaseClient): - def get_history_count( + async def get_history_count( self, chat_id: Union[int, str] ) -> int: @@ -51,9 +51,9 @@ def get_history_count( app.get_history_count("pyrogramchat") """ - 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=0, offset_date=0, add_offset=0, diff --git a/pyrogram/client/methods/messages/get_messages.py b/pyrogram/client/methods/messages/get_messages.py index caf5bea019..b4199b3070 100644 --- a/pyrogram/client/methods/messages/get_messages.py +++ b/pyrogram/client/methods/messages/get_messages.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 from typing import Union, Iterable, List @@ -30,7 +31,7 @@ class GetMessages(BaseClient): - def get_messages( + async def get_messages( self, chat_id: Union[int, str], message_ids: Union[int, Iterable[int]] = None, @@ -96,7 +97,7 @@ def get_messages( if ids is None: raise ValueError("No argument supplied. Either pass message_ids or reply_to_message_ids") - peer = self.resolve_peer(chat_id) + peer = await self.resolve_peer(chat_id) is_iterable = not isinstance(ids, int) ids = list(ids) if is_iterable else [ids] @@ -110,8 +111,8 @@ def get_messages( else: rpc = functions.messages.GetMessages(id=ids) - r = self.send(rpc) + r = await self.send(rpc) - messages = utils.parse_messages(self, r, replies=replies) + messages = await utils.parse_messages(self, r, replies=replies) return messages if is_iterable else messages[0] diff --git a/pyrogram/client/methods/messages/iter_history.py b/pyrogram/client/methods/messages/iter_history.py index 641f500096..04c7dc1a77 100644 --- a/pyrogram/client/methods/messages/iter_history.py +++ b/pyrogram/client/methods/messages/iter_history.py @@ -16,14 +16,17 @@ # 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, Generator import pyrogram +from async_generator import async_generator, yield_ + from ...ext import BaseClient class IterHistory(BaseClient): - def iter_history( + @async_generator + async def iter_history( self, chat_id: Union[int, str], limit: int = 0, @@ -31,7 +34,7 @@ def iter_history( offset_id: int = 0, offset_date: int = 0, reverse: bool = False - ) -> Generator["pyrogram.Message", None, None]: + ) -> Optional[Generator["pyrogram.Message", None, None]]: """Iterate through a chat history sequentially. This convenience method does the same as repeatedly calling :meth:`~Client.get_history` in a loop, thus saving @@ -76,7 +79,7 @@ def iter_history( limit = min(100, total) while True: - messages = self.get_history( + messages = await self.get_history( chat_id=chat_id, limit=limit, offset=offset, @@ -91,7 +94,7 @@ def iter_history( 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/methods/messages/read_history.py b/pyrogram/client/methods/messages/read_history.py index f23fa800b4..5e1e265bb3 100644 --- a/pyrogram/client/methods/messages/read_history.py +++ b/pyrogram/client/methods/messages/read_history.py @@ -23,7 +23,7 @@ class ReadHistory(BaseClient): - def read_history( + async def read_history( self, chat_id: Union[int, str], max_id: int = 0 @@ -53,7 +53,7 @@ def read_history( app.read_history("pyrogramlounge", 123456) """ - peer = self.resolve_peer(chat_id) + peer = await self.resolve_peer(chat_id) if isinstance(peer, types.InputPeerChannel): q = functions.channels.ReadHistory( @@ -66,6 +66,6 @@ def read_history( max_id=max_id ) - self.send(q) + await self.send(q) return True diff --git a/pyrogram/client/methods/messages/retract_vote.py b/pyrogram/client/methods/messages/retract_vote.py index fbb020b204..191c8c7539 100644 --- a/pyrogram/client/methods/messages/retract_vote.py +++ b/pyrogram/client/methods/messages/retract_vote.py @@ -24,7 +24,7 @@ class RetractVote(BaseClient): - def retract_vote( + async def retract_vote( self, chat_id: Union[int, str], message_id: int @@ -48,9 +48,9 @@ def retract_vote( app.retract_vote(chat_id, message_id) """ - r = self.send( + r = await self.send( functions.messages.SendVote( - peer=self.resolve_peer(chat_id), + peer=await self.resolve_peer(chat_id), msg_id=message_id, options=[] ) diff --git a/pyrogram/client/methods/messages/search_global.py b/pyrogram/client/methods/messages/search_global.py index 45262d7889..2a889e312e 100644 --- a/pyrogram/client/methods/messages/search_global.py +++ b/pyrogram/client/methods/messages/search_global.py @@ -16,7 +16,9 @@ # 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 Generator, Optional + +from async_generator import async_generator, yield_ import pyrogram from pyrogram.api import functions, types @@ -24,11 +26,12 @@ class SearchGlobal(BaseClient): - def search_global( + @async_generator + async def search_global( self, query: str, limit: int = 0, - ) -> Generator["pyrogram.Message", None, None]: + ) -> Optional[Generator["pyrogram.Message", None, None]]: """Search messages globally from all of your chats. .. note:: @@ -64,9 +67,9 @@ def search_global( offset_id = 0 while True: - messages = utils.parse_messages( + messages = await utils.parse_messages( self, - self.send( + await self.send( functions.messages.SearchGlobal( q=query, offset_rate=offset_date, @@ -84,11 +87,11 @@ def search_global( last = messages[-1] offset_date = last.date - offset_peer = self.resolve_peer(last.chat.id) + offset_peer = await self.resolve_peer(last.chat.id) offset_id = last.message_id for message in messages: - yield message + await yield_(message) current += 1 diff --git a/pyrogram/client/methods/messages/search_messages.py b/pyrogram/client/methods/messages/search_messages.py index 119c40e2ba..bfd56663db 100644 --- a/pyrogram/client/methods/messages/search_messages.py +++ b/pyrogram/client/methods/messages/search_messages.py @@ -16,11 +16,12 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Union, List, Generator +from typing import Union, List, Generator, Optional import pyrogram from pyrogram.client.ext import BaseClient, utils from pyrogram.api import functions, types +from async_generator import async_generator, yield_ class Filters: @@ -46,7 +47,7 @@ class Filters: # noinspection PyShadowingBuiltins -def get_chunk( +async def get_chunk( client: BaseClient, chat_id: Union[int, str], query: str = "", @@ -61,9 +62,9 @@ def get_chunk( raise ValueError('Invalid filter "{}". Possible values are: {}'.format( filter, ", ".join('"{}"'.format(v) for v in POSSIBLE_VALUES))) from None - r = client.send( + r = await client.send( functions.messages.Search( - peer=client.resolve_peer(chat_id), + peer=await client.resolve_peer(chat_id), q=query, filter=filter, min_date=0, @@ -74,7 +75,7 @@ def get_chunk( min_id=0, max_id=0, from_id=( - client.resolve_peer(from_user) + await client.resolve_peer(from_user) if from_user else None ), @@ -82,12 +83,13 @@ def get_chunk( ) ) - return utils.parse_messages(client, r) + return await utils.parse_messages(client, r) class SearchMessages(BaseClient): # noinspection PyShadowingBuiltins - def search_messages( + @async_generator + async def search_messages( self, chat_id: Union[int, str], query: str = "", @@ -95,7 +97,7 @@ def search_messages( filter: str = "empty", limit: int = 0, from_user: Union[int, str] = None - ) -> Generator["pyrogram.Message", None, None]: + ) -> Optional[Generator["pyrogram.Message", None, None]]: """Search for text and media messages inside a specific chat. Parameters: @@ -160,7 +162,7 @@ def search_messages( limit = min(100, total) while True: - messages = get_chunk( + messages = await get_chunk( client=self, chat_id=chat_id, query=query, @@ -176,7 +178,7 @@ def search_messages( offset += 100 for message in messages: - yield message + await yield_(message) current += 1 diff --git a/pyrogram/client/methods/messages/send_animation.py b/pyrogram/client/methods/messages/send_animation.py index e8d9285fb9..465622140d 100644 --- a/pyrogram/client/methods/messages/send_animation.py +++ b/pyrogram/client/methods/messages/send_animation.py @@ -27,7 +27,7 @@ class SendAnimation(BaseClient): - def send_animation( + async def send_animation( self, chat_id: Union[int, str], animation: Union[str, BinaryIO], @@ -167,8 +167,8 @@ def progress(current, total): try: if isinstance(animation, str): if os.path.isfile(animation): - thumb = self.save_file(thumb) - file = self.save_file(animation, progress=progress, progress_args=progress_args) + thumb = await self.save_file(thumb) + file = await self.save_file(animation, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(animation) or "video/mp4", file=file, @@ -191,8 +191,8 @@ def progress(current, total): else: media = utils.get_input_media_from_file_id(animation, file_ref, 10) else: - thumb = self.save_file(thumb) - file = self.save_file(animation, progress=progress, progress_args=progress_args) + thumb = await self.save_file(thumb) + file = await self.save_file(animation, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(animation.name) or "video/mp4", file=file, @@ -211,27 +211,27 @@ def progress(current, total): 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, random_id=self.rnd_id(), schedule_date=schedule_date, reply_markup=reply_markup.write() if reply_markup else None, - **self.parser.parse(caption, parse_mode) + **await self.parser.parse(caption, parse_mode) ) ) except FilePartMissing as e: - self.save_file(animation, file_id=file.id, file_part=e.x) + 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, types.UpdateNewScheduledMessage) ): - message = pyrogram.Message._parse( + message = await pyrogram.Message._parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats}, @@ -242,7 +242,7 @@ def progress(current, total): document = message.animation or message.document document_id = utils.get_input_media_from_file_id(document.file_id, document.file_ref).id - self.send( + await self.send( functions.messages.SaveGif( id=document_id, unsave=True diff --git a/pyrogram/client/methods/messages/send_audio.py b/pyrogram/client/methods/messages/send_audio.py index 8dfabe8c5c..dc460e9744 100644 --- a/pyrogram/client/methods/messages/send_audio.py +++ b/pyrogram/client/methods/messages/send_audio.py @@ -27,7 +27,7 @@ class SendAudio(BaseClient): - def send_audio( + async def send_audio( self, chat_id: Union[int, str], audio: Union[str, BinaryIO], @@ -37,8 +37,7 @@ def send_audio( duration: int = 0, performer: str = None, title: str = None, - thumb: Union[str, BinaryIO] = None, - file_name: str = None, + thumb: Union[str, BinaryIO] = None, file_name: str = None, disable_notification: bool = None, reply_to_message_id: int = None, schedule_date: int = None, @@ -167,8 +166,8 @@ def progress(current, total): try: if isinstance(audio, str): if os.path.isfile(audio): - thumb = self.save_file(thumb) - file = self.save_file(audio, progress=progress, progress_args=progress_args) + thumb = await self.save_file(thumb) + file = await self.save_file(audio, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(audio) or "audio/mpeg", file=file, @@ -189,8 +188,8 @@ def progress(current, total): else: media = utils.get_input_media_from_file_id(audio, file_ref, 9) else: - thumb = self.save_file(thumb) - file = self.save_file(audio, progress=progress, progress_args=progress_args) + thumb = await self.save_file(thumb) + file = await self.save_file(audio, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(audio.name) or "audio/mpeg", file=file, @@ -207,27 +206,27 @@ def progress(current, total): 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, random_id=self.rnd_id(), schedule_date=schedule_date, reply_markup=reply_markup.write() if reply_markup else None, - **self.parser.parse(caption, parse_mode) + **await self.parser.parse(caption, parse_mode) ) ) 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, types.UpdateNewScheduledMessage) ): - return pyrogram.Message._parse( + return await pyrogram.Message._parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats}, diff --git a/pyrogram/client/methods/messages/send_cached_media.py b/pyrogram/client/methods/messages/send_cached_media.py index 1ee139f535..d550cc279f 100644 --- a/pyrogram/client/methods/messages/send_cached_media.py +++ b/pyrogram/client/methods/messages/send_cached_media.py @@ -24,7 +24,7 @@ class SendCachedMedia(BaseClient): - def send_cached_media( + async def send_cached_media( self, chat_id: Union[int, str], file_id: str, @@ -94,22 +94,22 @@ def send_cached_media( app.send_cached_media("me", "CAADBAADzg4AAvLQYAEz_x2EOgdRwBYE") """ - r = self.send( + r = await self.send( functions.messages.SendMedia( - peer=self.resolve_peer(chat_id), + peer=await self.resolve_peer(chat_id), media=utils.get_input_media_from_file_id(file_id, file_ref), silent=disable_notification or None, reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), schedule_date=schedule_date, reply_markup=reply_markup.write() if reply_markup else None, - **self.parser.parse(caption, parse_mode) + **await self.parser.parse(caption, parse_mode) ) ) for i in r.updates: if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage, types.UpdateNewScheduledMessage)): - return pyrogram.Message._parse( + return await pyrogram.Message._parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats}, diff --git a/pyrogram/client/methods/messages/send_chat_action.py b/pyrogram/client/methods/messages/send_chat_action.py index 5d0dabb913..35a3d722af 100644 --- a/pyrogram/client/methods/messages/send_chat_action.py +++ b/pyrogram/client/methods/messages/send_chat_action.py @@ -43,7 +43,7 @@ class ChatAction: class SendChatAction(BaseClient): - def send_chat_action(self, chat_id: Union[int, str], action: str) -> bool: + async def send_chat_action(self, chat_id: Union[int, str], action: str) -> bool: """Tell the other party that something is happening on your side. Parameters: @@ -93,9 +93,9 @@ def send_chat_action(self, chat_id: Union[int, str], action: str) -> bool: 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/send_contact.py b/pyrogram/client/methods/messages/send_contact.py index 95ff6f3844..0eacd8fd4c 100644 --- a/pyrogram/client/methods/messages/send_contact.py +++ b/pyrogram/client/methods/messages/send_contact.py @@ -24,7 +24,7 @@ class SendContact(BaseClient): - def send_contact( + async def send_contact( self, chat_id: Union[int, str], phone_number: str, @@ -83,9 +83,9 @@ def send_contact( app.send_contact("me", "+39 123 456 7890", "Dan") """ - 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=phone_number, first_name=first_name, @@ -103,7 +103,7 @@ def send_contact( for i in r.updates: if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage, types.UpdateNewScheduledMessage)): - return pyrogram.Message._parse( + return await pyrogram.Message._parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats}, diff --git a/pyrogram/client/methods/messages/send_dice.py b/pyrogram/client/methods/messages/send_dice.py index b426f93905..155185cd3e 100644 --- a/pyrogram/client/methods/messages/send_dice.py +++ b/pyrogram/client/methods/messages/send_dice.py @@ -24,7 +24,7 @@ class SendDice(BaseClient): - def send_dice( + async def send_dice( self, chat_id: Union[int, str], emoji: str = "🎲", @@ -80,9 +80,9 @@ def send_dice( app.send_dice("pyrogramlounge", "🏀") """ - r = self.send( + r = await self.send( functions.messages.SendMedia( - peer=self.resolve_peer(chat_id), + peer=await self.resolve_peer(chat_id), media=types.InputMediaDice(emoticon=emoji), silent=disable_notification or None, reply_to_msg_id=reply_to_message_id, @@ -94,11 +94,8 @@ def send_dice( ) for i in r.updates: - if isinstance( - i, - (types.UpdateNewMessage, types.UpdateNewChannelMessage, types.UpdateNewScheduledMessage) - ): - return pyrogram.Message._parse( + if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage, types.UpdateNewScheduledMessage)): + return await pyrogram.Message._parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats}, diff --git a/pyrogram/client/methods/messages/send_document.py b/pyrogram/client/methods/messages/send_document.py index 8ca7fc4e8c..7f93d6cba0 100644 --- a/pyrogram/client/methods/messages/send_document.py +++ b/pyrogram/client/methods/messages/send_document.py @@ -27,7 +27,7 @@ class SendDocument(BaseClient): - def send_document( + async def send_document( self, chat_id: Union[int, str], document: Union[str, BinaryIO], @@ -147,8 +147,8 @@ def progress(current, total): try: if isinstance(document, str): if os.path.isfile(document): - thumb = self.save_file(thumb) - file = self.save_file(document, progress=progress, progress_args=progress_args) + thumb = await self.save_file(thumb) + file = await self.save_file(document, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(document) or "application/zip", file=file, @@ -165,8 +165,8 @@ def progress(current, total): else: media = utils.get_input_media_from_file_id(document, file_ref, 5) else: - thumb = self.save_file(thumb) - file = self.save_file(document, progress=progress, progress_args=progress_args) + thumb = await self.save_file(thumb) + file = await self.save_file(document, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(document.name) or "application/zip", file=file, @@ -178,27 +178,27 @@ def progress(current, total): 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, random_id=self.rnd_id(), schedule_date=schedule_date, reply_markup=reply_markup.write() if reply_markup else None, - **self.parser.parse(caption, parse_mode) + **await self.parser.parse(caption, parse_mode) ) ) 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, types.UpdateNewScheduledMessage) ): - return pyrogram.Message._parse( + return await pyrogram.Message._parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats}, diff --git a/pyrogram/client/methods/messages/send_location.py b/pyrogram/client/methods/messages/send_location.py index 04b614ce16..b23d9b10ef 100644 --- a/pyrogram/client/methods/messages/send_location.py +++ b/pyrogram/client/methods/messages/send_location.py @@ -24,7 +24,7 @@ class SendLocation(BaseClient): - def send_location( + async def send_location( self, chat_id: Union[int, str], latitude: float, @@ -75,9 +75,9 @@ def send_location( app.send_location("me", 51.500729, -0.124583) """ - 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( geo_point=types.InputGeoPoint( lat=latitude, @@ -95,7 +95,7 @@ def send_location( for i in r.updates: if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage, types.UpdateNewScheduledMessage)): - return pyrogram.Message._parse( + return await pyrogram.Message._parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats}, diff --git a/pyrogram/client/methods/messages/send_media_group.py b/pyrogram/client/methods/messages/send_media_group.py index 2a2cca74a9..0fad6e91e5 100644 --- a/pyrogram/client/methods/messages/send_media_group.py +++ b/pyrogram/client/methods/messages/send_media_group.py @@ -30,7 +30,7 @@ class SendMediaGroup(BaseClient): # TODO: Add progress parameter - def send_media_group( + async def send_media_group( self, chat_id: Union[int, str], media: List[Union["pyrogram.InputMediaPhoto", "pyrogram.InputMediaVideo"]], @@ -77,11 +77,11 @@ def send_media_group( for i in media: if isinstance(i, pyrogram.InputMediaPhoto): if os.path.isfile(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) ) ) ) @@ -94,9 +94,9 @@ def send_media_group( ) ) elif re.match("^https?://", 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.InputMediaPhotoExternal( url=i.media ) @@ -114,11 +114,11 @@ def send_media_group( media = utils.get_input_media_from_file_id(i.media, i.file_ref, 2) elif isinstance(i, pyrogram.InputMediaVideo): if os.path.isfile(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), thumb=self.save_file(i.thumb), mime_type=self.guess_mime_type(i.media) or "video/mp4", attributes=[ @@ -142,9 +142,9 @@ def send_media_group( ) ) elif re.match("^https?://", 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.InputMediaDocumentExternal( url=i.media ) @@ -165,20 +165,20 @@ def send_media_group( types.InputSingleMedia( media=media, random_id=self.rnd_id(), - **self.parser.parse(i.caption, i.parse_mode) + **await self.parser.parse(i.caption, i.parse_mode) ) ) - r = self.send( + r = 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 ) ) - return utils.parse_messages( + return await utils.parse_messages( self, types.messages.Messages( messages=[m.message for m in filter( diff --git a/pyrogram/client/methods/messages/send_message.py b/pyrogram/client/methods/messages/send_message.py index f719031c45..58385bcf7e 100644 --- a/pyrogram/client/methods/messages/send_message.py +++ b/pyrogram/client/methods/messages/send_message.py @@ -24,7 +24,7 @@ class SendMessage(BaseClient): - def send_message( + async def send_message( self, chat_id: Union[int, str], text: str, @@ -116,11 +116,11 @@ def send_message( ])) """ - message, entities = self.parser.parse(text, parse_mode).values() + message, entities = (await self.parser.parse(text, parse_mode)).values() - 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, @@ -133,7 +133,7 @@ def send_message( ) if isinstance(r, types.UpdateShortSentMessage): - peer = self.resolve_peer(chat_id) + peer = await self.resolve_peer(chat_id) peer_id = ( peer.user_id @@ -160,7 +160,7 @@ def send_message( for i in r.updates: if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage, types.UpdateNewScheduledMessage)): - return pyrogram.Message._parse( + return await pyrogram.Message._parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats}, diff --git a/pyrogram/client/methods/messages/send_photo.py b/pyrogram/client/methods/messages/send_photo.py index 6310168585..7c2c194c36 100644 --- a/pyrogram/client/methods/messages/send_photo.py +++ b/pyrogram/client/methods/messages/send_photo.py @@ -27,7 +27,7 @@ class SendPhoto(BaseClient): - def send_photo( + async def send_photo( self, chat_id: Union[int, str], photo: Union[str, BinaryIO], @@ -141,7 +141,7 @@ def send_photo( try: if isinstance(photo, str): if os.path.isfile(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 @@ -154,7 +154,7 @@ def send_photo( else: media = utils.get_input_media_from_file_id(photo, file_ref, 2) else: - 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 @@ -162,27 +162,27 @@ def send_photo( 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, random_id=self.rnd_id(), schedule_date=schedule_date, reply_markup=reply_markup.write() if reply_markup else None, - **self.parser.parse(caption, parse_mode) + **await self.parser.parse(caption, parse_mode) ) ) 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, types.UpdateNewScheduledMessage) ): - return pyrogram.Message._parse( + return await pyrogram.Message._parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats}, diff --git a/pyrogram/client/methods/messages/send_poll.py b/pyrogram/client/methods/messages/send_poll.py index e0baf5a86f..7607a54609 100644 --- a/pyrogram/client/methods/messages/send_poll.py +++ b/pyrogram/client/methods/messages/send_poll.py @@ -24,7 +24,7 @@ class SendPoll(BaseClient): - def send_poll( + async def send_poll( self, chat_id: Union[int, str], question: str, @@ -95,9 +95,9 @@ def send_poll( app.send_poll(chat_id, "Is this a poll question?", ["Yes", "No", "Maybe"]) """ - r = self.send( + r = await self.send( functions.messages.SendMedia( - peer=self.resolve_peer(chat_id), + peer=await self.resolve_peer(chat_id), media=types.InputMediaPoll( poll=types.Poll( id=0, @@ -123,7 +123,7 @@ def send_poll( for i in r.updates: if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage, types.UpdateNewScheduledMessage)): - return pyrogram.Message._parse( + return await pyrogram.Message._parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats}, diff --git a/pyrogram/client/methods/messages/send_sticker.py b/pyrogram/client/methods/messages/send_sticker.py index 529a4f8d59..8025a0dd73 100644 --- a/pyrogram/client/methods/messages/send_sticker.py +++ b/pyrogram/client/methods/messages/send_sticker.py @@ -27,7 +27,7 @@ class SendSticker(BaseClient): - def send_sticker( + async def send_sticker( self, chat_id: Union[int, str], sticker: Union[str, BinaryIO], @@ -117,7 +117,7 @@ def send_sticker( try: if isinstance(sticker, str): if os.path.isfile(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=self.guess_mime_type(sticker) or "image/webp", file=file, @@ -132,7 +132,7 @@ def send_sticker( else: media = utils.get_input_media_from_file_id(sticker, file_ref, 8) else: - 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=self.guess_mime_type(sticker.name) or "image/webp", file=file, @@ -143,9 +143,9 @@ def send_sticker( 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, @@ -156,14 +156,14 @@ def send_sticker( ) ) 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, types.UpdateNewScheduledMessage) ): - return pyrogram.Message._parse( + return await pyrogram.Message._parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats}, diff --git a/pyrogram/client/methods/messages/send_venue.py b/pyrogram/client/methods/messages/send_venue.py index 98ff4103d0..f6f09d5eca 100644 --- a/pyrogram/client/methods/messages/send_venue.py +++ b/pyrogram/client/methods/messages/send_venue.py @@ -24,7 +24,7 @@ class SendVenue(BaseClient): - def send_venue( + async def send_venue( self, chat_id: Union[int, str], latitude: float, @@ -94,9 +94,9 @@ def send_venue( "me", 51.500729, -0.124583, "Elizabeth Tower", "Westminster, London SW1A 0AA, UK") """ - 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, @@ -119,7 +119,7 @@ def send_venue( for i in r.updates: if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage, types.UpdateNewScheduledMessage)): - return pyrogram.Message._parse( + return await pyrogram.Message._parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats}, diff --git a/pyrogram/client/methods/messages/send_video.py b/pyrogram/client/methods/messages/send_video.py index 40691771ef..87cecf5a1f 100644 --- a/pyrogram/client/methods/messages/send_video.py +++ b/pyrogram/client/methods/messages/send_video.py @@ -27,7 +27,7 @@ class SendVideo(BaseClient): - def send_video( + async def send_video( self, chat_id: Union[int, str], video: Union[str, BinaryIO], @@ -164,8 +164,8 @@ def progress(current, total): try: if isinstance(video, str): if os.path.isfile(video): - thumb = self.save_file(thumb) - file = self.save_file(video, progress=progress, progress_args=progress_args) + thumb = await self.save_file(thumb) + file = await self.save_file(video, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(video) or "video/mp4", file=file, @@ -187,8 +187,8 @@ def progress(current, total): else: media = utils.get_input_media_from_file_id(video, file_ref, 4) else: - thumb = self.save_file(thumb) - file = self.save_file(video, progress=progress, progress_args=progress_args) + thumb = await self.save_file(thumb) + file = await self.save_file(video, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(video.name) or "video/mp4", file=file, @@ -206,27 +206,27 @@ def progress(current, total): 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, random_id=self.rnd_id(), schedule_date=schedule_date, reply_markup=reply_markup.write() if reply_markup else None, - **self.parser.parse(caption, parse_mode) + **await self.parser.parse(caption, parse_mode) ) ) 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, types.UpdateNewScheduledMessage) ): - return pyrogram.Message._parse( + return await pyrogram.Message._parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats}, diff --git a/pyrogram/client/methods/messages/send_video_note.py b/pyrogram/client/methods/messages/send_video_note.py index d3b32834ba..c204f6ca15 100644 --- a/pyrogram/client/methods/messages/send_video_note.py +++ b/pyrogram/client/methods/messages/send_video_note.py @@ -26,7 +26,7 @@ class SendVideoNote(BaseClient): - def send_video_note( + async def send_video_note( self, chat_id: Union[int, str], video_note: Union[str, BinaryIO], @@ -131,8 +131,8 @@ def send_video_note( try: if isinstance(video_note, str): if os.path.isfile(video_note): - thumb = self.save_file(thumb) - file = self.save_file(video_note, progress=progress, progress_args=progress_args) + thumb = await self.save_file(thumb) + file = await self.save_file(video_note, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(video_note) or "video/mp4", file=file, @@ -149,8 +149,8 @@ def send_video_note( else: media = utils.get_input_media_from_file_id(video_note, file_ref, 13) else: - thumb = self.save_file(thumb) - file = self.save_file(video_note, progress=progress, progress_args=progress_args) + thumb = await self.save_file(thumb) + file = await self.save_file(video_note, progress=progress, progress_args=progress_args) media = types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(video_note.name) or "video/mp4", file=file, @@ -167,9 +167,9 @@ def send_video_note( 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,14 +180,14 @@ def send_video_note( ) ) 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, types.UpdateNewScheduledMessage) ): - return pyrogram.Message._parse( + return await pyrogram.Message._parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats}, diff --git a/pyrogram/client/methods/messages/send_voice.py b/pyrogram/client/methods/messages/send_voice.py index f99b423681..98221e8dbd 100644 --- a/pyrogram/client/methods/messages/send_voice.py +++ b/pyrogram/client/methods/messages/send_voice.py @@ -27,7 +27,7 @@ class SendVoice(BaseClient): - def send_voice( + async def send_voice( self, chat_id: Union[int, str], voice: Union[str, BinaryIO], @@ -136,7 +136,7 @@ def send_voice( try: if isinstance(voice, str): if os.path.isfile(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=self.guess_mime_type(voice) or "audio/mpeg", file=file, @@ -154,7 +154,7 @@ def send_voice( else: media = utils.get_input_media_from_file_id(voice, file_ref, 3) else: - 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=self.guess_mime_type(voice.name) or "audio/mpeg", file=file, @@ -168,27 +168,27 @@ def send_voice( 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, random_id=self.rnd_id(), schedule_date=schedule_date, reply_markup=reply_markup.write() if reply_markup else None, - **self.parser.parse(caption, parse_mode) + **await self.parser.parse(caption, parse_mode) ) ) 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, types.UpdateNewScheduledMessage) ): - return pyrogram.Message._parse( + return await pyrogram.Message._parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats}, diff --git a/pyrogram/client/methods/messages/stop_poll.py b/pyrogram/client/methods/messages/stop_poll.py index 4d133f7fa6..79498e45ed 100644 --- a/pyrogram/client/methods/messages/stop_poll.py +++ b/pyrogram/client/methods/messages/stop_poll.py @@ -24,7 +24,7 @@ class StopPoll(BaseClient): - def stop_poll( + async def stop_poll( self, chat_id: Union[int, str], message_id: int, @@ -54,11 +54,11 @@ def stop_poll( app.stop_poll(chat_id, message_id) """ - poll = self.get_messages(chat_id, message_id).poll + poll = (await self.get_messages(chat_id, message_id)).poll - 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, media=types.InputMediaPoll( poll=types.Poll( diff --git a/pyrogram/client/methods/messages/vote_poll.py b/pyrogram/client/methods/messages/vote_poll.py index 335ca7cbaa..667df0fb16 100644 --- a/pyrogram/client/methods/messages/vote_poll.py +++ b/pyrogram/client/methods/messages/vote_poll.py @@ -24,7 +24,7 @@ class VotePoll(BaseClient): - def vote_poll( + async def vote_poll( self, chat_id: Union[int, str], message_id: id, @@ -53,12 +53,12 @@ def vote_poll( app.vote_poll(chat_id, message_id, 6) """ - poll = self.get_messages(chat_id, message_id).poll + poll = (await self.get_messages(chat_id, message_id)).poll options = [options] if not isinstance(options, list) else options - r = self.send( + r = await self.send( functions.messages.SendVote( - peer=self.resolve_peer(chat_id), + peer=await self.resolve_peer(chat_id), msg_id=message_id, options=[poll.options[option].data for option in options] ) diff --git a/pyrogram/client/methods/password/change_cloud_password.py b/pyrogram/client/methods/password/change_cloud_password.py index 20625657b2..54ec289193 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( + async def change_cloud_password( self, current_password: str, new_password: str, @@ -57,7 +57,7 @@ def change_cloud_password( # Change password and hint app.change_cloud_password("current_password", "new_password", new_hint="hint") """ - r = self.send(functions.account.GetPassword()) + r = await self.send(functions.account.GetPassword()) if not r.has_password: raise ValueError("There is no cloud password to change") @@ -66,7 +66,7 @@ def change_cloud_password( new_hash = btoi(compute_hash(r.new_algo, new_password)) new_hash = itob(pow(r.new_algo.g, new_hash, btoi(r.new_algo.p))) - self.send( + await self.send( functions.account.UpdatePasswordSettings( password=compute_check(r, current_password), 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 c8052aa8b1..b629194345 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( + async def enable_cloud_password( self, password: str, hint: str = "", @@ -62,7 +62,7 @@ def enable_cloud_password( # Enable password with hint and email app.enable_cloud_password("password", hint="hint", email="user@email.com") """ - r = self.send(functions.account.GetPassword()) + r = await self.send(functions.account.GetPassword()) if r.has_password: raise ValueError("There is already a cloud password enabled") @@ -71,7 +71,7 @@ def enable_cloud_password( new_hash = btoi(compute_hash(r.new_algo, password)) new_hash = itob(pow(r.new_algo.g, new_hash, btoi(r.new_algo.p))) - self.send( + await self.send( functions.account.UpdatePasswordSettings( password=types.InputCheckPasswordEmpty(), 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 21ebb8b371..9d41a9db8b 100644 --- a/pyrogram/client/methods/password/remove_cloud_password.py +++ b/pyrogram/client/methods/password/remove_cloud_password.py @@ -22,7 +22,7 @@ class RemoveCloudPassword(BaseClient): - def remove_cloud_password( + async def remove_cloud_password( self, password: str ) -> bool: @@ -43,12 +43,12 @@ def remove_cloud_password( app.remove_cloud_password("password") """ - r = self.send(functions.account.GetPassword()) + r = await self.send(functions.account.GetPassword()) if not r.has_password: raise ValueError("There is no cloud password to remove") - self.send( + await self.send( functions.account.UpdatePasswordSettings( password=compute_check(r, password), new_settings=types.account.PasswordInputSettings( diff --git a/pyrogram/client/methods/users/block_user.py b/pyrogram/client/methods/users/block_user.py index b61507f931..8dd6e09dff 100644 --- a/pyrogram/client/methods/users/block_user.py +++ b/pyrogram/client/methods/users/block_user.py @@ -23,7 +23,7 @@ class BlockUser(BaseClient): - def block_user( + async def block_user( self, user_id: Union[int, str] ) -> bool: @@ -44,9 +44,9 @@ def block_user( app.block_user(user_id) """ return bool( - self.send( + await self.send( functions.contacts.Block( - id=self.resolve_peer(user_id) + id=await self.resolve_peer(user_id) ) ) ) diff --git a/pyrogram/client/methods/users/delete_profile_photos.py b/pyrogram/client/methods/users/delete_profile_photos.py index 66ad219fe3..ac184da5d9 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( + async def delete_profile_photos( self, photo_ids: Union[str, List[str]] ) -> bool: @@ -53,7 +53,7 @@ def delete_profile_photos( photo_ids = photo_ids if isinstance(photo_ids, list) else [photo_ids] input_photos = [utils.get_input_media_from_file_id(i).id for i in photo_ids] - return bool(self.send( + return bool(await self.send( functions.photos.DeletePhotos( id=input_photos ) diff --git a/pyrogram/client/methods/users/get_common_chats.py b/pyrogram/client/methods/users/get_common_chats.py index 35d037fbd6..fab202fd36 100644 --- a/pyrogram/client/methods/users/get_common_chats.py +++ b/pyrogram/client/methods/users/get_common_chats.py @@ -24,7 +24,7 @@ class GetCommonChats(BaseClient): - def get_common_chats(self, user_id: Union[int, str]) -> list: + async def get_common_chats(self, user_id: Union[int, str]) -> list: """Get the common chats you have with a user. Parameters: @@ -46,10 +46,10 @@ def get_common_chats(self, user_id: Union[int, str]) -> list: print(common) """ - peer = self.resolve_peer(user_id) + peer = await self.resolve_peer(user_id) if isinstance(peer, types.InputPeerUser): - r = self.send( + r = await self.send( functions.messages.GetCommonChats( user_id=peer, max_id=0, diff --git a/pyrogram/client/methods/users/get_me.py b/pyrogram/client/methods/users/get_me.py index 1814fa6dee..0efbddb2e5 100644 --- a/pyrogram/client/methods/users/get_me.py +++ b/pyrogram/client/methods/users/get_me.py @@ -22,7 +22,7 @@ class GetMe(BaseClient): - def get_me(self) -> "pyrogram.User": + async def get_me(self) -> "pyrogram.User": """Get your own user identity. Returns: @@ -36,9 +36,9 @@ def get_me(self) -> "pyrogram.User": """ return pyrogram.User._parse( self, - self.send( + (await self.send( functions.users.GetFullUser( id=types.InputPeerSelf() ) - ).user + )).user ) diff --git a/pyrogram/client/methods/users/get_profile_photos.py b/pyrogram/client/methods/users/get_profile_photos.py index ec23e651c7..fded8dcb98 100644 --- a/pyrogram/client/methods/users/get_profile_photos.py +++ b/pyrogram/client/methods/users/get_profile_photos.py @@ -25,7 +25,7 @@ class GetProfilePhotos(BaseClient): - def get_profile_photos( + async def get_profile_photos( self, chat_id: Union[int, str], offset: int = 0, @@ -62,12 +62,12 @@ def get_profile_photos( # Get 3 profile photos of a user, skip the first 5 app.get_profile_photos("haskell", limit=3, offset=5) """ - peer_id = self.resolve_peer(chat_id) + peer_id = await self.resolve_peer(chat_id) if isinstance(peer_id, types.InputPeerChannel): - r = utils.parse_messages( + r = await utils.parse_messages( self, - self.send( + await self.send( functions.messages.Search( peer=peer_id, q="", @@ -86,7 +86,7 @@ def get_profile_photos( return pyrogram.List([message.new_chat_photo for message in r][:limit]) else: - r = self.send( + r = await self.send( functions.photos.GetUserPhotos( user_id=peer_id, offset=offset, diff --git a/pyrogram/client/methods/users/get_profile_photos_count.py b/pyrogram/client/methods/users/get_profile_photos_count.py index a927d8bd9e..affc00e110 100644 --- a/pyrogram/client/methods/users/get_profile_photos_count.py +++ b/pyrogram/client/methods/users/get_profile_photos_count.py @@ -23,7 +23,7 @@ class GetProfilePhotosCount(BaseClient): - def get_profile_photos_count(self, chat_id: Union[int, str]) -> int: + async def get_profile_photos_count(self, chat_id: Union[int, str]) -> int: """Get the total count of profile pictures for a user. Parameters: @@ -42,10 +42,10 @@ def get_profile_photos_count(self, chat_id: Union[int, str]) -> int: print(count) """ - peer_id = self.resolve_peer(chat_id) + peer_id = await self.resolve_peer(chat_id) if isinstance(peer_id, types.InputPeerChannel): - r = self.send( + r = await self.send( functions.messages.GetSearchCounters( peer=peer_id, filters=[types.InputMessagesFilterChatPhotos()], @@ -54,7 +54,7 @@ def get_profile_photos_count(self, chat_id: Union[int, str]) -> int: return r[0].count else: - r = self.send( + r = await self.send( functions.photos.GetUserPhotos( user_id=peer_id, offset=0, diff --git a/pyrogram/client/methods/users/get_users.py b/pyrogram/client/methods/users/get_users.py index b115cb032b..05476bc453 100644 --- a/pyrogram/client/methods/users/get_users.py +++ b/pyrogram/client/methods/users/get_users.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 from typing import Iterable, Union, List import pyrogram @@ -24,7 +25,7 @@ class GetUsers(BaseClient): - def get_users( + async def get_users( self, user_ids: Union[Iterable[Union[int, str]], int, str] ) -> Union["pyrogram.User", List["pyrogram.User"]]: @@ -53,9 +54,9 @@ def get_users( """ 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 ) diff --git a/pyrogram/client/methods/users/iter_profile_photos.py b/pyrogram/client/methods/users/iter_profile_photos.py index fb09cff7eb..bdd3d8b79e 100644 --- a/pyrogram/client/methods/users/iter_profile_photos.py +++ b/pyrogram/client/methods/users/iter_profile_photos.py @@ -16,19 +16,22 @@ # 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, Generator, Optional import pyrogram +from async_generator import async_generator, yield_ + from ...ext import BaseClient class IterProfilePhotos(BaseClient): - def iter_profile_photos( + @async_generator + async def iter_profile_photos( self, chat_id: Union[int, str], offset: int = 0, limit: int = 0, - ) -> Generator["pyrogram.Photo", None, None]: + ) -> Optional[Generator["pyrogram.Message", None, None]]: """Iterate through a chat or a user profile photos sequentially. This convenience method does the same as repeatedly calling :meth:`~Client.get_profile_photos` in a loop, thus @@ -62,7 +65,7 @@ def iter_profile_photos( limit = min(100, total) while True: - photos = self.get_profile_photos( + photos = await self.get_profile_photos( chat_id=chat_id, offset=offset, limit=limit @@ -74,7 +77,7 @@ def iter_profile_photos( offset += len(photos) for photo in photos: - yield photo + await yield_(photo) current += 1 diff --git a/pyrogram/client/methods/users/set_profile_photo.py b/pyrogram/client/methods/users/set_profile_photo.py index 01741df94c..b9dbbf1048 100644 --- a/pyrogram/client/methods/users/set_profile_photo.py +++ b/pyrogram/client/methods/users/set_profile_photo.py @@ -23,7 +23,7 @@ class SetProfilePhoto(BaseClient): - def set_profile_photo( + async def set_profile_photo( self, *, photo: Union[str, BinaryIO] = None, @@ -64,10 +64,10 @@ def set_profile_photo( """ return bool( - self.send( + await self.send( functions.photos.UploadProfilePhoto( - file=self.save_file(photo), - video=self.save_file(video) + file=await self.save_file(photo), + video=await self.save_file(video) ) ) ) diff --git a/pyrogram/client/methods/users/unblock_user.py b/pyrogram/client/methods/users/unblock_user.py index 8459cfd63c..fddf9ff655 100644 --- a/pyrogram/client/methods/users/unblock_user.py +++ b/pyrogram/client/methods/users/unblock_user.py @@ -23,7 +23,7 @@ class UnblockUser(BaseClient): - def unblock_user( + async def unblock_user( self, user_id: Union[int, str] ) -> bool: @@ -44,9 +44,9 @@ def unblock_user( app.unblock_user(user_id) """ return bool( - self.send( + await self.send( functions.contacts.Unblock( - id=self.resolve_peer(user_id) + id=await self.resolve_peer(user_id) ) ) ) diff --git a/pyrogram/client/methods/users/update_profile.py b/pyrogram/client/methods/users/update_profile.py index da7d62f4b3..145d503509 100644 --- a/pyrogram/client/methods/users/update_profile.py +++ b/pyrogram/client/methods/users/update_profile.py @@ -21,7 +21,7 @@ class UpdateProfile(BaseClient): - def update_profile( + async def update_profile( self, first_name: str = None, last_name: str = None, @@ -60,7 +60,7 @@ def update_profile( """ return bool( - self.send( + await self.send( functions.account.UpdateProfile( first_name=first_name, last_name=last_name, diff --git a/pyrogram/client/methods/users/update_username.py b/pyrogram/client/methods/users/update_username.py index 24a12a8e45..50e2388c7e 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: @@ -47,7 +47,7 @@ def update_username( """ return bool( - self.send( + await self.send( functions.account.UpdateUsername( username=username or "" ) diff --git a/pyrogram/client/parser/html.py b/pyrogram/client/parser/html.py index 35dd770c49..3ce70d5101 100644 --- a/pyrogram/client/parser/html.py +++ b/pyrogram/client/parser/html.py @@ -110,7 +110,7 @@ class HTML: def __init__(self, client: Union["pyrogram.BaseClient", None]): self.client = client - def parse(self, text: str): + async def parse(self, text: str): # Strip whitespace characters from the end of the message, but preserve closing tags text = re.sub(r"\s*()\s*$", r"\1", text) @@ -132,7 +132,7 @@ def parse(self, text: str): if isinstance(entity, types.InputMessageEntityMentionName): try: if self.client is not None: - entity.user_id = self.client.resolve_peer(entity.user_id) + entity.user_id = await self.client.resolve_peer(entity.user_id) except PeerIdInvalid: continue diff --git a/pyrogram/client/parser/markdown.py b/pyrogram/client/parser/markdown.py index 5f4ab2583d..4c954efd63 100644 --- a/pyrogram/client/parser/markdown.py +++ b/pyrogram/client/parser/markdown.py @@ -56,7 +56,7 @@ class Markdown: def __init__(self, client: Union["pyrogram.BaseClient", None]): self.html = HTML(client) - def parse(self, text: str, strict: bool = False): + async def parse(self, text: str, strict: bool = False): if strict: text = html.escape(text) @@ -102,7 +102,7 @@ def parse(self, text: str, strict: bool = False): text = utils.replace_once(text, delim, tag, start) - return self.html.parse(text) + return await self.html.parse(text) @staticmethod def unparse(text: str, entities: list): diff --git a/pyrogram/client/parser/parser.py b/pyrogram/client/parser/parser.py index 968b95f1b7..eb4f2e19b9 100644 --- a/pyrogram/client/parser/parser.py +++ b/pyrogram/client/parser/parser.py @@ -30,7 +30,7 @@ def __init__(self, client: Union["pyrogram.BaseClient", None]): self.html = HTML(client) self.markdown = Markdown(client) - def parse(self, text: str, mode: Union[str, None] = object): + async def parse(self, text: str, mode: Union[str, None] = object): text = str(text).strip() if mode == object: @@ -48,13 +48,13 @@ def parse(self, text: str, mode: Union[str, None] = object): mode = mode.lower() if mode == "combined": - return self.markdown.parse(text) + return await self.markdown.parse(text) if mode in ["markdown", "md"]: - return self.markdown.parse(text, True) + return await self.markdown.parse(text, True) if mode == "html": - return self.html.parse(text) + return await self.html.parse(text) raise ValueError('parse_mode must be one of {} or None. Not "{}"'.format( ", ".join('"{}"'.format(m) for m in pyrogram.Client.PARSE_MODES[:-1]), diff --git a/pyrogram/client/types/bots_and_keyboards/callback_query.py b/pyrogram/client/types/bots_and_keyboards/callback_query.py index ec4048fcc3..a15a7c35e3 100644 --- a/pyrogram/client/types/bots_and_keyboards/callback_query.py +++ b/pyrogram/client/types/bots_and_keyboards/callback_query.py @@ -89,12 +89,12 @@ def __init__( self.matches = matches @staticmethod - def _parse(client, callback_query, users) -> "CallbackQuery": + async def _parse(client, callback_query, users) -> "CallbackQuery": message = None inline_message_id = None if isinstance(callback_query, types.UpdateBotCallbackQuery): - message = client.get_messages(utils.get_peer_id(callback_query.peer), callback_query.msg_id) + message = await client.get_messages(utils.get_peer_id(callback_query.peer), callback_query.msg_id) elif isinstance(callback_query, types.UpdateInlineBotCallbackQuery): inline_message_id = b64encode( pack( @@ -124,7 +124,7 @@ def _parse(client, callback_query, users) -> "CallbackQuery": client=client ) - def answer(self, text: str = None, show_alert: bool = None, url: str = None, cache_time: int = 0): + async def answer(self, text: str = None, show_alert: bool = None, url: str = None, cache_time: int = 0): """Bound method *answer* of :obj:`CallbackQuery`. Use this method as a shortcut for: @@ -160,7 +160,7 @@ def answer(self, text: str = None, show_alert: bool = None, url: str = None, cac The maximum amount of time in seconds that the result of the callback query may be cached client-side. Telegram apps will support caching starting in version 3.14. Defaults to 0. """ - return self._client.answer_callback_query( + return await self._client.answer_callback_query( callback_query_id=self.id, text=text, show_alert=show_alert, @@ -168,7 +168,7 @@ def answer(self, text: str = None, show_alert: bool = None, url: str = None, cac cache_time=cache_time ) - def edit_message_text( + async def edit_message_text( self, text: str, parse_mode: Union[str, None] = object, @@ -204,7 +204,7 @@ def edit_message_text( RPCError: In case of a Telegram RPC error. """ if self.inline_message_id is None: - return self._client.edit_message_text( + return await self._client.edit_message_text( chat_id=self.message.chat.id, message_id=self.message.message_id, text=text, @@ -213,7 +213,7 @@ def edit_message_text( reply_markup=reply_markup ) else: - return self._client.edit_inline_text( + return await self._client.edit_inline_text( inline_message_id=self.inline_message_id, text=text, parse_mode=parse_mode, @@ -221,7 +221,7 @@ def edit_message_text( reply_markup=reply_markup ) - def edit_message_caption( + async def edit_message_caption( self, caption: str, parse_mode: Union[str, None] = object, @@ -252,9 +252,9 @@ def edit_message_caption( Raises: RPCError: In case of a Telegram RPC error. """ - return self.edit_message_text(caption, parse_mode, reply_markup) + return await self.edit_message_text(caption, parse_mode, reply_markup) - def edit_message_media( + async def edit_message_media( self, media: "pyrogram.InputMedia", reply_markup: "pyrogram.InlineKeyboardMarkup" = None @@ -278,20 +278,20 @@ def edit_message_media( RPCError: In case of a Telegram RPC error. """ if self.inline_message_id is None: - return self._client.edit_message_media( + return await self._client.edit_message_media( chat_id=self.message.chat.id, message_id=self.message.message_id, media=media, reply_markup=reply_markup ) else: - return self._client.edit_inline_media( + return await self._client.edit_inline_media( inline_message_id=self.inline_message_id, media=media, reply_markup=reply_markup ) - def edit_message_reply_markup( + async def edit_message_reply_markup( self, reply_markup: "pyrogram.InlineKeyboardMarkup" = None ) -> Union["pyrogram.Message", bool]: @@ -311,13 +311,13 @@ def edit_message_reply_markup( RPCError: In case of a Telegram RPC error. """ if self.inline_message_id is None: - return self._client.edit_message_reply_markup( + return await self._client.edit_message_reply_markup( chat_id=self.message.chat.id, message_id=self.message.message_id, reply_markup=reply_markup ) else: - return self._client.edit_inline_reply_markup( + return await self._client.edit_inline_reply_markup( inline_message_id=self.inline_message_id, reply_markup=reply_markup ) diff --git a/pyrogram/client/types/inline_mode/inline_query.py b/pyrogram/client/types/inline_mode/inline_query.py index eadc539c4f..c48bb05383 100644 --- a/pyrogram/client/types/inline_mode/inline_query.py +++ b/pyrogram/client/types/inline_mode/inline_query.py @@ -88,7 +88,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, @@ -151,7 +151,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, diff --git a/pyrogram/client/types/inline_mode/inline_query_result.py b/pyrogram/client/types/inline_mode/inline_query_result.py index c815aedf01..6525585bf4 100644 --- a/pyrogram/client/types/inline_mode/inline_query_result.py +++ b/pyrogram/client/types/inline_mode/inline_query_result.py @@ -67,5 +67,5 @@ def __init__( self.input_message_content = input_message_content self.reply_markup = reply_markup - def write(self): + async def write(self): pass diff --git a/pyrogram/client/types/inline_mode/inline_query_result_animation.py b/pyrogram/client/types/inline_mode/inline_query_result_animation.py index 756ee91aae..d53bbd594e 100644 --- a/pyrogram/client/types/inline_mode/inline_query_result_animation.py +++ b/pyrogram/client/types/inline_mode/inline_query_result_animation.py @@ -91,7 +91,7 @@ def __init__( self.reply_markup = reply_markup self.input_message_content = input_message_content - def write(self): + async def write(self): animation = types.InputWebDocument( url=self.animation_url, size=0, @@ -121,7 +121,7 @@ def write(self): if self.input_message_content else types.InputBotInlineMessageMediaAuto( reply_markup=self.reply_markup.write() if self.reply_markup else None, - **(Parser(None)).parse(self.caption, self.parse_mode) + **await(Parser(None)).parse(self.caption, self.parse_mode) ) ) ) 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 900bf477f1..65e85a0fda 100644 --- a/pyrogram/client/types/inline_mode/inline_query_result_article.py +++ b/pyrogram/client/types/inline_mode/inline_query_result_article.py @@ -66,11 +66,11 @@ def __init__( self.description = description self.thumb_url = thumb_url - def write(self): + async def write(self): return types.InputBotInlineResult( id=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/inline_mode/inline_query_result_photo.py b/pyrogram/client/types/inline_mode/inline_query_result_photo.py index 5905c14e2e..e3890b9bae 100644 --- a/pyrogram/client/types/inline_mode/inline_query_result_photo.py +++ b/pyrogram/client/types/inline_mode/inline_query_result_photo.py @@ -91,7 +91,7 @@ def __init__( self.reply_markup = reply_markup self.input_message_content = input_message_content - def write(self): + async def write(self): photo = types.InputWebDocument( url=self.photo_url, size=0, @@ -117,11 +117,11 @@ def write(self): thumb=thumb, content=photo, send_message=( - self.input_message_content.write(self.reply_markup) + await self.input_message_content.write(self.reply_markup) if self.input_message_content else types.InputBotInlineMessageMediaAuto( reply_markup=self.reply_markup.write() if self.reply_markup else None, - **(Parser(None)).parse(self.caption, self.parse_mode) + **await(Parser(None)).parse(self.caption, self.parse_mode) ) ) ) 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 699ab80d7d..1247011ea2 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 @@ -48,9 +48,9 @@ def __init__(self, message_text: str, parse_mode: Union[str, None] = object, dis 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, - **(Parser(None)).parse(self.message_text, self.parse_mode) + **await(Parser(None)).parse(self.message_text, self.parse_mode) ) diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index 0d1ee2b63d..8bf7cac4db 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -398,8 +398,8 @@ def __init__( self.reply_markup = reply_markup @staticmethod - def _parse(client, message: types.Message or types.MessageService or types.MessageEmpty, users: dict, chats: dict, - is_scheduled: bool = False, replies: int = 1): + async def _parse(client, message: types.Message or types.MessageService or types.MessageEmpty, users: dict, + chats: dict, is_scheduled: bool = False, replies: int = 1): if isinstance(message, types.MessageEmpty): return Message(message_id=message.id, empty=True, client=client) @@ -458,7 +458,7 @@ def _parse(client, message: types.Message or types.MessageService or types.Messa if isinstance(action, types.MessageActionPinMessage): try: - parsed_message.pinned_message = client.get_messages( + parsed_message.pinned_message = await client.get_messages( parsed_message.chat.id, reply_to_message_ids=message.id, replies=0 @@ -471,7 +471,7 @@ def _parse(client, message: types.Message or types.MessageService or types.Messa 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 @@ -567,7 +567,7 @@ def _parse(client, message: types.Message or types.MessageService or types.Messa video = pyrogram.Video._parse(client, doc, video_attributes, file_name, media.ttl_seconds) elif types.DocumentAttributeSticker in attributes: - sticker = pyrogram.Sticker._parse( + sticker = await pyrogram.Sticker._parse( client, doc, attributes.get(types.DocumentAttributeImageSize, None), attributes[types.DocumentAttributeSticker], @@ -663,7 +663,7 @@ def _parse(client, message: types.Message or types.MessageService or types.Messa 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=replies - 1 @@ -680,7 +680,7 @@ def link(self) -> str: else: return "https://t.me/c/{}/{}".format(utils.get_channel_id(self.chat.id), self.message_id) - def reply_text( + async def reply_text( self, text: str, quote: bool = None, @@ -749,7 +749,7 @@ def reply_text( 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, @@ -761,7 +761,7 @@ def reply_text( reply = reply_text - def reply_animation( + async def reply_animation( self, animation: Union[str, BinaryIO], file_ref: str = None, @@ -886,7 +886,7 @@ def reply_animation( if reply_to_message_id is None and quote: reply_to_message_id = self.message_id - return self._client.send_animation( + return await self._client.send_animation( chat_id=self.chat.id, animation=animation, file_ref=file_ref, @@ -903,7 +903,7 @@ def reply_animation( progress_args=progress_args ) - def reply_audio( + async def reply_audio( self, audio: Union[str, BinaryIO], file_ref: str = None, @@ -1028,7 +1028,7 @@ def reply_audio( if reply_to_message_id is None and quote: reply_to_message_id = self.message_id - return self._client.send_audio( + return await self._client.send_audio( chat_id=self.chat.id, audio=audio, file_ref=file_ref, @@ -1045,7 +1045,7 @@ def reply_audio( progress_args=progress_args ) - def reply_cached_media( + async def reply_cached_media( self, file_id: str, file_ref: str = None, @@ -1124,7 +1124,7 @@ def reply_cached_media( if reply_to_message_id is None and quote: reply_to_message_id = self.message_id - return self._client.send_cached_media( + return await self._client.send_cached_media( chat_id=self.chat.id, file_id=file_id, file_ref=file_ref, @@ -1135,7 +1135,7 @@ def reply_cached_media( reply_markup=reply_markup ) - def reply_chat_action(self, action: str) -> bool: + async def reply_chat_action(self, action: str) -> bool: """Bound method *reply_chat_action* of :obj:`Message`. Use as a shortcut for: @@ -1168,12 +1168,12 @@ def reply_chat_action(self, action: str) -> bool: RPCError: In case of a Telegram RPC error. ValueError: In case the provided string is not a valid chat action. """ - return self._client.send_chat_action( + return await self._client.send_chat_action( chat_id=self.chat.id, action=action ) - def reply_contact( + async def reply_contact( self, phone_number: str, first_name: str, @@ -1247,7 +1247,7 @@ def reply_contact( if reply_to_message_id is None and quote: reply_to_message_id = self.message_id - return self._client.send_contact( + return await self._client.send_contact( chat_id=self.chat.id, phone_number=phone_number, first_name=first_name, @@ -1258,7 +1258,7 @@ def reply_contact( reply_markup=reply_markup ) - def reply_document( + async def reply_document( self, document: Union[str, BinaryIO], file_ref: str = None, @@ -1371,7 +1371,7 @@ def reply_document( if reply_to_message_id is None and quote: reply_to_message_id = self.message_id - return self._client.send_document( + return await self._client.send_document( chat_id=self.chat.id, document=document, file_ref=file_ref, @@ -1385,7 +1385,7 @@ def reply_document( progress_args=progress_args ) - def reply_game( + async def reply_game( self, game_short_name: str, quote: bool = None, @@ -1446,7 +1446,7 @@ def reply_game( if reply_to_message_id is None and quote: reply_to_message_id = self.message_id - return self._client.send_game( + return await self._client.send_game( chat_id=self.chat.id, game_short_name=game_short_name, disable_notification=disable_notification, @@ -1454,7 +1454,7 @@ def reply_game( reply_markup=reply_markup ) - def reply_inline_bot_result( + async def reply_inline_bot_result( self, query_id: int, result_id: str, @@ -1514,7 +1514,7 @@ def reply_inline_bot_result( if reply_to_message_id is None and quote: reply_to_message_id = self.message_id - return self._client.send_inline_bot_result( + return await self._client.send_inline_bot_result( chat_id=self.chat.id, query_id=query_id, result_id=result_id, @@ -1523,7 +1523,7 @@ def reply_inline_bot_result( hide_via=hide_via ) - def reply_location( + async def reply_location( self, latitude: float, longitude: float, @@ -1589,7 +1589,7 @@ def reply_location( if reply_to_message_id is None and quote: reply_to_message_id = self.message_id - return self._client.send_location( + return await self._client.send_location( chat_id=self.chat.id, latitude=latitude, longitude=longitude, @@ -1598,7 +1598,7 @@ def reply_location( reply_markup=reply_markup ) - def reply_media_group( + async def reply_media_group( self, media: List[Union["pyrogram.InputMediaPhoto", "pyrogram.InputMediaVideo"]], quote: bool = None, @@ -1652,14 +1652,14 @@ def reply_media_group( if reply_to_message_id is None and quote: reply_to_message_id = self.message_id - return self._client.send_media_group( + return await self._client.send_media_group( chat_id=self.chat.id, media=media, disable_notification=disable_notification, reply_to_message_id=reply_to_message_id ) - def reply_photo( + async def reply_photo( self, photo: Union[str, BinaryIO], file_ref: str = None, @@ -1771,7 +1771,7 @@ def reply_photo( if reply_to_message_id is None and quote: reply_to_message_id = self.message_id - return self._client.send_photo( + return await self._client.send_photo( chat_id=self.chat.id, photo=photo, file_ref=file_ref, @@ -1785,7 +1785,7 @@ def reply_photo( progress_args=progress_args ) - def reply_poll( + async def reply_poll( self, question: str, options: List[str], @@ -1851,7 +1851,7 @@ def reply_poll( if reply_to_message_id is None and quote: reply_to_message_id = self.message_id - return self._client.send_poll( + return await self._client.send_poll( chat_id=self.chat.id, question=question, options=options, @@ -1860,7 +1860,7 @@ def reply_poll( reply_markup=reply_markup ) - def reply_sticker( + async def reply_sticker( self, sticker: Union[str, BinaryIO], file_ref: str = None, @@ -1954,7 +1954,7 @@ def reply_sticker( if reply_to_message_id is None and quote: reply_to_message_id = self.message_id - return self._client.send_sticker( + return await self._client.send_sticker( chat_id=self.chat.id, sticker=sticker, file_ref=file_ref, @@ -1965,7 +1965,7 @@ def reply_sticker( progress_args=progress_args ) - def reply_venue( + async def reply_venue( self, latitude: float, longitude: float, @@ -2050,7 +2050,7 @@ def reply_venue( if reply_to_message_id is None and quote: reply_to_message_id = self.message_id - return self._client.send_venue( + return await self._client.send_venue( chat_id=self.chat.id, latitude=latitude, longitude=longitude, @@ -2063,7 +2063,7 @@ def reply_venue( reply_markup=reply_markup ) - def reply_video( + async def reply_video( self, video: Union[str, BinaryIO], file_ref: str = None, @@ -2192,7 +2192,7 @@ def reply_video( if reply_to_message_id is None and quote: reply_to_message_id = self.message_id - return self._client.send_video( + return await self._client.send_video( chat_id=self.chat.id, video=video, file_ref=file_ref, @@ -2210,7 +2210,7 @@ def reply_video( progress_args=progress_args ) - def reply_video_note( + async def reply_video_note( self, video_note: Union[str, BinaryIO], file_ref: str = None, @@ -2319,7 +2319,7 @@ def reply_video_note( if reply_to_message_id is None and quote: reply_to_message_id = self.message_id - return self._client.send_video_note( + return await self._client.send_video_note( chat_id=self.chat.id, video_note=video_note, file_ref=file_ref, @@ -2333,7 +2333,7 @@ def reply_video_note( progress_args=progress_args ) - def reply_voice( + async def reply_voice( self, voice: Union[str, BinaryIO], file_ref: str = None, @@ -2443,7 +2443,7 @@ def reply_voice( if reply_to_message_id is None and quote: reply_to_message_id = self.message_id - return self._client.send_voice( + return await self._client.send_voice( chat_id=self.chat.id, voice=voice, file_ref=file_ref, @@ -2457,7 +2457,7 @@ def reply_voice( progress_args=progress_args ) - def edit_text( + async def edit_text( self, text: str, parse_mode: Union[str, None] = object, @@ -2504,7 +2504,7 @@ def edit_text( Raises: RPCError: In case of a Telegram RPC error. """ - return self._client.edit_message_text( + return await self._client.edit_message_text( chat_id=self.chat.id, message_id=self.message_id, text=text, @@ -2515,7 +2515,7 @@ def edit_text( edit = edit_text - def edit_caption( + async def edit_caption( self, caption: str, parse_mode: Union[str, None] = object, @@ -2558,7 +2558,7 @@ def edit_caption( Raises: RPCError: In case of a Telegram RPC error. """ - return self._client.edit_message_caption( + return await self._client.edit_message_caption( chat_id=self.chat.id, message_id=self.message_id, caption=caption, @@ -2566,7 +2566,7 @@ def edit_caption( reply_markup=reply_markup ) - def edit_media(self, media: InputMedia, reply_markup: "pyrogram.InlineKeyboardMarkup" = None) -> "Message": + async def edit_media(self, media: InputMedia, reply_markup: "pyrogram.InlineKeyboardMarkup" = None) -> "Message": """Bound method *edit_media* of :obj:`Message`. Use as a shortcut for: @@ -2597,14 +2597,14 @@ def edit_media(self, media: InputMedia, reply_markup: "pyrogram.InlineKeyboardMa Raises: RPCError: In case of a Telegram RPC error. """ - return self._client.edit_message_media( + return await self._client.edit_message_media( chat_id=self.chat.id, message_id=self.message_id, media=media, reply_markup=reply_markup ) - def edit_reply_markup(self, reply_markup: "pyrogram.InlineKeyboardMarkup" = None) -> "Message": + async def edit_reply_markup(self, reply_markup: "pyrogram.InlineKeyboardMarkup" = None) -> "Message": """Bound method *edit_reply_markup* of :obj:`Message`. Use as a shortcut for: @@ -2633,13 +2633,13 @@ def edit_reply_markup(self, reply_markup: "pyrogram.InlineKeyboardMarkup" = None Raises: RPCError: In case of a Telegram RPC error. """ - return self._client.edit_message_reply_markup( + return await self._client.edit_message_reply_markup( chat_id=self.chat.id, message_id=self.message_id, reply_markup=reply_markup ) - def forward( + async def forward( self, chat_id: int or str, disable_notification: bool = None, @@ -2700,7 +2700,7 @@ def forward( raise ValueError("Users cannot send messages with Game media type") if self.text: - return self._client.send_message( + return await self._client.send_message( chat_id, text=self.text.html, parse_mode="html", @@ -2743,7 +2743,7 @@ def forward( file_id = self.video_note.file_id file_ref = self.video_note.file_ref elif self.contact: - return self._client.send_contact( + return await self._client.send_contact( chat_id, phone_number=self.contact.phone_number, first_name=self.contact.first_name, @@ -2753,7 +2753,7 @@ def forward( schedule_date=schedule_date ) elif self.location: - return self._client.send_location( + return await self._client.send_location( chat_id, latitude=self.location.latitude, longitude=self.location.longitude, @@ -2761,7 +2761,7 @@ def forward( schedule_date=schedule_date ) elif self.venue: - return self._client.send_venue( + return await self._client.send_venue( chat_id, latitude=self.venue.location.latitude, longitude=self.venue.location.longitude, @@ -2773,7 +2773,7 @@ def forward( schedule_date=schedule_date ) elif self.poll: - return self._client.send_poll( + return await self._client.send_poll( chat_id, question=self.poll.question, options=[opt.text for opt in self.poll.options], @@ -2781,7 +2781,7 @@ def forward( schedule_date=schedule_date ) elif self.game: - return self._client.send_game( + return await self._client.send_game( chat_id, game_short_name=self.game.short_name, disable_notification=disable_notification @@ -2790,13 +2790,13 @@ def forward( raise ValueError("Unknown media type") if self.sticker or self.video_note: # Sticker and VideoNote should have no caption - return send_media(file_id=file_id, file_ref=file_ref) + return await send_media(file_id=file_id, file_ref=file_ref) else: - return send_media(file_id=file_id, file_ref=file_ref, caption=caption, parse_mode="html") + return await send_media(file_id=file_id, file_ref=file_ref, caption=caption, parse_mode="html") else: raise ValueError("Can't copy this message") else: - 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, @@ -2804,7 +2804,7 @@ def forward( schedule_date=schedule_date ) - def delete(self, revoke: bool = True): + async def delete(self, revoke: bool = True): """Bound method *delete* of :obj:`Message`. Use as a shortcut for: @@ -2834,13 +2834,13 @@ def delete(self, revoke: bool = True): Raises: RPCError: In case of a Telegram RPC error. """ - return self._client.delete_messages( + return await self._client.delete_messages( chat_id=self.chat.id, message_ids=self.message_id, revoke=revoke ) - def click(self, x: int or str = 0, y: int = None, quote: bool = None, timeout: int = 10): + async def click(self, x: int or str = 0, y: int = None, 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: @@ -2944,7 +2944,7 @@ def click(self, x: int or str = 0, y: int = None, quote: bool = None, timeout: i if is_inline: if button.callback_data: - return self._client.request_callback_answer( + return await self._client.request_callback_answer( chat_id=self.chat.id, message_id=self.message_id, callback_data=button.callback_data, @@ -2959,9 +2959,9 @@ def click(self, x: int or str = 0, y: int = None, quote: bool = None, timeout: i else: raise ValueError("This button is not supported yet") else: - self.reply(button, quote=quote) + await self.reply(button, quote=quote) - def retract_vote( + async def retract_vote( self, ) -> "pyrogram.Poll": """Bound method *retract_vote* of :obj:`Message`. @@ -2987,12 +2987,12 @@ def retract_vote( RPCError: In case of a Telegram RPC error. """ - return self._client.retract_vote( + return await self._client.retract_vote( chat_id=self.chat.id, message_id=self.message_id ) - def download( + async def download( self, file_name: str = "", block: bool = True, @@ -3052,7 +3052,7 @@ def download( RPCError: In case of a Telegram RPC 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, @@ -3060,7 +3060,7 @@ def download( progress_args=progress_args, ) - def vote( + async def vote( self, option: int, ) -> "pyrogram.Poll": @@ -3092,13 +3092,13 @@ def vote( RPCError: In case of a Telegram RPC error. """ - return self._client.vote_poll( + return await self._client.vote_poll( chat_id=self.chat.id, message_id=self.message_id, option=option ) - def pin(self, disable_notification: bool = None) -> "Message": + async def pin(self, disable_notification: bool = None) -> "Message": """Bound method *pin* of :obj:`Message`. Use as a shortcut for: @@ -3126,7 +3126,7 @@ def pin(self, disable_notification: bool = None) -> "Message": Raises: RPCError: In case of a Telegram RPC error. """ - return self._client.pin_chat_message( + return await self._client.pin_chat_message( chat_id=self.chat.id, message_id=self.message_id, disable_notification=disable_notification diff --git a/pyrogram/client/types/messages_and_media/sticker.py b/pyrogram/client/types/messages_and_media/sticker.py index b434e451d2..aca7d3a31b 100644 --- a/pyrogram/client/types/messages_and_media/sticker.py +++ b/pyrogram/client/types/messages_and_media/sticker.py @@ -16,10 +16,11 @@ # 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 typing import List +from async_lru import alru_cache + import pyrogram from pyrogram.api import types, functions from pyrogram.errors import StickersetInvalid @@ -105,28 +106,28 @@ def __init__( # self.mask_position = mask_position @staticmethod - @lru_cache(maxsize=256) - def _get_sticker_set_name(send, input_sticker_set_id): + @alru_cache(maxsize=256) + async def _get_sticker_set_name(send, input_sticker_set_id): try: - return send( + return (await send( functions.messages.GetStickerSet( stickerset=types.InputStickerSetID( id=input_sticker_set_id[0], access_hash=input_sticker_set_id[1] ) ) - ).set.short_name + )).set.short_name except StickersetInvalid: return None @staticmethod - def _parse(client, sticker: types.Document, image_size_attributes: types.DocumentAttributeImageSize, - sticker_attributes: types.DocumentAttributeSticker, file_name: str) -> "Sticker": + async def _parse(client, sticker: types.Document, image_size_attributes: types.DocumentAttributeImageSize, + sticker_attributes: types.DocumentAttributeSticker, file_name: str) -> "Sticker": sticker_set = sticker_attributes.stickerset if isinstance(sticker_set, types.InputStickerSetID): input_sticker_set_id = (sticker_set.id, sticker_set.access_hash) - set_name = Sticker._get_sticker_set_name(client.send, input_sticker_set_id) + set_name = await Sticker._get_sticker_set_name(client.send, input_sticker_set_id) else: set_name = None diff --git a/pyrogram/client/types/update.py b/pyrogram/client/types/update.py index 1e70944a5f..3f70f58007 100644 --- a/pyrogram/client/types/update.py +++ b/pyrogram/client/types/update.py @@ -16,11 +16,11 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -class StopPropagation(StopIteration): +class StopPropagation(StopAsyncIteration): pass -class ContinuePropagation(StopIteration): +class ContinuePropagation(StopAsyncIteration): pass diff --git a/pyrogram/client/types/user_and_chats/chat.py b/pyrogram/client/types/user_and_chats/chat.py index fe10357376..85ef0a1f87 100644 --- a/pyrogram/client/types/user_and_chats/chat.py +++ b/pyrogram/client/types/user_and_chats/chat.py @@ -233,13 +233,13 @@ 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 if chat_full.pinned_msg_id: - parsed_chat.pinned_message = client.get_messages( + parsed_chat.pinned_message = await client.get_messages( parsed_chat.id, message_ids=chat_full.pinned_msg_id ) @@ -273,7 +273,7 @@ def _parse_full(client, chat_full: types.messages.ChatFull or types.UserFull) -> parsed_chat.linked_chat = Chat._parse_channel_chat(client, linked_chat) 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 ) @@ -292,7 +292,7 @@ def _parse_chat(client, chat: Union[types.Chat, types.User, types.Channel]) -> " else: return Chat._parse_channel_chat(client, chat) - def archive(self): + async def archive(self): """Bound method *archive* of :obj:`Chat`. Use as a shortcut for: @@ -313,9 +313,9 @@ def archive(self): RPCError: In case of a Telegram RPC error. """ - return self._client.archive_chats(self.id) + return await self._client.archive_chats(self.id) - def unarchive(self): + async def unarchive(self): """Bound method *unarchive* of :obj:`Chat`. Use as a shortcut for: @@ -336,10 +336,10 @@ def unarchive(self): RPCError: In case of a Telegram RPC error. """ - return self._client.unarchive_chats(self.id) + return await self._client.unarchive_chats(self.id) # TODO: Remove notes about "All Members Are Admins" for basic groups, the attribute doesn't exist anymore - def set_title(self, title: str) -> bool: + async def set_title(self, title: str) -> bool: """Bound method *set_title* of :obj:`Chat`. Use as a shortcut for: @@ -372,12 +372,12 @@ def set_title(self, title: str) -> bool: ValueError: In case a chat_id belongs to user. """ - return self._client.set_chat_title( + return await self._client.set_chat_title( chat_id=self.id, title=title ) - def set_description(self, description: str) -> bool: + async def set_description(self, description: str) -> bool: """Bound method *set_description* of :obj:`Chat`. Use as a shortcut for: @@ -406,12 +406,12 @@ def set_description(self, description: str) -> bool: ValueError: If a chat_id doesn't belong to a supergroup or a channel. """ - return self._client.set_chat_description( + return await self._client.set_chat_description( chat_id=self.id, description=description ) - def set_photo(self, photo: str) -> bool: + async def set_photo(self, photo: str) -> bool: """Bound method *set_photo* of :obj:`Chat`. Use as a shortcut for: @@ -440,12 +440,12 @@ def set_photo(self, photo: str) -> bool: ValueError: if a chat_id belongs to user. """ - return self._client.set_chat_photo( + return await self._client.set_chat_photo( chat_id=self.id, photo=photo ) - def kick_member( + async def kick_member( self, user_id: Union[int, str], until_date: int = 0 @@ -489,13 +489,13 @@ def kick_member( RPCError: In case of a Telegram RPC error. """ - return self._client.kick_chat_member( + return await self._client.kick_chat_member( chat_id=self.id, user_id=user_id, until_date=until_date ) - def unban_member( + async def unban_member( self, user_id: Union[int, str] ) -> bool: @@ -527,12 +527,12 @@ def unban_member( RPCError: In case of a Telegram RPC error. """ - return self._client.unban_chat_member( + return await self._client.unban_chat_member( chat_id=self.id, user_id=user_id, ) - def restrict_member( + async def restrict_member( self, user_id: Union[int, str], permissions: ChatPermissions, @@ -575,14 +575,14 @@ def restrict_member( RPCError: In case of a Telegram RPC error. """ - return self._client.restrict_chat_member( + return await self._client.restrict_chat_member( chat_id=self.id, user_id=user_id, permissions=permissions, until_date=until_date, ) - def promote_member( + async def promote_member( self, user_id: Union[int, str], can_change_info: bool = True, @@ -649,7 +649,7 @@ def promote_member( RPCError: In case of a Telegram RPC error. """ - return self._client.promote_chat_member( + return await self._client.promote_chat_member( chat_id=self.id, user_id=user_id, can_change_info=can_change_info, @@ -662,7 +662,7 @@ def promote_member( can_promote_members=can_promote_members ) - def join(self): + async def join(self): """Bound method *join* of :obj:`Chat`. Use as a shortcut for: @@ -686,9 +686,9 @@ def join(self): RPCError: In case of a Telegram RPC error. """ - return self._client.join_chat(self.username or self.id) + return await self._client.join_chat(self.username or self.id) - def leave(self): + async def leave(self): """Bound method *leave* of :obj:`Chat`. Use as a shortcut for: @@ -706,9 +706,9 @@ def leave(self): RPCError: In case of a Telegram RPC error. """ - return self._client.leave_chat(self.id) + return await self._client.leave_chat(self.id) - def export_invite_link(self): + async def export_invite_link(self): """Bound method *export_invite_link* of :obj:`Chat`. Use as a shortcut for: @@ -729,9 +729,9 @@ def export_invite_link(self): ValueError: In case the chat_id belongs to a user. """ - return self._client.export_chat_invite_link(self.id) + return await self._client.export_chat_invite_link(self.id) - def get_member( + async def get_member( self, user_id: Union[int, str], ) -> "pyrogram.ChatMember": @@ -755,12 +755,12 @@ def get_member( :obj:`ChatMember`: On success, a chat member is returned. """ - return self._client.get_chat_member( + return await self._client.get_chat_member( self.id, user_id=user_id ) - def get_members( + async def get_members( self, offset: int = 0, limit: int = 200, @@ -785,7 +785,7 @@ def get_members( List of :obj:`ChatMember`: On success, a list of chat members is returned. """ - return self._client.get_chat_members( + return await self._client.get_chat_members( self.id, offset=offset, limit=limit, @@ -825,7 +825,7 @@ def iter_members( filter=filter ) - def add_members( + async def add_members( self, user_ids: Union[Union[int, str], List[Union[int, str]]], forward_limit: int = 100 @@ -847,7 +847,7 @@ def add_members( ``bool``: On success, True is returned. """ - return self._client.add_chat_members( + return await self._client.add_chat_members( self.id, user_ids=user_ids, forward_limit=forward_limit diff --git a/pyrogram/client/types/user_and_chats/user.py b/pyrogram/client/types/user_and_chats/user.py index b90db42af2..f386e1616b 100644 --- a/pyrogram/client/types/user_and_chats/user.py +++ b/pyrogram/client/types/user_and_chats/user.py @@ -231,7 +231,7 @@ def _parse_user_status(client, user_status: types.UpdateUserStatus): client=client ) - def archive(self): + async def archive(self): """Bound method *archive* of :obj:`User`. Use as a shortcut for: @@ -252,9 +252,9 @@ def archive(self): RPCError: In case of a Telegram RPC error. """ - return self._client.archive_chats(self.id) + return await self._client.archive_chats(self.id) - def unarchive(self): + async def unarchive(self): """Bound method *unarchive* of :obj:`User`. Use as a shortcut for: @@ -275,7 +275,7 @@ def unarchive(self): RPCError: In case of a Telegram RPC error. """ - return self._client.unarchive_chats(self.id) + return await self._client.unarchive_chats(self.id) def block(self): """Bound method *block* of :obj:`User`. diff --git a/pyrogram/connection/connection.py b/pyrogram/connection/connection.py index 50fb3b7f32..3a2126e99a 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 * from ..session.internals import DataCenter @@ -45,20 +44,19 @@ def __init__(self, dc_id: int, test_mode: bool, ipv6: bool, proxy: dict, mode: i self.address = DataCenter(dc_id, test_mode, ipv6) self.mode = self.MODES.get(mode, TCPAbridged) - self.lock = threading.Lock() - self.connection = None + self.protocol = None # type: TCP - def connect(self): + async def connect(self): for i in range(Connection.MAX_RETRIES): - self.connection = self.mode(self.ipv6, self.proxy) + self.protocol = self.mode(self.ipv6, self.proxy) try: log.info("Connecting...") - self.connection.connect(self.address) + await self.protocol.connect(self.address) except OSError as e: log.warning(e) # TODO: Remove - self.connection.close() - time.sleep(1) + self.protocol.close() + await asyncio.sleep(1) else: log.info("Connected! {} DC{} - IPv{} - {}".format( "Test" if self.test_mode else "Production", @@ -72,12 +70,14 @@ def connect(self): raise TimeoutError def close(self): - self.connection.close() + self.protocol.close() log.info("Disconnected") - def send(self, data: bytes): - with self.lock: - self.connection.sendall(data) + async def send(self, data: bytes): + try: + await self.protocol.send(data) + except Exception: + raise OSError - def recv(self) -> bytes or None: - return self.connection.recvall() + async def recv(self) -> bytes or None: + return await self.protocol.recv() diff --git a/pyrogram/connection/transport/tcp/__init__.py b/pyrogram/connection/transport/tcp/__init__.py index 9628d99e76..1909e7230c 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 diff --git a/pyrogram/connection/transport/tcp/tcp.py b/pyrogram/connection/transport/tcp/tcp.py index db1c3ee7fd..070907f469 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 ipaddress import logging import socket @@ -34,8 +35,17 @@ log = logging.getLogger(__name__) -class TCP(socks.socksocket): +class TCP: + TIMEOUT = 10 + def __init__(self, ipv6: bool, proxy: dict): + self.socket = None + + self.reader = None # type: asyncio.StreamReader + self.writer = None # type: asyncio.StreamWriter + + self.lock = asyncio.Lock() + if proxy.get("enabled", False): hostname = proxy.get("hostname", None) port = proxy.get("port", None) @@ -43,14 +53,14 @@ def __init__(self, ipv6: bool, proxy: dict): try: ip_address = ipaddress.ip_address(hostname) except ValueError: - super().__init__(socket.AF_INET) + self.socket = socks.socksocket(socket.AF_INET) else: if isinstance(ip_address, ipaddress.IPv6Address): - super().__init__(socket.AF_INET6) + self.socket = socks.socksocket(socket.AF_INET6) else: - super().__init__(socket.AF_INET) + self.socket = socks.socksocket(socket.AF_INET) - self.set_proxy( + self.socket.set_proxy( proxy_type=socks.SOCKS5, addr=hostname, port=port, @@ -60,35 +70,50 @@ 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 ) - self.settimeout(10) + self.socket.settimeout(TCP.TIMEOUT) + + 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: - # A tiny sleep placed here helps avoiding .recv(n) hanging until the timeout. - # This is a workaround that seems to fix the occasional delayed stop of a client. - time.sleep(0.001) - super().close() - - def recvall(self, length: int) -> bytes or None: + self.writer.close() + except AttributeError: + try: + self.socket.shutdown(socket.SHUT_RDWR) + except OSError: + pass + finally: + # A tiny sleep placed here helps avoiding .recv(n) hanging until the timeout. + # This is a workaround that seems to fix the occasional delayed stop of a client. + time.sleep(0.001) + self.socket.close() + + async def send(self, data: bytes): + async with self.lock: + self.writer.write(data) + await self.writer.drain() + + async def recv(self, length: int = 0): data = b"" while len(data) < length: try: - packet = super().recv(length - len(data)) - except OSError: + chunk = await asyncio.wait_for( + self.reader.read(length - len(data)), + TCP.TIMEOUT + ) + except (OSError, asyncio.TimeoutError): return None else: - if packet: - data += packet + if chunk: + data += chunk else: return None diff --git a/pyrogram/connection/transport/tcp/tcp_abridged.py b/pyrogram/connection/transport/tcp/tcp_abridged.py index e828aa4c4b..4b4de7b2b5 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) diff --git a/pyrogram/connection/transport/tcp/tcp_abridged_o.py b/pyrogram/connection/transport/tcp/tcp_abridged_o.py index 8417735c46..e8f8fba0d9 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)) @@ -51,12 +51,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 @@ -66,8 +66,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 @@ -75,14 +75,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 diff --git a/pyrogram/connection/transport/tcp/tcp_full.py b/pyrogram/connection/transport/tcp/tcp_full.py index 366c1e65d4..0490fa6543 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(" 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(" 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(" +# +# 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: + @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, 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)) + data.read(8) + + # 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 diff --git a/pyrogram/session/auth.py b/pyrogram/session/auth.py index c3d0b99cc7..6795928a7b 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 @@ -57,14 +58,14 @@ def unpack(b: BytesIO): b.seek(20) # Skip auth_key_id (8), message_id (8) and message_length (4) return TLObject.read(b) - def send(self, data: TLObject): + async def send(self, data: TLObject): 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 @@ -79,12 +80,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=nonce)) + res_pq = await self.send(functions.ReqPqMulti(nonce=nonce)) log.debug("Got ResPq: {}".format(res_pq.server_nonce)) log.debug("Server public key fingerprints: {}".format(res_pq.server_public_key_fingerprints)) @@ -128,7 +129,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=nonce, server_nonce=server_nonce, @@ -188,7 +189,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=nonce, server_nonce=server_nonce, @@ -255,7 +256,7 @@ def create(self): else: raise e - time.sleep(1) + await asyncio.sleep(1) continue else: return auth_key diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index 3387b3d20d..beb4affacf 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -16,23 +16,19 @@ # 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 datetime import timedelta, datetime -from hashlib import sha1, sha256 +from datetime import datetime, timedelta +from hashlib import sha1 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__ -from pyrogram.api import functions, types, core +from pyrogram.api import functions, types from pyrogram.api.all import layer -from pyrogram.api.core import Message, TLObject, MsgContainer, Long, FutureSalt, Int +from pyrogram.api.core import TLObject, MsgContainer, Int, Long, FutureSalt, FutureSalts from pyrogram.connection import Connection -from pyrogram.crypto import AES, KDF +from pyrogram.crypto import MTProto from pyrogram.errors import RPCError, InternalServerError, AuthKeyDuplicated, FloodWait from .internals import MsgId, MsgFactory @@ -42,7 +38,7 @@ class Result: def __init__(self): self.value = None - self.event = Event() + self.event = asyncio.Event() class Session: @@ -101,20 +97,21 @@ def __init__( 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( self.dc_id, @@ -124,35 +121,26 @@ def start(self): ) 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() + await self.connection.connect() - 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, Session.INITIAL_SALT) self.current_salt = FutureSalt( 0, 0, - self._send( + (await self._send( functions.Ping(ping_id=0), timeout=self.START_TIMEOUT - ).new_server_salt + )).new_server_salt ) - self.current_salt = self._send(functions.GetFutureSalts(num=1), timeout=self.START_TIMEOUT).salts[0] + self.current_salt = \ + (await self._send(functions.GetFutureSalts(num=1), timeout=self.START_TIMEOUT)).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=layer, query=functions.InitConnection( @@ -169,116 +157,81 @@ def start(self): timeout=self.START_TIMEOUT ) - self.ping_thread = Thread(target=self.ping, name="PingThread") - self.ping_thread.start() + self.ping_task = asyncio.ensure_future(self.ping()) log.info("Session initialized: Layer {}".format(layer)) log.info("Device: {} - {}".format(self.client.device_model, self.client.app_version)) 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, RPCError): - self.stop() + await self.stop() except Exception as e: - self.stop() + await self.stop() raise e else: break self.is_connected.set() - log.debug("Session started") + log.info("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) + if self.recv_task: + await self.recv_task - for i in self.net_worker_list: - i.join() - - self.net_worker_list.clear() - self.recv_queue.queue.clear() + if self.net_worker_task: + await self.net_worker_task for i in self.results.values(): i.event.set() if not self.is_media 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) - 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) + log.info("Session stopped") - def unpack(self, b: BytesIO) -> Message: - assert b.read(8) == self.auth_key_id, b.getvalue() + async def restart(self): + await self.stop() + await self.start() - 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)) + async def net_worker(self): + logging.info("NetWorkerTask started") while True: - packet = self.recv_queue.get() + packet = await self.recv_queue.get() if packet is None: break try: - data = self.unpack(BytesIO(packet)) + data = MTProto.unpack( + BytesIO(packet), + self.session_id, + self.auth_key, + self.auth_key_id + ) messages = ( data.body.messages @@ -306,13 +259,13 @@ 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 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) @@ -322,7 +275,7 @@ def net_worker(self): log.info("Send {} acks".format(len(self.pending_acks))) try: - self._send(types.MsgsAck(msg_ids=list(self.pending_acks)), False) + await self._send(types.MsgsAck(msg_ids=list(self.pending_acks)), False) except (OSError, TimeoutError): pass else: @@ -330,28 +283,32 @@ def net_worker(self): except Exception as e: log.error(e, exc_info=True) - log.debug("{} stopped".format(name)) + log.info("NetWorkerTask stopped") - def ping(self): - log.debug("PingThread started") + async def ping(self): + log.info("PingTask started") while True: - self.ping_thread_event.wait(self.PING_INTERVAL) - - if self.ping_thread_event.is_set(): + try: + await asyncio.wait_for(self.ping_task_event.wait(), self.PING_INTERVAL) + except asyncio.TimeoutError: + pass + else: break try: - self._send(functions.PingDelayDisconnect( - ping_id=0, disconnect_delay=self.WAIT_TIMEOUT + 10 - ), False) + await self._send( + functions.PingDelayDisconnect( + ping_id=0, disconnect_delay=self.WAIT_TIMEOUT + 10 + ), False + ) except (OSError, TimeoutError, RPCError): pass - log.debug("PingThread stopped") + log.info("PingTask stopped") - def next_salt(self): - log.debug("NextSaltThread started") + async def next_salt(self): + log.info("NextSaltTask started") while True: now = datetime.now() @@ -361,45 +318,48 @@ def next_salt(self): valid_until = datetime.fromtimestamp(self.current_salt.valid_until) dt = (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) )) - self.next_salt_thread_event.wait(dt) - - if self.next_salt_thread_event.is_set(): + try: + await asyncio.wait_for(self.next_salt_task_event.wait(), dt) + except asyncio.TimeoutError: + pass + else: break try: - self.current_salt = self._send(functions.GetFutureSalts(num=1)).salts[0] + self.current_salt = (await self._send(functions.GetFutureSalts(num=1))).salts[0] except (OSError, TimeoutError, RPCError): self.connection.close() break - log.debug("NextSaltThread stopped") + log.info("NextSaltTask stopped") - def recv(self): - log.debug("RecvThread started") + async def recv(self): + log.info("RecvTask started") while True: - packet = self.connection.recv() + packet = await self.connection.recv() 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)))) 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.info("RecvTask stopped") - def _send(self, data: TLObject, wait_response: bool = True, timeout: float = WAIT_TIMEOUT): + async def _send(self, data: TLObject, wait_response: bool = True, timeout: float = WAIT_TIMEOUT): message = self.msg_factory(data) msg_id = message.msg_id @@ -408,17 +368,27 @@ def _send(self, data: TLObject, wait_response: bool = True, timeout: float = WAI log.debug("Sent:\n{}".format(message)) - payload = self.pack(message) + payload = MTProto.pack( + message, + self.current_salt.salt, + self.session_id, + self.auth_key, + self.auth_key_id + ) 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(timeout) - result = self.results.pop(msg_id).value + try: + await asyncio.wait_for(self.results[msg_id].event.wait(), timeout) + except asyncio.TimeoutError: + pass + finally: + result = self.results.pop(msg_id).value if result is None: raise TimeoutError @@ -435,14 +405,17 @@ def _send(self, data: TLObject, wait_response: bool = True, timeout: float = WAI else: return result - def send( + async def send( self, data: TLObject, retries: int = MAX_RETRIES, timeout: float = WAIT_TIMEOUT, sleep_threshold: float = SLEEP_THRESHOLD ): - self.is_connected.wait(self.WAIT_TIMEOUT) + try: + await asyncio.wait_for(self.is_connected.wait(), self.WAIT_TIMEOUT) + except asyncio.TimeoutError: + pass if isinstance(data, (functions.InvokeWithoutUpdates, functions.InvokeWithTakeout)): query = data.query @@ -453,7 +426,7 @@ def send( while True: try: - return self._send(data, timeout=timeout) + return await self._send(data, timeout=timeout) except FloodWait as e: amount = e.x @@ -463,7 +436,7 @@ def send( log.warning('[{}] Sleeping for {}s (required by "{}")'.format( self.client.session_name, amount, query)) - time.sleep(amount) + await asyncio.sleep(amount) except (OSError, TimeoutError, InternalServerError) as e: if retries == 0: raise e from None @@ -473,6 +446,6 @@ def send( Session.MAX_RETRIES - retries + 1, query, e)) - time.sleep(0.5) + await asyncio.sleep(0.5) - return self.send(data, retries - 1, timeout) + return await self.send(data, retries - 1, timeout) diff --git a/requirements.txt b/requirements.txt index 9e6f759035..1af9706190 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,4 @@ pyaes==1.6.1 -pysocks==1.7.1 \ No newline at end of file +pysocks==1.7.1 +async_lru==1.0.1 +async_generator==1.10 \ No newline at end of file From ce0ddcddb2803f52880b2e5064f9a78c90c9f972 Mon Sep 17 00:00:00 2001 From: elandorr <56206745+elandorr@users.noreply.github.com> Date: Fri, 21 Aug 2020 05:30:42 +0000 Subject: [PATCH 0275/1185] Fix get_nearby_chats breaking with the new Layer (#446) * fix for new format This fixes the `AttributeError: 'PeerUser' object has no attribute 'channel_id'`. Maybe we should also have a method to show nearby users? * Update get_nearby_chats.py Use isinstance instead of type Co-authored-by: Dan <14043624+delivrance@users.noreply.github.com> --- pyrogram/client/methods/chats/get_nearby_chats.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pyrogram/client/methods/chats/get_nearby_chats.py b/pyrogram/client/methods/chats/get_nearby_chats.py index 6b4ab56ddd..4e76b7e6f7 100644 --- a/pyrogram/client/methods/chats/get_nearby_chats.py +++ b/pyrogram/client/methods/chats/get_nearby_chats.py @@ -64,11 +64,12 @@ async def get_nearby_chats( peers = r.updates[0].peers for peer in peers: - chat_id = utils.get_channel_id(peer.peer.channel_id) + if isinstance(peer.peer, types.PeerChannel): + chat_id = utils.get_channel_id(peer.peer.channel_id) - for chat in chats: - if chat.id == chat_id: - chat.distance = peer.distance - break + for chat in chats: + if chat.id == chat_id: + chat.distance = peer.distance + break return chats From ecab62ce84f9f2cb3eadd5d6664cf5b99c9ab66d Mon Sep 17 00:00:00 2001 From: Hasibul Kobir <46620128+HasibulKabir@users.noreply.github.com> Date: Fri, 21 Aug 2020 11:33:24 +0600 Subject: [PATCH 0276/1185] Add support for both sync and async filters (#437) * support for both sync and async filters * Add whitespace for readability * moving to handler.check for coroutine function Ref: https://github.com/pyrogram/pyrogram/pull/437#discussion_r451626488 * add last line Co-authored-by: Dan <14043624+delivrance@users.noreply.github.com> --- pyrogram/client/ext/dispatcher.py | 3 +- pyrogram/client/filters/filter.py | 40 ++++++++++++++++--- .../handlers/deleted_messages_handler.py | 4 +- pyrogram/client/handlers/handler.py | 20 +++++++--- 4 files changed, 52 insertions(+), 15 deletions(-) diff --git a/pyrogram/client/ext/dispatcher.py b/pyrogram/client/ext/dispatcher.py index 818bc60c15..d8308944db 100644 --- a/pyrogram/client/ext/dispatcher.py +++ b/pyrogram/client/ext/dispatcher.py @@ -188,8 +188,9 @@ async def update_worker(self, lock): if isinstance(handler, handler_type): try: - if handler.check(parsed_update): + if (await handler.check(parsed_update)): args = (parsed_update,) + except Exception as e: log.error(e, exc_info=True) continue diff --git a/pyrogram/client/filters/filter.py b/pyrogram/client/filters/filter.py index eb89b3c387..67067e0370 100644 --- a/pyrogram/client/filters/filter.py +++ b/pyrogram/client/filters/filter.py @@ -16,6 +16,9 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import asyncio + + class Filter: def __call__(self, message): raise NotImplementedError @@ -34,8 +37,13 @@ class InvertFilter(Filter): def __init__(self, base): self.base = base - def __call__(self, message): - return not self.base(message) + async def __call__(self, message): + if asyncio.iscoroutinefunction(self.base.__call__): + x = await self.base(message) + else: + x = self.base(message) + + return not x class AndFilter(Filter): @@ -43,8 +51,18 @@ def __init__(self, base, other): self.base = base self.other = other - def __call__(self, message): - return self.base(message) and self.other(message) + async def __call__(self, message): + if asyncio.iscoroutinefunction(self.base.__call__): + x = await self.base(message) + else: + x = self.base(message) + + if asyncio.iscoroutinefunction(self.other.__call__): + y = await self.other(message) + else: + y = self.other(message) + + return x and y class OrFilter(Filter): @@ -52,5 +70,15 @@ def __init__(self, base, other): self.base = base self.other = other - def __call__(self, message): - return self.base(message) or self.other(message) + async def __call__(self, message): + if asyncio.iscoroutinefunction(self.base.__call__): + x = await self.base(message) + else: + x = self.base(message) + + if asyncio.iscoroutinefunction(self.other.__call__): + y = await self.other(message) + else: + y = self.other(message) + + return x or y diff --git a/pyrogram/client/handlers/deleted_messages_handler.py b/pyrogram/client/handlers/deleted_messages_handler.py index 7312ba90f3..0386354163 100644 --- a/pyrogram/client/handlers/deleted_messages_handler.py +++ b/pyrogram/client/handlers/deleted_messages_handler.py @@ -46,5 +46,5 @@ class DeletedMessagesHandler(Handler): def __init__(self, callback: callable, filters=None): super().__init__(callback, filters) - def check(self, messages): - return super().check(messages[0]) + async def check(self, messages): + return (await super().check(messages[0])) diff --git a/pyrogram/client/handlers/handler.py b/pyrogram/client/handlers/handler.py index 0eb132d1e9..d50b069b33 100644 --- a/pyrogram/client/handlers/handler.py +++ b/pyrogram/client/handlers/handler.py @@ -16,14 +16,22 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import asyncio + + class Handler: def __init__(self, callback: callable, filters=None): self.callback = callback self.filters = filters - def check(self, update): - return ( - self.filters(update) - if callable(self.filters) - else True - ) + async def check(self, update): + + if callable(self.filters): + if asyncio.iscoroutinefunction(self.filters.__call__): + return (await self.filters(update)) + + else: + return self.filters(update) + + else: + return True From faab2f451c394e1030ae8c01c50c7dd865768a29 Mon Sep 17 00:00:00 2001 From: CyanBook Date: Fri, 21 Aug 2020 12:34:30 +0200 Subject: [PATCH 0277/1185] Update save_file to accept pathlib objects (#469) --- 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 4d0312f458..624c98c616 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -27,7 +27,7 @@ from configparser import ConfigParser from hashlib import sha256, md5 from importlib import import_module -from pathlib import Path +from pathlib import Path, Pureapath from signal import signal, SIGINT, SIGTERM, SIGABRT from typing import Union, List, BinaryIO @@ -1804,7 +1804,7 @@ async def worker(session): part_size = 512 * 1024 - if isinstance(path, str): + if isinstance(path, (str, PurePath)): fp = open(path, "rb") elif isinstance(path, io.IOBase): fp = path From 538f1e3972af63cdcc0232016c71250e7c72cc52 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 22 Aug 2020 08:05:05 +0200 Subject: [PATCH 0278/1185] Deep rewrite: preparing for v1.0 - Pyrogram core is now fully asynchronous - Ditched Python 3.5, welcome 3.6 as minimum version. - Moved all types to pyrogram.types - Turned the Filters class into a module (filters) - Moved all filters to pyrogram.filters - Moved all handlers to pyrogram.handlers - Moved all emoji to pyrogram.emoji - Renamed pyrogram.api to pyrogram.raw - Clock is now synced with server's time - Telegram schema updated to Layer 117 - Greatly improved the TL compiler (proper type-constructor hierarchy) - Added "do not edit" warning in generated files - Crypto parts are executed in a thread pool to avoid blocking the event loop - idle() is now a separate function (it doesn't deal with Client instances) - Async storage, async filters and async progress callback (optional, can be sync too) - Added getpass back, for hidden password inputs --- .gitignore | 7 +- MANIFEST.in | 6 +- README.md | 38 +- compiler/api/compiler.py | 685 +- compiler/api/source/main_api.tl | 21 +- compiler/api/template/combinator.txt | 35 + compiler/api/template/mtproto.txt | 30 - compiler/api/template/pyrogram.txt | 12 - compiler/api/template/type.txt | 17 + compiler/docs/compiler.py | 33 +- compiler/docs/template/bound-methods.rst | 2 +- compiler/docs/template/methods.rst | 17 +- compiler/docs/template/types.rst | 2 +- compiler/error/source/400_BAD_REQUEST.tsv | 2 +- docs/Makefile | 7 +- docs/requirements.txt | 3 +- docs/scripts/sitemap.py | 16 +- docs/source/api/decorators.rst | 1 + docs/source/api/errors.rst | 78 - docs/source/api/errors/bad-request.rst | 7 + docs/source/api/errors/flood.rst | 7 + docs/source/api/errors/forbidden.rst | 7 + docs/source/api/errors/index.rst | 37 + .../api/errors/internal-server-error.rst | 7 + docs/source/api/errors/not-acceptable.rst | 7 + docs/source/api/errors/see-other.rst | 7 + docs/source/api/errors/unauthorized.rst | 7 + docs/source/api/filters.rst | 5 +- docs/source/api/handlers.rst | 6 +- docs/source/faq.rst | 1 + docs/source/glossary.rst | 1 + docs/source/index.rst | 164 +- docs/source/intro/install.rst | 44 +- docs/source/intro/quickstart.rst | 4 +- docs/source/intro/setup.rst | 1 + docs/source/powered-by.rst | 1 + docs/source/start/auth.rst | 3 +- docs/source/start/errors.rst | 24 +- docs/source/start/examples/bot_keyboards.rst | 61 + .../start/examples/callback_queries.rst | 19 + docs/source/start/examples/echobot.rst | 21 + .../start/examples/get_chat_members.rst | 15 + docs/source/start/examples/get_dialogs.rst | 14 + docs/source/start/examples/get_history.rst | 15 + docs/source/start/examples/hello_world.rst | 21 + docs/source/start/examples/index.rst | 46 + docs/source/start/examples/inline_queries.rst | 61 + docs/source/start/examples/raw_updates.rst | 18 + .../source/start/examples/use_inline_bots.rst | 18 + docs/source/start/examples/welcomebot.rst | 33 + docs/source/start/invoking.rst | 93 +- docs/source/start/updates.rst | 92 +- docs/source/topics/advanced-usage.rst | 17 +- docs/source/topics/bots-interaction.rst | 1 + docs/source/topics/config-file.rst | 1 + docs/source/topics/create-filters.rst | 34 +- docs/source/topics/debugging.rst | 1 + docs/source/topics/more-on-updates.rst | 43 +- docs/source/topics/mtproto-vs-botapi.rst | 1 + docs/source/topics/proxy.rst | 1 + docs/source/topics/scheduling.rst | 4 +- docs/source/topics/serializing.rst | 1 + docs/source/topics/session-settings.rst | 1 + docs/source/topics/smart-plugins.rst | 18 +- docs/source/topics/storage-engines.rst | 1 + docs/source/topics/test-servers.rst | 1 + docs/source/topics/text-formatting.rst | 1 + docs/source/topics/use-filters.rst | 34 +- examples/LICENSE | 121 - examples/README.md | 23 - examples/bot_keyboards.py | 57 - examples/callback_queries.py | 16 - examples/echobot.py | 17 - examples/get_chat_members.py | 10 - examples/get_dialogs.py | 9 - examples/get_history.py | 10 - examples/hello_world.py | 16 - examples/inline_queries.py | 54 - examples/raw_updates.py | 13 - examples/use_inline_bots.py | 13 - examples/welcomebot.py | 29 - pyrogram/__init__.py | 21 +- pyrogram/client.py | 1069 +++ pyrogram/client/__init__.py | 25 - pyrogram/client/client.py | 2151 ----- pyrogram/client/ext/__init__.py | 24 - pyrogram/client/ext/emoji.py | 7850 ----------------- pyrogram/client/ext/file_data.py | 41 - pyrogram/client/ext/link.py | 52 - pyrogram/client/filters/__init__.py | 19 - pyrogram/client/filters/filter.py | 84 - pyrogram/client/filters/filters.py | 404 - pyrogram/client/methods/password/utils.py | 104 - .../types/inline_mode/inline_query_result.py | 71 - pyrogram/connection/transport/tcp/tcp.py | 2 +- .../transport/tcp/tcp_abridged_o.py | 12 +- .../transport/tcp/tcp_intermediate_o.py | 10 +- pyrogram/crypto/__init__.py | 6 - pyrogram/crypto/aes.py | 154 +- pyrogram/crypto/mtproto.py | 69 +- pyrogram/crypto/prime.py | 109 +- pyrogram/crypto/rsa.py | 362 +- pyrogram/{client/ext => }/dispatcher.py | 77 +- pyrogram/emoji.py | 3521 ++++++++ pyrogram/errors/rpc_error.py | 17 +- pyrogram/filters.py | 877 ++ pyrogram/{client => }/handlers/__init__.py | 7 +- .../handlers/callback_query_handler.py | 8 +- .../handlers/chosen_inline_result_handler.py | 8 +- .../handlers/deleted_messages_handler.py | 19 +- .../handlers/disconnect_handler.py | 6 +- pyrogram/{client => }/handlers/handler.py | 28 +- .../handlers/inline_query_handler.py | 8 +- .../{client => }/handlers/message_handler.py | 8 +- .../{client => }/handlers/poll_handler.py | 8 +- .../handlers/raw_update_handler.py | 12 +- .../handlers/user_status_handler.py | 8 +- pyrogram/{client => }/methods/__init__.py | 8 +- pyrogram/methods/advanced/__init__.py | 29 + pyrogram/methods/advanced/resolve_peer.py | 123 + pyrogram/methods/advanced/save_file.py | 222 + pyrogram/methods/advanced/send.py | 73 + pyrogram/methods/auth/__init__.py | 53 + .../methods/auth/accept_terms_of_service.py | 41 + pyrogram/methods/auth/check_password.py | 55 + pyrogram/methods/auth/connect.py | 50 + pyrogram/methods/auth/disconnect.py | 38 + pyrogram/methods/auth/get_password_hint.py | 34 + pyrogram/methods/auth/initialize.py | 49 + pyrogram/methods/auth/log_out.py | 47 + pyrogram/methods/auth/recover_password.py | 52 + pyrogram/methods/auth/resend_code.py | 58 + pyrogram/methods/auth/send_code.py | 74 + .../auth/send_recovery_code.py} | 26 +- pyrogram/methods/auth/sign_in.py | 78 + pyrogram/methods/auth/sign_in_bot.py | 74 + pyrogram/methods/auth/sign_up.py | 71 + pyrogram/methods/auth/terminate.py | 53 + .../{client => }/methods/bots/__init__.py | 0 .../methods/bots/answer_callback_query.py | 8 +- .../methods/bots/answer_inline_query.py | 22 +- .../methods/bots/get_game_high_scores.py | 16 +- .../methods/bots/get_inline_bot_results.py | 14 +- .../methods/bots/request_callback_answer.py | 8 +- .../{client => }/methods/bots/send_game.py | 34 +- .../methods/bots/send_inline_bot_result.py | 12 +- .../methods/bots/set_game_score.py | 21 +- .../{client => }/methods/chats/__init__.py | 0 .../methods/chats/add_chat_members.py | 12 +- .../methods/chats/archive_chats.py | 10 +- .../methods/chats/create_channel.py | 16 +- .../methods/chats/create_group.py | 16 +- .../methods/chats/create_supergroup.py | 16 +- .../methods/chats/delete_channel.py | 8 +- .../methods/chats/delete_chat_photo.py | 20 +- .../methods/chats/delete_supergroup.py | 8 +- .../methods/chats/delete_user_history.py | 18 +- .../methods/chats/export_chat_invite_link.py | 17 +- .../{client => }/methods/chats/get_chat.py | 35 +- .../methods/chats/get_chat_member.py | 29 +- .../methods/chats/get_chat_members.py | 42 +- .../methods/chats/get_chat_members_count.py | 16 +- .../{client => }/methods/chats/get_dialogs.py | 33 +- .../methods/chats/get_dialogs_count.py | 14 +- .../methods/chats/get_nearby_chats.py | 21 +- .../methods/chats/iter_chat_members.py | 27 +- .../methods/chats/iter_dialogs.py | 27 +- .../{client => }/methods/chats/join_chat.py | 24 +- .../methods/chats/kick_chat_member.py | 26 +- .../{client => }/methods/chats/leave_chat.py | 18 +- .../methods/chats/pin_chat_message.py | 8 +- .../methods/chats/promote_chat_member.py | 10 +- .../methods/chats/restrict_chat_member.py | 22 +- .../methods/chats/set_administrator_title.py | 16 +- .../methods/chats/set_chat_description.py | 12 +- .../methods/chats/set_chat_permissions.py | 22 +- .../methods/chats/set_chat_photo.py | 35 +- .../methods/chats/set_chat_title.py | 16 +- .../methods/chats/set_slow_mode.py | 8 +- .../methods/chats/unarchive_chats.py | 10 +- .../methods/chats/unban_chat_member.py | 10 +- .../methods/chats/unpin_chat_message.py | 8 +- .../methods/chats/update_chat_username.py | 14 +- .../{client => }/methods/contacts/__init__.py | 0 .../methods/contacts/add_contacts.py | 14 +- .../methods/contacts/delete_contacts.py | 10 +- .../methods/contacts/get_contacts.py | 17 +- .../methods/contacts/get_contacts_count.py | 8 +- .../methods/decorators/__init__.py | 2 +- .../methods/decorators/on_callback_query.py | 12 +- .../decorators/on_chosen_inline_result.py | 13 +- .../methods/decorators/on_deleted_messages.py | 12 +- .../methods/decorators/on_disconnect.py | 9 +- .../methods/decorators/on_inline_query.py | 13 +- .../methods/decorators/on_message.py | 13 +- .../methods/decorators/on_poll.py | 13 +- .../methods/decorators/on_raw_update.py | 11 +- .../methods/decorators/on_user_status.py | 13 +- .../{client => }/methods/messages/__init__.py | 0 .../methods/messages/delete_messages.py | 18 +- .../methods/messages/download_media.py | 58 +- .../methods/messages/edit_inline_caption.py | 10 +- .../methods/messages/edit_inline_media.py | 44 +- .../messages/edit_inline_reply_markup.py | 15 +- .../methods/messages/edit_inline_text.py | 15 +- .../methods/messages/edit_message_caption.py | 14 +- .../methods/messages/edit_message_media.py | 110 +- .../messages/edit_message_reply_markup.py | 22 +- .../methods/messages/edit_message_text.py | 22 +- .../methods/messages/forward_messages.py | 29 +- .../methods/messages/get_history.py | 19 +- .../methods/messages/get_history_count.py | 10 +- .../methods/messages/get_messages.py | 28 +- .../methods/messages/iter_history.py | 19 +- .../methods/messages/read_history.py | 12 +- .../methods/messages/retract_vote.py | 16 +- .../methods/messages/search_global.py | 24 +- .../methods/messages/search_messages.py | 59 +- .../methods/messages/send_animation.py | 64 +- .../methods/messages/send_audio.py | 59 +- .../methods/messages/send_cached_media.py | 33 +- .../methods/messages/send_chat_action.py | 34 +- .../methods/messages/send_contact.py | 34 +- .../methods/messages/send_dice.py | 34 +- .../methods/messages/send_document.py | 56 +- .../methods/messages/send_location.py | 36 +- .../methods/messages/send_media_group.py | 67 +- .../methods/messages/send_message.py | 42 +- .../methods/messages/send_photo.py | 45 +- .../methods/messages/send_poll.py | 38 +- .../methods/messages/send_sticker.py | 52 +- .../methods/messages/send_venue.py | 36 +- .../methods/messages/send_video.py | 57 +- .../methods/messages/send_video_note.py | 50 +- .../methods/messages/send_voice.py | 51 +- .../methods/messages/stop_poll.py | 24 +- .../methods/messages/vote_poll.py | 16 +- .../{client => }/methods/password/__init__.py | 0 .../methods/password/change_cloud_password.py | 18 +- .../methods/password/enable_cloud_password.py | 18 +- .../methods/password/remove_cloud_password.py | 18 +- .../{client => }/methods/users/__init__.py | 0 .../{client => }/methods/users/block_user.py | 8 +- .../methods/users/delete_profile_photos.py | 12 +- .../methods/users/get_common_chats.py | 18 +- pyrogram/{client => }/methods/users/get_me.py | 18 +- .../methods/users/get_profile_photos.py | 26 +- .../methods/users/get_profile_photos_count.py | 16 +- .../{client => }/methods/users/get_users.py | 22 +- .../methods/users/iter_profile_photos.py | 23 +- .../methods/users/set_profile_photo.py | 8 +- .../methods/users/unblock_user.py | 8 +- .../methods/users/update_profile.py | 8 +- .../methods/users/update_username.py | 10 +- pyrogram/methods/utilities/__init__.py | 39 + pyrogram/methods/utilities/add_handler.py | 63 + .../utilities/export_session_string.py | 44 + pyrogram/methods/utilities/idle.py | 78 + pyrogram/methods/utilities/remove_handler.py | 59 + pyrogram/methods/utilities/restart.py | 70 + pyrogram/methods/utilities/run.py | 57 + pyrogram/methods/utilities/start.py | 69 + pyrogram/methods/utilities/stop.py | 65 + .../methods/utilities/stop_transmission.py | 47 + pyrogram/{client/ext => }/mime.types | 0 pyrogram/{client => }/parser/__init__.py | 0 pyrogram/{client => }/parser/html.py | 54 +- pyrogram/{client => }/parser/markdown.py | 8 +- pyrogram/{client => }/parser/parser.py | 4 +- pyrogram/{client => }/parser/utils.py | 0 pyrogram/{api => raw}/__init__.py | 1 + pyrogram/{api => raw}/core/__init__.py | 7 +- pyrogram/{api => raw}/core/future_salt.py | 13 +- pyrogram/{api => raw}/core/future_salts.py | 19 +- pyrogram/{api => raw}/core/gzip_packed.py | 16 +- pyrogram/{api => raw}/core/list.py | 12 +- pyrogram/{api => raw}/core/message.py | 17 +- pyrogram/{api => raw}/core/msg_container.py | 15 +- .../{api => raw}/core/primitives/__init__.py | 2 - pyrogram/{api => raw}/core/primitives/bool.py | 19 +- .../{api => raw}/core/primitives/bytes.py | 21 +- .../{api => raw}/core/primitives/double.py | 11 +- pyrogram/{api => raw}/core/primitives/int.py | 9 +- .../{api => raw}/core/primitives/string.py | 11 +- .../{api => raw}/core/primitives/vector.py | 27 +- pyrogram/{api => raw}/core/tl_object.py | 37 +- .../ext/base_client.py => scaffold.py} | 103 +- pyrogram/session/auth.py | 60 +- pyrogram/session/internals/msg_factory.py | 12 +- pyrogram/session/internals/msg_id.py | 20 +- pyrogram/session/session.py | 257 +- pyrogram/{client => }/storage/__init__.py | 0 pyrogram/{client => }/storage/file_storage.py | 8 +- .../{client => }/storage/memory_storage.py | 18 +- pyrogram/{client => }/storage/schema.sql | 9 +- .../{client => }/storage/sqlite_storage.py | 54 +- pyrogram/{client => }/storage/storage.py | 40 +- pyrogram/sync.py | 80 + pyrogram/{client/ext => }/syncer.py | 15 +- pyrogram/{client => }/types/__init__.py | 2 +- .../types/authorization/__init__.py | 2 +- .../types/authorization/sent_code.py | 26 +- .../types/authorization/terms_of_service.py | 14 +- .../types/bots_and_keyboards/__init__.py | 0 .../types/bots_and_keyboards/callback_game.py | 0 .../bots_and_keyboards/callback_query.py | 76 +- .../types/bots_and_keyboards/force_reply.py | 4 +- .../bots_and_keyboards/game_high_score.py | 21 +- .../inline_keyboard_button.py | 30 +- .../inline_keyboard_markup.py | 18 +- .../bots_and_keyboards/keyboard_button.py | 16 +- .../reply_keyboard_markup.py | 18 +- .../reply_keyboard_remove.py | 5 +- .../types/inline_mode/__init__.py | 2 +- .../types/inline_mode/chosen_inline_result.py | 42 +- .../types/inline_mode/inline_query.py | 28 +- .../types/inline_mode/inline_query_result.py | 70 + .../inline_query_result_animation.py | 23 +- .../inline_query_result_article.py | 18 +- .../inline_mode/inline_query_result_photo.py | 23 +- .../types/input_media/__init__.py | 0 .../types/input_media/input_media.py | 10 +- .../input_media/input_media_animation.py | 2 +- .../types/input_media/input_media_audio.py | 6 +- .../types/input_media/input_media_document.py | 2 +- .../types/input_media/input_media_photo.py | 4 +- .../types/input_media/input_media_video.py | 4 +- .../types/input_media/input_phone_contact.py | 5 +- .../types/input_message_content/__init__.py | 0 .../input_message_content.py | 8 +- .../input_text_message_content.py | 6 +- pyrogram/{client => }/types/list.py | 4 +- .../types/messages_and_media/__init__.py | 2 +- .../types/messages_and_media/animation.py | 18 +- .../types/messages_and_media/audio.py | 18 +- .../types/messages_and_media/contact.py | 7 +- .../types/messages_and_media/dice.py | 6 +- .../types/messages_and_media/document.py | 16 +- .../types/messages_and_media/game.py | 27 +- .../types/messages_and_media/location.py | 8 +- .../types/messages_and_media/message.py | 618 +- .../messages_and_media/message_entity.py | 45 +- .../types/messages_and_media/photo.py | 20 +- .../types/messages_and_media/poll.py | 22 +- .../types/messages_and_media/poll_option.py | 2 +- .../types/messages_and_media/sticker.py | 29 +- .../messages_and_media/stripped_thumbnail.py | 6 +- .../types/messages_and_media/thumbnail.py | 18 +- .../types/messages_and_media/venue.py | 14 +- .../types/messages_and_media/video.py | 18 +- .../types/messages_and_media/video_note.py | 20 +- .../types/messages_and_media/voice.py | 8 +- .../types/messages_and_media/webpage.py | 55 +- pyrogram/{client => }/types/object.py | 35 +- pyrogram/{client => }/types/update.py | 11 +- .../types/user_and_chats/__init__.py | 0 .../{client => }/types/user_and_chats/chat.py | 148 +- .../types/user_and_chats/chat_member.py | 42 +- .../types/user_and_chats/chat_permissions.py | 6 +- .../types/user_and_chats/chat_photo.py | 18 +- .../types/user_and_chats/chat_preview.py | 21 +- .../types/user_and_chats/dialog.py | 21 +- .../types/user_and_chats/restriction.py | 4 +- .../{client => }/types/user_and_chats/user.py | 82 +- pyrogram/{client/ext => }/utils.py | 164 +- requirements.txt | 3 +- setup.py | 12 +- 367 files changed, 12063 insertions(+), 15068 deletions(-) create mode 100644 compiler/api/template/combinator.txt delete mode 100644 compiler/api/template/mtproto.txt delete mode 100644 compiler/api/template/pyrogram.txt create mode 100644 compiler/api/template/type.txt delete mode 100644 docs/source/api/errors.rst create mode 100644 docs/source/api/errors/bad-request.rst create mode 100644 docs/source/api/errors/flood.rst create mode 100644 docs/source/api/errors/forbidden.rst create mode 100644 docs/source/api/errors/index.rst create mode 100644 docs/source/api/errors/internal-server-error.rst create mode 100644 docs/source/api/errors/not-acceptable.rst create mode 100644 docs/source/api/errors/see-other.rst create mode 100644 docs/source/api/errors/unauthorized.rst create mode 100644 docs/source/start/examples/bot_keyboards.rst create mode 100644 docs/source/start/examples/callback_queries.rst create mode 100644 docs/source/start/examples/echobot.rst create mode 100644 docs/source/start/examples/get_chat_members.rst create mode 100644 docs/source/start/examples/get_dialogs.rst create mode 100644 docs/source/start/examples/get_history.rst create mode 100644 docs/source/start/examples/hello_world.rst create mode 100644 docs/source/start/examples/index.rst create mode 100644 docs/source/start/examples/inline_queries.rst create mode 100644 docs/source/start/examples/raw_updates.rst create mode 100644 docs/source/start/examples/use_inline_bots.rst create mode 100644 docs/source/start/examples/welcomebot.rst delete mode 100644 examples/LICENSE delete mode 100644 examples/README.md delete mode 100644 examples/bot_keyboards.py delete mode 100644 examples/callback_queries.py delete mode 100644 examples/echobot.py delete mode 100644 examples/get_chat_members.py delete mode 100644 examples/get_dialogs.py delete mode 100644 examples/get_history.py delete mode 100644 examples/hello_world.py delete mode 100644 examples/inline_queries.py delete mode 100644 examples/raw_updates.py delete mode 100644 examples/use_inline_bots.py delete mode 100644 examples/welcomebot.py create mode 100644 pyrogram/client.py delete mode 100644 pyrogram/client/__init__.py delete mode 100644 pyrogram/client/client.py delete mode 100644 pyrogram/client/ext/__init__.py delete mode 100644 pyrogram/client/ext/emoji.py delete mode 100644 pyrogram/client/ext/file_data.py delete mode 100644 pyrogram/client/ext/link.py delete mode 100644 pyrogram/client/filters/__init__.py delete mode 100644 pyrogram/client/filters/filter.py delete mode 100644 pyrogram/client/filters/filters.py delete mode 100644 pyrogram/client/methods/password/utils.py delete mode 100644 pyrogram/client/types/inline_mode/inline_query_result.py rename pyrogram/{client/ext => }/dispatcher.py (72%) create mode 100644 pyrogram/emoji.py create mode 100644 pyrogram/filters.py rename pyrogram/{client => }/handlers/__init__.py (85%) rename pyrogram/{client => }/handlers/callback_query_handler.py (88%) rename pyrogram/{client => }/handlers/chosen_inline_result_handler.py (87%) rename pyrogram/{client => }/handlers/deleted_messages_handler.py (77%) rename pyrogram/{client => }/handlers/disconnect_handler.py (91%) rename pyrogram/{client => }/handlers/handler.py (59%) rename pyrogram/{client => }/handlers/inline_query_handler.py (88%) rename pyrogram/{client => }/handlers/message_handler.py (90%) rename pyrogram/{client => }/handlers/poll_handler.py (89%) rename pyrogram/{client => }/handlers/raw_update_handler.py (88%) rename pyrogram/{client => }/handlers/user_status_handler.py (89%) rename pyrogram/{client => }/methods/__init__.py (88%) create mode 100644 pyrogram/methods/advanced/__init__.py create mode 100644 pyrogram/methods/advanced/resolve_peer.py create mode 100644 pyrogram/methods/advanced/save_file.py create mode 100644 pyrogram/methods/advanced/send.py create mode 100644 pyrogram/methods/auth/__init__.py create mode 100644 pyrogram/methods/auth/accept_terms_of_service.py create mode 100644 pyrogram/methods/auth/check_password.py create mode 100644 pyrogram/methods/auth/connect.py create mode 100644 pyrogram/methods/auth/disconnect.py create mode 100644 pyrogram/methods/auth/get_password_hint.py create mode 100644 pyrogram/methods/auth/initialize.py create mode 100644 pyrogram/methods/auth/log_out.py create mode 100644 pyrogram/methods/auth/recover_password.py create mode 100644 pyrogram/methods/auth/resend_code.py create mode 100644 pyrogram/methods/auth/send_code.py rename pyrogram/{crypto/kdf.py => methods/auth/send_recovery_code.py} (58%) create mode 100644 pyrogram/methods/auth/sign_in.py create mode 100644 pyrogram/methods/auth/sign_in_bot.py create mode 100644 pyrogram/methods/auth/sign_up.py create mode 100644 pyrogram/methods/auth/terminate.py rename pyrogram/{client => }/methods/bots/__init__.py (100%) rename pyrogram/{client => }/methods/bots/answer_callback_query.py (94%) rename pyrogram/{client => }/methods/bots/answer_inline_query.py (88%) rename pyrogram/{client => }/methods/bots/get_game_high_scores.py (85%) rename pyrogram/{client => }/methods/bots/get_inline_bot_results.py (88%) rename pyrogram/{client => }/methods/bots/request_callback_answer.py (93%) rename pyrogram/{client => }/methods/bots/send_game.py (77%) rename pyrogram/{client => }/methods/bots/send_inline_bot_result.py (87%) rename pyrogram/{client => }/methods/bots/set_game_score.py (85%) rename pyrogram/{client => }/methods/chats/__init__.py (100%) rename pyrogram/{client => }/methods/chats/add_chat_members.py (92%) rename pyrogram/{client => }/methods/chats/archive_chats.py (91%) rename pyrogram/{client => }/methods/chats/create_channel.py (81%) rename pyrogram/{client => }/methods/chats/create_group.py (85%) rename pyrogram/{client => }/methods/chats/create_supergroup.py (82%) rename pyrogram/{client => }/methods/chats/delete_channel.py (90%) rename pyrogram/{client => }/methods/chats/delete_chat_photo.py (77%) rename pyrogram/{client => }/methods/chats/delete_supergroup.py (90%) rename pyrogram/{client => }/methods/chats/delete_user_history.py (82%) rename pyrogram/{client => }/methods/chats/export_chat_invite_link.py (80%) rename pyrogram/{client => }/methods/chats/get_chat.py (67%) rename pyrogram/{client => }/methods/chats/get_chat_member.py (77%) rename pyrogram/{client => }/methods/chats/get_chat_members.py (79%) rename pyrogram/{client => }/methods/chats/get_chat_members_count.py (82%) rename pyrogram/{client => }/methods/chats/get_dialogs.py (75%) rename pyrogram/{client => }/methods/chats/get_dialogs_count.py (81%) rename pyrogram/{client => }/methods/chats/get_nearby_chats.py (77%) rename pyrogram/{client => }/methods/chats/iter_chat_members.py (85%) rename pyrogram/{client => }/methods/chats/iter_dialogs.py (79%) rename pyrogram/{client => }/methods/chats/join_chat.py (75%) rename pyrogram/{client => }/methods/chats/kick_chat_member.py (83%) rename pyrogram/{client => }/methods/chats/leave_chat.py (83%) rename pyrogram/{client => }/methods/chats/pin_chat_message.py (93%) rename pyrogram/{client => }/methods/chats/promote_chat_member.py (95%) rename pyrogram/{client => }/methods/chats/restrict_chat_member.py (88%) rename pyrogram/{client => }/methods/chats/set_administrator_title.py (90%) rename pyrogram/{client => }/methods/chats/set_chat_description.py (85%) rename pyrogram/{client => }/methods/chats/set_chat_permissions.py (85%) rename pyrogram/{client => }/methods/chats/set_chat_photo.py (74%) rename pyrogram/{client => }/methods/chats/set_chat_title.py (84%) rename pyrogram/{client => }/methods/chats/set_slow_mode.py (92%) rename pyrogram/{client => }/methods/chats/unarchive_chats.py (91%) rename pyrogram/{client => }/methods/chats/unban_chat_member.py (90%) rename pyrogram/{client => }/methods/chats/unpin_chat_message.py (91%) rename pyrogram/{client => }/methods/chats/update_chat_username.py (84%) rename pyrogram/{client => }/methods/contacts/__init__.py (100%) rename pyrogram/{client => }/methods/contacts/add_contacts.py (84%) rename pyrogram/{client => }/methods/contacts/delete_contacts.py (88%) rename pyrogram/{client => }/methods/contacts/get_contacts.py (72%) rename pyrogram/{client => }/methods/contacts/get_contacts_count.py (86%) rename pyrogram/{client => }/methods/decorators/__init__.py (100%) rename pyrogram/{client => }/methods/decorators/on_callback_query.py (84%) rename pyrogram/{client => }/methods/decorators/on_chosen_inline_result.py (82%) rename pyrogram/{client => }/methods/decorators/on_deleted_messages.py (83%) rename pyrogram/{client => }/methods/decorators/on_disconnect.py (84%) rename pyrogram/{client => }/methods/decorators/on_inline_query.py (83%) rename pyrogram/{client => }/methods/decorators/on_message.py (83%) rename pyrogram/{client => }/methods/decorators/on_poll.py (84%) rename pyrogram/{client => }/methods/decorators/on_raw_update.py (83%) rename pyrogram/{client => }/methods/decorators/on_user_status.py (83%) rename pyrogram/{client => }/methods/messages/__init__.py (100%) rename pyrogram/{client => }/methods/messages/delete_messages.py (86%) rename pyrogram/{client => }/methods/messages/download_media.py (84%) rename pyrogram/{client => }/methods/messages/edit_inline_caption.py (89%) rename pyrogram/{client => }/methods/messages/edit_inline_media.py (76%) rename pyrogram/{client => }/methods/messages/edit_inline_reply_markup.py (83%) rename pyrogram/{client => }/methods/messages/edit_inline_text.py (88%) rename pyrogram/{client => }/methods/messages/edit_message_caption.py (87%) rename pyrogram/{client => }/methods/messages/edit_message_media.py (74%) rename pyrogram/{client => }/methods/messages/edit_message_reply_markup.py (79%) rename pyrogram/{client => }/methods/messages/edit_message_text.py (84%) rename pyrogram/{client => }/methods/messages/forward_messages.py (83%) rename pyrogram/{client => }/methods/messages/get_history.py (90%) rename pyrogram/{client => }/methods/messages/get_history_count.py (90%) rename pyrogram/{client => }/methods/messages/get_messages.py (81%) rename pyrogram/{client => }/methods/messages/iter_history.py (88%) rename pyrogram/{client => }/methods/messages/read_history.py (89%) rename pyrogram/{client => }/methods/messages/retract_vote.py (83%) rename pyrogram/{client => }/methods/messages/search_global.py (83%) rename pyrogram/{client => }/methods/messages/search_messages.py (80%) rename pyrogram/{client => }/methods/messages/send_animation.py (82%) rename pyrogram/{client => }/methods/messages/send_audio.py (82%) rename pyrogram/{client => }/methods/messages/send_cached_media.py (80%) rename pyrogram/{client => }/methods/messages/send_chat_action.py (78%) rename pyrogram/{client => }/methods/messages/send_contact.py (77%) rename pyrogram/{client => }/methods/messages/send_dice.py (76%) rename pyrogram/{client => }/methods/messages/send_document.py (81%) rename pyrogram/{client => }/methods/messages/send_location.py (74%) rename pyrogram/{client => }/methods/messages/send_media_group.py (74%) rename pyrogram/{client => }/methods/messages/send_message.py (82%) rename pyrogram/{client => }/methods/messages/send_photo.py (83%) rename pyrogram/{client => }/methods/messages/send_poll.py (79%) rename pyrogram/{client => }/methods/messages/send_sticker.py (79%) rename pyrogram/{client => }/methods/messages/send_venue.py (79%) rename pyrogram/{client => }/methods/messages/send_video.py (82%) rename pyrogram/{client => }/methods/messages/send_video_note.py (83%) rename pyrogram/{client => }/methods/messages/send_voice.py (82%) rename pyrogram/{client => }/methods/messages/stop_poll.py (79%) rename pyrogram/{client => }/methods/messages/vote_poll.py (86%) rename pyrogram/{client => }/methods/password/__init__.py (100%) rename pyrogram/{client => }/methods/password/change_cloud_password.py (80%) rename pyrogram/{client => }/methods/password/enable_cloud_password.py (83%) rename pyrogram/{client => }/methods/password/remove_cloud_password.py (77%) rename pyrogram/{client => }/methods/users/__init__.py (100%) rename pyrogram/{client => }/methods/users/block_user.py (92%) rename pyrogram/{client => }/methods/users/delete_profile_photos.py (86%) rename pyrogram/{client => }/methods/users/get_common_chats.py (78%) rename pyrogram/{client => }/methods/users/get_me.py (74%) rename pyrogram/{client => }/methods/users/get_profile_photos.py (81%) rename pyrogram/{client => }/methods/users/get_profile_photos_count.py (83%) rename pyrogram/{client => }/methods/users/get_users.py (77%) rename pyrogram/{client => }/methods/users/iter_profile_photos.py (80%) rename pyrogram/{client => }/methods/users/set_profile_photo.py (94%) rename pyrogram/{client => }/methods/users/unblock_user.py (91%) rename pyrogram/{client => }/methods/users/update_profile.py (93%) rename pyrogram/{client => }/methods/users/update_username.py (88%) create mode 100644 pyrogram/methods/utilities/__init__.py create mode 100644 pyrogram/methods/utilities/add_handler.py create mode 100644 pyrogram/methods/utilities/export_session_string.py create mode 100644 pyrogram/methods/utilities/idle.py create mode 100644 pyrogram/methods/utilities/remove_handler.py create mode 100644 pyrogram/methods/utilities/restart.py create mode 100644 pyrogram/methods/utilities/run.py create mode 100644 pyrogram/methods/utilities/start.py create mode 100644 pyrogram/methods/utilities/stop.py create mode 100644 pyrogram/methods/utilities/stop_transmission.py rename pyrogram/{client/ext => }/mime.types (100%) rename pyrogram/{client => }/parser/__init__.py (100%) rename pyrogram/{client => }/parser/html.py (74%) rename pyrogram/{client => }/parser/markdown.py (95%) rename pyrogram/{client => }/parser/parser.py (93%) rename pyrogram/{client => }/parser/utils.py (100%) rename pyrogram/{api => raw}/__init__.py (95%) rename pyrogram/{api => raw}/core/__init__.py (80%) rename pyrogram/{api => raw}/core/future_salt.py (83%) rename pyrogram/{api => raw}/core/future_salts.py (73%) rename pyrogram/{api => raw}/core/gzip_packed.py (82%) rename pyrogram/{api => raw}/core/list.py (81%) rename pyrogram/{api => raw}/core/message.py (81%) rename pyrogram/{api => raw}/core/msg_container.py (79%) rename pyrogram/{api => raw}/core/primitives/__init__.py (89%) rename pyrogram/{api => raw}/core/primitives/bool.py (73%) rename pyrogram/{api => raw}/core/primitives/bytes.py (74%) rename pyrogram/{api => raw}/core/primitives/double.py (78%) rename pyrogram/{api => raw}/core/primitives/int.py (79%) rename pyrogram/{api => raw}/core/primitives/string.py (76%) rename pyrogram/{api => raw}/core/primitives/vector.py (73%) rename pyrogram/{api => raw}/core/tl_object.py (71%) rename pyrogram/{client/ext/base_client.py => scaffold.py} (65%) rename pyrogram/{client => }/storage/__init__.py (100%) rename pyrogram/{client => }/storage/file_storage.py (93%) rename pyrogram/{client => }/storage/memory_storage.py (81%) rename pyrogram/{client => }/storage/schema.sql (95%) rename pyrogram/{client => }/storage/sqlite_storage.py (76%) rename pyrogram/{client => }/storage/storage.py (65%) create mode 100644 pyrogram/sync.py rename pyrogram/{client/ext => }/syncer.py (87%) rename pyrogram/{client => }/types/__init__.py (100%) rename pyrogram/{client => }/types/authorization/__init__.py (100%) rename pyrogram/{client => }/types/authorization/sent_code.py (74%) rename pyrogram/{client => }/types/authorization/terms_of_service.py (77%) rename pyrogram/{client => }/types/bots_and_keyboards/__init__.py (100%) rename pyrogram/{client => }/types/bots_and_keyboards/callback_game.py (100%) rename pyrogram/{client => }/types/bots_and_keyboards/callback_query.py (79%) rename pyrogram/{client => }/types/bots_and_keyboards/force_reply.py (95%) rename pyrogram/{client => }/types/bots_and_keyboards/game_high_score.py (75%) rename pyrogram/{client => }/types/bots_and_keyboards/inline_keyboard_button.py (85%) rename pyrogram/{client => }/types/bots_and_keyboards/inline_keyboard_markup.py (79%) rename pyrogram/{client => }/types/bots_and_keyboards/keyboard_button.py (82%) rename pyrogram/{client => }/types/bots_and_keyboards/reply_keyboard_markup.py (88%) rename pyrogram/{client => }/types/bots_and_keyboards/reply_keyboard_remove.py (96%) rename pyrogram/{client => }/types/inline_mode/__init__.py (100%) rename pyrogram/{client => }/types/inline_mode/chosen_inline_result.py (67%) rename pyrogram/{client => }/types/inline_mode/inline_query.py (89%) create mode 100644 pyrogram/types/inline_mode/inline_query_result.py rename pyrogram/{client => }/types/inline_mode/inline_query_result_animation.py (87%) rename pyrogram/{client => }/types/inline_mode/inline_query_result_article.py (83%) rename pyrogram/{client => }/types/inline_mode/inline_query_result_photo.py (86%) rename pyrogram/{client => }/types/input_media/__init__.py (100%) rename pyrogram/{client => }/types/input_media/input_media.py (83%) rename pyrogram/{client => }/types/input_media/input_media_animation.py (98%) rename pyrogram/{client => }/types/input_media/input_media_audio.py (94%) rename pyrogram/{client => }/types/input_media/input_media_document.py (98%) rename pyrogram/{client => }/types/input_media/input_media_photo.py (94%) rename pyrogram/{client => }/types/input_media/input_media_video.py (96%) rename pyrogram/{client => }/types/input_media/input_phone_contact.py (93%) rename pyrogram/{client => }/types/input_message_content/__init__.py (100%) rename pyrogram/{client => }/types/input_message_content/input_message_content.py (83%) rename pyrogram/{client => }/types/input_message_content/input_text_message_content.py (95%) rename pyrogram/{client => }/types/list.py (88%) rename pyrogram/{client => }/types/messages_and_media/__init__.py (100%) rename pyrogram/{client => }/types/messages_and_media/animation.py (88%) rename pyrogram/{client => }/types/messages_and_media/audio.py (88%) rename pyrogram/{client => }/types/messages_and_media/contact.py (92%) rename pyrogram/{client => }/types/messages_and_media/dice.py (88%) rename pyrogram/{client => }/types/messages_and_media/document.py (86%) rename pyrogram/{client => }/types/messages_and_media/game.py (79%) rename pyrogram/{client => }/types/messages_and_media/location.py (88%) rename pyrogram/{client => }/types/messages_and_media/message.py (83%) rename pyrogram/{client => }/types/messages_and_media/message_entity.py (68%) rename pyrogram/{client => }/types/messages_and_media/photo.py (82%) rename pyrogram/{client => }/types/messages_and_media/poll.py (88%) rename pyrogram/{client => }/types/messages_and_media/poll_option.py (96%) rename pyrogram/{client => }/types/messages_and_media/sticker.py (84%) rename pyrogram/{client => }/types/messages_and_media/stripped_thumbnail.py (88%) rename pyrogram/{client => }/types/messages_and_media/thumbnail.py (86%) rename pyrogram/{client => }/types/messages_and_media/venue.py (86%) rename pyrogram/{client => }/types/messages_and_media/video.py (89%) rename pyrogram/{client => }/types/messages_and_media/video_note.py (84%) rename pyrogram/{client => }/types/messages_and_media/voice.py (91%) rename pyrogram/{client => }/types/messages_and_media/webpage.py (73%) rename pyrogram/{client => }/types/object.py (77%) rename pyrogram/{client => }/types/update.py (83%) rename pyrogram/{client => }/types/user_and_chats/__init__.py (100%) rename pyrogram/{client => }/types/user_and_chats/chat.py (81%) rename pyrogram/{client => }/types/user_and_chats/chat_member.py (89%) rename pyrogram/{client => }/types/user_and_chats/chat_permissions.py (96%) rename pyrogram/{client => }/types/user_and_chats/chat_photo.py (86%) rename pyrogram/{client => }/types/user_and_chats/chat_preview.py (79%) rename pyrogram/{client => }/types/user_and_chats/dialog.py (83%) rename pyrogram/{client => }/types/user_and_chats/restriction.py (93%) rename pyrogram/{client => }/types/user_and_chats/user.py (79%) rename pyrogram/{client/ext => }/utils.py (58%) diff --git a/.gitignore b/.gitignore index eb4fcccc55..357f483fbf 100644 --- a/.gitignore +++ b/.gitignore @@ -6,9 +6,10 @@ unknown_errors.txt # Pyrogram generated code pyrogram/errors/exceptions/ -pyrogram/api/functions/ -pyrogram/api/types/ -pyrogram/api/all.py +pyrogram/raw/functions/ +pyrogram/raw/types/ +pyrogram/raw/base/ +pyrogram/raw/all.py # PyCharm stuff .idea/ diff --git a/MANIFEST.in b/MANIFEST.in index 9f4d328c73..67900cba5e 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -5,6 +5,6 @@ recursive-include pyrogram mime.types schema.sql ## Exclude prune pyrogram/errors/exceptions -prune pyrogram/api/functions -prune pyrogram/api/types -exclude pyrogram/api/all.py \ No newline at end of file +prune pyrogram/raw/functions +prune pyrogram/raw/types +exclude pyrogram/raw/all.py \ No newline at end of file diff --git a/README.md b/README.md index d9fa613e0c..61ece5685c 100644 --- a/README.md +++ b/README.md @@ -21,25 +21,22 @@ ## Pyrogram ``` python -from pyrogram import Client, Filters +from pyrogram import Client, filters app = Client("my_account") -@app.on_message(Filters.private) -def hello(client, message): - message.reply_text("Hello {}".format(message.from_user.first_name)) +@app.on_message(filters.private) +async def hello(client, message): + await message.reply_text(f"Hello {message.from_user.mention}") 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) +**Pyrogram** is a modern, elegant and easy-to-use [Telegram](https://telegram.org/) 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). ### Features @@ -47,8 +44,9 @@ ground up in Python and C. It enables you to easily create custom apps for both - **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. +- **Asynchronous**: Allows both synchronous and asynchronous usages to fit all usage needs. +- **Documented**: API methods, types and public interfaces are all [well documented](https://docs.pyrogram.org). +- **Type-hinted**: Types and methods are all type-hinted, enabling excellent editor support. - **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. @@ -56,7 +54,7 @@ ground up in Python and C. It enables you to easily create custom apps for both ### Requirements -- Python 3.5.3 or higher. +- Python 3.6 or higher. - A [Telegram API key](https://docs.pyrogram.org/intro/setup#api-keys). ### Installing @@ -67,17 +65,9 @@ pip3 install pyrogram ### Resources -- 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:dan@pyrogram.org) 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! +- The docs contain lots of resources to help you get started with Pyrogram: https://docs.pyrogram.org. +- Seeking extra help? Come join and ask our community: https://t.me/pyrogram. +- For other kind of inquiries, you can send a [message](https://t.me/haskell) or an [e-mail](mailto:dan@pyrogram.org). ### Copyright & License diff --git a/compiler/api/compiler.py b/compiler/api/compiler.py index 3b0876f90e..e956085f32 100644 --- a/compiler/api/compiler.py +++ b/compiler/api/compiler.py @@ -19,140 +19,108 @@ import os import re import shutil +from functools import partial +from pathlib import Path +from typing import NamedTuple, List, Tuple -HOME = "compiler/api" -DESTINATION = "pyrogram/api" +# from autoflake import fix_code +# from black import format_str, FileMode + +HOME_PATH = Path("compiler/api") +DESTINATION_PATH = Path("pyrogram/raw") NOTICE_PATH = "NOTICE" + SECTION_RE = re.compile(r"---(\w+)---") LAYER_RE = re.compile(r"//\sLAYER\s(\d+)") -COMBINATOR_RE = re.compile(r"^([\w.]+)#([0-9a-f]+)\s(?:.*)=\s([\w<>.]+);(?: // Docs: (.+))?$", re.MULTILINE) +COMBINATOR_RE = re.compile(r"^([\w.]+)#([0-9a-f]+)\s(?:.*)=\s([\w<>.]+);$", re.MULTILINE) ARGS_RE = re.compile(r"[^{](\w+):([\w?!.<>#]+)") FLAGS_RE = re.compile(r"flags\.(\d+)\?") FLAGS_RE_2 = re.compile(r"flags\.(\d+)\?([\w<>.]+)") FLAGS_RE_3 = re.compile(r"flags:#") INT_RE = re.compile(r"int(\d+)") -core_types = ["int", "long", "int128", "int256", "double", "bytes", "string", "Bool"] -types_to_constructors = {} -types_to_functions = {} -constructors_to_functions = {} +CORE_TYPES = ["int", "long", "int128", "int256", "double", "bytes", "string", "Bool", "true"] +WARNING = """ +# # # # # # # # # # # # # # # # # # # # # # # # +# !!! WARNING !!! # +# This is a generated file! # +# All changes made in this file will be lost! # +# # # # # # # # # # # # # # # # # # # # # # # # +""".strip() -def get_docstring_arg_type(t: str, is_list: bool = False, is_pyrogram_type: bool = False): - if t in core_types: - if t == "long": - return "``int`` ``64-bit``" - elif "int" in t: - size = INT_RE.match(t) - return "``int`` ``{}-bit``".format(size.group(1)) if size else "``int`` ``32-bit``" - elif t == "double": - return "``float`` ``64-bit``" - elif t == "string": - return "``str``" - else: - return "``{}``".format(t.lower()) - elif t == "true": - return "``bool``" - elif t == "TLObject" or t == "X": - return "Any object from :obj:`~pyrogram.api.types`" - elif t == "!X": - return "Any method from :obj:`~pyrogram.api.functions`" - elif t.startswith("Vector"): - return "List of " + get_docstring_arg_type(t.split("<", 1)[1][:-1], True, is_pyrogram_type) - else: - if is_pyrogram_type: - t = "pyrogram." + t - - t = types_to_constructors.get(t, [t]) - n = len(t) - 1 +# noinspection PyShadowingBuiltins +open = partial(open, encoding="utf-8") - t = (("e" if is_list else "E") + "ither " if n else "") + ", ".join( - ":obj:`{1} <{0}.{1}>`".format( - "pyrogram.types" if is_pyrogram_type else "pyrogram.api.types", - i.replace("pyrogram.", "") - ) - for i in t - ) +types_to_constructors = {} +types_to_functions = {} +constructors_to_functions = {} +namespaces_to_types = {} +namespaces_to_constructors = {} +namespaces_to_functions = {} + + +class Combinator(NamedTuple): + section: str + qualname: str + namespace: str + name: str + id: str + has_flags: bool + args: List[Tuple[str, str]] + qualtype: str + typespace: str + type: str + + +def snake(s: str): + # https://stackoverflow.com/q/1175208 + s = re.sub(r"(.)([A-Z][a-z]+)", r"\1_\2", s) + return re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", s).lower() - if n: - t = t.split(", ") - t = ", ".join(t[:-1]) + " or " + t[-1] - return t +def camel(s: str): + return "".join([i[0].upper() + i[1:] for i in s.split("_")]) -def get_references(t: str): - t = constructors_to_functions.get(t) +# noinspection PyShadowingBuiltins, PyShadowingNames +def get_type_hint(type: str) -> str: + is_flag = FLAGS_RE.match(type) + is_core = False - if t: - n = len(t) - 1 + if is_flag: + type = type.split("?")[1] - t = ", ".join( - ":obj:`{0} `".format(i) - for i in t - ) + if type in CORE_TYPES: + is_core = True - if n: - t = t.split(", ") - t = ", ".join(t[:-1]) + " and " + t[-1] + if type == "long" or "int" in type: + type = "int" + elif type == "double": + type = "float" + elif type == "string": + type = "str" + elif type in ["Bool", "true"]: + type = "bool" + else: # bytes and object + type = "bytes" - return t + if type in ["Object", "!X"]: + return "TLObject" + if re.match("^vector", type, re.I): + is_core = True -def get_argument_type(arg): - is_flag = FLAGS_RE.match(arg[1]) - name, t = arg + sub_type = type.split("<")[1][:-1] + type = f"List[{get_type_hint(sub_type)}]" - if is_flag: - t = t.split("?")[1] - - if t in core_types: - if t == "long" or "int" in t: - t = ": int" - elif t == "double": - t = ": float" - elif t == "string": - t = ": str" - else: - t = ": {}".format(t.lower()) - elif t == "true": - t = ": bool" - elif t.startswith("Vector"): - t = ": list" + if is_core: + return f"Union[None, {type}] = None" if is_flag else type else: - return name + ("=None" if is_flag else "") - - return name + t + (" = None" if is_flag else "") - - -class Combinator: - def __init__(self, - section: str, - namespace: str, - name: str, - id: str, - args: list, - has_flags: bool, - return_type: str, - docs: str): - self.section = section - self.namespace = namespace - self.name = name - self.id = id - self.args = args - self.has_flags = has_flags - self.return_type = return_type - self.docs = docs - - -def snek(s: str): - # https://stackoverflow.com/questions/1175208/elegant-python-function-to-convert-camelcase-to-snake-case - s = re.sub(r"(.)([A-Z][a-z]+)", r"\1_\2", s) - return re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", s).lower() + ns, name = type.split(".") if "." in type else ("", type) + type = f'"raw.base.' + ".".join([ns, name]).strip(".") + '"' - -def capit(s: str): - return "".join([i[0].upper() + i[1:] for i in s.split("_")]) + return f'{type}{" = None" if is_flag else ""}' def sort_args(args): @@ -163,99 +131,167 @@ def sort_args(args): for i in flags: args.remove(i) + try: + args.remove(("flags", "#")) + except ValueError: + pass + return args + flags -def start(): - shutil.rmtree("{}/types".format(DESTINATION), ignore_errors=True) - shutil.rmtree("{}/functions".format(DESTINATION), ignore_errors=True) +def remove_whitespaces(source: str) -> str: + """Remove whitespaces from blank lines""" + lines = source.split("\n") + + for i, _ in enumerate(lines): + if re.match(r"^\s+$", lines[i]): + lines[i] = "" + + return "\n".join(lines) + + +def get_docstring_arg_type(t: str, is_list: bool = False, is_pyrogram_type: bool = False): + if t in CORE_TYPES: + if t == "long": + return "``int`` ``64-bit``" + elif "int" in t: + size = INT_RE.match(t) + return f"``int`` ``{size.group(1)}-bit``" if size else "``int`` ``32-bit``" + elif t == "double": + return "``float`` ``64-bit``" + elif t == "string": + return "``str``" + elif t == "true": + return "``bool``" + else: + return f"``{t.lower()}``" + elif t == "TLObject" or t == "X": + return "Any object from :obj:`~pyrogram.raw.types`" + elif t == "!X": + return "Any method from :obj:`~pyrogram.raw.functions`" + elif t.lower().startswith("vector"): + return "List of " + get_docstring_arg_type(t.split("<", 1)[1][:-1], True) + else: + return f":obj:`{t} `" + + +def get_references(t: str, kind: str): + if kind == "constructors": + t = constructors_to_functions.get(t) + elif kind == "types": + t = types_to_functions.get(t) + else: + raise ValueError("Invalid kind") + + if t: + return "\n ".join( + f"- :obj:`{i} `" + for i in t + ), len(t) - with open("{}/source/auth_key.tl".format(HOME), encoding="utf-8") as auth, \ - open("{}/source/sys_msgs.tl".format(HOME), encoding="utf-8") as system, \ - open("{}/source/main_api.tl".format(HOME), encoding="utf-8") as api: - schema = (auth.read() + system.read() + api.read()).splitlines() + return None, 0 - with open("{}/template/mtproto.txt".format(HOME), encoding="utf-8") as f: - mtproto_template = f.read() - with open("{}/template/pyrogram.txt".format(HOME), encoding="utf-8") as f: - pyrogram_template = f.read() +# noinspection PyShadowingBuiltins +def start(format: bool = False): + shutil.rmtree(DESTINATION_PATH / "types", ignore_errors=True) + shutil.rmtree(DESTINATION_PATH / "functions", ignore_errors=True) + shutil.rmtree(DESTINATION_PATH / "base", ignore_errors=True) + + with open(HOME_PATH / "source/auth_key.tl") as f1, \ + open(HOME_PATH / "source/sys_msgs.tl") as f2, \ + open(HOME_PATH / "source/main_api.tl") as f3: + schema = (f1.read() + f2.read() + f3.read()).splitlines() + + with open(HOME_PATH / "template/type.txt") as f1, \ + open(HOME_PATH / "template/combinator.txt") as f2: + type_tmpl = f1.read() + combinator_tmpl = f2.read() with open(NOTICE_PATH, encoding="utf-8") as f: notice = [] for line in f.readlines(): - notice.append("# {}".format(line).strip()) + notice.append(f"# {line}".strip()) notice = "\n".join(notice) section = None layer = None - namespaces = {"types": set(), "functions": set()} combinators = [] for line in schema: # Check for section changer lines - s = SECTION_RE.match(line) - if s: - section = s.group(1) + section_match = SECTION_RE.match(line) + if section_match: + section = section_match.group(1) continue # Save the layer version - l = LAYER_RE.match(line) - if l: - layer = l.group(1) + layer_match = LAYER_RE.match(line) + if layer_match: + layer = layer_match.group(1) continue - combinator = COMBINATOR_RE.match(line) - if combinator: - name, id, return_type, docs = combinator.groups() - namespace, name = name.split(".") if "." in name else ("", name) - args = ARGS_RE.findall(line.split(" //")[0]) + combinator_match = COMBINATOR_RE.match(line) + if combinator_match: + # noinspection PyShadowingBuiltins + qualname, id, qualtype = combinator_match.groups() + + namespace, name = qualname.split(".") if "." in qualname else ("", qualname) + name = camel(name) + qualname = ".".join([namespace, name]).lstrip(".") + + typespace, type = qualtype.split(".") if "." in qualtype else ("", qualtype) + type = camel(type) + qualtype = ".".join([typespace, type]).lstrip(".") # Pingu! has_flags = not not FLAGS_RE_3.findall(line) - # Fix file and folder name collision - if name == "updates": - name = "update" + args = ARGS_RE.findall(line) - # Fix arg name being "self" (reserved keyword) + # Fix arg name being "self" (reserved python keyword) for i, item in enumerate(args): if item[0] == "self": args[i] = ("is_self", item[1]) - if namespace: - namespaces[section].add(namespace) - - combinators.append( - Combinator( - section, - namespace, - capit(name), - "0x{}".format(id.zfill(8)), - args, - has_flags, - ".".join( - return_type.split(".")[:-1] - + [capit(return_type.split(".")[-1])] - ), - docs - ) + combinator = Combinator( + section=section, + qualname=qualname, + namespace=namespace, + name=name, + id=f"0x{id}", + has_flags=has_flags, + args=args, + qualtype=qualtype, + typespace=typespace, + type=type ) + combinators.append(combinator) + for c in combinators: - return_type = c.return_type + qualtype = c.qualtype - if return_type.startswith("Vector"): - return_type = return_type.split("<")[1][:-1] + if qualtype.startswith("Vector"): + qualtype = qualtype.split("<")[1][:-1] d = types_to_constructors if c.section == "types" else types_to_functions - if return_type not in d: - d[return_type] = [] + if qualtype not in d: + d[qualtype] = [] + + d[qualtype].append(c.qualname) + + if c.section == "types": + key = c.namespace - d[return_type].append(".".join(filter(None, [c.namespace, c.name]))) + if key not in namespaces_to_types: + namespaces_to_types[key] = [] + + if c.type not in namespaces_to_types[key]: + namespaces_to_types[key].append(c.type) for k, v in types_to_constructors.items(): for i in v: @@ -264,85 +300,100 @@ def start(): except KeyError: pass - total = len(combinators) - current = 0 - for c in combinators: # type: Combinator - print("Compiling APIs... [{}%] {}".format( - str(round(current * 100 / total)).rjust(3), - ".".join(filter(None, [c.section, c.namespace, c.name])) - ), end=" \r", flush=True) - current += 1 + # import json + # print(json.dumps(namespaces_to_types, indent=2)) + + for qualtype in types_to_constructors: + typespace, type = qualtype.split(".") if "." in qualtype else ("", qualtype) + dir_path = DESTINATION_PATH / "base" / typespace + + module = type + + if module == "Updates": + module = "UpdatesT" - path = "{}/{}/{}".format(DESTINATION, c.section, c.namespace) - os.makedirs(path, exist_ok=True) + os.makedirs(dir_path, exist_ok=True) - init = "{}/__init__.py".format(path) + constructors = sorted(types_to_constructors[qualtype]) + constr_count = len(constructors) + items = "\n ".join([f"- :obj:`{c} `" for c in constructors]) - if not os.path.exists(init): - with open(init, "w", encoding="utf-8") as f: - f.write(notice + "\n\n") + docstring = f"This base type has {constr_count} constructor{'s' if constr_count > 1 else ''} available.\n\n" + docstring += f" Constructors:\n .. hlist::\n :columns: 2\n\n {items}" - with open(init, "a", encoding="utf-8") as f: - f.write("from .{} import {}\n".format(snek(c.name), capit(c.name))) + references, ref_count = get_references(qualtype, "types") + + if references: + docstring += f"\n\n See Also:\n This object can be returned by " \ + f"{ref_count} method{'s' if ref_count > 1 else ''}:" \ + f"\n\n .. hlist::\n :columns: 2\n\n " + references + + with open(dir_path / f"{snake(module)}.py", "w") as f: + f.write( + type_tmpl.format( + notice=notice, + warning=WARNING, + docstring=docstring, + name=type, + qualname=qualtype, + types=", ".join([f"raw.types.{c}" for c in constructors]) + ) + ) + for c in combinators: sorted_args = sort_args(c.args) arguments = ( - ", " - + ("*, " if c.args else "") - + (", ".join([get_argument_type(i) for i in sorted_args if i != ("flags", "#")]) if c.args else "") + (", *, " if c.args else "") + + (", ".join( + [f"{i[0]}: {get_type_hint(i[1])}" + for i in sorted_args] + ) if sorted_args else "") ) fields = "\n ".join( - ["self.{0} = {0} # {1}".format(i[0], i[1]) for i in c.args if i != ("flags", "#")] - ) if c.args else "pass" + [f"self.{i[0]} = {i[0]} # {i[1]}" + for i in sorted_args] + ) if sorted_args else "pass" + docstring = "" docstring_args = [] - docs = c.docs.split("|")[1:] if c.docs else None for i, arg in enumerate(sorted_args): - if arg == ("flags", "#"): - continue - arg_name, arg_type = arg is_optional = FLAGS_RE.match(arg_type) flag_number = is_optional.group(1) if is_optional else -1 arg_type = arg_type.split("?")[-1] - if docs: - docstring_args.append( - "{} ({}{}):\n {}\n".format( - arg_name, - get_docstring_arg_type(arg_type, is_pyrogram_type=True), - ", optional" if "Optional" in docs[i] else "", - re.sub("Optional\. ", "", docs[i].split("§")[1].rstrip(".") + ".") - ) - ) - else: - docstring_args.append( - "{}{}: {}".format( - arg_name, - " (optional)".format(flag_number) if is_optional else "", - get_docstring_arg_type(arg_type, is_pyrogram_type=c.namespace == "pyrogram") - ) + docstring_args.append( + "{}{}: {}".format( + arg_name, + " (optional)".format(flag_number) if is_optional else "", + get_docstring_arg_type(arg_type, is_pyrogram_type=c.namespace == "pyrogram") ) + ) - if docstring_args: - docstring_args = "Parameters:\n " + "\n ".join(docstring_args) + if c.section == "types": + docstring += f"This object is a constructor of the base type :obj:`~pyrogram.raw.base.{c.qualtype}`.\n\n" else: - docstring_args = "No parameters required." + docstring += f"Telegram API method.\n\n" - docstring_args = "Attributes:\n ID: ``{}``\n\n ".format(c.id) + docstring_args - docstring_args = "Attributes:\n LAYER: ``{}``\n\n ".format(layer) + docstring_args + docstring += f" Details:\n - Layer: ``{layer}``\n - ID: ``{c.id}``\n\n" - if c.section == "functions": - docstring_args += "\n\n Returns:\n " + get_docstring_arg_type(c.return_type) + if docstring_args: + docstring += " Parameters:\n " + "\n ".join(docstring_args) + else: + docstring += " **No parameters required.**" + if c.section == "functions": + docstring += "\n\n Returns:\n " + get_docstring_arg_type(c.qualtype) else: - references = get_references(".".join(filter(None, [c.namespace, c.name]))) + references, count = get_references(c.qualname, "constructors") if references: - docstring_args += "\n\n See Also:\n This object can be returned by " + references + "." + docstring += f"\n\n See Also:\n This object can be returned by " \ + f"{count} method{'s' if count > 1 else ''}:" \ + f"\n\n .. hlist::\n :columns: 2\n\n " + references write_types = read_types = "" if c.has_flags else "# No flags\n " @@ -355,17 +406,16 @@ def start(): for i in c.args: flag = FLAGS_RE.match(i[1]) if flag: - write_flags.append( - "flags |= (1 << {}) if self.{} is not None else 0".format(flag.group(1), i[0])) + write_flags.append(f"flags |= (1 << {flag.group(1)}) if self.{i[0]} is not None else 0") write_flags = "\n ".join([ "flags = 0", "\n ".join(write_flags), - "b.write(Int(flags))\n " + "data.write(Int(flags))\n " ]) write_types += write_flags - read_types += "flags = Int.read(b)\n " + read_types += "flags = Int.read(data)\n " continue @@ -374,126 +424,171 @@ def start(): if flag_type == "true": read_types += "\n " - read_types += "{} = True if flags & (1 << {}) else False".format(arg_name, index) - elif flag_type in core_types: + read_types += f"{arg_name} = True if flags & (1 << {index}) else False" + elif flag_type in CORE_TYPES: write_types += "\n " - write_types += "if self.{} is not None:\n ".format(arg_name) - write_types += "b.write({}(self.{}))\n ".format(flag_type.title(), arg_name) + write_types += f"if self.{arg_name} is not None:\n " + write_types += f"data.write({flag_type.title()}(self.{arg_name}))\n " read_types += "\n " - read_types += "{} = {}.read(b) if flags & (1 << {}) else None".format( - arg_name, flag_type.title(), index - ) + read_types += f"{arg_name} = {flag_type.title()}.read(data) if flags & (1 << {index}) else None" elif "vector" in flag_type.lower(): sub_type = arg_type.split("<")[1][:-1] write_types += "\n " - write_types += "if self.{} is not None:\n ".format(arg_name) - write_types += "b.write(Vector(self.{}{}))\n ".format( - arg_name, ", {}".format(sub_type.title()) if sub_type in core_types else "" + write_types += f"if self.{arg_name} is not None:\n " + write_types += "data.write(Vector(self.{}{}))\n ".format( + arg_name, f", {sub_type.title()}" if sub_type in CORE_TYPES else "" ) read_types += "\n " - read_types += "{} = TLObject.read(b{}) if flags & (1 << {}) else []\n ".format( - arg_name, ", {}".format(sub_type.title()) if sub_type in core_types else "", index + read_types += "{} = TLObject.read(data{}) if flags & (1 << {}) else []\n ".format( + arg_name, f", {sub_type.title()}" if sub_type in CORE_TYPES else "", index ) else: write_types += "\n " - write_types += "if self.{} is not None:\n ".format(arg_name) - write_types += "b.write(self.{}.write())\n ".format(arg_name) + write_types += f"if self.{arg_name} is not None:\n " + write_types += f"data.write(self.{arg_name}.write())\n " read_types += "\n " - read_types += "{} = TLObject.read(b) if flags & (1 << {}) else None\n ".format( - arg_name, index - ) + read_types += f"{arg_name} = TLObject.read(data) if flags & (1 << {index}) else None\n " else: - if arg_type in core_types: + if arg_type in CORE_TYPES: write_types += "\n " - write_types += "b.write({}(self.{}))\n ".format(arg_type.title(), arg_name) + write_types += f"data.write({arg_type.title()}(self.{arg_name}))\n " read_types += "\n " - read_types += "{} = {}.read(b)\n ".format(arg_name, arg_type.title()) + read_types += f"{arg_name} = {arg_type.title()}.read(data)\n " elif "vector" in arg_type.lower(): sub_type = arg_type.split("<")[1][:-1] write_types += "\n " - write_types += "b.write(Vector(self.{}{}))\n ".format( - arg_name, ", {}".format(sub_type.title()) if sub_type in core_types else "" + write_types += "data.write(Vector(self.{}{}))\n ".format( + arg_name, f", {sub_type.title()}" if sub_type in CORE_TYPES else "" ) read_types += "\n " - read_types += "{} = TLObject.read(b{})\n ".format( - arg_name, ", {}".format(sub_type.title()) if sub_type in core_types else "" + read_types += "{} = TLObject.read(data{})\n ".format( + arg_name, f", {sub_type.title()}" if sub_type in CORE_TYPES else "" ) else: write_types += "\n " - write_types += "b.write(self.{}.write())\n ".format(arg_name) + write_types += f"data.write(self.{arg_name}.write())\n " read_types += "\n " - read_types += "{} = TLObject.read(b)\n ".format(arg_name) - - if c.docs: - description = c.docs.split("|")[0].split("§")[1] - docstring_args = description + "\n\n " + docstring_args - - with open("{}/{}.py".format(path, snek(c.name)), "w", encoding="utf-8") as f: - if c.docs: - f.write( - pyrogram_template.format( - notice=notice, - class_name=capit(c.name), - docstring_args=docstring_args, - object_id=c.id, - arguments=arguments, - fields=fields - ) - ) - else: - f.write( - mtproto_template.format( - notice=notice, - class_name=capit(c.name), - docstring_args=docstring_args, - object_id=c.id, - arguments=arguments, - fields=fields, - read_types=read_types, - write_types=write_types, - return_arguments=", ".join( - ["{0}={0}".format(i[0]) for i in sorted_args if i != ("flags", "#")] - ), - slots=", ".join(['"{}"'.format(i[0]) for i in sorted_args if i != ("flags", "#")]), - qualname="{}.{}{}".format(c.section, "{}.".format(c.namespace) if c.namespace else "", c.name) - ) - ) + read_types += f"{arg_name} = TLObject.read(data)\n " + + slots = ", ".join([f'"{i[0]}"' for i in sorted_args]) + return_arguments = ", ".join([f"{i[0]}={i[0]}" for i in sorted_args]) + + compiled_combinator = combinator_tmpl.format( + notice=notice, + warning=WARNING, + name=c.name, + docstring=docstring, + slots=slots, + id=c.id, + qualname=f"pyrogram.raw.{c.section}.{c.qualname}", + arguments=arguments, + fields=fields, + read_types=read_types, + write_types=write_types, + return_arguments=return_arguments + ) + + directory = "types" if c.section == "types" else c.section + + dir_path = DESTINATION_PATH / directory / c.namespace + + os.makedirs(dir_path, exist_ok=True) + + module = c.name + + if module == "Updates": + module = "UpdatesT" + + with open(dir_path / f"{snake(module)}.py", "w") as f: + f.write(compiled_combinator) + + d = namespaces_to_constructors if c.section == "types" else namespaces_to_functions + + if c.namespace not in d: + d[c.namespace] = [] + + d[c.namespace].append(c.name) + + for namespace, types in namespaces_to_types.items(): + with open(DESTINATION_PATH / "base" / namespace / "__init__.py", "w") as f: + f.write(f"{notice}\n\n") + f.write(f"{WARNING}\n\n") + + for t in types: + module = t - with open("{}/all.py".format(DESTINATION), "w", encoding="utf-8") as f: + if module == "Updates": + module = "UpdatesT" + + f.write(f"from .{snake(module)} import {t}\n") + + if not namespace: + f.write(f"from . import {', '.join(filter(bool, namespaces_to_types))}") + + for namespace, types in namespaces_to_constructors.items(): + with open(DESTINATION_PATH / "types" / namespace / "__init__.py", "w") as f: + f.write(f"{notice}\n\n") + f.write(f"{WARNING}\n\n") + + for t in types: + module = t + + if module == "Updates": + module = "UpdatesT" + + f.write(f"from .{snake(module)} import {t}\n") + + if not namespace: + f.write(f"from . import {', '.join(filter(bool, namespaces_to_constructors))}\n") + + for namespace, types in namespaces_to_functions.items(): + with open(DESTINATION_PATH / "functions" / namespace / "__init__.py", "w") as f: + f.write(f"{notice}\n\n") + f.write(f"{WARNING}\n\n") + + for t in types: + module = t + + if module == "Updates": + module = "UpdatesT" + + f.write(f"from .{snake(module)} import {t}\n") + + if not namespace: + f.write(f"from . import {', '.join(filter(bool, namespaces_to_functions))}") + + with open(DESTINATION_PATH / "all.py", "w", encoding="utf-8") as f: f.write(notice + "\n\n") - f.write("layer = {}\n\n".format(layer)) + f.write(WARNING + "\n\n") + f.write(f"layer = {layer}\n\n") f.write("objects = {") for c in combinators: - path = ".".join(filter(None, [c.section, c.namespace, capit(c.name)])) - f.write("\n {}: \"pyrogram.api.{}\",".format(c.id, path)) - - f.write("\n 0xbc799737: \"pyrogram.api.core.BoolFalse\",") - f.write("\n 0x997275b5: \"pyrogram.api.core.BoolTrue\",") - f.write("\n 0x1cb5c415: \"pyrogram.api.core.Vector\",") - f.write("\n 0x73f1f8dc: \"pyrogram.api.core.MsgContainer\",") - f.write("\n 0xae500895: \"pyrogram.api.core.FutureSalts\",") - f.write("\n 0x0949d9dc: \"pyrogram.api.core.FutureSalt\",") - f.write("\n 0x3072cfa1: \"pyrogram.api.core.GzipPacked\",") - f.write("\n 0x5bb8e511: \"pyrogram.api.core.Message\",") + f.write(f'\n {c.id}: "pyrogram.raw.{c.section}.{c.qualname}",') - f.write("\n}\n") + f.write('\n 0xbc799737: "pyrogram.raw.core.BoolFalse",') + f.write('\n 0x997275b5: "pyrogram.raw.core.BoolTrue",') + f.write('\n 0x1cb5c415: "pyrogram.raw.core.Vector",') + f.write('\n 0x73f1f8dc: "pyrogram.raw.core.MsgContainer",') + f.write('\n 0xae500895: "pyrogram.raw.core.FutureSalts",') + f.write('\n 0x0949d9dc: "pyrogram.raw.core.FutureSalt",') + f.write('\n 0x3072cfa1: "pyrogram.raw.core.GzipPacked",') + f.write('\n 0x5bb8e511: "pyrogram.raw.core.Message",') - for k, v in namespaces.items(): - with open("{}/{}/__init__.py".format(DESTINATION, k), "a", encoding="utf-8") as f: - f.write("from . import {}\n".format(", ".join([i for i in v])) if v else "") + f.write("\n}\n") if "__main__" == __name__: - HOME = "." - DESTINATION = "../../pyrogram/api" - NOTICE_PATH = "../../NOTICE" - start() + HOME_PATH = Path(".") + DESTINATION_PATH = Path("../../pyrogram/raw") + NOTICE_PATH = Path("../../NOTICE") + + start(format=False) diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl index 3c073c436b..9b50b802b7 100644 --- a/compiler/api/source/main_api.tl +++ b/compiler/api/source/main_api.tl @@ -87,7 +87,7 @@ storage.fileMp4#b3cea0e4 = storage.FileType; storage.fileWebp#1081464c = storage.FileType; userEmpty#200250ba id:int = User; -user#938458c1 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true id:int access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector bot_inline_placeholder:flags.19?string lang_code:flags.22?string = User; +user#938458c1 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true apply_min_photo:flags.25?true id:int access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector bot_inline_placeholder:flags.19?string lang_code:flags.22?string = User; userProfilePhotoEmpty#4f11bae1 = UserProfilePhoto; userProfilePhoto#69d3ab26 flags:# has_video:flags.0?true photo_id:long photo_small:FileLocation photo_big:FileLocation dc_id:int = UserProfilePhoto; @@ -106,7 +106,7 @@ channel#d31a961e flags:# creator:flags.0?true left:flags.2?true broadcast:flags. channelForbidden#289da732 flags:# broadcast:flags.5?true megagroup:flags.8?true id:int access_hash:long title:string until_date:flags.16?int = Chat; chatFull#1b7c9db3 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:int about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int = ChatFull; -channelFull#f0e6672a flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_view_stats:flags.12?true can_set_location:flags.16?true has_scheduled:flags.19?true id:int about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?int migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?int location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int = ChatFull; +channelFull#f0e6672a flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true id:int about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?int migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?int location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int = ChatFull; chatParticipant#c8d7493e user_id:int inviter_id:int date:int = ChatParticipant; chatParticipantCreator#da13538a user_id:int = ChatParticipant; @@ -203,7 +203,7 @@ inputReportReasonOther#e1746d0a text:string = ReportReason; inputReportReasonCopyright#9b89f93a = ReportReason; inputReportReasonGeoIrrelevant#dbd4feed = ReportReason; -userFull#edf17c12 flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true user:User about:flags.1?string settings:PeerSettings profile_photo:flags.2?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int = UserFull; +userFull#edf17c12 flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true user:User about:flags.1?string settings:PeerSettings profile_photo:flags.2?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int = UserFull; contact#f911c994 user_id:int mutual:Bool = Contact; @@ -796,13 +796,14 @@ inputStickerSetItem#ffa0a496 flags:# document:InputDocument emoji:string mask_co inputPhoneCall#1e36fded id:long access_hash:long = InputPhoneCall; phoneCallEmpty#5366c915 id:long = PhoneCall; -phoneCallWaiting#1b8f4ad1 flags:# video:flags.5?true id:long access_hash:long date:int admin_id:int participant_id:int protocol:PhoneCallProtocol receive_date:flags.0?int = PhoneCall; -phoneCallRequested#87eabb53 flags:# video:flags.5?true id:long access_hash:long date:int admin_id:int participant_id:int g_a_hash:bytes protocol:PhoneCallProtocol = PhoneCall; -phoneCallAccepted#997c454a flags:# video:flags.5?true id:long access_hash:long date:int admin_id:int participant_id:int g_b:bytes protocol:PhoneCallProtocol = PhoneCall; -phoneCall#8742ae7f flags:# p2p_allowed:flags.5?true id:long access_hash:long date:int admin_id:int participant_id:int g_a_or_b:bytes key_fingerprint:long protocol:PhoneCallProtocol connections:Vector start_date:int = PhoneCall; -phoneCallDiscarded#50ca4de1 flags:# need_rating:flags.2?true need_debug:flags.3?true video:flags.5?true id:long reason:flags.0?PhoneCallDiscardReason duration:flags.1?int = PhoneCall; +phoneCallWaiting#1b8f4ad1 flags:# video:flags.6?true id:long access_hash:long date:int admin_id:int participant_id:int protocol:PhoneCallProtocol receive_date:flags.0?int = PhoneCall; +phoneCallRequested#87eabb53 flags:# video:flags.6?true id:long access_hash:long date:int admin_id:int participant_id:int g_a_hash:bytes protocol:PhoneCallProtocol = PhoneCall; +phoneCallAccepted#997c454a flags:# video:flags.6?true id:long access_hash:long date:int admin_id:int participant_id:int g_b:bytes protocol:PhoneCallProtocol = PhoneCall; +phoneCall#8742ae7f flags:# p2p_allowed:flags.5?true video:flags.6?true id:long access_hash:long date:int admin_id:int participant_id:int g_a_or_b:bytes key_fingerprint:long protocol:PhoneCallProtocol connections:Vector start_date:int = PhoneCall; +phoneCallDiscarded#50ca4de1 flags:# need_rating:flags.2?true need_debug:flags.3?true video:flags.6?true id:long reason:flags.0?PhoneCallDiscardReason duration:flags.1?int = PhoneCall; phoneConnection#9d4c17c0 id:long ip:string ipv6:string port:int peer_tag:bytes = PhoneConnection; +phoneConnectionWebrtc#635fe375 flags:# turn:flags.0?true stun:flags.1?true id:long ip:string ipv6:string port:int username:string password:string = PhoneConnection; phoneCallProtocol#fc878fc8 flags:# udp_p2p:flags.0?true udp_reflector:flags.1?true min_layer:int max_layer:int library_versions:Vector = PhoneCallProtocol; @@ -1376,7 +1377,7 @@ updates.getState#edd4882a = updates.State; updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference; updates.getChannelDifference#3173d78 flags:# force:flags.0?true channel:InputChannel filter:ChannelMessagesFilter pts:int limit:int = updates.ChannelDifference; -photos.updateProfilePhoto#f0bb5152 id:InputPhoto = UserProfilePhoto; +photos.updateProfilePhoto#72d4742c id:InputPhoto = photos.Photo; photos.uploadProfilePhoto#89f30f69 flags:# file:flags.0?InputFile video:flags.1?InputFile video_start_ts:flags.2?double = photos.Photo; photos.deletePhotos#87cf7f2f id:Vector = Vector; photos.getUserPhotos#91cd32a8 user_id:InputUser offset:int max_id:long limit:int = photos.Photos; @@ -1489,4 +1490,4 @@ stats.getBroadcastStats#ab42441a flags:# dark:flags.0?true channel:InputChannel stats.loadAsyncGraph#621d5fa0 flags:# token:string x:flags.0?long = StatsGraph; stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel = stats.MegagroupStats; -// LAYER 116 \ No newline at end of file +// LAYER 117 \ No newline at end of file diff --git a/compiler/api/template/combinator.txt b/compiler/api/template/combinator.txt new file mode 100644 index 0000000000..318052a7a9 --- /dev/null +++ b/compiler/api/template/combinator.txt @@ -0,0 +1,35 @@ +{notice} + +from io import BytesIO + +from pyrogram.raw.core.primitives import Int, Long, Int128, Int256, Bool, Bytes, String, Double, Vector +from pyrogram.raw.core import TLObject +from pyrogram import raw +from typing import List, Union, Any + +{warning} + + +class {name}(TLObject): # type: ignore + """{docstring} + """ + + __slots__: List[str] = [{slots}] + + ID = {id} + QUALNAME = "{qualname}" + + def __init__(self{arguments}) -> None: + {fields} + + @staticmethod + def read(data: BytesIO, *args: Any) -> "{name}": + {read_types} + return {name}({return_arguments}) + + def write(self) -> bytes: + data = BytesIO() + data.write(Int(self.ID, False)) + + {write_types} + return data.getvalue() diff --git a/compiler/api/template/mtproto.txt b/compiler/api/template/mtproto.txt deleted file mode 100644 index d7d3c7b7aa..0000000000 --- a/compiler/api/template/mtproto.txt +++ /dev/null @@ -1,30 +0,0 @@ -{notice} - -from io import BytesIO - -from pyrogram.api.core import * - - -class {class_name}(TLObject): - """{docstring_args} - """ - - __slots__ = [{slots}] - - ID = {object_id} - QUALNAME = "{qualname}" - - def __init__(self{arguments}): - {fields} - - @staticmethod - def read(b: BytesIO, *args) -> "{class_name}": - {read_types} - return {class_name}({return_arguments}) - - def write(self) -> bytes: - b = BytesIO() - b.write(Int(self.ID, False)) - - {write_types} - return b.getvalue() diff --git a/compiler/api/template/pyrogram.txt b/compiler/api/template/pyrogram.txt deleted file mode 100644 index 00ad8e3317..0000000000 --- a/compiler/api/template/pyrogram.txt +++ /dev/null @@ -1,12 +0,0 @@ -{notice} - -from pyrogram.api.core import Object - - -class {class_name}(Object): - """{docstring_args} - """ - ID = {object_id} - - def __init__(self{arguments}): - {fields} diff --git a/compiler/api/template/type.txt b/compiler/api/template/type.txt new file mode 100644 index 0000000000..4dd6a2503b --- /dev/null +++ b/compiler/api/template/type.txt @@ -0,0 +1,17 @@ +{notice} + +{warning} + +from typing import Union +from pyrogram import raw +from pyrogram.raw.core import TLObject + +{name} = Union[{types}] + + +# noinspection PyRedeclaration +class {name}(TLObject): # type: ignore + """{docstring} + """ + + QUALNAME = "pyrogram.raw.base.{qualname}" diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index 8afd99f841..315c6b73a8 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -25,11 +25,13 @@ DESTINATION = "docs/source/telegram" PYROGRAM_API_DEST = "docs/source/api" -FUNCTIONS_PATH = "pyrogram/api/functions" -TYPES_PATH = "pyrogram/api/types" +FUNCTIONS_PATH = "pyrogram/raw/functions" +TYPES_PATH = "pyrogram/raw/types" +BASE_PATH = "pyrogram/raw/base" FUNCTIONS_BASE = "functions" TYPES_BASE = "types" +BASE_BASE = "base" def snek(s: str): @@ -70,7 +72,7 @@ def build(path, level=0): page_template.format( title=name, title_markup="=" * len(name), - full_class_path="pyrogram.api.{}".format( + full_class_path="pyrogram.raw.{}".format( ".".join(full_path.split("/")[:-1]) + "." + name ) ) @@ -92,14 +94,14 @@ def build(path, level=0): if k != base: inner_path = base + "/" + k + "/index" + ".rst" - module = "pyrogram.api.{}.{}".format(base, k) + module = "pyrogram.raw.{}.{}".format(base, k) else: for i in sorted(list(all_entities), reverse=True): if i != base: entities.insert(0, "{0}/index".format(i)) inner_path = base + "/index" + ".rst" - module = "pyrogram.api.{}".format(base) + module = "pyrogram.raw.{}".format(base) with open(DESTINATION + "/" + inner_path, "w", encoding="utf-8") as f: if k == base: @@ -128,7 +130,6 @@ def get_title_list(s: str) -> list: utilities=""" Utilities start - idle stop run restart @@ -264,6 +265,7 @@ def get_title_list(s: str) -> list: send_code resend_code sign_in + sign_in_bot sign_up get_password_hint check_password @@ -302,6 +304,15 @@ def get_title_list(s: str) -> list: f2.write(title + "\n" + "=" * len(title) + "\n\n") f2.write(".. automethod:: pyrogram.Client.{}()".format(method)) + functions = ["idle"] + + for func in functions: + with open(root + "/{}.rst".format(func), "w") as f2: + title = "{}()".format(func) + + f2.write(title + "\n" + "=" * len(title) + "\n\n") + f2.write(".. autofunction:: pyrogram.{}()".format(func)) + f.write(template.format(**fmt_keys)) # Types @@ -405,7 +416,7 @@ def get_title_list(s: str) -> list: title = "{}".format(type) f2.write(title + "\n" + "=" * len(title) + "\n\n") - f2.write(".. autoclass:: pyrogram.{}()".format(type)) + f2.write(".. autoclass:: pyrogram.types.{}()".format(type)) f.write(template.format(**fmt_keys)) @@ -506,7 +517,7 @@ def get_title_list(s: str) -> list: title = "{}()".format(bm) f2.write(title + "\n" + "=" * len(title) + "\n\n") - f2.write(".. automethod:: pyrogram.{}()".format(bm)) + f2.write(".. automethod:: pyrogram.types.{}()".format(bm)) f.write(template.format(**fmt_keys)) @@ -525,12 +536,14 @@ def start(): generate(TYPES_PATH, TYPES_BASE) generate(FUNCTIONS_PATH, FUNCTIONS_BASE) + generate(BASE_PATH, BASE_BASE) pyrogram_api() if "__main__" == __name__: - FUNCTIONS_PATH = "../../pyrogram/api/functions" - TYPES_PATH = "../../pyrogram/api/types" + FUNCTIONS_PATH = "../../pyrogram/raw/functions" + TYPES_PATH = "../../pyrogram/raw/types" + BASE_PATH = "../../pyrogram/raw/base" HOME = "." DESTINATION = "../../docs/source/telegram" PYROGRAM_API_DEST = "../../docs/source/api" diff --git a/compiler/docs/template/bound-methods.rst b/compiler/docs/template/bound-methods.rst index 4238df5296..963b5251c9 100644 --- a/compiler/docs/template/bound-methods.rst +++ b/compiler/docs/template/bound-methods.rst @@ -26,7 +26,7 @@ some of the required arguments. ----- -.. currentmodule:: pyrogram +.. currentmodule:: pyrogram.types Message ------- diff --git a/compiler/docs/template/methods.rst b/compiler/docs/template/methods.rst index 5a287f87a7..f99605ca45 100644 --- a/compiler/docs/template/methods.rst +++ b/compiler/docs/template/methods.rst @@ -1,7 +1,8 @@ Available Methods ================= -This page is about Pyrogram methods. All the methods listed here are bound to a :class:`~pyrogram.Client` instance. +This page is about Pyrogram methods. All the methods listed here are bound to a :class:`~pyrogram.Client` instance, +except for :meth:`~pyrogram.idle()`, which is a special function that can be found in the main package directly. .. code-block:: python :emphasize-lines: 6 @@ -34,6 +35,20 @@ Utilities {utilities} +.. currentmodule:: pyrogram + +.. autosummary:: + :nosignatures: + + idle + +.. toctree:: + :hidden: + + idle + +.. currentmodule:: pyrogram.Client + Messages -------- diff --git a/compiler/docs/template/types.rst b/compiler/docs/template/types.rst index 2c79c11dee..4cc0cf924e 100644 --- a/compiler/docs/template/types.rst +++ b/compiler/docs/template/types.rst @@ -19,7 +19,7 @@ This page is about Pyrogram types. All types listed here are accessible through ----- -.. currentmodule:: pyrogram +.. currentmodule:: pyrogram.types Users & Chats ------------- diff --git a/compiler/error/source/400_BAD_REQUEST.tsv b/compiler/error/source/400_BAD_REQUEST.tsv index 608bcc9d92..fadbbb4971 100644 --- a/compiler/error/source/400_BAD_REQUEST.tsv +++ b/compiler/error/source/400_BAD_REQUEST.tsv @@ -28,7 +28,7 @@ PASSWORD_HASH_INVALID Two-step verification password is invalid USERNAME_NOT_OCCUPIED The username is not occupied by anyone USERNAME_INVALID The username is invalid MESSAGE_ID_INVALID The message id is invalid -MESSAGE_NOT_MODIFIED The message was not modified +MESSAGE_NOT_MODIFIED The message was not modified because you tried to edit it using the same content ENTITY_MENTION_USER_INVALID The mentioned entity is not an user MESSAGE_TOO_LONG The message text is over 4096 characters ACCESS_TOKEN_EXPIRED The bot token is invalid diff --git a/docs/Makefile b/docs/Makefile index c647eb13e9..cabe3ecb33 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -2,7 +2,7 @@ # # You can set these variables from the command line. -SPHINXOPTS = +SPHINXOPTS = -j $(shell nproc --all) SPHINXBUILD = sphinx-build SPHINXPROJ = Pyrogram SOURCEDIR = source @@ -17,4 +17,7 @@ help: # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) \ No newline at end of file + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +lhtml: # live html + sphinx-autobuild -H $(shell ipconfig getifaddr en3) -b html "$(SOURCEDIR)" "$(BUILDDIR)/html" $(SPHINXOPTS) diff --git a/docs/requirements.txt b/docs/requirements.txt index 0e754f9f8a..995cfe8cc4 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -3,4 +3,5 @@ sphinx_rtd_theme sphinx_copybutton sphinx_tabs pypandoc -requests \ No newline at end of file +requests +sphinx-autobuild \ No newline at end of file diff --git a/docs/scripts/sitemap.py b/docs/scripts/sitemap.py index cc3da354d7..8e9bdded41 100644 --- a/docs/scripts/sitemap.py +++ b/docs/scripts/sitemap.py @@ -46,7 +46,7 @@ def now(): def search(path): try: for j in os.listdir(path): - search("{}/{}".format(path, j)) + search(f"{path}/{j}") except NotADirectoryError: if not path.endswith(".rst"): return @@ -58,7 +58,7 @@ def search(path): else: folder = path[0] - path = "{}{}".format(canonical, "/".join(path))[:-len(".rst")] + path = f"{canonical}{'/'.join(path)}"[:-len(".rst")] if path.endswith("index"): path = path[:-len("index")] @@ -71,11 +71,11 @@ def search(path): urls.sort(key=lambda x: x[3], reverse=True) for i in urls: - f.write(" \n") - f.write(" {}\n".format(i[0])) - f.write(" {}\n".format(i[1])) - f.write(" {}\n".format(i[2])) - f.write(" {}\n".format(i[3])) - f.write(" \n\n") + f.write(f" \n") + f.write(f" {i[0]}\n") + f.write(f" {i[1]}\n") + f.write(f" {i[2]}\n") + f.write(f" {i[3]}\n") + f.write(f" \n\n") f.write("") diff --git a/docs/source/api/decorators.rst b/docs/source/api/decorators.rst index cacb43b16f..13bfc9e408 100644 --- a/docs/source/api/decorators.rst +++ b/docs/source/api/decorators.rst @@ -26,6 +26,7 @@ functions. .. contents:: Contents :backlinks: none + :depth: 1 :local: ----- diff --git a/docs/source/api/errors.rst b/docs/source/api/errors.rst deleted file mode 100644 index 5c443cf314..0000000000 --- a/docs/source/api/errors.rst +++ /dev/null @@ -1,78 +0,0 @@ -RPC Errors -========== - -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. - -.. code-block:: python - :emphasize-lines: 1, 5 - - from pyrogram.errors import FloodWait - - try: - ... - except FloodWait as e: - ... - -.. contents:: Contents - :backlinks: none - :local: - ------ - -303 - SeeOther --------------- - -.. csv-table:: - :file: ../../../compiler/error/source/303_SEE_OTHER.tsv - :delim: tab - :header-rows: 1 - -400 - BadRequest ----------------- - -.. 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 - NotAcceptable -------------------- - -.. 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 - InternalServerError -------------------------- - -.. csv-table:: - :file: ../../../compiler/error/source/500_INTERNAL_SERVER_ERROR.tsv - :delim: tab - :header-rows: 1 diff --git a/docs/source/api/errors/bad-request.rst b/docs/source/api/errors/bad-request.rst new file mode 100644 index 0000000000..130cc57404 --- /dev/null +++ b/docs/source/api/errors/bad-request.rst @@ -0,0 +1,7 @@ +400 - BadRequest +---------------- + +.. csv-table:: + :file: ../../../../compiler/error/source/400_BAD_REQUEST.tsv + :delim: tab + :header-rows: 1 diff --git a/docs/source/api/errors/flood.rst b/docs/source/api/errors/flood.rst new file mode 100644 index 0000000000..eae3359170 --- /dev/null +++ b/docs/source/api/errors/flood.rst @@ -0,0 +1,7 @@ +420 - Flood +----------- + +.. csv-table:: + :file: ../../../../compiler/error/source/420_FLOOD.tsv + :delim: tab + :header-rows: 1 \ No newline at end of file diff --git a/docs/source/api/errors/forbidden.rst b/docs/source/api/errors/forbidden.rst new file mode 100644 index 0000000000..280adf40cc --- /dev/null +++ b/docs/source/api/errors/forbidden.rst @@ -0,0 +1,7 @@ +403 - Forbidden +--------------- + +.. csv-table:: + :file: ../../../../compiler/error/source/403_FORBIDDEN.tsv + :delim: tab + :header-rows: 1 \ No newline at end of file diff --git a/docs/source/api/errors/index.rst b/docs/source/api/errors/index.rst new file mode 100644 index 0000000000..be2b80d46a --- /dev/null +++ b/docs/source/api/errors/index.rst @@ -0,0 +1,37 @@ +RPC Errors +========== + +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. + +.. code-block:: python + + from pyrogram.errors import FloodWait + + try: + ... + except FloodWait as e: + ... + +.. hlist:: + :columns: 1 + + - :doc:`see-other` + - :doc:`bad-request` + - :doc:`unauthorized` + - :doc:`forbidden` + - :doc:`not-acceptable` + - :doc:`flood` + - :doc:`internal-server-error` + +.. toctree:: + :hidden: + + see-other + bad-request + unauthorized + forbidden + not-acceptable + flood + internal-server-error \ No newline at end of file diff --git a/docs/source/api/errors/internal-server-error.rst b/docs/source/api/errors/internal-server-error.rst new file mode 100644 index 0000000000..bbc9177a6c --- /dev/null +++ b/docs/source/api/errors/internal-server-error.rst @@ -0,0 +1,7 @@ +500 - InternalServerError +------------------------- + +.. csv-table:: + :file: ../../../../compiler/error/source/500_INTERNAL_SERVER_ERROR.tsv + :delim: tab + :header-rows: 1 \ No newline at end of file diff --git a/docs/source/api/errors/not-acceptable.rst b/docs/source/api/errors/not-acceptable.rst new file mode 100644 index 0000000000..cfe0383e73 --- /dev/null +++ b/docs/source/api/errors/not-acceptable.rst @@ -0,0 +1,7 @@ +406 - NotAcceptable +------------------- + +.. csv-table:: + :file: ../../../../compiler/error/source/406_NOT_ACCEPTABLE.tsv + :delim: tab + :header-rows: 1 \ No newline at end of file diff --git a/docs/source/api/errors/see-other.rst b/docs/source/api/errors/see-other.rst new file mode 100644 index 0000000000..f2cde9cf5e --- /dev/null +++ b/docs/source/api/errors/see-other.rst @@ -0,0 +1,7 @@ +303 - SeeOther +-------------- + +.. csv-table:: + :file: ../../../../compiler/error/source/303_SEE_OTHER.tsv + :delim: tab + :header-rows: 1 \ No newline at end of file diff --git a/docs/source/api/errors/unauthorized.rst b/docs/source/api/errors/unauthorized.rst new file mode 100644 index 0000000000..4f2b24a5cb --- /dev/null +++ b/docs/source/api/errors/unauthorized.rst @@ -0,0 +1,7 @@ +401 - Unauthorized +------------------ + +.. csv-table:: + :file: ../../../../compiler/error/source/401_UNAUTHORIZED.tsv + :delim: tab + :header-rows: 1 diff --git a/docs/source/api/filters.rst b/docs/source/api/filters.rst index 6cb01cda94..eb3c9522b3 100644 --- a/docs/source/api/filters.rst +++ b/docs/source/api/filters.rst @@ -1,8 +1,11 @@ Update Filters ============== +Filters are objects that can be used to filter the content of incoming updates. +:doc:`Read more about how filters work <../topics/use-filters>`. + Details ------- -.. autoclass:: pyrogram.Filters +.. automodule:: pyrogram.filters :members: diff --git a/docs/source/api/handlers.rst b/docs/source/api/handlers.rst index 4fdc511e54..80d2cf73a9 100644 --- a/docs/source/api/handlers.rst +++ b/docs/source/api/handlers.rst @@ -7,7 +7,8 @@ For a much more convenient way of registering callback functions have a look at .. code-block:: python :emphasize-lines: 1, 10 - from pyrogram import Client, MessageHandler + from pyrogram import Client + from pyrogram.handlers import MessageHandler app = Client("my_account") @@ -22,11 +23,12 @@ For a much more convenient way of registering callback functions have a look at .. contents:: Contents :backlinks: none + :depth: 1 :local: ----- -.. currentmodule:: pyrogram +.. currentmodule:: pyrogram.handlers Index ----- diff --git a/docs/source/faq.rst b/docs/source/faq.rst index e56736d8f8..a8386732ce 100644 --- a/docs/source/faq.rst +++ b/docs/source/faq.rst @@ -12,6 +12,7 @@ This FAQ page provides answers to common questions about Pyrogram and, to some e .. contents:: Contents :backlinks: none + :depth: 1 :local: ----- diff --git a/docs/source/glossary.rst b/docs/source/glossary.rst index a0e970ae95..fa6086ce6c 100644 --- a/docs/source/glossary.rst +++ b/docs/source/glossary.rst @@ -10,6 +10,7 @@ general. Some words may as well link to dedicated articles in case the topic is .. contents:: Contents :backlinks: none + :depth: 1 :local: ----- diff --git a/docs/source/index.rst b/docs/source/index.rst index fd227079e9..0fda6da652 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -1,77 +1,6 @@ Welcome to Pyrogram =================== -.. toctree:: - :hidden: - :caption: Introduction - - intro/quickstart - intro/install - intro/setup - -.. toctree:: - :hidden: - :caption: Getting Started - - start/auth - start/invoking - start/updates - start/errors - -.. toctree:: - :hidden: - :caption: API Reference - - api/client - api/methods/index - api/types/index - api/bound-methods/index - api/handlers - api/decorators - api/errors - api/filters - -.. toctree:: - :hidden: - :caption: Topic Guides - - topics/use-filters - topics/create-filters - topics/more-on-updates - topics/config-file - topics/smart-plugins - topics/session-settings - topics/tgcrypto - topics/storage-engines - topics/text-formatting - topics/serializing - topics/proxy - topics/scheduling - topics/bots-interaction - topics/mtproto-vs-botapi - topics/debugging - topics/test-servers - topics/advanced-usage - topics/voice-calls - -.. toctree:: - :hidden: - :caption: Meta - - faq - glossary - powered-by - support-pyrogram - license - releases/index - -.. toctree:: - :hidden: - :caption: Telegram API - - telegram/functions/index - telegram/types/index - .. raw:: html
@@ -99,20 +28,20 @@ Welcome to Pyrogram .. code-block:: python - from pyrogram import Client, Filters + from pyrogram import Client, filters app = Client("my_account") - @app.on_message(Filters.private) - def hello(client, message): - message.reply_text(f"Hello {message.from_user.first_name}") + @app.on_message(filters.private) + async def hello(client, message): + await message.reply_text(f"Hello {message.from_user.mention}") 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 for both user and bot identities (bot API alternative) via the +**Pyrogram** is a modern, elegant and easy-to-use Telegram_ 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 :doc:`MTProto API `. .. _Telegram: https://telegram.org @@ -120,9 +49,9 @@ C. It enables you to easily create custom apps for both user and bot identities 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 :guilabel:`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. +Contents are organized into sections composed of self-contained topics which can be all accessed from the sidebar, or by +following them in order using the :guilabel:`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. First Steps ^^^^^^^^^^^ @@ -159,4 +88,77 @@ Meta - :doc:`About the License `: Information about the Project license. - :doc:`Release Notes `: Release notes for Pyrogram releases. -Last updated on |today| \ No newline at end of file +Last updated on |today| + +.. toctree:: + :hidden: + :caption: Introduction + + intro/quickstart + intro/install + intro/setup + +.. toctree:: + :hidden: + :caption: Getting Started + + start/auth + start/invoking + start/updates + start/errors + start/examples/index + +.. toctree:: + :hidden: + :caption: API Reference + + api/client + api/methods/index + api/types/index + api/bound-methods/index + api/handlers + api/decorators + api/errors/index + api/filters + +.. toctree:: + :hidden: + :caption: Topic Guides + + topics/use-filters + topics/create-filters + topics/more-on-updates + topics/config-file + topics/smart-plugins + topics/session-settings + topics/tgcrypto + topics/storage-engines + topics/text-formatting + topics/serializing + topics/proxy + topics/scheduling + topics/bots-interaction + topics/mtproto-vs-botapi + topics/debugging + topics/test-servers + topics/advanced-usage + topics/voice-calls + +.. toctree:: + :hidden: + :caption: Meta + + faq + glossary + powered-by + support-pyrogram + license + releases/index + +.. toctree:: + :hidden: + :caption: Telegram API + + telegram/functions/index + telegram/types/index + telegram/base/index \ No newline at end of file diff --git a/docs/source/intro/install.rst b/docs/source/intro/install.rst index 5312e44a84..9056d1f50f 100644 --- a/docs/source/intro/install.rst +++ b/docs/source/intro/install.rst @@ -1,18 +1,19 @@ Install Guide ============= -Being a Python library, **Pyrogram** requires Python to be installed in your system. +Being a modern Python library, **Pyrogram** requires Python 3.6+ to be installed in your system. We recommend using the latest versions of both Python 3 and pip. -- Get **Python 3** from https://www.python.org/downloads/ (or with your package manager) +- Get **Python 3** from https://www.python.org/downloads/ (or with your package manager). - Get **pip** by following the instructions at https://pip.pypa.io/en/latest/installing/. .. important:: - Pyrogram supports **Python 3** only, starting from version 3.5.3. **PyPy** is supported too. + Pyrogram supports **Python 3** only, starting from version 3.6. **PyPy** is supported too. .. contents:: Contents :backlinks: none + :depth: 1 :local: ----- @@ -46,43 +47,6 @@ the link): $ pip3 install -U https://github.com/pyrogram/pyrogram/archive/develop.zip -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 and efficiency while running on a single OS-level thread -only. - -**A fully asynchronous variant of Pyrogram is therefore available** (Python 3.5.3 or higher is required). -Use this command to install (note "asyncio.zip" in the link): - -.. code-block:: text - - $ pip3 install -U https://github.com/pyrogram/pyrogram/archive/asyncio.zip - - -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. - - -.. tip:: - - The idea to turn Pyrogram fully asynchronous is still under consideration, but is wise to expect that in future this - would be the one and only way to work with Pyrogram. - - You can start using Pyrogram Async variant right now as an excuse to learn more about asynchronous programming and - do experiments with it! - -.. raw:: html - - - -.. centered:: Subscribe to `@Pyrogram `_ for news and announcements - Verifying --------- diff --git a/docs/source/intro/quickstart.rst b/docs/source/intro/quickstart.rst index 13f646d1cb..593403b99c 100644 --- a/docs/source/intro/quickstart.rst +++ b/docs/source/intro/quickstart.rst @@ -1,8 +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. -Let's go! +The next few steps serve as a quick start for all new Pyrogrammers that want to see Pyrogram in action as fast as +possible. Let's go! Get Pyrogram Real Fast ---------------------- diff --git a/docs/source/intro/setup.rst b/docs/source/intro/setup.rst index f5bf607b3a..e4c6e64065 100644 --- a/docs/source/intro/setup.rst +++ b/docs/source/intro/setup.rst @@ -6,6 +6,7 @@ project with the library. Let's see how it's done. .. contents:: Contents :backlinks: none + :depth: 1 :local: ----- diff --git a/docs/source/powered-by.rst b/docs/source/powered-by.rst index 77e593e7ca..6fee50b25c 100644 --- a/docs/source/powered-by.rst +++ b/docs/source/powered-by.rst @@ -11,6 +11,7 @@ This is a collection of remarkable projects made with Pyrogram. .. contents:: Contents :backlinks: none + :depth: 1 :local: ----- diff --git a/docs/source/start/auth.rst b/docs/source/start/auth.rst index 0bbd4c5e4c..23ff9fe276 100644 --- a/docs/source/start/auth.rst +++ b/docs/source/start/auth.rst @@ -6,6 +6,7 @@ API calls. This section provides all the information you need in order to author .. contents:: Contents :backlinks: none + :depth: 1 :local: ----- @@ -41,7 +42,7 @@ keep the session alive, Pyrogram won't ask you again to enter your phone number. .. important:: - Your ``*.session`` files are personal and must be kept secret. + Your ``*.session`` file is personal and must be kept secret. .. note:: diff --git a/docs/source/start/errors.rst b/docs/source/start/errors.rst index a9707d0895..ff9ccf5b59 100644 --- a/docs/source/start/errors.rst +++ b/docs/source/start/errors.rst @@ -10,6 +10,7 @@ to control the behaviour of your application. Pyrogram errors all live inside th .. contents:: Contents :backlinks: none + :depth: 1 :local: ----- @@ -17,9 +18,8 @@ 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 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. +The father of all errors is named ``RPCError`` 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 @@ -34,19 +34,19 @@ 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 and are subclass-ed from the RPCError: +provides categories of errors, which are named after the common HTTP errors and are subclass-ed from the ``RPCError``: .. 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 - NotAcceptable <../api/errors#notacceptable>`_ -- `420 - Flood <../api/errors#flood>`_ -- `500 - InternalServerError <../api/errors#internalservererror>`_ +- :doc:`303 - SeeOther <../api/errors/see-other>` +- :doc:`400 - BadRequest <../api/errors/bad-request>` +- :doc:`401 - Unauthorized <../api/errors/unauthorized>` +- :doc:`403 - Forbidden <../api/errors/forbidden>` +- :doc:`406 - NotAcceptable <../api/errors/not-acceptable>` +- :doc:`420 - Flood <../api/errors/flood>` +- :doc:`500 - InternalServerError <../api/errors/internal-server-error>` Single Errors ------------- @@ -59,7 +59,7 @@ issue. For example: 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``, thus building a class of error hierarchy such as this: - RPCError - BadRequest diff --git a/docs/source/start/examples/bot_keyboards.rst b/docs/source/start/examples/bot_keyboards.rst new file mode 100644 index 0000000000..343e22c927 --- /dev/null +++ b/docs/source/start/examples/bot_keyboards.rst @@ -0,0 +1,61 @@ +bot_keyboards +============= + +This example will show you how to send normal and inline keyboards (as bot). + +You must log-in as a regular bot in order to send keyboards (use the token from @BotFather). +Any attempt in sending keyboards with a user account will be simply ignored by the server. + +send_message() is used as example, but a keyboard can be sent with any other send_* methods, +like send_audio(), send_document(), send_location(), etc... + +.. code-block:: python + + from pyrogram import Client, ReplyKeyboardMarkup, InlineKeyboardMarkup, InlineKeyboardButton + + # Create a client using your bot token + app = Client("my_bot", bot_token="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11") + + with app: + app.send_message( + "haskell", # Edit this + "This is a ReplyKeyboardMarkup example", + reply_markup=ReplyKeyboardMarkup( + [ + ["A", "B", "C", "D"], # First row + ["E", "F", "G"], # Second row + ["H", "I"], # Third row + ["J"] # Fourth row + ], + resize_keyboard=True # Make the keyboard smaller + ) + ) + + app.send_message( + "haskell", # Edit this + "This is a InlineKeyboardMarkup example", + reply_markup=InlineKeyboardMarkup( + [ + [ # First row + InlineKeyboardButton( # Generates a callback query when pressed + "Button", + callback_data="data" + ), + InlineKeyboardButton( # Opens a web URL + "URL", + url="https://docs.pyrogram.org" + ), + ], + [ # Second row + InlineKeyboardButton( # Opens the inline interface + "Choose chat", + switch_inline_query="pyrogram" + ), + InlineKeyboardButton( # Opens the inline interface in the current chat + "Inline here", + switch_inline_query_current_chat="pyrogram" + ) + ] + ] + ) + ) diff --git a/docs/source/start/examples/callback_queries.rst b/docs/source/start/examples/callback_queries.rst new file mode 100644 index 0000000000..73273058a1 --- /dev/null +++ b/docs/source/start/examples/callback_queries.rst @@ -0,0 +1,19 @@ +callback_queries +================ + +This example shows how to handle callback queries, i.e.: queries coming from inline button presses. +It uses the @on_callback_query decorator to register a CallbackQueryHandler. + +.. code-block:: python + + from pyrogram import Client + + app = Client("my_bot", bot_token="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11") + + + @app.on_callback_query() + def answer(client, callback_query): + callback_query.answer(f"Button contains: '{callback_query.data}'", show_alert=True) + + + app.run() # Automatically start() and idle() \ No newline at end of file diff --git a/docs/source/start/examples/echobot.rst b/docs/source/start/examples/echobot.rst new file mode 100644 index 0000000000..a751a2cddd --- /dev/null +++ b/docs/source/start/examples/echobot.rst @@ -0,0 +1,21 @@ +echobot +======= + +This simple echo bot replies to every private text message. + +It uses the @on_message decorator to register a MessageHandler and applies two filters on it: +Filters.text and Filters.private to make sure it will reply to private text messages only. + +.. code-block:: python + + from pyrogram import Client, Filters + + app = Client("my_account") + + + @app.on_message(Filters.text & Filters.private) + def echo(client, message): + message.reply(message.text) + + + app.run() # Automatically start() and idle() \ No newline at end of file diff --git a/docs/source/start/examples/get_chat_members.rst b/docs/source/start/examples/get_chat_members.rst new file mode 100644 index 0000000000..8d47797604 --- /dev/null +++ b/docs/source/start/examples/get_chat_members.rst @@ -0,0 +1,15 @@ +get_chat_members +================ + +This example shows how to get all the members of a chat. + +.. code-block:: python + + from pyrogram import Client + + app = Client("my_account") + target = "pyrogramchat" # Target channel/supergroup + + with app: + for member in app.iter_chat_members(target): + print(member.user.first_name) \ No newline at end of file diff --git a/docs/source/start/examples/get_dialogs.rst b/docs/source/start/examples/get_dialogs.rst new file mode 100644 index 0000000000..b1a4806427 --- /dev/null +++ b/docs/source/start/examples/get_dialogs.rst @@ -0,0 +1,14 @@ +get_dialogs +=========== + +This example shows how to get the full dialogs list (as user). + +.. code-block:: python + + from pyrogram import Client + + app = Client("my_account") + + with app: + for dialog in app.iter_dialogs(): + print(dialog.chat.title or dialog.chat.first_name) \ No newline at end of file diff --git a/docs/source/start/examples/get_history.rst b/docs/source/start/examples/get_history.rst new file mode 100644 index 0000000000..01433d91fe --- /dev/null +++ b/docs/source/start/examples/get_history.rst @@ -0,0 +1,15 @@ +get_history +=========== + +This example shows how to get the full message history of a chat, starting from the latest message. + +.. code-block:: python + + from pyrogram import Client + + app = Client("my_account") + target = "me" # "me" refers to your own chat (Saved Messages) + + with app: + for message in app.iter_history(target): + print(message.text) \ No newline at end of file diff --git a/docs/source/start/examples/hello_world.rst b/docs/source/start/examples/hello_world.rst new file mode 100644 index 0000000000..e68847798a --- /dev/null +++ b/docs/source/start/examples/hello_world.rst @@ -0,0 +1,21 @@ +hello_world +=========== + +This example demonstrates a basic API usage + +.. code-block:: python + + from pyrogram import Client + + # Create a new Client instance + app = Client("my_account") + + with app: + # Send a message, Markdown is enabled by default + app.send_message("me", "Hi there! I'm using **Pyrogram**") + + # Send a location + app.send_location("me", 51.500729, -0.124583) + + # Send a sticker + app.send_sticker("me", "CAADBAADzg4AAvLQYAEz_x2EOgdRwBYE") \ No newline at end of file diff --git a/docs/source/start/examples/index.rst b/docs/source/start/examples/index.rst new file mode 100644 index 0000000000..7d8a69a41d --- /dev/null +++ b/docs/source/start/examples/index.rst @@ -0,0 +1,46 @@ +Examples +======== + +This page contains example scripts to show you how Pyrogram looks like. + +Every script is working right away (provided you correctly set up your credentials), meaning you can simply copy-paste +and run. The only things you have to change are session names and target chats, where applicable. + +The examples listed below can be treated as building blocks for your own applications and are meant to be simple enough +to give you a basic idea. + +----- + +.. csv-table:: + :header: Example, Description + :widths: auto + :align: center + + :doc:`hello_world`, "Demonstration of basic API usage" + :doc:`echobot`, "Echo every private text message" + :doc:`welcomebot`, "The Welcome Bot in @PyrogramChat" + :doc:`get_history`, "Get the full message history of a chat" + :doc:`get_chat_members`, "Get all the members of a chat" + :doc:`get_dialogs`, "Get all of your dialog chats" + :doc:`callback_queries`, "Handle callback queries (as bot) coming from inline button presses" + :doc:`inline_queries`, "Handle inline queries (as bot) and answer with results" + :doc:`use_inline_bots`, "Query an inline bot (as user) and send a result to a chat" + :doc:`bot_keyboards`, "Send normal and inline keyboards using regular bots" + :doc:`raw_updates`, "Handle raw updates (old, should be avoided)" + +For more advanced examples, see https://snippets.pyrogram.org. + +.. toctree:: + :hidden: + + hello_world + echobot + welcomebot + get_history + get_chat_members + get_dialogs + callback_queries + inline_queries + use_inline_bots + bot_keyboards + raw_updates diff --git a/docs/source/start/examples/inline_queries.rst b/docs/source/start/examples/inline_queries.rst new file mode 100644 index 0000000000..158394e8d1 --- /dev/null +++ b/docs/source/start/examples/inline_queries.rst @@ -0,0 +1,61 @@ +inline_queries +============== + +This example shows how to handle inline queries. + +Two results are generated when users invoke the bot inline mode, e.g.: @pyrogrambot hi. +It uses the @on_inline_query decorator to register an InlineQueryHandler. + +.. code-block:: python + + from pyrogram import ( + Client, InlineQueryResultArticle, InputTextMessageContent, InlineKeyboardMarkup, InlineKeyboardButton + ) + + app = Client("my_bot", bot_token="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11") + + + @app.on_inline_query() + def answer(client, inline_query): + inline_query.answer( + results=[ + InlineQueryResultArticle( + title="Installation", + input_message_content=InputTextMessageContent( + "Here's how to install **Pyrogram**" + ), + 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.org/intro/install" + )] + ] + ) + ), + InlineQueryResultArticle( + title="Usage", + input_message_content=InputTextMessageContent( + "Here's how to use **Pyrogram**" + ), + 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.org/start/invoking" + )] + ] + ) + ) + ], + cache_time=1 + ) + + + app.run() # Automatically start() and idle() \ No newline at end of file diff --git a/docs/source/start/examples/raw_updates.rst b/docs/source/start/examples/raw_updates.rst new file mode 100644 index 0000000000..6086a9686c --- /dev/null +++ b/docs/source/start/examples/raw_updates.rst @@ -0,0 +1,18 @@ +raw_updates +=========== + +This example shows how to handle raw updates. + +.. code-block:: python + + from pyrogram import Client + + app = Client("my_account") + + + @app.on_raw_update() + def raw(client, update, users, chats): + print(update) + + + app.run() # Automatically start() and idle() diff --git a/docs/source/start/examples/use_inline_bots.rst b/docs/source/start/examples/use_inline_bots.rst new file mode 100644 index 0000000000..284432d8a0 --- /dev/null +++ b/docs/source/start/examples/use_inline_bots.rst @@ -0,0 +1,18 @@ +use_inline_bots +=============== + +This example shows how to query an inline bot (as user). + +.. code-block:: python + + from pyrogram import Client + + # Create a new Client + app = Client("my_account") + + with app: + # Get bot results for "Fuzz Universe" from the inline bot @vid + bot_results = app.get_inline_bot_results("vid", "Fuzz Universe") + + # Send the first result (bot_results.results[0]) to your own chat (Saved Messages) + app.send_inline_bot_result("me", bot_results.query_id, bot_results.results[0].id) \ No newline at end of file diff --git a/docs/source/start/examples/welcomebot.rst b/docs/source/start/examples/welcomebot.rst new file mode 100644 index 0000000000..a3bb32998a --- /dev/null +++ b/docs/source/start/examples/welcomebot.rst @@ -0,0 +1,33 @@ +welcomebot +========== + +This is the Welcome Bot in @PyrogramChat. + +It uses the Emoji module to easily add emojis in your text messages and Filters +to make it only work for specific messages in a specific chat. + +.. code-block:: python + + from pyrogram import Client, Emoji, Filters + + 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.org/)'s group chat {}!" # Welcome message + + app = Client("my_account") + + + # Filter in only new_chat_members updates generated in TARGET chat + @app.on_message(Filters.chat(TARGET) & Filters.new_chat_members) + def welcome(client, message): + # Build the new members list (with mentions) by using their first_name + new_members = [MENTION.format(i.first_name, i.id) for i in message.new_chat_members] + + # Build the welcome message by using an emoji and the list we built above + text = MESSAGE.format(Emoji.SPARKLES, ", ".join(new_members)) + + # Send the welcome message, without the web page preview + message.reply(text, disable_web_page_preview=True) + + + app.run() # Automatically start() and idle() \ No newline at end of file diff --git a/docs/source/start/invoking.rst b/docs/source/start/invoking.rst index 74b313e42b..0abea089e9 100644 --- a/docs/source/start/invoking.rst +++ b/docs/source/start/invoking.rst @@ -6,6 +6,7 @@ account; we are now aiming towards the core of the library. It's time to start p .. contents:: Contents :backlinks: none + :depth: 1 :local: ----- @@ -13,7 +14,8 @@ account; we are now aiming towards the core of the library. It's time to start p Basic Usage ----------- -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 a basic example we are going to examine step by step and +then expand to explain what happens underneath: .. code-block:: python @@ -21,16 +23,13 @@ Making API method calls with Pyrogram is very simple. Here's an example we are g app = Client("my_account") - app.start() + with app: + app.send_message("me", "Hi!") - print(app.get_me()) - app.send_message("me", "Hi, it's me!") - app.send_location("me", 51.500729, -0.124583) - app.send_sticker("me", "CAADBAADyg4AAvLQYAEYD4F7vcZ43AI") +Basic step-by-step +^^^^^^^^^^^^^^^^^^ - app.stop() - -#. Let's begin by importing the Client class from the Pyrogram package: +#. Let's begin by importing the Client class: .. code-block:: python @@ -42,35 +41,42 @@ Making API method calls with Pyrogram is very simple. Here's an example we are g app = Client("my_account") -#. To actually make use of any method, the client has to be started first: +#. The ``with`` context manager is a shortcut for starting, executing and stopping the Client: .. code-block:: python - app.start() + with app: #. Now, you can call any method you like: .. code-block:: python - print(app.get_me()) # Print information about yourself + app.send_message("me", "Hi!") - # Send messages to yourself: - app.send_message("me", "Hi!") # Text message - app.send_location("me", 51.500729, -0.124583) # Location - app.send_sticker("me", "CAADBAADyg4AAvLQYAEYD4F7vcZ43AI") # Sticker +Context Manager +--------------- -#. Finally, when done, simply stop the client: +The ``with`` statement starts a context manager, which is used as a shortcut to automatically call +:meth:`~pyrogram.Client.start` and :meth:`~pyrogram.Client.stop`, which are methods required for Pyrogram to work +properly. The context manager does also gracefully stop the client, even in case of unhandled exceptions in your code. - .. code-block:: python +This is how Pyrogram looks without the context manager: - app.stop() +.. code-block:: python -Context Manager ---------------- + from pyrogram import Client + + app = Client("my_account") + + app.start() + app.send_message("me", "Hi!") + app.stop() + +Asynchronous Calls +------------------ -You can also use Pyrogram's Client in a context manager with the ``with`` statement. The client will automatically -:meth:`~pyrogram.Client.start` and :meth:`~pyrogram.Client.stop` gracefully, even in case of unhandled exceptions in -your code. The example above can be therefore rewritten in a much nicer way: +In case you want Pyrogram to run asynchronously (e.g.: if you are using third party libraries that require you to call +them with ``await``), use the asynchronous context manager: .. code-block:: python @@ -78,10 +84,37 @@ your code. The example above can be therefore rewritten in a much nicer way: app = Client("my_account") - with app: - print(app.get_me()) - app.send_message("me", "Hi there! I'm using **Pyrogram**") - app.send_location("me", 51.500729, -0.124583) - app.send_sticker("me", "CAADBAADyg4AAvLQYAEYD4F7vcZ43AI") + async def main(): + async with app: + await app.send_message("me", "Hi!") + + app.run(main()) + +Asynchronous step-by-step +^^^^^^^^^^^^^^^^^^^^^^^^^ + +#. Import the Client class and create an instance: + + .. code-block:: python + + from pyrogram import Client + + app = Client("my_account") + +#. Async methods can't normally be executed at the top level, because they must be inside an async-defined function; + here we define one and put our code inside; the context manager is also being used differently in asyncio and + method calls require the await keyword: + + .. code-block:: python + + async def main(): + async with app: + await app.send_message("me", "Hi!") + +#. Finally, we tell Python to schedule our ``main()`` async function, which in turn will execute Pyrogram's code. Using + :meth:`~pyrogram.Client.run` this way is a friendly alternative for the much more verbose + ``asyncio.get_event_loop().run_until_complete(main())``: + + .. code-block:: python -More examples can be found on `GitHub `_. + app.run(main()) diff --git a/docs/source/start/updates.rst b/docs/source/start/updates.rst index 4f010e27e8..ef569bf903 100644 --- a/docs/source/start/updates.rst +++ b/docs/source/start/updates.rst @@ -6,6 +6,7 @@ This page deals with updates and how to handle such events in Pyrogram. Let's ha .. contents:: Contents :backlinks: none + :depth: 1 :local: ----- @@ -24,83 +25,76 @@ function will be called back by the framework and its body executed. Registering a Handler --------------------- -To explain how handlers work let's have a look at the most used one, the :class:`~pyrogram.MessageHandler`, which will -be in charge for handling :class:`~pyrogram.Message` updates coming from all around your chats. Every other handler shares -the same setup logic; you should not have troubles settings them up once you learn from this section. +To explain how handlers work let's examine the one which will be in charge for handling :class:`~pyrogram.types.Message` +updates coming from all around your chats. Every other handler shares the same setup logic; you should not have +troubles settings them up once you learn from this section. -Using add_handler() -------------------- +Using Decorators +^^^^^^^^^^^^^^^^ -The :meth:`~pyrogram.Client.add_handler` method takes any handler instance that wraps around your defined callback -function and registers it in your Client. Here's a full example that prints out the content of a message as soon as it -arrives: +The most elegant way to register a message handler is by using the :meth:`~pyrogram.Client.on_message` decorator: .. code-block:: python - from pyrogram import Client, MessageHandler - - - def my_function(client, message): - print(message) - + from pyrogram import Client app = Client("my_account") - my_handler = MessageHandler(my_function) - app.add_handler(my_handler) - - app.run() -Let's examine these four new pieces. + @app.on_message() + def my_handler(client, message): + message.forward("me") -#. A callback function we defined which accepts two arguments - - *(client, message)*. This will be the function that gets executed every time a new message arrives and Pyrogram will - call that function by passing the client instance and the new message instance as argument. - .. code-block:: python + app.run() - def my_function(client, message): - print(message) +The defined function ``my_handler``, which accepts the two arguments *(client, message)*, will be the function that gets +executed every time a new message arrives. -#. The :class:`~pyrogram.MessageHandler`. This object tells Pyrogram the function we defined above must only handle - updates that are in form of a :class:`~pyrogram.Message`: +Asynchronous handlers +^^^^^^^^^^^^^^^^^^^^^ - .. code-block:: python +You can also have asynchronous handlers; you only need to define the callback function using ``async def`` and call API +methods by placing ``await`` in front of them: - my_handler = MessageHandler(my_function) +.. code-block:: python -#. The method :meth:`~pyrogram.Client.add_handler`. 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 internal dispatching phase - begins. + @app.on_message() + async def my_handler(client, message): + await message.forward("me") - .. code-block:: python +.. note:: - app.add_handler(my_handler) + You can mix ``def`` and ``async def`` handlers as much as you need, Pyrogram will still work concurrently and + efficiently regardless of what you choose. -#. The :meth:`~pyrogram.Client.run` method. What this does is simply call :meth:`~pyrogram.Client.start` and - a special method :meth:`~pyrogram.Client.idle` that keeps your main scripts alive until you press ``CTRL+C``; the - client will be automatically stopped after that. +Using add_handler() +^^^^^^^^^^^^^^^^^^^ - .. code-block:: python +The :meth:`~pyrogram.Client.add_handler` method takes any handler instance that wraps around your defined callback +function and registers it in your Client. It us useful in case you want to programmatically add handlers (or in case, +for some reason, you don't like to use decorators). - app.run() +.. code-block:: python -Using Decorators ----------------- + from pyrogram import Client + from pyrogram.handlers import MessageHandler -All of the above will become quite verbose, especially in case you have lots of handlers to register. A much nicer way -to do so is by decorating your callback function with the :meth:`~pyrogram.Client.on_message` decorator. -.. code-block:: python + def my_function(client, message): + message.forward("me") - from pyrogram import Client app = Client("my_account") + my_handler = MessageHandler(my_function) + app.add_handler(my_handler) - @app.on_message() - def my_handler(client, message): - print(message) + app.run() +The same about asynchronous handlers applies for :meth:`~pyrogram.Client.add_handler`: - app.run() +.. code-block:: python + + async def my_function(client, message): + await message.forward("me") diff --git a/docs/source/topics/advanced-usage.rst b/docs/source/topics/advanced-usage.rst index a56e0da1dc..9df028ad31 100644 --- a/docs/source/topics/advanced-usage.rst +++ b/docs/source/topics/advanced-usage.rst @@ -10,6 +10,7 @@ Telegram API with its functions and types. .. contents:: Contents :backlinks: none + :depth: 1 :local: ----- @@ -18,7 +19,7 @@ Telegram Raw API ---------------- If you can't find a high-level method for your needs or if you want complete, low-level access to the whole -Telegram API, you have to use the raw :mod:`~pyrogram.api.functions` and :mod:`~pyrogram.api.types`. +Telegram API, you have to use the raw :mod:`~pyrogram.raw.functions` and :mod:`~pyrogram.raw.types`. As already hinted, raw functions and types can be really confusing, mainly because people don't realize soon enough they accept *only* the right types and that all required parameters must be filled in. This section will therefore explain @@ -41,7 +42,7 @@ Unlike the :doc:`methods <../api/methods/index>` found in Pyrogram's API, which functions to be invoked from the raw Telegram API have a different way of usage and are more complex. First of all, both :doc:`raw functions <../telegram/functions/index>` and :doc:`raw types <../telegram/types/index>` -live in their respective packages (and sub-packages): ``pyrogram.api.functions``, ``pyrogram.api.types``. They all exist +live in their respective packages (and sub-packages): ``pyrogram.raw.functions``, ``pyrogram.raw.types``. They all exist as Python classes, meaning you need to create an instance of each every time you need them and fill them in with the correct values using named arguments. @@ -55,7 +56,7 @@ Here's some examples: .. code-block:: python from pyrogram import Client - from pyrogram.api import functions + from pyrogram.raw import functions with Client("my_account") as app: app.send( @@ -70,7 +71,7 @@ Here's some examples: .. code-block:: python from pyrogram import Client - from pyrogram.api import functions, types + from pyrogram.raw import functions, types with Client("my_account") as app: app.send( @@ -85,7 +86,7 @@ Here's some examples: .. code-block:: python from pyrogram import Client - from pyrogram.api import functions, types + from pyrogram.raw import functions, types with Client("my_account") as app: app.send( @@ -109,9 +110,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: -- :class:`~pyrogram.api.types.InputPeerUser` - Users -- :class:`~pyrogram.api.types.InputPeerChat` - Basic Chats -- :class:`~pyrogram.api.types.InputPeerChannel` - Either Channels or Supergroups +- :class:`~pyrogram.raw.types.InputPeerUser` - Users +- :class:`~pyrogram.raw.types.InputPeerChat` - Basic Chats +- :class:`~pyrogram.raw.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:`~pyrogram.Client.resolve_peer` as a convenience utility method that returns the correct InputPeer diff --git a/docs/source/topics/bots-interaction.rst b/docs/source/topics/bots-interaction.rst index 97a72e05c0..1fad206988 100644 --- a/docs/source/topics/bots-interaction.rst +++ b/docs/source/topics/bots-interaction.rst @@ -5,6 +5,7 @@ Users can interact with other bots via plain text messages as well as inline que .. contents:: Contents :backlinks: none + :depth: 1 :local: ----- diff --git a/docs/source/topics/config-file.rst b/docs/source/topics/config-file.rst index b6faee058d..38bb9e3375 100644 --- a/docs/source/topics/config-file.rst +++ b/docs/source/topics/config-file.rst @@ -6,6 +6,7 @@ This page explains how this file is structured, how to use it and why. .. contents:: Contents :backlinks: none + :depth: 1 :local: ----- diff --git a/docs/source/topics/create-filters.rst b/docs/source/topics/create-filters.rst index 060357def3..3e87a3deed 100644 --- a/docs/source/topics/create-filters.rst +++ b/docs/source/topics/create-filters.rst @@ -1,16 +1,12 @@ Creating Filters ================ -Pyrogram already provides lots of built-in :class:`~pyrogram.Filters` to work with, but in case you can't find -a specific one for your needs or want to build a custom filter by yourself (to be used in a different kind of handler, -for example) you can use :meth:`~pyrogram.Filters.create`. - -.. note:: - - At the moment, the built-in filters are intended to be used with the :class:`~pyrogram.MessageHandler` only. +Pyrogram already provides lots of built-in :class:`~pyrogram.filters` to work with, but in case you can't find a +specific one for your needs or want to build a custom filter by yourself you can use :meth:`~pyrogram.filters.create`. .. contents:: Contents :backlinks: none + :depth: 1 :local: ----- @@ -19,14 +15,14 @@ Custom Filters -------------- An example to demonstrate how custom filters work is to show how to create and use one for the -:class:`~pyrogram.CallbackQueryHandler`. Note that callback queries updates are only received by bots as result of a -user pressing an inline button attached to the bot's message; create and :doc:`authorize your bot <../start/auth>`, +:class:`~pyrogram.handlers.CallbackQueryHandler`. Note that callback queries updates are only received by bots as result +of a user pressing an inline button attached to the bot's message; create and :doc:`authorize your bot <../start/auth>`, then send a message with an inline keyboard to yourself. This allows you to test your filter by pressing the inline button: .. code-block:: python - from pyrogram import InlineKeyboardMarkup, InlineKeyboardButton + from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton app.send_message( "username", # Change this to your username or id @@ -39,7 +35,7 @@ button: Basic Filters ------------- -For this basic filter we will be using only the first parameter of :meth:`~pyrogram.Filters.create`. +For this basic filter we will be using only the first parameter of :meth:`~pyrogram.filters.create`. The code below creates a simple filter for hardcoded, static callback data. This filter will only allow callback queries containing "pyrogram" as data, that is, the function *func* you pass returns True in case the callback query data @@ -47,17 +43,21 @@ equals to ``"pyrogram"``. .. code-block:: python - static_data_filter = Filters.create(lambda _, query: query.data == "pyrogram") + from pyrogram import filters + + static_data_filter = filters.create(lambda _, query: query.data == "pyrogram") The ``lambda`` operator in python is used to create small anonymous functions and is perfect for this example, the same -could be achieved with a normal function, but we don't really need it as it makes sense only inside the filter scope: +could be achieved with a normal function, but we don't really need it as it makes sense only inside the filter's scope: .. code-block:: python + from pyrogram import filters + def func(_, query): return query.data == "pyrogram" - static_data_filter = Filters.create(func) + static_data_filter = filters.create(func) The filter usage remains the same: @@ -71,14 +71,16 @@ Filters with Arguments ---------------------- A much cooler filter would be one that accepts "pyrogram" or any other data as argument at usage time. -A dynamic filter like this will make use of named arguments for the :meth:`~pyrogram.Filters.create` method. +A dynamic filter like this will make use of named arguments for the :meth:`~pyrogram.filters.create` method. This is how a dynamic custom filter looks like: .. code-block:: python + from pyrogram import filters + def dynamic_data_filter(data): - return Filters.create( + return filters.create( lambda flt, query: flt.data == query.data, data=data # "data" kwarg is accessed with "flt.data" above ) diff --git a/docs/source/topics/debugging.rst b/docs/source/topics/debugging.rst index 7284884af2..995ce9e5bb 100644 --- a/docs/source/topics/debugging.rst +++ b/docs/source/topics/debugging.rst @@ -6,6 +6,7 @@ to actually worry about -- that's normal -- and luckily for you, Pyrogram provid .. contents:: Contents :backlinks: none + :depth: 1 :local: ----- diff --git a/docs/source/topics/more-on-updates.rst b/docs/source/topics/more-on-updates.rst index 1bffaeef2b..a636008ace 100644 --- a/docs/source/topics/more-on-updates.rst +++ b/docs/source/topics/more-on-updates.rst @@ -6,6 +6,7 @@ Here we'll show some advanced usages when working with :doc:`update handlers <.. .. contents:: Contents :backlinks: none + :depth: 1 :local: ----- @@ -25,21 +26,21 @@ For example, take these two handlers: .. code-block:: python :emphasize-lines: 1, 6 - @app.on_message(Filters.text | Filters.sticker) + @app.on_message(filters.text | filters.sticker) def text_or_sticker(client, message): print("Text or Sticker") - @app.on_message(Filters.text) + @app.on_message(filters.text) def just_text(client, message): print("Just Text") Here, ``just_text`` is never executed because ``text_or_sticker``, which has been registered first, already handles -texts (``Filters.text`` is shared and conflicting). To enable it, register the handler using a different group: +texts (``filters.text`` is shared and conflicting). To enable it, register the handler using a different group: .. code-block:: python - @app.on_message(Filters.text, group=1) + @app.on_message(filters.text, group=1) def just_text(client, message): print("Just Text") @@ -47,7 +48,7 @@ Or, if you want ``just_text`` to be executed *before* ``text_or_sticker`` (note .. code-block:: python - @app.on_message(Filters.text, group=-1) + @app.on_message(filters.text, group=-1) def just_text(client, message): print("Just Text") @@ -55,7 +56,7 @@ With :meth:`~pyrogram.Client.add_handler` (without decorators) the same can be a .. code-block:: python - app.add_handler(MessageHandler(just_text, Filters.text), -1) + app.add_handler(MessageHandler(just_text, filters.text), -1) Update propagation ------------------ @@ -67,17 +68,17 @@ continue to propagate the same update to the next groups until all the handlers .. code-block:: python - @app.on_message(Filters.private) + @app.on_message(filters.private) def _(client, message): print(0) - @app.on_message(Filters.private, group=1) + @app.on_message(filters.private, group=1) def _(client, message): raise Exception("Unhandled exception!") # Simulate an unhandled exception - @app.on_message(Filters.private, group=2) + @app.on_message(filters.private, group=2) def _(client, message): print(2) @@ -109,18 +110,18 @@ Example with ``stop_propagation()``: .. code-block:: python - @app.on_message(Filters.private) + @app.on_message(filters.private) def _(client, message): print(0) - @app.on_message(Filters.private, group=1) + @app.on_message(filters.private, group=1) def _(client, message): print(1) message.stop_propagation() - @app.on_message(Filters.private, group=2) + @app.on_message(filters.private, group=2) def _(client, message): print(2) @@ -130,18 +131,18 @@ Example with ``raise StopPropagation``: from pyrogram import StopPropagation - @app.on_message(Filters.private) + @app.on_message(filters.private) def _(client, message): print(0) - @app.on_message(Filters.private, group=1) + @app.on_message(filters.private, group=1) def _(client, message): print(1) raise StopPropagation - @app.on_message(Filters.private, group=2) + @app.on_message(filters.private, group=2) def _(client, message): print(2) @@ -177,19 +178,19 @@ Example with ``continue_propagation()``: .. code-block:: python - @app.on_message(Filters.private) + @app.on_message(filters.private) def _(client, message): print(0) message.continue_propagation() - @app.on_message(Filters.private) + @app.on_message(filters.private) def _(client, message): print(1) message.continue_propagation() - @app.on_message(Filters.private) + @app.on_message(filters.private) def _(client, message): print(2) @@ -199,19 +200,19 @@ Example with ``raise ContinuePropagation``: from pyrogram import ContinuePropagation - @app.on_message(Filters.private) + @app.on_message(filters.private) def _(client, message): print(0) raise ContinuePropagation - @app.on_message(Filters.private) + @app.on_message(filters.private) def _(client, message): print(1) raise ContinuePropagation - @app.on_message(Filters.private) + @app.on_message(filters.private) def _(client, message): print(2) diff --git a/docs/source/topics/mtproto-vs-botapi.rst b/docs/source/topics/mtproto-vs-botapi.rst index aab3525e44..c73d669219 100644 --- a/docs/source/topics/mtproto-vs-botapi.rst +++ b/docs/source/topics/mtproto-vs-botapi.rst @@ -8,6 +8,7 @@ actually is the MTProto and the Bot API. .. contents:: Contents :backlinks: none + :depth: 1 :local: ----- diff --git a/docs/source/topics/proxy.rst b/docs/source/topics/proxy.rst index cde55cc7da..7d7e2c88db 100644 --- a/docs/source/topics/proxy.rst +++ b/docs/source/topics/proxy.rst @@ -6,6 +6,7 @@ through an intermediate SOCKS5 proxy server. .. contents:: Contents :backlinks: none + :depth: 1 :local: ----- diff --git a/docs/source/topics/scheduling.rst b/docs/source/topics/scheduling.rst index c5f410bb46..0776d601ed 100644 --- a/docs/source/topics/scheduling.rst +++ b/docs/source/topics/scheduling.rst @@ -10,6 +10,7 @@ visit and learn from each library documentation. .. contents:: Contents :backlinks: none + :depth: 1 :local: ----- @@ -69,8 +70,7 @@ Using ``apscheduler`` scheduler.start() app.run() -``apscheduler`` does also support async code, here's an example with -`Pyrogram Asyncio `_: +``apscheduler`` does also support async code, here's an example: .. code-block:: python diff --git a/docs/source/topics/serializing.rst b/docs/source/topics/serializing.rst index 8e6f29de7b..7e01357330 100644 --- a/docs/source/topics/serializing.rst +++ b/docs/source/topics/serializing.rst @@ -7,6 +7,7 @@ humans and another more compact for machines that is able to recover the origina .. contents:: Contents :backlinks: none + :depth: 1 :local: ----- diff --git a/docs/source/topics/session-settings.rst b/docs/source/topics/session-settings.rst index a97dbc063f..847427cf25 100644 --- a/docs/source/topics/session-settings.rst +++ b/docs/source/topics/session-settings.rst @@ -22,6 +22,7 @@ That's how a session looks like on the Android app, showing the three main piece .. contents:: Contents :backlinks: none + :depth: 1 :local: ----- diff --git a/docs/source/topics/smart-plugins.rst b/docs/source/topics/smart-plugins.rst index 7ffe1cb2ee..fece636622 100644 --- a/docs/source/topics/smart-plugins.rst +++ b/docs/source/topics/smart-plugins.rst @@ -11,6 +11,7 @@ different Pyrogram applications with **minimal boilerplate code**. .. contents:: Contents :backlinks: none + :depth: 1 :local: ----- @@ -51,7 +52,8 @@ after importing your modules, like this: .. code-block:: python - from pyrogram import Client, MessageHandler, Filters + from pyrogram import Client, filters + from pyrogram.handlers import MessageHandler from handlers import echo, echo_reversed @@ -60,19 +62,19 @@ after importing your modules, like this: app.add_handler( MessageHandler( echo, - Filters.text & Filters.private)) + filters.text & filters.private)) app.add_handler( MessageHandler( echo_reversed, - Filters.text & Filters.private), + filters.text & filters.private), group=1) 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:`~pyrogram.Client.add_handler` and manually instantiate each -:class:`~pyrogram.MessageHandler` object because **you can't use those cool decorators** for your +:class:`~pyrogram.handlers.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. Using Smart Plugins @@ -102,15 +104,15 @@ Setting up your Pyrogram project to accommodate Smart Plugins is pretty straight .. code-block:: python :emphasize-lines: 4, 9 - from pyrogram import Client, Filters + from pyrogram import Client, filters - @Client.on_message(Filters.text & Filters.private) + @Client.on_message(filters.text & filters.private) def echo(client, message): message.reply(message.text) - @Client.on_message(Filters.text & Filters.private, group=1) + @Client.on_message(filters.text & filters.private, group=1) def echo_reversed(client, message): message.reply(message.text[::-1]) @@ -306,7 +308,7 @@ updates) will be modified in such a way that a special ``handler`` attribute poi .. code-block:: python :emphasize-lines: 5, 6 - @Client.on_message(Filters.text & Filters.private) + @Client.on_message(filters.text & filters.private) def echo(client, message): message.reply(message.text) diff --git a/docs/source/topics/storage-engines.rst b/docs/source/topics/storage-engines.rst index e7494e2bd0..1a86cad8b5 100644 --- a/docs/source/topics/storage-engines.rst +++ b/docs/source/topics/storage-engines.rst @@ -7,6 +7,7 @@ decide to manually terminate it) and is used to authorize a client to execute AP .. contents:: Contents :backlinks: none + :depth: 1 :local: ----- diff --git a/docs/source/topics/test-servers.rst b/docs/source/topics/test-servers.rst index 1906c2f159..3ed996eea9 100644 --- a/docs/source/topics/test-servers.rst +++ b/docs/source/topics/test-servers.rst @@ -20,6 +20,7 @@ Telegram's test servers without hassle. All you need to do is start a new sessio .. contents:: Contents :backlinks: none + :depth: 1 :local: ----- diff --git a/docs/source/topics/text-formatting.rst b/docs/source/topics/text-formatting.rst index f4ac23e1ef..5d9a9376c2 100644 --- a/docs/source/topics/text-formatting.rst +++ b/docs/source/topics/text-formatting.rst @@ -19,6 +19,7 @@ variety of decorations that can also be nested in order to combine multiple styl .. contents:: Contents :backlinks: none + :depth: 1 :local: ----- diff --git a/docs/source/topics/use-filters.rst b/docs/source/topics/use-filters.rst index 8806052e94..7e5219e14f 100644 --- a/docs/source/topics/use-filters.rst +++ b/docs/source/topics/use-filters.rst @@ -1,14 +1,15 @@ Using Filters ============= -So far we've seen :doc:`how to register a callback function <../start/updates>` that executes every time a specific update -comes from the server, but there's much more than that to come. +So far we've seen :doc:`how to register a callback function <../start/updates>` that executes every time an update comes +from the server, but there's much more than that to come. -Here we'll discuss about :class:`~pyrogram.Filters`. Filters enable a fine-grain control over what kind of +Here we'll discuss about :obj:`~pyrogram.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. .. contents:: Contents :backlinks: none + :depth: 1 :local: ----- @@ -24,10 +25,10 @@ Let's start right away with a simple example: .. code-block:: python :emphasize-lines: 4 - from pyrogram import Filters + from pyrogram import filters - @app.on_message(Filters.audio) + @app.on_message(filters.audio) def my_handler(client, message): print(message) @@ -35,16 +36,17 @@ Let's start right away with a simple example: callback function itself: .. code-block:: python - :emphasize-lines: 8 + :emphasize-lines: 9 - from pyrogram import Filters, MessageHandler + from pyrogram import filters + from pyrogram.handlers import MessageHandler def my_handler(client, message): print(message) - app.add_handler(MessageHandler(my_handler, Filters.audio)) + app.add_handler(MessageHandler(my_handler, filters.audio)) Combining Filters ----------------- @@ -61,7 +63,7 @@ Here are some examples: .. code-block:: python - @app.on_message(Filters.text & ~Filters.edited) + @app.on_message(filters.text & ~filters.edited) def my_handler(client, message): print(message) @@ -69,21 +71,21 @@ Here are some examples: .. code-block:: python - @app.on_message(Filters.sticker & (Filters.channel | Filters.private)) + @app.on_message(filters.sticker & (filters.channel | filters.private)) def my_handler(client, message): print(message) Advanced Filters ---------------- -Some filters, like :meth:`~pyrogram.Filters.command` or :meth:`~pyrogram.Filters.regex` +Some filters, like :meth:`~pyrogram.filters.command` or :meth:`~pyrogram.filters.regex` can also accept arguments: - Message is either a */start* or */help* **command**. .. code-block:: python - @app.on_message(Filters.command(["start", "help"])) + @app.on_message(filters.command(["start", "help"])) def my_handler(client, message): print(message) @@ -91,7 +93,7 @@ can also accept arguments: .. code-block:: python - @app.on_message(Filters.regex("pyrogram")) + @app.on_message(filters.regex("pyrogram")) def my_handler(client, message): print(message) @@ -99,16 +101,16 @@ More handlers using different filters can also live together. .. code-block:: python - @app.on_message(Filters.command("start")) + @app.on_message(filters.command("start")) def start_command(client, message): print("This is the /start command") - @app.on_message(Filters.command("help")) + @app.on_message(filters.command("help")) def help_command(client, message): print("This is the /help command") - @app.on_message(Filters.chat("PyrogramChat")) + @app.on_message(filters.chat("PyrogramChat")) def from_pyrogramchat(client, message): print("New message in @PyrogramChat") diff --git a/examples/LICENSE b/examples/LICENSE deleted file mode 100644 index 1625c17936..0000000000 --- a/examples/LICENSE +++ /dev/null @@ -1,121 +0,0 @@ -Creative Commons Legal Code - -CC0 1.0 Universal - - CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE - LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN - ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS - INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES - REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS - PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM - THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED - HEREUNDER. - -Statement of Purpose - -The laws of most jurisdictions throughout the world automatically confer -exclusive Copyright and Related Rights (defined below) upon the creator -and subsequent owner(s) (each and all, an "owner") of an original work of -authorship and/or a database (each, a "Work"). - -Certain owners wish to permanently relinquish those rights to a Work for -the purpose of contributing to a commons of creative, cultural and -scientific works ("Commons") that the public can reliably and without fear -of later claims of infringement build upon, modify, incorporate in other -works, reuse and redistribute as freely as possible in any form whatsoever -and for any purposes, including without limitation commercial purposes. -These owners may contribute to the Commons to promote the ideal of a free -culture and the further production of creative, cultural and scientific -works, or to gain reputation or greater distribution for their Work in -part through the use and efforts of others. - -For these and/or other purposes and motivations, and without any -expectation of additional consideration or compensation, the person -associating CC0 with a Work (the "Affirmer"), to the extent that he or she -is an owner of Copyright and Related Rights in the Work, voluntarily -elects to apply CC0 to the Work and publicly distribute the Work under its -terms, with knowledge of his or her Copyright and Related Rights in the -Work and the meaning and intended legal effect of CC0 on those rights. - -1. Copyright and Related Rights. A Work made available under CC0 may be -protected by copyright and related or neighboring rights ("Copyright and -Related Rights"). Copyright and Related Rights include, but are not -limited to, the following: - - i. the right to reproduce, adapt, distribute, perform, display, - communicate, and translate a Work; - ii. moral rights retained by the original author(s) and/or performer(s); -iii. publicity and privacy rights pertaining to a person's image or - likeness depicted in a Work; - iv. rights protecting against unfair competition in regards to a Work, - subject to the limitations in paragraph 4(a), below; - v. rights protecting the extraction, dissemination, use and reuse of data - in a Work; - vi. database rights (such as those arising under Directive 96/9/EC of the - European Parliament and of the Council of 11 March 1996 on the legal - protection of databases, and under any national implementation - thereof, including any amended or successor version of such - directive); and -vii. other similar, equivalent or corresponding rights throughout the - world based on applicable law or treaty, and any national - implementations thereof. - -2. Waiver. To the greatest extent permitted by, but not in contravention -of, applicable law, Affirmer hereby overtly, fully, permanently, -irrevocably and unconditionally waives, abandons, and surrenders all of -Affirmer's Copyright and Related Rights and associated claims and causes -of action, whether now known or unknown (including existing as well as -future claims and causes of action), in the Work (i) in all territories -worldwide, (ii) for the maximum duration provided by applicable law or -treaty (including future time extensions), (iii) in any current or future -medium and for any number of copies, and (iv) for any purpose whatsoever, -including without limitation commercial, advertising or promotional -purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each -member of the public at large and to the detriment of Affirmer's heirs and -successors, fully intending that such Waiver shall not be subject to -revocation, rescission, cancellation, termination, or any other legal or -equitable action to disrupt the quiet enjoyment of the Work by the public -as contemplated by Affirmer's express Statement of Purpose. - -3. Public License Fallback. Should any part of the Waiver for any reason -be judged legally invalid or ineffective under applicable law, then the -Waiver shall be preserved to the maximum extent permitted taking into -account Affirmer's express Statement of Purpose. In addition, to the -extent the Waiver is so judged Affirmer hereby grants to each affected -person a royalty-free, non transferable, non sublicensable, non exclusive, -irrevocable and unconditional license to exercise Affirmer's Copyright and -Related Rights in the Work (i) in all territories worldwide, (ii) for the -maximum duration provided by applicable law or treaty (including future -time extensions), (iii) in any current or future medium and for any number -of copies, and (iv) for any purpose whatsoever, including without -limitation commercial, advertising or promotional purposes (the -"License"). The License shall be deemed effective as of the date CC0 was -applied by Affirmer to the Work. Should any part of the License for any -reason be judged legally invalid or ineffective under applicable law, such -partial invalidity or ineffectiveness shall not invalidate the remainder -of the License, and in such case Affirmer hereby affirms that he or she -will not (i) exercise any of his or her remaining Copyright and Related -Rights in the Work or (ii) assert any associated claims and causes of -action with respect to the Work, in either case contrary to Affirmer's -express Statement of Purpose. - -4. Limitations and Disclaimers. - - a. No trademark or patent rights held by Affirmer are waived, abandoned, - surrendered, licensed or otherwise affected by this document. - b. Affirmer offers the Work as-is and makes no representations or - warranties of any kind concerning the Work, express, implied, - statutory or otherwise, including without limitation warranties of - title, merchantability, fitness for a particular purpose, non - infringement, or the absence of latent or other defects, accuracy, or - the present or absence of errors, whether or not discoverable, all to - the greatest extent permissible under applicable law. - c. Affirmer disclaims responsibility for clearing rights of other persons - that may apply to the Work or any use thereof, including without - limitation any person's Copyright and Related Rights in the Work. - Further, Affirmer disclaims responsibility for obtaining any necessary - consents, permissions or other rights required for any use of the - Work. - d. Affirmer understands and acknowledges that Creative Commons is not a - party to this document and has no duty or obligation with respect to - this CC0 or use of the Work. \ No newline at end of file diff --git a/examples/README.md b/examples/README.md deleted file mode 100644 index b8898a718a..0000000000 --- a/examples/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# Examples - -This folder contains example scripts to show you how **Pyrogram** looks like. - -Every script is working right away (provided you correctly set up your credentials), meaning you can simply copy-paste -and run. The only things you have to change are session names and target chats. - -All the examples listed in this directory are licensed under the terms of the [CC0 1.0 Universal](LICENSE) license and -can be freely used as basic building blocks for your own applications without worrying about copyrights. - -Example | Description ----: | :--- -[**hello_world**](hello_world.py) | Demonstration of basic API usage -[**echobot**](echobot.py) | Echo every private text message -[**welcomebot**](welcomebot.py) | The Welcome Bot in [@PyrogramChat](https://t.me/pyrogramchat) -[**get_history**](get_history.py) | Get the full message history of a chat -[**get_chat_members**](get_chat_members.py) | Get all the members of a chat -[**get_dialogs**](get_dialogs.py) | Get all of your dialog chats -[**callback_queries**](callback_queries.py) | Handle callback queries (as bot) coming from inline button presses -[**inline_queries**](inline_queries.py) | Handle inline queries (as bot) and answer with results -[**use_inline_bots**](use_inline_bots.py) | Query an inline bot (as user) and send a result to a chat -[**bot_keyboards**](bot_keyboards.py) | Send normal and inline keyboards using regular bots -[**raw_updates**](raw_updates.py) | Handle raw updates (old, should be avoided) diff --git a/examples/bot_keyboards.py b/examples/bot_keyboards.py deleted file mode 100644 index e1ff1e7e3f..0000000000 --- a/examples/bot_keyboards.py +++ /dev/null @@ -1,57 +0,0 @@ -"""This example will show you how to send normal and inline keyboards (as bot). - -You must log-in as a regular bot in order to send keyboards (use the token from @BotFather). -Any attempt in sending keyboards with a user account will be simply ignored by the server. - -send_message() is used as example, but a keyboard can be sent with any other send_* methods, -like send_audio(), send_document(), send_location(), etc... -""" - -from pyrogram import Client, ReplyKeyboardMarkup, InlineKeyboardMarkup, InlineKeyboardButton - -# Create a client using your bot token -app = Client("my_bot", bot_token="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11") - -with app: - app.send_message( - "haskell", # Edit this - "This is a ReplyKeyboardMarkup example", - reply_markup=ReplyKeyboardMarkup( - [ - ["A", "B", "C", "D"], # First row - ["E", "F", "G"], # Second row - ["H", "I"], # Third row - ["J"] # Fourth row - ], - resize_keyboard=True # Make the keyboard smaller - ) - ) - - app.send_message( - "haskell", # Edit this - "This is a InlineKeyboardMarkup example", - reply_markup=InlineKeyboardMarkup( - [ - [ # First row - InlineKeyboardButton( # Generates a callback query when pressed - "Button", - callback_data=b"data" # Note how callback_data must be bytes - ), - InlineKeyboardButton( # Opens a web URL - "URL", - url="https://docs.pyrogram.org" - ), - ], - [ # Second row - InlineKeyboardButton( # Opens the inline interface - "Choose chat", - switch_inline_query="pyrogram" - ), - InlineKeyboardButton( # Opens the inline interface in the current chat - "Inline here", - switch_inline_query_current_chat="pyrogram" - ) - ] - ] - ) - ) diff --git a/examples/callback_queries.py b/examples/callback_queries.py deleted file mode 100644 index f4a87b0041..0000000000 --- a/examples/callback_queries.py +++ /dev/null @@ -1,16 +0,0 @@ -"""This example shows how to handle callback queries, i.e.: queries coming from inline button presses. - -It uses the @on_callback_query decorator to register a CallbackQueryHandler. -""" - -from pyrogram import Client - -app = Client("my_bot", bot_token="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11") - - -@app.on_callback_query() -def answer(client, callback_query): - callback_query.answer("Button contains: '{}'".format(callback_query.data), show_alert=True) - - -app.run() # Automatically start() and idle() diff --git a/examples/echobot.py b/examples/echobot.py deleted file mode 100644 index c60ae2917b..0000000000 --- a/examples/echobot.py +++ /dev/null @@ -1,17 +0,0 @@ -"""This simple echo bot replies to every private text message. - -It uses the @on_message decorator to register a MessageHandler and applies two filters on it: -Filters.text and Filters.private to make sure it will reply to private text messages only. -""" - -from pyrogram import Client, Filters - -app = Client("my_account") - - -@app.on_message(Filters.text & Filters.private) -def echo(client, message): - message.reply(message.text) - - -app.run() # Automatically start() and idle() diff --git a/examples/get_chat_members.py b/examples/get_chat_members.py deleted file mode 100644 index 468ac7dec5..0000000000 --- a/examples/get_chat_members.py +++ /dev/null @@ -1,10 +0,0 @@ -"""This example shows how to get all the members of a chat.""" - -from pyrogram import Client - -app = Client("my_account") -target = "pyrogramchat" # Target channel/supergroup - -with app: - for member in app.iter_chat_members(target): - print(member.user.first_name) diff --git a/examples/get_dialogs.py b/examples/get_dialogs.py deleted file mode 100644 index 92da8834c9..0000000000 --- a/examples/get_dialogs.py +++ /dev/null @@ -1,9 +0,0 @@ -"""This example shows how to get the full dialogs list (as user).""" - -from pyrogram import Client - -app = Client("my_account") - -with app: - for dialog in app.iter_dialogs(): - print(dialog.chat.title or dialog.chat.first_name) diff --git a/examples/get_history.py b/examples/get_history.py deleted file mode 100644 index e8bb14e31d..0000000000 --- a/examples/get_history.py +++ /dev/null @@ -1,10 +0,0 @@ -"""This example shows how to get the full message history of a chat, starting from the latest message""" - -from pyrogram import Client - -app = Client("my_account") -target = "me" # "me" refers to your own chat (Saved Messages) - -with app: - for message in app.iter_history(target): - print(message.text) diff --git a/examples/hello_world.py b/examples/hello_world.py deleted file mode 100644 index 5764e97972..0000000000 --- a/examples/hello_world.py +++ /dev/null @@ -1,16 +0,0 @@ -"""This example demonstrates a basic API usage""" - -from pyrogram import Client - -# Create a new Client instance -app = Client("my_account") - -with app: - # Send a message, Markdown is enabled by default - app.send_message("me", "Hi there! I'm using **Pyrogram**") - - # Send a location - app.send_location("me", 51.500729, -0.124583) - - # Send a sticker - app.send_sticker("me", "CAADBAADzg4AAvLQYAEz_x2EOgdRwBYE") diff --git a/examples/inline_queries.py b/examples/inline_queries.py deleted file mode 100644 index d86d90d5c7..0000000000 --- a/examples/inline_queries.py +++ /dev/null @@ -1,54 +0,0 @@ -"""This example shows how to handle inline queries. -Two results are generated when users invoke the bot inline mode, e.g.: @pyrogrambot hi. -It uses the @on_inline_query decorator to register an InlineQueryHandler. -""" - -from uuid import uuid4 - -from pyrogram import ( - Client, InlineQueryResultArticle, InputTextMessageContent, InlineKeyboardMarkup, InlineKeyboardButton -) - -app = Client("my_bot", bot_token="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11") - - -@app.on_inline_query() -def answer(client, inline_query): - inline_query.answer( - results=[ - InlineQueryResultArticle( - id=uuid4(), - title="Installation", - input_message_content=InputTextMessageContent( - "Here's how to install **Pyrogram**" - ), - 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.org/intro/install")] - ] - ) - ), - InlineQueryResultArticle( - id=uuid4(), - title="Usage", - input_message_content=InputTextMessageContent( - "Here's how to use **Pyrogram**" - ), - 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.org/start/invoking")] - ] - ) - ) - ], - cache_time=1 - ) - - -app.run() # Automatically start() and idle() diff --git a/examples/raw_updates.py b/examples/raw_updates.py deleted file mode 100644 index 27d87eb37b..0000000000 --- a/examples/raw_updates.py +++ /dev/null @@ -1,13 +0,0 @@ -"""This example shows how to handle raw updates""" - -from pyrogram import Client - -app = Client("my_account") - - -@app.on_raw_update() -def raw(client, update, users, chats): - print(update) - - -app.run() # Automatically start() and idle() diff --git a/examples/use_inline_bots.py b/examples/use_inline_bots.py deleted file mode 100644 index 5681df8778..0000000000 --- a/examples/use_inline_bots.py +++ /dev/null @@ -1,13 +0,0 @@ -"""This example shows how to query an inline bot (as user)""" - -from pyrogram import Client - -# Create a new Client -app = Client("my_account") - -with app: - # Get bot results for "Fuzz Universe" from the inline bot @vid - bot_results = app.get_inline_bot_results("vid", "Fuzz Universe") - - # Send the first result (bot_results.results[0]) to your own chat (Saved Messages) - app.send_inline_bot_result("me", bot_results.query_id, bot_results.results[0].id) diff --git a/examples/welcomebot.py b/examples/welcomebot.py deleted file mode 100644 index 35f72afff8..0000000000 --- a/examples/welcomebot.py +++ /dev/null @@ -1,29 +0,0 @@ -"""This is the Welcome Bot in @PyrogramChat. - -It uses the Emoji module to easily add emojis in your text messages and Filters -to make it only work for specific messages in a specific chat. -""" - -from pyrogram import Client, Emoji, Filters - -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.org/)'s group chat {}!" # Welcome message - -app = Client("my_account") - - -# Filter in only new_chat_members updates generated in TARGET chat -@app.on_message(Filters.chat(TARGET) & Filters.new_chat_members) -def welcome(client, message): - # Build the new members list (with mentions) by using their first_name - new_members = [MENTION.format(i.first_name, i.id) for i in message.new_chat_members] - - # Build the welcome message by using an emoji and the list we built above - text = MESSAGE.format(Emoji.SPARKLES, ", ".join(new_members)) - - # Send the welcome message, without the web page preview - message.reply(text, disable_web_page_preview=True) - - -app.run() # Automatically start() and idle() diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 053d33981a..65ac3d7175 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,10 +16,23 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "0.18.0-async" +__version__ = "1.0.0b1" __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)" __copyright__ = "Copyright (C) 2017-2020 Dan " -from .client import * -from .client.handlers import * -from .client.types import * + +class StopTransmission(StopAsyncIteration): + pass + + +class StopPropagation(StopAsyncIteration): + pass + + +class ContinuePropagation(StopAsyncIteration): + pass + + +from . import types, filters, handlers, emoji +from .client import Client +from .sync import idle diff --git a/pyrogram/client.py b/pyrogram/client.py new file mode 100644 index 0000000000..a293efd544 --- /dev/null +++ b/pyrogram/client.py @@ -0,0 +1,1069 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan +# +# 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 asyncio +import functools +import logging +import os +import re +import shutil +import tempfile +from concurrent.futures.thread import ThreadPoolExecutor +from configparser import ConfigParser +from hashlib import sha256 +from importlib import import_module +from pathlib import Path +from typing import Union, List + +import pyrogram +from pyrogram import raw +from pyrogram import utils +from pyrogram.crypto import aes +from pyrogram.errors import ( + SessionPasswordNeeded, + VolumeLocNotFound, ChannelPrivate, + AuthBytesInvalid, BadRequest +) +from pyrogram.handlers.handler import Handler +from pyrogram.methods import Methods +from pyrogram.session import Auth, Session +from pyrogram.storage import Storage, FileStorage, MemoryStorage +from pyrogram.types import User, TermsOfService +from pyrogram.utils import ainput +from .dispatcher import Dispatcher +from .scaffold import Scaffold + +log = logging.getLogger(__name__) + + +class Client(Methods, Scaffold): + """Pyrogram Client, the main means for interacting with Telegram. + + Parameters: + session_name (``str``): + Pass a string of your choice to give a name to the client session, e.g.: "*my_account*". This name will be + used to save a file on disk that stores details needed to reconnect without asking again for credentials. + Alternatively, if you don't want a file to be saved on disk, pass the special name "**:memory:**" to start + an in-memory session that will be discarded as soon as you stop the Client. In order to reconnect again + using a memory storage without having to login again, you can use + :meth:`~pyrogram.Client.export_session_string` before stopping the client to get a session string you can + pass here as argument. + + api_id (``int`` | ``str``, *optional*): + The *api_id* part of your Telegram API Key, as integer. E.g.: "12345". + This is an alternative way to pass it if you don't want to use the *config.ini* file. + + api_hash (``str``, *optional*): + The *api_hash* part of your Telegram API Key, as string. E.g.: "0123456789abcdef0123456789abcdef". + This is an alternative way to set it if you don't want to use the *config.ini* file. + + app_version (``str``, *optional*): + Application version. Defaults to "Pyrogram |version|". + This is an alternative way to set it if you don't want to use the *config.ini* file. + + device_model (``str``, *optional*): + Device model. Defaults to *platform.python_implementation() + " " + platform.python_version()*. + This is an alternative way to set it if you don't want to use the *config.ini* file. + + system_version (``str``, *optional*): + Operating System version. Defaults to *platform.system() + " " + platform.release()*. + This is an alternative way to set it if you don't want to use the *config.ini* file. + + lang_code (``str``, *optional*): + Code of the language used on the client, in ISO 639-1 standard. Defaults to "en". + This is an alternative way to set it if you don't want to use the *config.ini* file. + + ipv6 (``bool``, *optional*): + Pass True to connect to Telegram using IPv6. + Defaults to False (IPv4). + + proxy (``dict``, *optional*): + Your SOCKS5 Proxy settings as dict, + e.g.: *dict(hostname="11.22.33.44", port=1080, username="user", password="pass")*. + The *username* and *password* can be omitted if your proxy doesn't require authorization. + This is an alternative way to setup a proxy if you don't want to use the *config.ini* file. + + test_mode (``bool``, *optional*): + Enable or disable login to the test servers. + Only applicable for new sessions and will be ignored in case previously created sessions are loaded. + Defaults to False. + + 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. + This is an alternative way to set it if you don't want to use the *config.ini* file. + + phone_number (``str``, *optional*): + Pass your phone number as string (with your Country Code prefix included) to avoid entering it manually. + Only applicable for new sessions. + + phone_code (``str``, *optional*): + Pass the phone code as string (for test numbers only) to avoid entering it manually. + Only applicable for new sessions. + + password (``str``, *optional*): + Pass your Two-Step Verification password as string (if you have one) to avoid entering it manually. + Only applicable for new sessions. + + force_sms (``bool``, *optional*): + Pass True to force Telegram sending the authorization code via SMS. + Only applicable for new sessions. + Defaults to False. + + workers (``int``, *optional*): + Number of maximum concurrent workers for handling incoming updates. + Defaults to ``min(32, os.cpu_count() + 4)``. + + workdir (``str``, *optional*): + Define a custom working directory. The working directory is the location in your filesystem where Pyrogram + will store your session files. + Defaults to the parent directory of the main script. + + config_file (``str``, *optional*): + Path of the configuration file. + Defaults to ./config.ini + + plugins (``dict``, *optional*): + Your Smart Plugins settings as dict, e.g.: *dict(root="plugins")*. + This is an alternative way to setup plugins if you don't want to use the *config.ini* file. + + parse_mode (``str``, *optional*): + The parse mode, can be any of: *"combined"*, for the default combined mode. *"markdown"* or *"md"* + to force Markdown-only styles. *"html"* to force HTML-only styles. *None* to disable the parser + completely. + + no_updates (``bool``, *optional*): + Pass True to completely disable incoming updates for the current session. + When updates are disabled your client can't receive any new message. + Useful for batch programs that don't need to deal with updates. + Defaults to False (updates enabled and always received). + + takeout (``bool``, *optional*): + Pass True to let the client use a takeout session instead of a normal one, implies *no_updates=True*. + Useful for exporting your Telegram data. Methods invoked inside a takeout session (such as get_history, + download_media, ...) are less prone to throw FloodWait exceptions. + Only available for users, bots will ignore this parameter. + Defaults to False (normal session). + + sleep_threshold (``int``, *optional*): + Set a sleep threshold for flood wait exceptions happening globally in this client instance, below which any + request that raises a flood wait will be automatically invoked again after sleeping for the required amount + of time. Flood wait exceptions requiring higher waiting times will be raised. + Defaults to 60 (seconds). + """ + + def __init__( + self, + session_name: Union[str, Storage], + api_id: Union[int, str] = None, + api_hash: str = None, + app_version: str = None, + device_model: str = None, + system_version: str = None, + lang_code: str = None, + ipv6: bool = False, + proxy: dict = None, + test_mode: bool = False, + bot_token: str = None, + phone_number: str = None, + phone_code: str = None, + password: str = None, + force_sms: bool = False, + workers: int = Scaffold.WORKERS, + workdir: str = Scaffold.WORKDIR, + config_file: str = Scaffold.CONFIG_FILE, + plugins: dict = None, + parse_mode: str = Scaffold.PARSE_MODES[0], + no_updates: bool = None, + takeout: bool = None, + sleep_threshold: int = Session.SLEEP_THRESHOLD + ): + super().__init__() + + self.session_name = session_name + self.api_id = int(api_id) if api_id else None + self.api_hash = api_hash + self.app_version = app_version + self.device_model = device_model + self.system_version = system_version + self.lang_code = lang_code + self.ipv6 = ipv6 + # 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.force_sms = force_sms + self.workers = workers + self.workdir = Path(workdir) + self.config_file = Path(config_file) + self.plugins = plugins + self.parse_mode = parse_mode + self.no_updates = no_updates + self.takeout = takeout + self.sleep_threshold = sleep_threshold + + self.executor = ThreadPoolExecutor(self.workers, thread_name_prefix="Handler") + + if isinstance(session_name, str): + if session_name == ":memory:" or len(session_name) >= MemoryStorage.SESSION_STRING_SIZE: + session_name = re.sub(r"[\n\s]+", "", session_name) + self.storage = MemoryStorage(session_name) + else: + self.storage = FileStorage(session_name, self.workdir) + elif isinstance(session_name, Storage): + self.storage = session_name + else: + raise ValueError("Unknown storage engine") + + self.dispatcher = Dispatcher(self) + self.loop = asyncio.get_event_loop() + + def __enter__(self): + return self.start() + + def __exit__(self, *args): + try: + self.stop() + except ConnectionError: + pass + + async def __aenter__(self): + return await self.start() + + async def __aexit__(self, *args): + await self.stop() + + @property + def proxy(self): + return self._proxy + + @proxy.setter + def proxy(self, value): + if value is None: + self._proxy = None + return + + if self._proxy is None: + self._proxy = {} + + self._proxy["enabled"] = bool(value.get("enabled", True)) + self._proxy.update(value) + + async def authorize(self) -> User: + if self.bot_token: + return await self.sign_in_bot(self.bot_token) + + while True: + try: + if not self.phone_number: + while True: + value = await ainput("Enter phone number or bot token: ") + + if not value: + continue + + confirm = (await ainput(f'Is "{value}" correct? (y/N): ')).lower() + + if confirm == "y": + break + + if ":" in value: + self.bot_token = value + return await self.sign_in_bot(value) + else: + self.phone_number = value + + sent_code = await self.send_code(self.phone_number) + except BadRequest as e: + print(e.MESSAGE) + self.phone_number = None + self.bot_token = None + else: + break + + if self.force_sms: + sent_code = await self.resend_code(self.phone_number, sent_code.phone_code_hash) + + print("The confirmation code has been sent via {}".format( + { + "app": "Telegram app", + "sms": "SMS", + "call": "phone call", + "flash_call": "phone flash call" + }[sent_code.type] + )) + + while True: + if not self.phone_code: + self.phone_code = await ainput("Enter confirmation code: ") + + try: + signed_in = await self.sign_in(self.phone_number, sent_code.phone_code_hash, self.phone_code) + except BadRequest as e: + print(e.MESSAGE) + self.phone_code = None + except SessionPasswordNeeded as e: + print(e.MESSAGE) + + while True: + print("Password hint: {}".format(await self.get_password_hint())) + + if not self.password: + self.password = await ainput("Enter password (empty to recover): ", hide=True) + + try: + if not self.password: + confirm = await ainput("Confirm password recovery (y/n): ") + + if confirm == "y": + email_pattern = await self.send_recovery_code() + print(f"The recovery code has been sent to {email_pattern}") + + while True: + recovery_code = await ainput("Enter recovery code: ") + + try: + return await self.recover_password(recovery_code) + except BadRequest as e: + print(e.MESSAGE) + except Exception as e: + log.error(e, exc_info=True) + raise + else: + self.password = None + else: + return await self.check_password(self.password) + except BadRequest as e: + print(e.MESSAGE) + self.password = None + else: + break + + if isinstance(signed_in, User): + return signed_in + + while True: + first_name = await ainput("Enter first name: ") + last_name = await ainput("Enter last name (empty to skip): ") + + try: + signed_up = await self.sign_up( + self.phone_number, + sent_code.phone_code_hash, + first_name, + last_name + ) + except BadRequest as e: + print(e.MESSAGE) + else: + break + + if isinstance(signed_in, TermsOfService): + print("\n" + signed_in.text + "\n") + await self.accept_terms_of_service(signed_in.id) + + return signed_up + + @property + def parse_mode(self): + return self._parse_mode + + @parse_mode.setter + def parse_mode(self, parse_mode: Union[str, None] = "combined"): + if parse_mode not in self.PARSE_MODES: + raise ValueError('parse_mode must be one of {} or None. Not "{}"'.format( + ", ".join(f'"{m}"' for m in self.PARSE_MODES[:-1]), + parse_mode + )) + + self._parse_mode = parse_mode + + # TODO: redundant, remove in next major version + def set_parse_mode(self, parse_mode: Union[str, None] = "combined"): + """Set the parse mode to be used globally by the client. + + When setting the parse mode with this method, all other methods having a *parse_mode* parameter will follow the + global value by default. The default value *"combined"* enables both Markdown and HTML styles to be used and + combined together. + + Parameters: + parse_mode (``str``): + The new parse mode, can be any of: *"combined"*, for the default combined mode. *"markdown"* or *"md"* + to force Markdown-only styles. *"html"* to force HTML-only styles. *None* to disable the parser + completely. + + Raises: + ValueError: In case the provided *parse_mode* is not a valid parse mode. + + Example: + .. code-block:: python + :emphasize-lines: 10,14,18,22 + + from pyrogram import Client + + app = Client("my_account") + + with app: + # Default combined mode: Markdown + HTML + app.send_message("haskell", "1. **markdown** and html") + + # Force Markdown-only, HTML is disabled + app.set_parse_mode("markdown") + app.send_message("haskell", "2. **markdown** and html") + + # Force HTML-only, Markdown is disabled + app.set_parse_mode("html") + app.send_message("haskell", "3. **markdown** and html") + + # Disable the parser completely + app.set_parse_mode(None) + app.send_message("haskell", "4. **markdown** and html") + + # Bring back the default combined mode + app.set_parse_mode() + app.send_message("haskell", "5. **markdown** and html") + """ + + self.parse_mode = parse_mode + + async def fetch_peers(self, peers: List[Union[raw.types.User, raw.types.Chat, raw.types.Channel]]) -> bool: + is_min = False + parsed_peers = [] + + for peer in peers: + if getattr(peer, "min", False): + is_min = True + continue + + username = None + phone_number = None + + if isinstance(peer, raw.types.User): + peer_id = peer.id + access_hash = peer.access_hash + username = (peer.username or "").lower() or None + phone_number = peer.phone + peer_type = "bot" if peer.bot else "user" + elif isinstance(peer, (raw.types.Chat, raw.types.ChatForbidden)): + peer_id = -peer.id + access_hash = 0 + peer_type = "group" + elif isinstance(peer, (raw.types.Channel, raw.types.ChannelForbidden)): + peer_id = utils.get_channel_id(peer.id) + access_hash = peer.access_hash + username = (getattr(peer, "username", None) or "").lower() or None + peer_type = "channel" if peer.broadcast else "supergroup" + else: + continue + + parsed_peers.append((peer_id, access_hash, peer_type, username, phone_number)) + + await self.storage.update_peers(parsed_peers) + + return is_min + + async def handle_download(self, packet): + temp_file_path = "" + final_file_path = "" + + try: + data, directory, file_name, progress, progress_args = packet + + temp_file_path = await self.get_file( + media_type=data.media_type, + dc_id=data.dc_id, + document_id=data.document_id, + access_hash=data.access_hash, + thumb_size=data.thumb_size, + peer_id=data.peer_id, + peer_type=data.peer_type, + peer_access_hash=data.peer_access_hash, + volume_id=data.volume_id, + local_id=data.local_id, + file_ref=data.file_ref, + file_size=data.file_size, + is_big=data.is_big, + progress=progress, + progress_args=progress_args + ) + + if temp_file_path: + final_file_path = os.path.abspath(re.sub("\\\\", "/", os.path.join(directory, file_name))) + os.makedirs(directory, exist_ok=True) + shutil.move(temp_file_path, final_file_path) + except Exception as e: + log.error(e, exc_info=True) + + try: + os.remove(temp_file_path) + except OSError: + pass + else: + return final_file_path or None + + async def handle_updates(self, updates): + if isinstance(updates, (raw.types.Updates, raw.types.UpdatesCombined)): + is_min = (await self.fetch_peers(updates.users)) or (await self.fetch_peers(updates.chats)) + + users = {u.id: u for u in updates.users} + chats = {c.id: c for c in updates.chats} + + for update in updates.updates: + channel_id = getattr( + getattr( + getattr( + update, "message", None + ), "to_id", None + ), "channel_id", None + ) or getattr(update, "channel_id", None) + + pts = getattr(update, "pts", None) + pts_count = getattr(update, "pts_count", None) + + if isinstance(update, raw.types.UpdateChannelTooLong): + log.warning(update) + + if isinstance(update, raw.types.UpdateNewChannelMessage) and is_min: + message = update.message + + if not isinstance(message, raw.types.MessageEmpty): + try: + diff = await self.send( + raw.functions.updates.GetChannelDifference( + channel=await self.resolve_peer(utils.get_channel_id(channel_id)), + filter=raw.types.ChannelMessagesFilter( + ranges=[raw.types.MessageRange( + min_id=update.message.id, + max_id=update.message.id + )] + ), + pts=pts - pts_count, + limit=pts + ) + ) + except ChannelPrivate: + pass + else: + if not isinstance(diff, raw.types.updates.ChannelDifferenceEmpty): + users.update({u.id: u for u in diff.users}) + chats.update({c.id: c for c in diff.chats}) + + self.dispatcher.updates_queue.put_nowait((update, users, chats)) + elif isinstance(updates, (raw.types.UpdateShortMessage, raw.types.UpdateShortChatMessage)): + diff = await self.send( + raw.functions.updates.GetDifference( + pts=updates.pts - updates.pts_count, + date=updates.date, + qts=-1 + ) + ) + + if diff.new_messages: + self.dispatcher.updates_queue.put_nowait(( + raw.types.UpdateNewMessage( + message=diff.new_messages[0], + pts=updates.pts, + pts_count=updates.pts_count + ), + {u.id: u for u in diff.users}, + {c.id: c for c in diff.chats} + )) + else: + self.dispatcher.updates_queue.put_nowait((diff.other_updates[0], {}, {})) + elif isinstance(updates, raw.types.UpdateShort): + self.dispatcher.updates_queue.put_nowait((updates.update, {}, {})) + elif isinstance(updates, raw.types.UpdatesTooLong): + log.info(updates) + + def load_config(self): + parser = ConfigParser() + parser.read(str(self.config_file)) + + if self.bot_token: + pass + else: + self.bot_token = parser.get("pyrogram", "bot_token", fallback=None) + + if self.api_id and self.api_hash: + pass + else: + if parser.has_section("pyrogram"): + self.api_id = parser.getint("pyrogram", "api_id") + self.api_hash = parser.get("pyrogram", "api_hash") + else: + raise AttributeError("No API Key found. More info: https://docs.pyrogram.org/intro/setup") + + for option in ["app_version", "device_model", "system_version", "lang_code"]: + if getattr(self, option): + pass + else: + if parser.has_section("pyrogram"): + setattr(self, option, parser.get( + "pyrogram", + option, + fallback=getattr(Client, option.upper()) + )) + else: + setattr(self, option, getattr(Client, option.upper())) + + if self._proxy: + self._proxy["enabled"] = bool(self._proxy.get("enabled", True)) + else: + self._proxy = {} + + if parser.has_section("proxy"): + self._proxy["enabled"] = parser.getboolean("proxy", "enabled", fallback=True) + self._proxy["hostname"] = parser.get("proxy", "hostname") + self._proxy["port"] = parser.getint("proxy", "port") + self._proxy["username"] = parser.get("proxy", "username", fallback=None) or None + self._proxy["password"] = parser.get("proxy", "password", fallback=None) or None + + if self.plugins: + self.plugins = { + "enabled": bool(self.plugins.get("enabled", True)), + "root": self.plugins.get("root", None), + "include": self.plugins.get("include", []), + "exclude": self.plugins.get("exclude", []) + } + else: + try: + section = parser["plugins"] + + self.plugins = { + "enabled": section.getboolean("enabled", True), + "root": section.get("root", None), + "include": section.get("include", []), + "exclude": section.get("exclude", []) + } + + include = self.plugins["include"] + exclude = self.plugins["exclude"] + + if include: + self.plugins["include"] = include.strip().split("\n") + + if exclude: + self.plugins["exclude"] = exclude.strip().split("\n") + + except KeyError: + self.plugins = None + + async def load_session(self): + await self.storage.open() + + session_empty = any([ + await self.storage.test_mode() is None, + await self.storage.auth_key() is None, + await self.storage.user_id() is None, + await self.storage.is_bot() is None + ]) + + if session_empty: + await self.storage.dc_id(2) + await self.storage.date(0) + + await self.storage.test_mode(self.test_mode) + await self.storage.auth_key( + await Auth( + self, await self.storage.dc_id(), + await self.storage.test_mode() + ).create() + ) + await self.storage.user_id(None) + await self.storage.is_bot(None) + + def load_plugins(self): + if self.plugins: + plugins = self.plugins.copy() + + for option in ["include", "exclude"]: + if plugins[option]: + plugins[option] = [ + (i.split()[0], i.split()[1:] or None) + for i in self.plugins[option] + ] + else: + return + + if plugins.get("enabled", False): + root = plugins["root"] + include = plugins["include"] + exclude = plugins["exclude"] + + count = 0 + + if not include: + for path in sorted(Path(root.replace(".", "/")).rglob("*.py")): + module_path = '.'.join(path.parent.parts + (path.stem,)) + module = import_module(module_path) + + for name in vars(module).keys(): + # noinspection PyBroadException + try: + handler, group = getattr(module, name).handler + + if isinstance(handler, Handler) and isinstance(group, int): + self.add_handler(handler, group) + + log.info('[{}] [LOAD] {}("{}") in group {} from "{}"'.format( + self.session_name, type(handler).__name__, name, group, module_path)) + + count += 1 + except Exception: + pass + else: + for path, handlers in include: + module_path = root + "." + path + warn_non_existent_functions = True + + try: + module = import_module(module_path) + except ImportError: + log.warning(f'[{self.session_name}] [LOAD] Ignoring non-existent module "{module_path}"') + continue + + if "__path__" in dir(module): + log.warning(f'[{self.session_name}] [LOAD] Ignoring namespace "{module_path}"') + continue + + if handlers is None: + handlers = vars(module).keys() + warn_non_existent_functions = False + + for name in handlers: + # noinspection PyBroadException + try: + handler, group = getattr(module, name).handler + + if isinstance(handler, Handler) and isinstance(group, int): + self.add_handler(handler, group) + + 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( + self.session_name, name, module_path)) + + if exclude: + for path, handlers in exclude: + module_path = root + "." + path + warn_non_existent_functions = True + + try: + module = import_module(module_path) + except ImportError: + log.warning(f'[{self.session_name}] [UNLOAD] Ignoring non-existent module "{module_path}"') + continue + + if "__path__" in dir(module): + log.warning(f'[{self.session_name}] [UNLOAD] Ignoring namespace "{module_path}"') + continue + + if handlers is None: + handlers = vars(module).keys() + warn_non_existent_functions = False + + for name in handlers: + # noinspection PyBroadException + try: + handler, group = getattr(module, name).handler + + if isinstance(handler, Handler) and isinstance(group, int): + self.remove_handler(handler, group) + + 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( + self.session_name, name, module_path)) + + if count > 0: + log.info('[{}] Successfully loaded {} plugin{} from "{}"'.format( + self.session_name, count, "s" if count > 1 else "", root)) + else: + log.warning(f'[{self.session_name}] No plugin loaded from "{root}"') + + async def get_file( + self, + media_type: int, + dc_id: int, + document_id: int, + access_hash: int, + thumb_size: str, + peer_id: int, + peer_type: str, + peer_access_hash: int, + volume_id: int, + local_id: int, + file_ref: str, + file_size: int, + is_big: bool, + progress: callable, + progress_args: tuple = () + ) -> str: + async with self.media_sessions_lock: + session = self.media_sessions.get(dc_id, None) + + if session is None: + if dc_id != await self.storage.dc_id(): + session = Session( + self, dc_id, await Auth(self, dc_id, await self.storage.test_mode()).create(), + await self.storage.test_mode(), is_media=True + ) + await session.start() + + for _ in range(3): + exported_auth = await self.send( + raw.functions.auth.ExportAuthorization( + dc_id=dc_id + ) + ) + + try: + await session.send( + raw.functions.auth.ImportAuthorization( + id=exported_auth.id, + bytes=exported_auth.bytes + ) + ) + except AuthBytesInvalid: + continue + else: + break + else: + await session.stop() + raise AuthBytesInvalid + else: + session = Session( + self, dc_id, await self.storage.auth_key(), + await self.storage.test_mode(), is_media=True + ) + await session.start() + + self.media_sessions[dc_id] = session + + file_ref = utils.decode_file_ref(file_ref) + + if media_type == 1: + if peer_type == "user": + peer = raw.types.InputPeerUser( + user_id=peer_id, + access_hash=peer_access_hash + ) + elif peer_type == "chat": + peer = raw.types.InputPeerChat( + chat_id=peer_id + ) + else: + peer = raw.types.InputPeerChannel( + channel_id=peer_id, + access_hash=peer_access_hash + ) + + location = raw.types.InputPeerPhotoFileLocation( + peer=peer, + volume_id=volume_id, + local_id=local_id, + big=is_big or None + ) + elif media_type in (0, 2): + location = raw.types.InputPhotoFileLocation( + id=document_id, + access_hash=access_hash, + file_reference=file_ref, + thumb_size=thumb_size + ) + elif media_type == 14: + location = raw.types.InputDocumentFileLocation( + id=document_id, + access_hash=access_hash, + file_reference=file_ref, + thumb_size=thumb_size + ) + else: + location = raw.types.InputDocumentFileLocation( + id=document_id, + access_hash=access_hash, + file_reference=file_ref, + thumb_size="" + ) + + limit = 1024 * 1024 + offset = 0 + file_name = "" + + try: + r = await session.send( + raw.functions.upload.GetFile( + location=location, + offset=offset, + limit=limit + ) + ) + + if isinstance(r, raw.types.upload.File): + with tempfile.NamedTemporaryFile("wb", delete=False) as f: + file_name = f.name + + while True: + chunk = r.bytes + + if not chunk: + break + + f.write(chunk) + + offset += limit + + if progress: + await progress( + min(offset, file_size) + if file_size != 0 + else offset, + file_size, + *progress_args + ) + + r = await session.send( + raw.functions.upload.GetFile( + location=location, + offset=offset, + limit=limit + ) + ) + + elif isinstance(r, raw.types.upload.FileCdnRedirect): + async with self.media_sessions_lock: + cdn_session = self.media_sessions.get(r.dc_id, None) + + if cdn_session is None: + cdn_session = Session( + self, r.dc_id, await Auth(self, r.dc_id, await self.storage.test_mode()).create(), + await self.storage.test_mode(), is_media=True, is_cdn=True + ) + + await cdn_session.start() + + self.media_sessions[r.dc_id] = cdn_session + + try: + with tempfile.NamedTemporaryFile("wb", delete=False) as f: + file_name = f.name + + while True: + r2 = await cdn_session.send( + raw.functions.upload.GetCdnFile( + file_token=r.file_token, + offset=offset, + limit=limit + ) + ) + + if isinstance(r2, raw.types.upload.CdnFileReuploadNeeded): + try: + await session.send( + raw.functions.upload.ReuploadCdnFile( + file_token=r.file_token, + request_token=r2.request_token + ) + ) + except VolumeLocNotFound: + break + else: + continue + + chunk = r2.bytes + + # https://core.telegram.org/cdn#decrypting-files + decrypted_chunk = aes.ctr256_decrypt( + chunk, + r.encryption_key, + bytearray( + r.encryption_iv[:-4] + + (offset // 16).to_bytes(4, "big") + ) + ) + + hashes = await session.send( + raw.functions.upload.GetCdnFileHashes( + file_token=r.file_token, + offset=offset + ) + ) + + # https://core.telegram.org/cdn#verifying-files + for i, h in enumerate(hashes): + cdn_chunk = decrypted_chunk[h.limit * i: h.limit * (i + 1)] + assert h.hash == sha256(cdn_chunk).digest(), f"Invalid CDN hash part {i}" + + f.write(decrypted_chunk) + + offset += limit + + if progress: + if asyncio.iscoroutinefunction(progress): + await progress( + min(offset, file_size) if file_size != 0 else offset, + file_size, + *progress_args + ) + else: + func = functools.partial( + progress, + min(offset, file_size) if file_size != 0 else offset, + file_size, + *progress_args + ) + + await self.loop.run_in_executor(self.executor, func) + + if len(chunk) < limit: + break + except Exception as e: + raise e + except Exception as e: + if not isinstance(e, pyrogram.StopTransmission): + log.error(e, exc_info=True) + + try: + os.remove(file_name) + except OSError: + pass + + return "" + else: + return file_name + + def guess_mime_type(self, filename: str): + extension = os.path.splitext(filename)[1] + return self.extensions_to_mime_types.get(extension) + + def guess_extension(self, mime_type: str): + extensions = self.mime_types_to_extensions.get(mime_type) + + if extensions: + return extensions.split(" ")[0] diff --git a/pyrogram/client/__init__.py b/pyrogram/client/__init__.py deleted file mode 100644 index d0b82f9184..0000000000 --- a/pyrogram/client/__init__.py +++ /dev/null @@ -1,25 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan -# -# 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 .client import Client -from .ext import BaseClient, Emoji -from .filters import Filters - -__all__ = [ - "Client", "BaseClient", "Emoji", "Filters", -] diff --git a/pyrogram/client/client.py b/pyrogram/client/client.py deleted file mode 100644 index 624c98c616..0000000000 --- a/pyrogram/client/client.py +++ /dev/null @@ -1,2151 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan -# -# 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 asyncio -import io -import logging -import math -import os -import re -import shutil -import tempfile -from configparser import ConfigParser -from hashlib import sha256, md5 -from importlib import import_module -from pathlib import Path, Pureapath -from signal import signal, SIGINT, SIGTERM, SIGABRT -from typing import Union, List, BinaryIO - -from pyrogram.api import functions, types -from pyrogram.api.core import TLObject -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 -from pyrogram.errors import ( - PhoneMigrate, NetworkMigrate, SessionPasswordNeeded, - PeerIdInvalid, VolumeLocNotFound, UserMigrate, ChannelPrivate, - AuthBytesInvalid, BadRequest -) -from pyrogram.session import Auth, Session -from .ext import utils, Syncer, BaseClient, Dispatcher -from .ext.utils import ainput -from .methods import Methods -from .storage import Storage, FileStorage, MemoryStorage -from .types import User, SentCode, TermsOfService - -log = logging.getLogger(__name__) - - -class Client(Methods, BaseClient): - """Pyrogram Client, the main means for interacting with Telegram. - - Parameters: - session_name (``str``): - Pass a string of your choice to give a name to the client session, e.g.: "*my_account*". This name will be - used to save a file on disk that stores details needed to reconnect without asking again for credentials. - Alternatively, if you don't want a file to be saved on disk, pass the special name "**:memory:**" to start - an in-memory session that will be discarded as soon as you stop the Client. In order to reconnect again - using a memory storage without having to login again, you can use - :meth:`~pyrogram.Client.export_session_string` before stopping the client to get a session string you can - pass here as argument. - - api_id (``int`` | ``str``, *optional*): - The *api_id* part of your Telegram API Key, as integer. E.g.: "12345". - This is an alternative way to pass it if you don't want to use the *config.ini* file. - - api_hash (``str``, *optional*): - The *api_hash* part of your Telegram API Key, as string. E.g.: "0123456789abcdef0123456789abcdef". - This is an alternative way to set it if you don't want to use the *config.ini* file. - - app_version (``str``, *optional*): - Application version. Defaults to "Pyrogram |version|". - This is an alternative way to set it if you don't want to use the *config.ini* file. - - device_model (``str``, *optional*): - Device model. Defaults to *platform.python_implementation() + " " + platform.python_version()*. - This is an alternative way to set it if you don't want to use the *config.ini* file. - - system_version (``str``, *optional*): - Operating System version. Defaults to *platform.system() + " " + platform.release()*. - This is an alternative way to set it if you don't want to use the *config.ini* file. - - lang_code (``str``, *optional*): - Code of the language used on the client, in ISO 639-1 standard. Defaults to "en". - This is an alternative way to set it if you don't want to use the *config.ini* file. - - ipv6 (``bool``, *optional*): - Pass True to connect to Telegram using IPv6. - Defaults to False (IPv4). - - proxy (``dict``, *optional*): - Your SOCKS5 Proxy settings as dict, - e.g.: *dict(hostname="11.22.33.44", port=1080, username="user", password="pass")*. - The *username* and *password* can be omitted if your proxy doesn't require authorization. - This is an alternative way to setup a proxy if you don't want to use the *config.ini* file. - - test_mode (``bool``, *optional*): - Enable or disable login to the test servers. - Only applicable for new sessions and will be ignored in case previously created sessions are loaded. - Defaults to False. - - 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. - This is an alternative way to set it if you don't want to use the *config.ini* file. - - phone_number (``str``, *optional*): - Pass your phone number as string (with your Country Code prefix included) to avoid entering it manually. - Only applicable for new sessions. - - phone_code (``str``, *optional*): - Pass the phone code as string (for test numbers only) to avoid entering it manually. - Only applicable for new sessions. - - password (``str``, *optional*): - Pass your Two-Step Verification password as string (if you have one) to avoid entering it manually. - Only applicable for new sessions. - - force_sms (``bool``, *optional*): - Pass True to force Telegram sending the authorization code via SMS. - Only applicable for new sessions. - Defaults to False. - - workers (``int``, *optional*): - 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 where Pyrogram - will store your session files. - Defaults to the parent directory of the main script. - - config_file (``str``, *optional*): - Path of the configuration file. - Defaults to ./config.ini - - plugins (``dict``, *optional*): - Your Smart Plugins settings as dict, e.g.: *dict(root="plugins")*. - This is an alternative way to setup plugins if you don't want to use the *config.ini* file. - - parse_mode (``str``, *optional*): - The parse mode, can be any of: *"combined"*, for the default combined mode. *"markdown"* or *"md"* - to force Markdown-only styles. *"html"* to force HTML-only styles. *None* to disable the parser - completely. - - no_updates (``bool``, *optional*): - Pass True to completely disable incoming updates for the current session. - When updates are disabled your client can't receive any new message. - Useful for batch programs that don't need to deal with updates. - Defaults to False (updates enabled and always received). - - takeout (``bool``, *optional*): - Pass True to let the client use a takeout session instead of a normal one, implies *no_updates=True*. - Useful for exporting your Telegram data. Methods invoked inside a takeout session (such as get_history, - download_media, ...) are less prone to throw FloodWait exceptions. - Only available for users, bots will ignore this parameter. - Defaults to False (normal session). - - sleep_threshold (``int``, *optional*): - Set a sleep threshold for flood wait exceptions happening globally in this client instance, below which any - request that raises a flood wait will be automatically invoked again after sleeping for the required amount - of time. Flood wait exceptions requiring higher waiting times will be raised. - Defaults to 60 (seconds). - """ - - def __init__( - self, - session_name: Union[str, Storage], - api_id: Union[int, str] = None, - api_hash: str = None, - app_version: str = None, - device_model: str = None, - system_version: str = None, - lang_code: str = None, - ipv6: bool = False, - proxy: dict = None, - test_mode: bool = False, - bot_token: str = None, - phone_number: str = None, - phone_code: str = None, - password: str = None, - force_sms: bool = False, - workers: int = BaseClient.WORKERS, - workdir: str = BaseClient.WORKDIR, - config_file: str = BaseClient.CONFIG_FILE, - plugins: dict = None, - parse_mode: str = BaseClient.PARSE_MODES[0], - no_updates: bool = None, - takeout: bool = None, - sleep_threshold: int = Session.SLEEP_THRESHOLD - ): - super().__init__() - - self.session_name = session_name - self.api_id = int(api_id) if api_id else None - self.api_hash = api_hash - self.app_version = app_version - self.device_model = device_model - self.system_version = system_version - self.lang_code = lang_code - self.ipv6 = ipv6 - # 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.force_sms = force_sms - self.workers = workers - self.workdir = Path(workdir) - self.config_file = Path(config_file) - self.plugins = plugins - self.parse_mode = parse_mode - self.no_updates = no_updates - self.takeout = takeout - self.sleep_threshold = sleep_threshold - - if isinstance(session_name, str): - if session_name == ":memory:" or len(session_name) >= MemoryStorage.SESSION_STRING_SIZE: - session_name = re.sub(r"[\n\s]+", "", session_name) - self.storage = MemoryStorage(session_name) - else: - self.storage = FileStorage(session_name, self.workdir) - elif isinstance(session_name, Storage): - self.storage = session_name - else: - raise ValueError("Unknown storage engine") - - self.dispatcher = Dispatcher(self, 0 if no_updates else workers) - - def __enter__(self): - return self.start() - - def __exit__(self, *args): - try: - self.stop() - except ConnectionError: - pass - - async def __aenter__(self): - return await self.start() - - async def __aexit__(self, *args): - await self.stop() - - @property - def proxy(self): - return self._proxy - - @proxy.setter - def proxy(self, value): - if value is None: - self._proxy = None - return - - if self._proxy is None: - self._proxy = {} - - self._proxy["enabled"] = bool(value.get("enabled", True)) - self._proxy.update(value) - - async def connect(self) -> bool: - """ - Connect the client to Telegram servers. - - Returns: - ``bool``: On success, in case the passed-in session is authorized, True is returned. Otherwise, in case - the session needs to be authorized, False is returned. - - Raises: - ConnectionError: In case you try to connect an already connected client. - """ - if self.is_connected: - raise ConnectionError("Client is already connected") - - self.load_config() - await self.load_session() - - self.session = Session(self, self.storage.dc_id(), self.storage.auth_key()) - - await self.session.start() - - self.is_connected = True - - return bool(self.storage.user_id()) - - async def disconnect(self): - """Disconnect the client from Telegram servers. - - Raises: - ConnectionError: In case you try to disconnect an already disconnected client or in case you try to - disconnect a client that needs to be terminated first. - """ - if not self.is_connected: - raise ConnectionError("Client is already disconnected") - - if self.is_initialized: - raise ConnectionError("Can't disconnect an initialized client") - - await self.session.stop() - self.storage.close() - self.is_connected = False - - async def initialize(self): - """Initialize the client by starting up workers. - - This method will start updates and download workers. - It will also load plugins and start the internal dispatcher. - - Raises: - ConnectionError: In case you try to initialize a disconnected client or in case you try to initialize an - already initialized client. - """ - if not self.is_connected: - raise ConnectionError("Can't initialize a disconnected client") - - if self.is_initialized: - raise ConnectionError("Client is already initialized") - - self.load_plugins() - - if not self.no_updates: - for _ in range(Client.UPDATES_WORKERS): - self.updates_worker_tasks.append( - asyncio.ensure_future(self.updates_worker()) - ) - - logging.info("Started {} UpdatesWorkerTasks".format(Client.UPDATES_WORKERS)) - - for _ in range(Client.DOWNLOAD_WORKERS): - self.download_worker_tasks.append( - asyncio.ensure_future(self.download_worker()) - ) - - logging.info("Started {} DownloadWorkerTasks".format(Client.DOWNLOAD_WORKERS)) - - await self.dispatcher.start() - await Syncer.add(self) - - self.is_initialized = True - - async def terminate(self): - """Terminate the client by shutting down workers. - - This method does the opposite of :meth:`~Client.initialize`. - It will stop the dispatcher and shut down updates and download workers. - - Raises: - ConnectionError: In case you try to terminate a client that is already terminated. - """ - if not self.is_initialized: - raise ConnectionError("Client is already terminated") - - if self.takeout_id: - await self.send(functions.account.FinishTakeoutSession()) - log.warning("Takeout session {} finished".format(self.takeout_id)) - - 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() - - logging.info("Stopped {} DownloadWorkerTasks".format(Client.DOWNLOAD_WORKERS)) - - if not self.no_updates: - for _ in range(Client.UPDATES_WORKERS): - self.updates_queue.put_nowait(None) - - for task in self.updates_worker_tasks: - await task - - self.updates_worker_tasks.clear() - - logging.info("Stopped {} UpdatesWorkerTasks".format(Client.UPDATES_WORKERS)) - - for media_session in self.media_sessions.values(): - await media_session.stop() - - self.media_sessions.clear() - - self.is_initialized = False - - async def send_code(self, phone_number: str) -> SentCode: - """Send the confirmation code to the given phone number. - - Parameters: - phone_number (``str``): - Phone number in international format (includes the country prefix). - - Returns: - :obj:`SentCode`: On success, an object containing information on the sent confirmation code is returned. - - Raises: - BadRequest: In case the phone number is invalid. - """ - phone_number = phone_number.strip(" +") - - while True: - try: - r = await self.send( - functions.auth.SendCode( - phone_number=phone_number, - api_id=self.api_id, - api_hash=self.api_hash, - settings=types.CodeSettings() - ) - ) - except (PhoneMigrate, NetworkMigrate) as e: - await self.session.stop() - - self.storage.dc_id(e.x) - self.storage.auth_key(await Auth(self, self.storage.dc_id()).create()) - self.session = Session(self, self.storage.dc_id(), self.storage.auth_key()) - - await self.session.start() - else: - return SentCode._parse(r) - - async def resend_code(self, phone_number: str, phone_code_hash: str) -> SentCode: - """Re-send the confirmation code using a different type. - - The type of the code to be re-sent is specified in the *next_type* attribute of the :obj:`SentCode` object - returned by :meth:`send_code`. - - Parameters: - phone_number (``str``): - Phone number in international format (includes the country prefix). - - phone_code_hash (``str``): - Confirmation code identifier. - - Returns: - :obj:`SentCode`: On success, an object containing information on the re-sent confirmation code is returned. - - Raises: - BadRequest: In case the arguments are invalid. - """ - phone_number = phone_number.strip(" +") - - r = await self.send( - functions.auth.ResendCode( - phone_number=phone_number, - phone_code_hash=phone_code_hash - ) - ) - - return SentCode._parse(r) - - async def sign_in(self, phone_number: str, phone_code_hash: str, phone_code: str) -> Union[ - User, TermsOfService, bool]: - """Authorize a user in Telegram with a valid confirmation code. - - Parameters: - phone_number (``str``): - Phone number in international format (includes the country prefix). - - phone_code_hash (``str``): - Code identifier taken from the result of :meth:`~Client.send_code`. - - phone_code (``str``): - The valid confirmation code you received (either as Telegram message or as SMS in your phone number). - - Returns: - :obj:`User` | :obj:`TermsOfService` | bool: On success, in case the authorization completed, the user is - returned. In case the phone number needs to be registered first AND the terms of services accepted (with - :meth:`~Client.accept_terms_of_service`), an object containing them is returned. In case the phone number - needs to be registered, but the terms of services don't need to be accepted, False is returned instead. - - Raises: - BadRequest: In case the arguments are invalid. - SessionPasswordNeeded: In case a password is needed to sign in. - """ - phone_number = phone_number.strip(" +") - - r = await self.send( - functions.auth.SignIn( - phone_number=phone_number, - phone_code_hash=phone_code_hash, - phone_code=phone_code - ) - ) - - if isinstance(r, types.auth.AuthorizationSignUpRequired): - if r.terms_of_service: - return TermsOfService._parse(terms_of_service=r.terms_of_service) - - return False - else: - self.storage.user_id(r.user.id) - self.storage.is_bot(False) - - return User._parse(self, r.user) - - async def sign_up(self, phone_number: str, phone_code_hash: str, first_name: str, last_name: str = "") -> User: - """Register a new user in Telegram. - - Parameters: - phone_number (``str``): - Phone number in international format (includes the country prefix). - - phone_code_hash (``str``): - Code identifier taken from the result of :meth:`~Client.send_code`. - - first_name (``str``): - New user first name. - - last_name (``str``, *optional*): - New user last name. Defaults to "" (empty string, no last name). - - Returns: - :obj:`User`: On success, the new registered user is returned. - - Raises: - BadRequest: In case the arguments are invalid. - """ - phone_number = phone_number.strip(" +") - - r = await self.send( - functions.auth.SignUp( - phone_number=phone_number, - first_name=first_name, - last_name=last_name, - phone_code_hash=phone_code_hash - ) - ) - - self.storage.user_id(r.user.id) - self.storage.is_bot(False) - - return User._parse(self, r.user) - - async def sign_in_bot(self, bot_token: str) -> User: - """Authorize a bot using its bot token generated by BotFather. - - Parameters: - bot_token (``str``): - The bot token generated by BotFather - - Returns: - :obj:`User`: On success, the bot identity is return in form of a user object. - - Raises: - BadRequest: In case the bot token is invalid. - """ - while True: - try: - r = await self.send( - functions.auth.ImportBotAuthorization( - flags=0, - api_id=self.api_id, - api_hash=self.api_hash, - bot_auth_token=bot_token - ) - ) - except UserMigrate as e: - await self.session.stop() - - self.storage.dc_id(e.x) - self.storage.auth_key(await Auth(self, self.storage.dc_id()).create()) - self.session = Session(self, self.storage.dc_id(), self.storage.auth_key()) - - await self.session.start() - else: - self.storage.user_id(r.user.id) - self.storage.is_bot(True) - - return User._parse(self, r.user) - - async def get_password_hint(self) -> str: - """Get your Two-Step Verification password hint. - - Returns: - ``str``: On success, the password hint as string is returned. - """ - return (await self.send(functions.account.GetPassword())).hint - - async def check_password(self, password: str) -> User: - """Check your Two-Step Verification password and log in. - - Parameters: - password (``str``): - Your Two-Step Verification password. - - Returns: - :obj:`User`: On success, the authorized user is returned. - - Raises: - BadRequest: In case the password is invalid. - """ - r = await self.send( - functions.auth.CheckPassword( - password=compute_check( - await self.send(functions.account.GetPassword()), - password - ) - ) - ) - - self.storage.user_id(r.user.id) - self.storage.is_bot(False) - - return User._parse(self, r.user) - - async def send_recovery_code(self) -> str: - """Send a code to your email to recover your password. - - Returns: - ``str``: On success, the hidden email pattern is returned and a recovery code is sent to that email. - - Raises: - BadRequest: In case no recovery email was set up. - """ - return (await self.send( - functions.auth.RequestPasswordRecovery() - )).email_pattern - - async def recover_password(self, recovery_code: str) -> User: - """Recover your password with a recovery code and log in. - - Parameters: - recovery_code (``str``): - The recovery code sent via email. - - Returns: - :obj:`User`: On success, the authorized user is returned and the Two-Step Verification password reset. - - Raises: - BadRequest: In case the recovery code is invalid. - """ - r = await self.send( - functions.auth.RecoverPassword( - code=recovery_code - ) - ) - - self.storage.user_id(r.user.id) - self.storage.is_bot(False) - - return User._parse(self, r.user) - - async def accept_terms_of_service(self, terms_of_service_id: str) -> bool: - """Accept the given terms of service. - - Parameters: - terms_of_service_id (``str``): - The terms of service identifier. - """ - r = await self.send( - functions.help.AcceptTermsOfService( - id=types.DataJSON( - data=terms_of_service_id - ) - ) - ) - - assert r - - return True - - async def authorize(self) -> User: - if self.bot_token: - return await self.sign_in_bot(self.bot_token) - - while True: - try: - if not self.phone_number: - while True: - value = await ainput("Enter phone number or bot token: ") - - if not value: - continue - - confirm = input("Is \"{}\" correct? (y/N): ".format(value)).lower() - - if confirm == "y": - break - - if ":" in value: - self.bot_token = value - return await self.sign_in_bot(value) - else: - self.phone_number = value - - sent_code = await self.send_code(self.phone_number) - except BadRequest as e: - print(e.MESSAGE) - self.phone_number = None - self.bot_token = None - else: - break - - if self.force_sms: - sent_code = await self.resend_code(self.phone_number, sent_code.phone_code_hash) - - print("The confirmation code has been sent via {}".format( - { - "app": "Telegram app", - "sms": "SMS", - "call": "phone call", - "flash_call": "phone flash call" - }[sent_code.type] - )) - - while True: - if not self.phone_code: - self.phone_code = await ainput("Enter confirmation code: ") - - try: - signed_in = await self.sign_in(self.phone_number, sent_code.phone_code_hash, self.phone_code) - except BadRequest as e: - print(e.MESSAGE) - self.phone_code = None - except SessionPasswordNeeded as e: - print(e.MESSAGE) - - while True: - print("Password hint: {}".format(await self.get_password_hint())) - - if not self.password: - self.password = await ainput("Enter password (empty to recover): ") - - try: - if not self.password: - confirm = await ainput("Confirm password recovery (y/n): ") - - if confirm == "y": - email_pattern = await self.send_recovery_code() - print("The recovery code has been sent to {}".format(email_pattern)) - - while True: - recovery_code = await ainput("Enter recovery code: ") - - try: - return await self.recover_password(recovery_code) - except BadRequest as e: - print(e.MESSAGE) - except Exception as e: - log.error(e, exc_info=True) - raise - else: - self.password = None - else: - return await self.check_password(self.password) - except BadRequest as e: - print(e.MESSAGE) - self.password = None - else: - break - - if isinstance(signed_in, User): - return signed_in - - while True: - first_name = await ainput("Enter first name: ") - last_name = await ainput("Enter last name (empty to skip): ") - - try: - signed_up = await self.sign_up( - self.phone_number, - sent_code.phone_code_hash, - first_name, - last_name - ) - except BadRequest as e: - print(e.MESSAGE) - else: - break - - if isinstance(signed_in, TermsOfService): - print("\n" + signed_in.text + "\n") - await self.accept_terms_of_service(signed_in.id) - - return signed_up - - async def log_out(self): - """Log out from Telegram and delete the *\\*.session* file. - - When you log out, the current client is stopped and the storage session deleted. - No more API calls can be made until you start the client and re-authorize again. - - Returns: - ``bool``: On success, True is returned. - - Example: - .. code-block:: python - - # Log out. - app.log_out() - """ - await self.send(functions.auth.LogOut()) - await self.stop() - self.storage.delete() - - return True - - async def start(self): - """Start the client. - - This method connects the client to Telegram and, in case of new sessions, automatically manages the full - authorization process using an interactive prompt. - - Returns: - :obj:`Client`: The started client itself. - - Raises: - ConnectionError: In case you try to start an already started client. - - Example: - .. code-block:: python - :emphasize-lines: 4 - - from pyrogram import Client - - app = Client("my_account") - app.start() - - ... # Call API methods - - app.stop() - """ - is_authorized = await self.connect() - - try: - if not is_authorized: - await self.authorize() - - if not self.storage.is_bot() and self.takeout: - self.takeout_id = (await self.send(functions.account.InitTakeoutSession())).id - log.warning("Takeout session {} initiated".format(self.takeout_id)) - - await self.send(functions.updates.GetState()) - except (Exception, KeyboardInterrupt): - await self.disconnect() - raise - else: - await self.initialize() - return self - - async def stop(self, block: bool = True): - """Stop the Client. - - This method disconnects the client from Telegram and stops the underlying tasks. - - Parameters: - block (``bool``, *optional*): - Blocks the code execution until the client has been restarted. It is useful with ``block=False`` in case - you want to stop the own client *within* an handler in order not to cause a deadlock. - Defaults to True. - - Returns: - :obj:`Client`: The stopped client itself. - - Raises: - ConnectionError: In case you try to stop an already stopped client. - - Example: - .. code-block:: python - :emphasize-lines: 8 - - from pyrogram import Client - - app = Client("my_account") - app.start() - - ... # Call API methods - - app.stop() - """ - - async def do_it(): - await self.terminate() - await self.disconnect() - - if block: - await do_it() - else: - asyncio.ensure_future(do_it()) - - return self - - async def restart(self, block: bool = True): - """Restart the Client. - - This method will first call :meth:`~Client.stop` and then :meth:`~Client.start` in a row in order to restart - a client using a single method. - - Parameters: - block (``bool``, *optional*): - Blocks the code execution until the client has been restarted. It is useful with ``block=False`` in case - you want to restart the own client *within* an handler in order not to cause a deadlock. - Defaults to True. - - Returns: - :obj:`Client`: The restarted client itself. - - Raises: - ConnectionError: In case you try to restart a stopped Client. - - Example: - .. code-block:: python - :emphasize-lines: 8 - - from pyrogram import Client - - app = Client("my_account") - app.start() - - ... # Call API methods - - app.restart() - - ... # Call other API methods - - app.stop() - """ - - async def do_it(): - await self.stop() - await self.start() - - if block: - await do_it() - else: - asyncio.ensure_future(do_it()) - - return self - - @staticmethod - async def idle(stop_signals: tuple = (SIGINT, SIGTERM, SIGABRT)): - """Block the main script execution until a signal is received. - - This static method will run an infinite loop in order to block the main script execution and prevent it from - exiting while having client(s) that are still running in the background. - - It is useful for event-driven application only, that are, applications which react upon incoming Telegram - updates through handlers, rather than executing a set of methods sequentially. - - The way Pyrogram works, it will keep your handlers in a pool of worker threads, which are executed concurrently - outside the main thread; calling idle() will ensure the client(s) will be kept alive by not letting the main - script to end, until you decide to quit. - - Once a signal is received (e.g.: from CTRL+C) the inner infinite loop will break and your main script will - continue. Don't forget to call :meth:`~Client.stop` for each running client before the script ends. - - Parameters: - stop_signals (``tuple``, *optional*): - Iterable containing signals the signal handler will listen to. - Defaults to *(SIGINT, SIGTERM, SIGABRT)*. - - Example: - .. code-block:: python - :emphasize-lines: 13 - - from pyrogram import Client - - app1 = Client("account1") - app2 = Client("account2") - app3 = Client("account3") - - ... # Set handlers up - - app1.start() - app2.start() - app3.start() - - Client.idle() - - app1.stop() - app2.stop() - app3.stop() - """ - - def signal_handler(_, __): - logging.info("Stop signal received ({}). Exiting...".format(_)) - Client.is_idling = False - - for s in stop_signals: - signal(s, signal_handler) - - Client.is_idling = True - - while Client.is_idling: - await asyncio.sleep(1) - - def run(self, coroutine=None): - """Start the client, idle the main script and finally stop the client. - - This is a convenience method that calls :meth:`~Client.start`, :meth:`~Client.idle` and :meth:`~Client.stop` in - sequence. 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, since idle() will block after starting the own client. - - Raises: - ConnectionError: In case you try to run an already started client. - - Example: - .. code-block:: python - :emphasize-lines: 7 - - from pyrogram import Client - - app = Client("my_account") - - ... # Set handlers up - - app.run() - """ - loop = asyncio.get_event_loop() - run = loop.run_until_complete - - if coroutine is not None: - run(coroutine) - else: - run(self.start()) - run(Client.idle()) - run(self.stop()) - - loop.close() - - def add_handler(self, handler: Handler, group: int = 0): - """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 your handler using a different group id (lower group id - == higher priority). This mechanism is explained in greater details at - :doc:`More on Updates <../../topics/more-on-updates>`. - - Parameters: - handler (``Handler``): - The handler to be registered. - - group (``int``, *optional*): - The group identifier, defaults to 0. - - Returns: - ``tuple``: A tuple consisting of *(handler, group)*. - - Example: - .. code-block:: python - :emphasize-lines: 8 - - from pyrogram import Client, MessageHandler - - def dump(client, message): - print(message) - - app = Client("my_account") - - app.add_handler(MessageHandler(dump)) - - app.run() - """ - if isinstance(handler, DisconnectHandler): - self.disconnect_handler = handler.callback - else: - self.dispatcher.add_handler(handler, group) - - return handler, group - - def remove_handler(self, handler: Handler, group: int = 0): - """Remove a previously-registered update handler. - - Make sure to provide the right group where the handler was added in. You can use the return value of the - :meth:`~Client.add_handler` method, a tuple of *(handler, group)*, and pass it directly. - - Parameters: - handler (``Handler``): - The handler to be removed. - - group (``int``, *optional*): - The group identifier, defaults to 0. - - Example: - .. code-block:: python - :emphasize-lines: 11 - - from pyrogram import Client, MessageHandler - - def dump(client, message): - print(message) - - app = Client("my_account") - - handler = app.add_handler(MessageHandler(dump)) - - # Starred expression to unpack (handler, group) - app.remove_handler(*handler) - - app.run() - """ - if isinstance(handler, DisconnectHandler): - self.disconnect_handler = None - else: - self.dispatcher.remove_handler(handler, group) - - def stop_transmission(self): - """Stop downloading or uploading a file. - - This method must be called inside a progress callback function in order to stop the transmission at the - desired time. The progress callback is called every time a file chunk is uploaded/downloaded. - - Example: - .. code-block:: python - :emphasize-lines: 9 - - from pyrogram import Client - - app = Client("my_account") - - # Example to stop transmission once the upload progress reaches 50% - # Useless in practice, but shows how to stop on command - def progress(current, total, client): - if (current * 100 / total) > 50: - client.stop_transmission() - - with app: - app.send_document("me", "files.zip", progress=progress, progress_args=(app,)) - """ - raise Client.StopTransmission - - def export_session_string(self): - """Export the current authorized session as a serialized string. - - Session strings are useful for storing in-memory authorized sessions in a portable, serialized string. - More detailed information about session strings can be found at the dedicated page of - :doc:`Storage Engines <../../topics/storage-engines>`. - - Returns: - ``str``: The session serialized into a printable, url-safe string. - - Example: - .. code-block:: python - :emphasize-lines: 6 - - from pyrogram import Client - - app = Client("my_account") - - with app: - print(app.export_session_string()) - """ - return self.storage.export_session_string() - - @property - def parse_mode(self): - return self._parse_mode - - @parse_mode.setter - def parse_mode(self, parse_mode: Union[str, None] = "combined"): - if parse_mode not in self.PARSE_MODES: - raise ValueError('parse_mode must be one of {} or None. Not "{}"'.format( - ", ".join('"{}"'.format(m) for m in self.PARSE_MODES[:-1]), - parse_mode - )) - - self._parse_mode = parse_mode - - # TODO: redundant, remove in next major version - def set_parse_mode(self, parse_mode: Union[str, None] = "combined"): - """Set the parse mode to be used globally by the client. - - When setting the parse mode with this method, all other methods having a *parse_mode* parameter will follow the - global value by default. The default value *"combined"* enables both Markdown and HTML styles to be used and - combined together. - - Parameters: - parse_mode (``str``): - The new parse mode, can be any of: *"combined"*, for the default combined mode. *"markdown"* or *"md"* - to force Markdown-only styles. *"html"* to force HTML-only styles. *None* to disable the parser - completely. - - Raises: - ValueError: In case the provided *parse_mode* is not a valid parse mode. - - Example: - .. code-block:: python - :emphasize-lines: 10,14,18,22 - - from pyrogram import Client - - app = Client("my_account") - - with app: - # Default combined mode: Markdown + HTML - app.send_message("haskell", "1. **markdown** and html") - - # Force Markdown-only, HTML is disabled - app.set_parse_mode("markdown") - app.send_message("haskell", "2. **markdown** and html") - - # Force HTML-only, Markdown is disabled - app.set_parse_mode("html") - app.send_message("haskell", "3. **markdown** and html") - - # Disable the parser completely - app.set_parse_mode(None) - app.send_message("haskell", "4. **markdown** and html") - - # Bring back the default combined mode - app.set_parse_mode() - app.send_message("haskell", "5. **markdown** and html") - """ - - self.parse_mode = parse_mode - - def fetch_peers(self, peers: List[Union[types.User, types.Chat, types.Channel]]) -> bool: - is_min = False - parsed_peers = [] - - for peer in peers: - if getattr(peer, "min", False): - is_min = True - continue - - username = None - phone_number = None - - if isinstance(peer, types.User): - peer_id = peer.id - access_hash = peer.access_hash - username = (peer.username or "").lower() or None - phone_number = peer.phone - peer_type = "bot" if peer.bot else "user" - elif isinstance(peer, (types.Chat, types.ChatForbidden)): - peer_id = -peer.id - access_hash = 0 - peer_type = "group" - elif isinstance(peer, (types.Channel, types.ChannelForbidden)): - peer_id = utils.get_channel_id(peer.id) - access_hash = peer.access_hash - username = (getattr(peer, "username", None) or "").lower() or None - peer_type = "channel" if peer.broadcast else "supergroup" - else: - continue - - parsed_peers.append((peer_id, access_hash, peer_type, username, phone_number)) - - self.storage.update_peers(parsed_peers) - - return is_min - - async def download_worker(self): - while True: - packet = await self.download_queue.get() - - if packet is None: - break - - temp_file_path = "" - final_file_path = "" - - try: - data, directory, file_name, done, progress, progress_args, path = packet - - temp_file_path = await self.get_file( - media_type=data.media_type, - dc_id=data.dc_id, - document_id=data.document_id, - access_hash=data.access_hash, - thumb_size=data.thumb_size, - peer_id=data.peer_id, - peer_type=data.peer_type, - peer_access_hash=data.peer_access_hash, - volume_id=data.volume_id, - local_id=data.local_id, - file_ref=data.file_ref, - file_size=data.file_size, - is_big=data.is_big, - progress=progress, - progress_args=progress_args - ) - - if temp_file_path: - final_file_path = os.path.abspath(re.sub("\\\\", "/", os.path.join(directory, file_name))) - os.makedirs(directory, exist_ok=True) - shutil.move(temp_file_path, final_file_path) - except Exception as e: - log.error(e, exc_info=True) - - try: - os.remove(temp_file_path) - except OSError: - pass - else: - # TODO: "" or None for faulty download, which is better? - # os.path methods return "" in case something does not exist, I prefer this. - # For now let's keep None - path[0] = final_file_path or None - finally: - done.set() - - async def updates_worker(self): - while True: - updates = await self.updates_queue.get() - - if updates is None: - break - - try: - if isinstance(updates, (types.Update, types.UpdatesCombined)): - is_min = self.fetch_peers(updates.users) or self.fetch_peers(updates.chats) - - users = {u.id: u for u in updates.users} - chats = {c.id: c for c in updates.chats} - - for update in updates.updates: - channel_id = getattr( - getattr( - getattr( - update, "message", None - ), "to_id", None - ), "channel_id", None - ) or getattr(update, "channel_id", None) - - pts = getattr(update, "pts", None) - pts_count = getattr(update, "pts_count", None) - - if isinstance(update, types.UpdateChannelTooLong): - log.warning(update) - - if isinstance(update, types.UpdateNewChannelMessage) and is_min: - message = update.message - - if not isinstance(message, types.MessageEmpty): - try: - diff = await self.send( - functions.updates.GetChannelDifference( - channel=await self.resolve_peer(utils.get_channel_id(channel_id)), - filter=types.ChannelMessagesFilter( - ranges=[types.MessageRange( - min_id=update.message.id, - max_id=update.message.id - )] - ), - pts=pts - pts_count, - limit=pts - ) - ) - except ChannelPrivate: - pass - else: - if not isinstance(diff, types.updates.ChannelDifferenceEmpty): - users.update({u.id: u for u in diff.users}) - chats.update({c.id: c for c in diff.chats}) - - self.dispatcher.updates_queue.put_nowait((update, users, chats)) - elif isinstance(updates, (types.UpdateShortMessage, types.UpdateShortChatMessage)): - diff = await self.send( - functions.updates.GetDifference( - pts=updates.pts - updates.pts_count, - date=updates.date, - qts=-1 - ) - ) - - if diff.new_messages: - self.dispatcher.updates_queue.put_nowait(( - types.UpdateNewMessage( - message=diff.new_messages[0], - pts=updates.pts, - pts_count=updates.pts_count - ), - {u.id: u for u in diff.users}, - {c.id: c for c in diff.chats} - )) - else: - self.dispatcher.updates_queue.put_nowait((diff.other_updates[0], {}, {})) - elif isinstance(updates, types.UpdateShort): - self.dispatcher.updates_queue.put_nowait((updates.update, {}, {})) - elif isinstance(updates, types.UpdatesTooLong): - log.info(updates) - except Exception as e: - log.error(e, exc_info=True) - - async def send(self, data: TLObject, retries: int = Session.MAX_RETRIES, timeout: float = Session.WAIT_TIMEOUT): - """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 - data types from :obj:`types ` as well as bare types such as ``int``, ``str``, etc... - - .. note:: - - This is a utility method intended to be used **only** when working with raw - :obj:`functions ` (i.e: a Telegram API method you wish to use which is not - available yet in the Client class as an easy-to-use method). - - Parameters: - data (``RawFunction``): - The API Schema function filled with proper arguments. - - retries (``int``): - Number of retries. - - timeout (``float``): - Timeout in seconds. - - Returns: - ``RawType``: The raw type response generated by the query. - - Raises: - RPCError: In case of a Telegram RPC error. - """ - if not self.is_connected: - raise ConnectionError("Client has not been started yet") - - if self.no_updates: - data = functions.InvokeWithoutUpdates(query=data) - - if self.takeout_id: - data = functions.InvokeWithTakeout(takeout_id=self.takeout_id, query=data) - - r = await self.session.send(data, retries, timeout, self.sleep_threshold) - - self.fetch_peers(getattr(r, "users", [])) - self.fetch_peers(getattr(r, "chats", [])) - - return r - - def load_config(self): - parser = ConfigParser() - parser.read(str(self.config_file)) - - if self.bot_token: - pass - else: - self.bot_token = parser.get("pyrogram", "bot_token", fallback=None) - - if self.api_id and self.api_hash: - pass - else: - if parser.has_section("pyrogram"): - self.api_id = parser.getint("pyrogram", "api_id") - self.api_hash = parser.get("pyrogram", "api_hash") - else: - raise AttributeError("No API Key found. More info: https://docs.pyrogram.org/intro/setup") - - for option in ["app_version", "device_model", "system_version", "lang_code"]: - if getattr(self, option): - pass - else: - if parser.has_section("pyrogram"): - setattr(self, option, parser.get( - "pyrogram", - option, - fallback=getattr(Client, option.upper()) - )) - else: - setattr(self, option, getattr(Client, option.upper())) - - if self._proxy: - self._proxy["enabled"] = bool(self._proxy.get("enabled", True)) - else: - self._proxy = {} - - if parser.has_section("proxy"): - self._proxy["enabled"] = parser.getboolean("proxy", "enabled", fallback=True) - self._proxy["hostname"] = parser.get("proxy", "hostname") - self._proxy["port"] = parser.getint("proxy", "port") - self._proxy["username"] = parser.get("proxy", "username", fallback=None) or None - self._proxy["password"] = parser.get("proxy", "password", fallback=None) or None - - if self.plugins: - self.plugins = { - "enabled": bool(self.plugins.get("enabled", True)), - "root": self.plugins.get("root", None), - "include": self.plugins.get("include", []), - "exclude": self.plugins.get("exclude", []) - } - else: - try: - section = parser["plugins"] - - self.plugins = { - "enabled": section.getboolean("enabled", True), - "root": section.get("root", None), - "include": section.get("include", []), - "exclude": section.get("exclude", []) - } - - include = self.plugins["include"] - exclude = self.plugins["exclude"] - - if include: - self.plugins["include"] = include.strip().split("\n") - - if exclude: - self.plugins["exclude"] = exclude.strip().split("\n") - - except KeyError: - self.plugins = None - - async def load_session(self): - self.storage.open() - - session_empty = any([ - self.storage.test_mode() is None, - self.storage.auth_key() is None, - self.storage.user_id() is None, - self.storage.is_bot() is None - ]) - - if session_empty: - self.storage.dc_id(2) - self.storage.date(0) - - self.storage.test_mode(self.test_mode) - self.storage.auth_key(await Auth(self, self.storage.dc_id()).create()) - self.storage.user_id(None) - self.storage.is_bot(None) - - def load_plugins(self): - if self.plugins: - plugins = self.plugins.copy() - - for option in ["include", "exclude"]: - if plugins[option]: - plugins[option] = [ - (i.split()[0], i.split()[1:] or None) - for i in self.plugins[option] - ] - else: - return - - if plugins.get("enabled", False): - root = plugins["root"] - include = plugins["include"] - exclude = plugins["exclude"] - - count = 0 - - if not include: - for path in sorted(Path(root.replace(".", "/")).rglob("*.py")): - module_path = '.'.join(path.parent.parts + (path.stem,)) - module = import_module(module_path) - - for name in vars(module).keys(): - # noinspection PyBroadException - try: - handler, group = getattr(module, name).handler - - if isinstance(handler, Handler) and isinstance(group, int): - self.add_handler(handler, group) - - log.info('[{}] [LOAD] {}("{}") in group {} from "{}"'.format( - self.session_name, type(handler).__name__, name, group, module_path)) - - count += 1 - except Exception: - pass - else: - for path, handlers in include: - module_path = root + "." + path - warn_non_existent_functions = True - - try: - module = import_module(module_path) - except ImportError: - 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( - self.session_name, module_path)) - continue - - if handlers is None: - handlers = vars(module).keys() - warn_non_existent_functions = False - - for name in handlers: - # noinspection PyBroadException - try: - handler, group = getattr(module, name).handler - - if isinstance(handler, Handler) and isinstance(group, int): - self.add_handler(handler, group) - - 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( - self.session_name, name, module_path)) - - if exclude: - for path, handlers in exclude: - module_path = root + "." + path - warn_non_existent_functions = True - - try: - module = import_module(module_path) - except ImportError: - 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( - self.session_name, module_path)) - continue - - if handlers is None: - handlers = vars(module).keys() - warn_non_existent_functions = False - - for name in handlers: - # noinspection PyBroadException - try: - handler, group = getattr(module, name).handler - - if isinstance(handler, Handler) and isinstance(group, int): - self.remove_handler(handler, group) - - 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( - self.session_name, name, module_path)) - - if count > 0: - log.info('[{}] Successfully loaded {} plugin{} from "{}"'.format( - self.session_name, count, "s" if count > 1 else "", root)) - else: - log.warning('[{}] No plugin loaded from "{}"'.format( - self.session_name, root)) - - async def resolve_peer(self, peer_id: Union[int, str]): - """Get the InputPeer of a known peer id. - Useful whenever an InputPeer type is required. - - .. note:: - - This is a utility method intended to be used **only** when working with raw - :obj:`functions ` (i.e: a Telegram API method you wish to use which is not - available yet in the Client class as an easy-to-use method). - - Parameters: - peer_id (``int`` | ``str``): - The peer id you want to extract the InputPeer from. - Can be a direct id (int), a username (str) or a phone number (str). - - Returns: - ``InputPeer``: On success, the resolved peer id is returned in form of an InputPeer object. - - Raises: - KeyError: In case the peer doesn't exist in the internal database. - """ - if not self.is_connected: - raise ConnectionError("Client has not been started yet") - - try: - return self.storage.get_peer_by_id(peer_id) - except KeyError: - if isinstance(peer_id, str): - if peer_id in ("self", "me"): - return types.InputPeerSelf() - - peer_id = re.sub(r"[@+\s]", "", peer_id.lower()) - - try: - int(peer_id) - except ValueError: - try: - return self.storage.get_peer_by_username(peer_id) - except KeyError: - await self.send( - functions.contacts.ResolveUsername( - username=peer_id - ) - ) - - return self.storage.get_peer_by_username(peer_id) - else: - try: - return self.storage.get_peer_by_phone_number(peer_id) - except KeyError: - raise PeerIdInvalid - - peer_type = utils.get_peer_type(peer_id) - - if peer_type == "user": - self.fetch_peers( - await self.send( - functions.users.GetUsers( - id=[ - types.InputUser( - user_id=peer_id, - access_hash=0 - ) - ] - ) - ) - ) - elif peer_type == "chat": - await self.send( - functions.messages.GetChats( - id=[-peer_id] - ) - ) - else: - await self.send( - functions.channels.GetChannels( - id=[ - types.InputChannel( - channel_id=utils.get_channel_id(peer_id), - access_hash=0 - ) - ] - ) - ) - - try: - return self.storage.get_peer_by_id(peer_id) - except KeyError: - raise PeerIdInvalid - - async def save_file( - self, - path: Union[str, BinaryIO], - file_id: int = None, - file_part: int = 0, - progress: callable = None, - progress_args: tuple = () - ): - """Upload a file onto Telegram servers, without actually sending the message to anyone. - Useful whenever an InputFile type is required. - - .. note:: - - This is a utility method intended to be used **only** when working with raw - :obj:`functions ` (i.e: a Telegram API method you wish to use which is not - available yet in the Client class as an easy-to-use method). - - Parameters: - path (``str``): - The path of the file you want to upload that exists on your local machine. - - file_id (``int``, *optional*): - In case a file part expired, pass the file_id and the file_part to retry uploading that specific chunk. - - file_part (``int``, *optional*): - In case a file part expired, pass the file_id and the file_part to retry uploading that specific chunk. - - progress (``callable``, *optional*): - Pass a callback function to view the file transmission progress. - The function must take *(current, total)* as positional arguments (look at Other Parameters below for a - detailed description) and will be called back each time a new file chunk has been successfully - transmitted. - - progress_args (``tuple``, *optional*): - Extra custom arguments for the progress callback function. - You can pass anything you need to be available in the progress callback scope; for example, a Message - object or a Client instance in order to edit the message with the updated progress status. - - Other Parameters: - current (``int``): - The amount of bytes transmitted so far. - - total (``int``): - The total size of the file. - - *args (``tuple``, *optional*): - Extra custom arguments as defined in the *progress_args* parameter. - You can either keep *\*args* or add every single extra argument in your function signature. - - Returns: - ``InputFile``: On success, the uploaded file is returned in form of an InputFile object. - - Raises: - RPCError: In case of a Telegram RPC error. - """ - if path is None: - return None - - async def worker(session): - while True: - data = await queue.get() - - if data is None: - return - - try: - await asyncio.ensure_future(session.send(data)) - except Exception as e: - logging.error(e) - - part_size = 512 * 1024 - - if isinstance(path, (str, PurePath)): - fp = open(path, "rb") - elif isinstance(path, io.IOBase): - fp = path - else: - raise ValueError("Invalid file. Expected a file path as string or a binary (not text) file pointer") - - file_name = fp.name - - fp.seek(0, os.SEEK_END) - file_size = fp.tell() - fp.seek(0) - - if file_size == 0: - raise ValueError("File size equals to 0 B") - - if file_size > 2000 * 1024 * 1024: - raise ValueError("Telegram doesn't support uploading files bigger than 2000 MiB") - - 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.storage.dc_id(), self.storage.auth_key(), is_media=True) for _ in range(pool_size)] - workers = [asyncio.ensure_future(worker(session)) for session in pool for _ in range(workers_count)] - queue = asyncio.Queue(16) - - try: - for session in pool: - await session.start() - - with fp: - fp.seek(part_size * file_part) - - while True: - chunk = fp.read(part_size) - - if not chunk: - if not is_big: - md5_sum = "".join([hex(i)[2:].zfill(2) for i in md5_sum.digest()]) - break - - if is_big: - rpc = functions.upload.SaveBigFilePart( - file_id=file_id, - file_part=file_part, - file_total_parts=file_total_parts, - bytes=chunk - ) - else: - rpc = functions.upload.SaveFilePart( - file_id=file_id, - file_part=file_part, - bytes=chunk - ) - - await queue.put(rpc) - - if is_missing_part: - return - - if not is_big: - md5_sum.update(chunk) - - file_part += 1 - - if progress: - await progress(min(file_part * part_size, file_size), file_size, *progress_args) - except Client.StopTransmission: - raise - except Exception as e: - log.error(e, exc_info=True) - else: - if is_big: - return types.InputFileBig( - id=file_id, - parts=file_total_parts, - name=file_name, - - ) - else: - return types.InputFile( - id=file_id, - parts=file_total_parts, - name=file_name, - md5_checksum=md5_sum - ) - finally: - for _ in workers: - await queue.put(None) - - await asyncio.gather(*workers) - - for session in pool: - await session.stop() - - async def get_file( - self, - media_type: int, - dc_id: int, - document_id: int, - access_hash: int, - thumb_size: str, - peer_id: int, - peer_type: str, - peer_access_hash: int, - volume_id: int, - local_id: int, - file_ref: str, - file_size: int, - is_big: bool, - progress: callable, - progress_args: tuple = () - ) -> str: - async with self.media_sessions_lock: - session = self.media_sessions.get(dc_id, None) - - if session is None: - if dc_id != self.storage.dc_id(): - session = Session(self, dc_id, await Auth(self, dc_id).create(), is_media=True) - await session.start() - - for _ in range(3): - exported_auth = await self.send( - functions.auth.ExportAuthorization( - dc_id=dc_id - ) - ) - - try: - await session.send( - functions.auth.ImportAuthorization( - id=exported_auth.id, - bytes=exported_auth.bytes - ) - ) - except AuthBytesInvalid: - continue - else: - break - else: - await session.stop() - raise AuthBytesInvalid - else: - session = Session(self, dc_id, self.storage.auth_key(), is_media=True) - await session.start() - - self.media_sessions[dc_id] = session - - file_ref = utils.decode_file_ref(file_ref) - - if media_type == 1: - if peer_type == "user": - peer = types.InputPeerUser( - user_id=peer_id, - access_hash=peer_access_hash - ) - elif peer_type == "chat": - peer = types.InputPeerChat( - chat_id=peer_id - ) - else: - peer = types.InputPeerChannel( - channel_id=peer_id, - access_hash=peer_access_hash - ) - - location = types.InputPeerPhotoFileLocation( - peer=peer, - volume_id=volume_id, - local_id=local_id, - big=is_big or None - ) - elif media_type in (0, 2): - location = types.InputPhotoFileLocation( - id=document_id, - access_hash=access_hash, - file_reference=file_ref, - thumb_size=thumb_size - ) - elif media_type == 14: - location = types.InputDocumentFileLocation( - id=document_id, - access_hash=access_hash, - file_reference=file_ref, - thumb_size=thumb_size - ) - else: - location = types.InputDocumentFileLocation( - id=document_id, - access_hash=access_hash, - file_reference=file_ref, - thumb_size="" - ) - - limit = 1024 * 1024 - offset = 0 - file_name = "" - - try: - r = await session.send( - functions.upload.GetFile( - location=location, - offset=offset, - limit=limit - ) - ) - - if isinstance(r, types.upload.File): - with tempfile.NamedTemporaryFile("wb", delete=False) as f: - file_name = f.name - - while True: - chunk = r.bytes - - if not chunk: - break - - f.write(chunk) - - offset += limit - - if progress: - await progress( - min(offset, file_size) - if file_size != 0 - else offset, - file_size, - *progress_args - ) - - r = await session.send( - functions.upload.GetFile( - location=location, - offset=offset, - limit=limit - ) - ) - - elif isinstance(r, types.upload.FileCdnRedirect): - async with self.media_sessions_lock: - cdn_session = self.media_sessions.get(r.dc_id, None) - - if cdn_session is None: - cdn_session = Session( - self, - r.dc_id, - await Auth(self, r.dc_id).create(), is_media=True, is_cdn=True) - - await cdn_session.start() - - self.media_sessions[r.dc_id] = cdn_session - - try: - with tempfile.NamedTemporaryFile("wb", delete=False) as f: - file_name = f.name - - while True: - r2 = await cdn_session.send( - functions.upload.GetCdnFile( - file_token=r.file_token, - offset=offset, - limit=limit - ) - ) - - if isinstance(r2, types.upload.CdnFileReuploadNeeded): - try: - await session.send( - functions.upload.ReuploadCdnFile( - file_token=r.file_token, - request_token=r2.request_token - ) - ) - except VolumeLocNotFound: - break - else: - continue - - chunk = r2.bytes - - # https://core.telegram.org/cdn#decrypting-files - decrypted_chunk = AES.ctr256_decrypt( - chunk, - r.encryption_key, - bytearray( - r.encryption_iv[:-4] - + (offset // 16).to_bytes(4, "big") - ) - ) - - hashes = await session.send( - functions.upload.GetCdnFileHashes( - file_token=r.file_token, - offset=offset - ) - ) - - # https://core.telegram.org/cdn#verifying-files - for i, h in enumerate(hashes): - cdn_chunk = decrypted_chunk[h.limit * i: h.limit * (i + 1)] - assert h.hash == sha256(cdn_chunk).digest(), "Invalid CDN hash part {}".format(i) - - f.write(decrypted_chunk) - - offset += limit - - if progress: - await progress( - min(offset, file_size) - if file_size != 0 - else offset, - file_size, - *progress_args - ) - - if len(chunk) < limit: - break - except Exception as e: - raise e - except Exception as e: - if not isinstance(e, Client.StopTransmission): - log.error(e, exc_info=True) - - try: - os.remove(file_name) - except OSError: - pass - - return "" - else: - return file_name - - def guess_mime_type(self, filename: str): - extension = os.path.splitext(filename)[1] - return self.extensions_to_mime_types.get(extension) - - def guess_extension(self, mime_type: str): - extensions = self.mime_types_to_extensions.get(mime_type) - - if extensions: - return extensions.split(" ")[0] diff --git a/pyrogram/client/ext/__init__.py b/pyrogram/client/ext/__init__.py deleted file mode 100644 index e363d3d4db..0000000000 --- a/pyrogram/client/ext/__init__.py +++ /dev/null @@ -1,24 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan -# -# 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 .base_client import BaseClient -from .dispatcher import Dispatcher -from .emoji import Emoji -from .file_data import FileData -from .link import Link -from .syncer import Syncer diff --git a/pyrogram/client/ext/emoji.py b/pyrogram/client/ext/emoji.py deleted file mode 100644 index 97bfc52961..0000000000 --- a/pyrogram/client/ext/emoji.py +++ /dev/null @@ -1,7850 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan -# -# 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 . - -class Emoji: - HELMET_WITH_WHITE_CROSS_TYPE_1_2 = "\u26d1\U0001f3fb" - HELMET_WITH_WHITE_CROSS_TYPE_3 = "\u26d1\U0001f3fc" - HELMET_WITH_WHITE_CROSS_TYPE_4 = "\u26d1\U0001f3fd" - HELMET_WITH_WHITE_CROSS_TYPE_5 = "\u26d1\U0001f3fe" - HELMET_WITH_WHITE_CROSS_TYPE_6 = "\u26d1\U0001f3ff" - KISS_TYPE_1_2 = "\U0001f48f\U0001f3fb" - KISS_TYPE_3 = "\U0001f48f\U0001f3fc" - KISS_TYPE_4 = "\U0001f48f\U0001f3fd" - KISS_TYPE_5 = "\U0001f48f\U0001f3fe" - KISS_TYPE_6 = "\U0001f48f\U0001f3ff" - COUPLE_WITH_HEART_TYPE_1_2 = "\U0001f491\U0001f3fb" - COUPLE_WITH_HEART_TYPE_3 = "\U0001f491\U0001f3fc" - COUPLE_WITH_HEART_TYPE_4 = "\U0001f491\U0001f3fd" - COUPLE_WITH_HEART_TYPE_5 = "\U0001f491\U0001f3fe" - COUPLE_WITH_HEART_TYPE_6 = "\U0001f491\U0001f3ff" - SKIER_TYPE_1_2 = "\u26f7\U0001f3fb" - SKIER_TYPE_3 = "\u26f7\U0001f3fc" - SKIER_TYPE_4 = "\u26f7\U0001f3fd" - SKIER_TYPE_5 = "\u26f7\U0001f3fe" - SKIER_TYPE_6 = "\u26f7\U0001f3ff" - GRINNING_FACE = "\U0001f600" - BEAMING_FACE_WITH_SMILING_EYES = "\U0001f601" - FACE_WITH_TEARS_OF_JOY = "\U0001f602" - ROLLING_ON_THE_FLOOR_LAUGHING = "\U0001f923" - GRINNING_FACE_WITH_BIG_EYES = "\U0001f603" - GRINNING_FACE_WITH_SMILING_EYES = "\U0001f604" - GRINNING_FACE_WITH_SWEAT = "\U0001f605" - GRINNING_SQUINTING_FACE = "\U0001f606" - WINKING_FACE = "\U0001f609" - SMILING_FACE_WITH_SMILING_EYES = "\U0001f60a" - FACE_SAVORING_FOOD = "\U0001f60b" - SMILING_FACE_WITH_SUNGLASSES = "\U0001f60e" - SMILING_FACE_WITH_HEART_EYES = "\U0001f60d" - FACE_BLOWING_A_KISS = "\U0001f618" - SMILING_FACE_WITH_3_HEARTS = "\U0001f970" - KISSING_FACE = "\U0001f617" - KISSING_FACE_WITH_SMILING_EYES = "\U0001f619" - KISSING_FACE_WITH_CLOSED_EYES = "\U0001f61a" - SMILING_FACE = "\u263a\ufe0f" - SLIGHTLY_SMILING_FACE = "\U0001f642" - HUGGING_FACE = "\U0001f917" - STAR_STRUCK = "\U0001f929" - THINKING_FACE = "\U0001f914" - FACE_WITH_RAISED_EYEBROW = "\U0001f928" - NEUTRAL_FACE = "\U0001f610" - EXPRESSIONLESS_FACE = "\U0001f611" - FACE_WITHOUT_MOUTH = "\U0001f636" - FACE_WITH_ROLLING_EYES = "\U0001f644" - SMIRKING_FACE = "\U0001f60f" - PERSEVERING_FACE = "\U0001f623" - SAD_BUT_RELIEVED_FACE = "\U0001f625" - FACE_WITH_OPEN_MOUTH = "\U0001f62e" - ZIPPER_MOUTH_FACE = "\U0001f910" - HUSHED_FACE = "\U0001f62f" - SLEEPY_FACE = "\U0001f62a" - TIRED_FACE = "\U0001f62b" - SLEEPING_FACE = "\U0001f634" - RELIEVED_FACE = "\U0001f60c" - FACE_WITH_TONGUE = "\U0001f61b" - WINKING_FACE_WITH_TONGUE = "\U0001f61c" - SQUINTING_FACE_WITH_TONGUE = "\U0001f61d" - DROOLING_FACE = "\U0001f924" - UNAMUSED_FACE = "\U0001f612" - DOWNCAST_FACE_WITH_SWEAT = "\U0001f613" - PENSIVE_FACE = "\U0001f614" - CONFUSED_FACE = "\U0001f615" - UPSIDE_DOWN_FACE = "\U0001f643" - MONEY_MOUTH_FACE = "\U0001f911" - ASTONISHED_FACE = "\U0001f632" - FROWNING_FACE = "\u2639\ufe0f" - SLIGHTLY_FROWNING_FACE = "\U0001f641" - CONFOUNDED_FACE = "\U0001f616" - DISAPPOINTED_FACE = "\U0001f61e" - WORRIED_FACE = "\U0001f61f" - FACE_WITH_STEAM_FROM_NOSE = "\U0001f624" - CRYING_FACE = "\U0001f622" - LOUDLY_CRYING_FACE = "\U0001f62d" - FROWNING_FACE_WITH_OPEN_MOUTH = "\U0001f626" - ANGUISHED_FACE = "\U0001f627" - FEARFUL_FACE = "\U0001f628" - WEARY_FACE = "\U0001f629" - EXPLODING_HEAD = "\U0001f92f" - GRIMACING_FACE = "\U0001f62c" - ANXIOUS_FACE_WITH_SWEAT = "\U0001f630" - FACE_SCREAMING_IN_FEAR = "\U0001f631" - HOT_FACE = "\U0001f975" - COLD_FACE = "\U0001f976" - FLUSHED_FACE = "\U0001f633" - ZANY_FACE = "\U0001f92a" - DIZZY_FACE = "\U0001f635" - POUTING_FACE = "\U0001f621" - ANGRY_FACE = "\U0001f620" - FACE_WITH_SYMBOLS_ON_MOUTH = "\U0001f92c" - FACE_WITH_MEDICAL_MASK = "\U0001f637" - FACE_WITH_THERMOMETER = "\U0001f912" - FACE_WITH_HEAD_BANDAGE = "\U0001f915" - NAUSEATED_FACE = "\U0001f922" - FACE_VOMITING = "\U0001f92e" - SNEEZING_FACE = "\U0001f927" - SMILING_FACE_WITH_HALO = "\U0001f607" - COWBOY_HAT_FACE = "\U0001f920" - CLOWN_FACE = "\U0001f921" - PARTYING_FACE = "\U0001f973" - WOOZY_FACE = "\U0001f974" - PLEADING_FACE = "\U0001f97a" - LYING_FACE = "\U0001f925" - SHUSHING_FACE = "\U0001f92b" - FACE_WITH_HAND_OVER_MOUTH = "\U0001f92d" - FACE_WITH_MONOCLE = "\U0001f9d0" - NERD_FACE = "\U0001f913" - SMILING_FACE_WITH_HORNS = "\U0001f608" - ANGRY_FACE_WITH_HORNS = "\U0001f47f" - OGRE = "\U0001f479" - GOBLIN = "\U0001f47a" - SKULL = "\U0001f480" - SKULL_AND_CROSSBONES = "\u2620\ufe0f" - GHOST = "\U0001f47b" - ALIEN = "\U0001f47d" - ALIEN_MONSTER = "\U0001f47e" - ROBOT_FACE = "\U0001f916" - PILE_OF_POO = "\U0001f4a9" - GRINNING_CAT_FACE = "\U0001f63a" - GRINNING_CAT_FACE_WITH_SMILING_EYES = "\U0001f638" - CAT_FACE_WITH_TEARS_OF_JOY = "\U0001f639" - SMILING_CAT_FACE_WITH_HEART_EYES = "\U0001f63b" - CAT_FACE_WITH_WRY_SMILE = "\U0001f63c" - KISSING_CAT_FACE = "\U0001f63d" - WEARY_CAT_FACE = "\U0001f640" - CRYING_CAT_FACE = "\U0001f63f" - POUTING_CAT_FACE = "\U0001f63e" - SEE_NO_EVIL_MONKEY = "\U0001f648" - HEAR_NO_EVIL_MONKEY = "\U0001f649" - SPEAK_NO_EVIL_MONKEY = "\U0001f64a" - LIGHT_SKIN_TONE = "\U0001f3fb" - MEDIUM_LIGHT_SKIN_TONE = "\U0001f3fc" - MEDIUM_SKIN_TONE = "\U0001f3fd" - MEDIUM_DARK_SKIN_TONE = "\U0001f3fe" - DARK_SKIN_TONE = "\U0001f3ff" - BABY = "\U0001f476" - BABY_LIGHT_SKIN_TONE = "\U0001f476\U0001f3fb" - BABY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f476\U0001f3fc" - BABY_MEDIUM_SKIN_TONE = "\U0001f476\U0001f3fd" - BABY_MEDIUM_DARK_SKIN_TONE = "\U0001f476\U0001f3fe" - BABY_DARK_SKIN_TONE = "\U0001f476\U0001f3ff" - CHILD = "\U0001f9d2" - CHILD_LIGHT_SKIN_TONE = "\U0001f9d2\U0001f3fb" - CHILD_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d2\U0001f3fc" - CHILD_MEDIUM_SKIN_TONE = "\U0001f9d2\U0001f3fd" - CHILD_MEDIUM_DARK_SKIN_TONE = "\U0001f9d2\U0001f3fe" - CHILD_DARK_SKIN_TONE = "\U0001f9d2\U0001f3ff" - BOY = "\U0001f466" - BOY_LIGHT_SKIN_TONE = "\U0001f466\U0001f3fb" - BOY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f466\U0001f3fc" - BOY_MEDIUM_SKIN_TONE = "\U0001f466\U0001f3fd" - BOY_MEDIUM_DARK_SKIN_TONE = "\U0001f466\U0001f3fe" - BOY_DARK_SKIN_TONE = "\U0001f466\U0001f3ff" - GIRL = "\U0001f467" - GIRL_LIGHT_SKIN_TONE = "\U0001f467\U0001f3fb" - GIRL_MEDIUM_LIGHT_SKIN_TONE = "\U0001f467\U0001f3fc" - GIRL_MEDIUM_SKIN_TONE = "\U0001f467\U0001f3fd" - GIRL_MEDIUM_DARK_SKIN_TONE = "\U0001f467\U0001f3fe" - GIRL_DARK_SKIN_TONE = "\U0001f467\U0001f3ff" - PERSON = "\U0001f9d1" - ADULT_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fb" - ADULT_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fc" - ADULT_MEDIUM_SKIN_TONE = "\U0001f9d1\U0001f3fd" - ADULT_MEDIUM_DARK_SKIN_TONE = "\U0001f9d1\U0001f3fe" - ADULT_DARK_SKIN_TONE = "\U0001f9d1\U0001f3ff" - MAN = "\U0001f468" - MAN_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb" - MAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc" - MAN_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd" - MAN_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe" - MAN_DARK_SKIN_TONE = "\U0001f468\U0001f3ff" - WOMAN = "\U0001f469" - WOMAN_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb" - WOMAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc" - WOMAN_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd" - WOMAN_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe" - WOMAN_DARK_SKIN_TONE = "\U0001f469\U0001f3ff" - OLDER_PERSON = "\U0001f9d3" - OLDER_ADULT_LIGHT_SKIN_TONE = "\U0001f9d3\U0001f3fb" - OLDER_ADULT_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d3\U0001f3fc" - OLDER_ADULT_MEDIUM_SKIN_TONE = "\U0001f9d3\U0001f3fd" - OLDER_ADULT_MEDIUM_DARK_SKIN_TONE = "\U0001f9d3\U0001f3fe" - OLDER_ADULT_DARK_SKIN_TONE = "\U0001f9d3\U0001f3ff" - OLD_MAN = "\U0001f474" - OLD_MAN_LIGHT_SKIN_TONE = "\U0001f474\U0001f3fb" - OLD_MAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f474\U0001f3fc" - OLD_MAN_MEDIUM_SKIN_TONE = "\U0001f474\U0001f3fd" - OLD_MAN_MEDIUM_DARK_SKIN_TONE = "\U0001f474\U0001f3fe" - OLD_MAN_DARK_SKIN_TONE = "\U0001f474\U0001f3ff" - OLD_WOMAN = "\U0001f475" - OLD_WOMAN_LIGHT_SKIN_TONE = "\U0001f475\U0001f3fb" - OLD_WOMAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f475\U0001f3fc" - OLD_WOMAN_MEDIUM_SKIN_TONE = "\U0001f475\U0001f3fd" - OLD_WOMAN_MEDIUM_DARK_SKIN_TONE = "\U0001f475\U0001f3fe" - OLD_WOMAN_DARK_SKIN_TONE = "\U0001f475\U0001f3ff" - MAN_HEALTH_WORKER = "\U0001f468\u200d\u2695\ufe0f" - MAN_HEALTH_WORKER_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\u2695\ufe0f" - MAN_HEALTH_WORKER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\u2695\ufe0f" - MAN_HEALTH_WORKER_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\u2695\ufe0f" - MAN_HEALTH_WORKER_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\u2695\ufe0f" - MAN_HEALTH_WORKER_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\u2695\ufe0f" - WOMAN_HEALTH_WORKER = "\U0001f469\u200d\u2695\ufe0f" - WOMAN_HEALTH_WORKER_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\u2695\ufe0f" - WOMAN_HEALTH_WORKER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\u2695\ufe0f" - WOMAN_HEALTH_WORKER_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\u2695\ufe0f" - WOMAN_HEALTH_WORKER_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\u2695\ufe0f" - WOMAN_HEALTH_WORKER_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\u2695\ufe0f" - MAN_STUDENT = "\U0001f468\u200d\U0001f393" - MAN_STUDENT_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f393" - MAN_STUDENT_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f393" - MAN_STUDENT_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f393" - MAN_STUDENT_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f393" - MAN_STUDENT_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f393" - WOMAN_STUDENT = "\U0001f469\u200d\U0001f393" - WOMAN_STUDENT_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f393" - WOMAN_STUDENT_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f393" - WOMAN_STUDENT_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f393" - WOMAN_STUDENT_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f393" - WOMAN_STUDENT_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f393" - MAN_TEACHER = "\U0001f468\u200d\U0001f3eb" - MAN_TEACHER_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f3eb" - MAN_TEACHER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f3eb" - MAN_TEACHER_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f3eb" - MAN_TEACHER_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f3eb" - MAN_TEACHER_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f3eb" - WOMAN_TEACHER = "\U0001f469\u200d\U0001f3eb" - WOMAN_TEACHER_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f3eb" - WOMAN_TEACHER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f3eb" - WOMAN_TEACHER_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f3eb" - WOMAN_TEACHER_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f3eb" - WOMAN_TEACHER_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f3eb" - MAN_JUDGE = "\U0001f468\u200d\u2696\ufe0f" - MAN_JUDGE_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\u2696\ufe0f" - MAN_JUDGE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\u2696\ufe0f" - MAN_JUDGE_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\u2696\ufe0f" - MAN_JUDGE_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\u2696\ufe0f" - MAN_JUDGE_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\u2696\ufe0f" - WOMAN_JUDGE = "\U0001f469\u200d\u2696\ufe0f" - WOMAN_JUDGE_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\u2696\ufe0f" - WOMAN_JUDGE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\u2696\ufe0f" - WOMAN_JUDGE_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\u2696\ufe0f" - WOMAN_JUDGE_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\u2696\ufe0f" - WOMAN_JUDGE_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\u2696\ufe0f" - MAN_FARMER = "\U0001f468\u200d\U0001f33e" - MAN_FARMER_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f33e" - MAN_FARMER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f33e" - MAN_FARMER_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f33e" - MAN_FARMER_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f33e" - MAN_FARMER_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f33e" - WOMAN_FARMER = "\U0001f469\u200d\U0001f33e" - WOMAN_FARMER_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f33e" - WOMAN_FARMER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f33e" - WOMAN_FARMER_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f33e" - WOMAN_FARMER_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f33e" - WOMAN_FARMER_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f33e" - MAN_COOK = "\U0001f468\u200d\U0001f373" - MAN_COOK_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f373" - MAN_COOK_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f373" - MAN_COOK_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f373" - MAN_COOK_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f373" - MAN_COOK_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f373" - WOMAN_COOK = "\U0001f469\u200d\U0001f373" - WOMAN_COOK_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f373" - WOMAN_COOK_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f373" - WOMAN_COOK_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f373" - WOMAN_COOK_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f373" - WOMAN_COOK_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f373" - MAN_MECHANIC = "\U0001f468\u200d\U0001f527" - MAN_MECHANIC_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f527" - MAN_MECHANIC_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f527" - MAN_MECHANIC_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f527" - MAN_MECHANIC_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f527" - MAN_MECHANIC_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f527" - WOMAN_MECHANIC = "\U0001f469\u200d\U0001f527" - WOMAN_MECHANIC_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f527" - WOMAN_MECHANIC_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f527" - WOMAN_MECHANIC_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f527" - WOMAN_MECHANIC_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f527" - WOMAN_MECHANIC_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f527" - MAN_FACTORY_WORKER = "\U0001f468\u200d\U0001f3ed" - MAN_FACTORY_WORKER_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f3ed" - MAN_FACTORY_WORKER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f3ed" - MAN_FACTORY_WORKER_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f3ed" - MAN_FACTORY_WORKER_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f3ed" - MAN_FACTORY_WORKER_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f3ed" - WOMAN_FACTORY_WORKER = "\U0001f469\u200d\U0001f3ed" - WOMAN_FACTORY_WORKER_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f3ed" - WOMAN_FACTORY_WORKER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f3ed" - WOMAN_FACTORY_WORKER_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f3ed" - WOMAN_FACTORY_WORKER_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f3ed" - WOMAN_FACTORY_WORKER_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f3ed" - MAN_OFFICE_WORKER = "\U0001f468\u200d\U0001f4bc" - MAN_OFFICE_WORKER_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f4bc" - MAN_OFFICE_WORKER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f4bc" - MAN_OFFICE_WORKER_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f4bc" - MAN_OFFICE_WORKER_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f4bc" - MAN_OFFICE_WORKER_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f4bc" - WOMAN_OFFICE_WORKER = "\U0001f469\u200d\U0001f4bc" - WOMAN_OFFICE_WORKER_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f4bc" - WOMAN_OFFICE_WORKER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f4bc" - WOMAN_OFFICE_WORKER_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f4bc" - WOMAN_OFFICE_WORKER_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f4bc" - WOMAN_OFFICE_WORKER_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f4bc" - MAN_SCIENTIST = "\U0001f468\u200d\U0001f52c" - MAN_SCIENTIST_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f52c" - MAN_SCIENTIST_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f52c" - MAN_SCIENTIST_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f52c" - MAN_SCIENTIST_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f52c" - MAN_SCIENTIST_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f52c" - WOMAN_SCIENTIST = "\U0001f469\u200d\U0001f52c" - WOMAN_SCIENTIST_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f52c" - WOMAN_SCIENTIST_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f52c" - WOMAN_SCIENTIST_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f52c" - WOMAN_SCIENTIST_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f52c" - WOMAN_SCIENTIST_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f52c" - MAN_TECHNOLOGIST = "\U0001f468\u200d\U0001f4bb" - MAN_TECHNOLOGIST_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f4bb" - MAN_TECHNOLOGIST_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f4bb" - MAN_TECHNOLOGIST_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f4bb" - MAN_TECHNOLOGIST_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f4bb" - MAN_TECHNOLOGIST_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f4bb" - WOMAN_TECHNOLOGIST = "\U0001f469\u200d\U0001f4bb" - WOMAN_TECHNOLOGIST_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f4bb" - WOMAN_TECHNOLOGIST_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f4bb" - WOMAN_TECHNOLOGIST_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f4bb" - WOMAN_TECHNOLOGIST_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f4bb" - WOMAN_TECHNOLOGIST_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f4bb" - MAN_SINGER = "\U0001f468\u200d\U0001f3a4" - MAN_SINGER_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f3a4" - MAN_SINGER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f3a4" - MAN_SINGER_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f3a4" - MAN_SINGER_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f3a4" - MAN_SINGER_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f3a4" - WOMAN_SINGER = "\U0001f469\u200d\U0001f3a4" - WOMAN_SINGER_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f3a4" - WOMAN_SINGER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f3a4" - WOMAN_SINGER_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f3a4" - WOMAN_SINGER_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f3a4" - WOMAN_SINGER_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f3a4" - MAN_ARTIST = "\U0001f468\u200d\U0001f3a8" - MAN_ARTIST_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f3a8" - MAN_ARTIST_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f3a8" - MAN_ARTIST_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f3a8" - MAN_ARTIST_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f3a8" - MAN_ARTIST_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f3a8" - WOMAN_ARTIST = "\U0001f469\u200d\U0001f3a8" - WOMAN_ARTIST_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f3a8" - WOMAN_ARTIST_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f3a8" - WOMAN_ARTIST_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f3a8" - WOMAN_ARTIST_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f3a8" - WOMAN_ARTIST_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f3a8" - MAN_PILOT = "\U0001f468\u200d\u2708\ufe0f" - MAN_PILOT_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\u2708\ufe0f" - MAN_PILOT_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\u2708\ufe0f" - MAN_PILOT_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\u2708\ufe0f" - MAN_PILOT_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\u2708\ufe0f" - MAN_PILOT_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\u2708\ufe0f" - WOMAN_PILOT = "\U0001f469\u200d\u2708\ufe0f" - WOMAN_PILOT_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\u2708\ufe0f" - WOMAN_PILOT_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\u2708\ufe0f" - WOMAN_PILOT_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\u2708\ufe0f" - WOMAN_PILOT_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\u2708\ufe0f" - WOMAN_PILOT_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\u2708\ufe0f" - MAN_ASTRONAUT = "\U0001f468\u200d\U0001f680" - MAN_ASTRONAUT_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f680" - MAN_ASTRONAUT_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f680" - MAN_ASTRONAUT_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f680" - MAN_ASTRONAUT_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f680" - MAN_ASTRONAUT_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f680" - WOMAN_ASTRONAUT = "\U0001f469\u200d\U0001f680" - WOMAN_ASTRONAUT_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f680" - WOMAN_ASTRONAUT_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f680" - WOMAN_ASTRONAUT_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f680" - WOMAN_ASTRONAUT_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f680" - WOMAN_ASTRONAUT_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f680" - MAN_FIREFIGHTER = "\U0001f468\u200d\U0001f692" - MAN_FIREFIGHTER_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f692" - MAN_FIREFIGHTER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f692" - MAN_FIREFIGHTER_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f692" - MAN_FIREFIGHTER_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f692" - MAN_FIREFIGHTER_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f692" - WOMAN_FIREFIGHTER = "\U0001f469\u200d\U0001f692" - WOMAN_FIREFIGHTER_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f692" - WOMAN_FIREFIGHTER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f692" - WOMAN_FIREFIGHTER_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f692" - WOMAN_FIREFIGHTER_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f692" - WOMAN_FIREFIGHTER_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f692" - POLICE_OFFICER = "\U0001f46e" - POLICE_OFFICER_LIGHT_SKIN_TONE = "\U0001f46e\U0001f3fb" - POLICE_OFFICER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f46e\U0001f3fc" - POLICE_OFFICER_MEDIUM_SKIN_TONE = "\U0001f46e\U0001f3fd" - POLICE_OFFICER_MEDIUM_DARK_SKIN_TONE = "\U0001f46e\U0001f3fe" - POLICE_OFFICER_DARK_SKIN_TONE = "\U0001f46e\U0001f3ff" - MAN_POLICE_OFFICER = "\U0001f46e\u200d\u2642\ufe0f" - MAN_POLICE_OFFICER_LIGHT_SKIN_TONE = "\U0001f46e\U0001f3fb\u200d\u2642\ufe0f" - MAN_POLICE_OFFICER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f46e\U0001f3fc\u200d\u2642\ufe0f" - MAN_POLICE_OFFICER_MEDIUM_SKIN_TONE = "\U0001f46e\U0001f3fd\u200d\u2642\ufe0f" - MAN_POLICE_OFFICER_MEDIUM_DARK_SKIN_TONE = "\U0001f46e\U0001f3fe\u200d\u2642\ufe0f" - MAN_POLICE_OFFICER_DARK_SKIN_TONE = "\U0001f46e\U0001f3ff\u200d\u2642\ufe0f" - WOMAN_POLICE_OFFICER = "\U0001f46e\u200d\u2640\ufe0f" - WOMAN_POLICE_OFFICER_LIGHT_SKIN_TONE = "\U0001f46e\U0001f3fb\u200d\u2640\ufe0f" - WOMAN_POLICE_OFFICER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f46e\U0001f3fc\u200d\u2640\ufe0f" - WOMAN_POLICE_OFFICER_MEDIUM_SKIN_TONE = "\U0001f46e\U0001f3fd\u200d\u2640\ufe0f" - WOMAN_POLICE_OFFICER_MEDIUM_DARK_SKIN_TONE = "\U0001f46e\U0001f3fe\u200d\u2640\ufe0f" - WOMAN_POLICE_OFFICER_DARK_SKIN_TONE = "\U0001f46e\U0001f3ff\u200d\u2640\ufe0f" - DETECTIVE = "\U0001f575\ufe0f" - DETECTIVE_LIGHT_SKIN_TONE = "\U0001f575\U0001f3fb" - DETECTIVE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f575\U0001f3fc" - DETECTIVE_MEDIUM_SKIN_TONE = "\U0001f575\U0001f3fd" - DETECTIVE_MEDIUM_DARK_SKIN_TONE = "\U0001f575\U0001f3fe" - DETECTIVE_DARK_SKIN_TONE = "\U0001f575\U0001f3ff" - MAN_DETECTIVE = "\U0001f575\ufe0f\u200d\u2642\ufe0f" - MAN_DETECTIVE_LIGHT_SKIN_TONE = "\U0001f575\U0001f3fb\u200d\u2642\ufe0f" - MAN_DETECTIVE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f575\U0001f3fc\u200d\u2642\ufe0f" - MAN_DETECTIVE_MEDIUM_SKIN_TONE = "\U0001f575\U0001f3fd\u200d\u2642\ufe0f" - MAN_DETECTIVE_MEDIUM_DARK_SKIN_TONE = "\U0001f575\U0001f3fe\u200d\u2642\ufe0f" - MAN_DETECTIVE_DARK_SKIN_TONE = "\U0001f575\U0001f3ff\u200d\u2642\ufe0f" - WOMAN_DETECTIVE = "\U0001f575\ufe0f\u200d\u2640\ufe0f" - WOMAN_DETECTIVE_LIGHT_SKIN_TONE = "\U0001f575\U0001f3fb\u200d\u2640\ufe0f" - WOMAN_DETECTIVE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f575\U0001f3fc\u200d\u2640\ufe0f" - WOMAN_DETECTIVE_MEDIUM_SKIN_TONE = "\U0001f575\U0001f3fd\u200d\u2640\ufe0f" - WOMAN_DETECTIVE_MEDIUM_DARK_SKIN_TONE = "\U0001f575\U0001f3fe\u200d\u2640\ufe0f" - WOMAN_DETECTIVE_DARK_SKIN_TONE = "\U0001f575\U0001f3ff\u200d\u2640\ufe0f" - GUARD = "\U0001f482" - GUARD_LIGHT_SKIN_TONE = "\U0001f482\U0001f3fb" - GUARD_MEDIUM_LIGHT_SKIN_TONE = "\U0001f482\U0001f3fc" - GUARD_MEDIUM_SKIN_TONE = "\U0001f482\U0001f3fd" - GUARD_MEDIUM_DARK_SKIN_TONE = "\U0001f482\U0001f3fe" - GUARD_DARK_SKIN_TONE = "\U0001f482\U0001f3ff" - MAN_GUARD = "\U0001f482\u200d\u2642\ufe0f" - MAN_GUARD_LIGHT_SKIN_TONE = "\U0001f482\U0001f3fb\u200d\u2642\ufe0f" - MAN_GUARD_MEDIUM_LIGHT_SKIN_TONE = "\U0001f482\U0001f3fc\u200d\u2642\ufe0f" - MAN_GUARD_MEDIUM_SKIN_TONE = "\U0001f482\U0001f3fd\u200d\u2642\ufe0f" - MAN_GUARD_MEDIUM_DARK_SKIN_TONE = "\U0001f482\U0001f3fe\u200d\u2642\ufe0f" - MAN_GUARD_DARK_SKIN_TONE = "\U0001f482\U0001f3ff\u200d\u2642\ufe0f" - WOMAN_GUARD = "\U0001f482\u200d\u2640\ufe0f" - WOMAN_GUARD_LIGHT_SKIN_TONE = "\U0001f482\U0001f3fb\u200d\u2640\ufe0f" - WOMAN_GUARD_MEDIUM_LIGHT_SKIN_TONE = "\U0001f482\U0001f3fc\u200d\u2640\ufe0f" - WOMAN_GUARD_MEDIUM_SKIN_TONE = "\U0001f482\U0001f3fd\u200d\u2640\ufe0f" - WOMAN_GUARD_MEDIUM_DARK_SKIN_TONE = "\U0001f482\U0001f3fe\u200d\u2640\ufe0f" - WOMAN_GUARD_DARK_SKIN_TONE = "\U0001f482\U0001f3ff\u200d\u2640\ufe0f" - CONSTRUCTION_WORKER = "\U0001f477" - CONSTRUCTION_WORKER_LIGHT_SKIN_TONE = "\U0001f477\U0001f3fb" - CONSTRUCTION_WORKER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f477\U0001f3fc" - CONSTRUCTION_WORKER_MEDIUM_SKIN_TONE = "\U0001f477\U0001f3fd" - CONSTRUCTION_WORKER_MEDIUM_DARK_SKIN_TONE = "\U0001f477\U0001f3fe" - CONSTRUCTION_WORKER_DARK_SKIN_TONE = "\U0001f477\U0001f3ff" - MAN_CONSTRUCTION_WORKER = "\U0001f477\u200d\u2642\ufe0f" - MAN_CONSTRUCTION_WORKER_LIGHT_SKIN_TONE = "\U0001f477\U0001f3fb\u200d\u2642\ufe0f" - MAN_CONSTRUCTION_WORKER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f477\U0001f3fc\u200d\u2642\ufe0f" - MAN_CONSTRUCTION_WORKER_MEDIUM_SKIN_TONE = "\U0001f477\U0001f3fd\u200d\u2642\ufe0f" - MAN_CONSTRUCTION_WORKER_MEDIUM_DARK_SKIN_TONE = "\U0001f477\U0001f3fe\u200d\u2642\ufe0f" - MAN_CONSTRUCTION_WORKER_DARK_SKIN_TONE = "\U0001f477\U0001f3ff\u200d\u2642\ufe0f" - WOMAN_CONSTRUCTION_WORKER = "\U0001f477\u200d\u2640\ufe0f" - WOMAN_CONSTRUCTION_WORKER_LIGHT_SKIN_TONE = "\U0001f477\U0001f3fb\u200d\u2640\ufe0f" - WOMAN_CONSTRUCTION_WORKER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f477\U0001f3fc\u200d\u2640\ufe0f" - WOMAN_CONSTRUCTION_WORKER_MEDIUM_SKIN_TONE = "\U0001f477\U0001f3fd\u200d\u2640\ufe0f" - WOMAN_CONSTRUCTION_WORKER_MEDIUM_DARK_SKIN_TONE = "\U0001f477\U0001f3fe\u200d\u2640\ufe0f" - WOMAN_CONSTRUCTION_WORKER_DARK_SKIN_TONE = "\U0001f477\U0001f3ff\u200d\u2640\ufe0f" - PRINCE = "\U0001f934" - PRINCE_LIGHT_SKIN_TONE = "\U0001f934\U0001f3fb" - PRINCE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f934\U0001f3fc" - PRINCE_MEDIUM_SKIN_TONE = "\U0001f934\U0001f3fd" - PRINCE_MEDIUM_DARK_SKIN_TONE = "\U0001f934\U0001f3fe" - PRINCE_DARK_SKIN_TONE = "\U0001f934\U0001f3ff" - PRINCESS = "\U0001f478" - PRINCESS_LIGHT_SKIN_TONE = "\U0001f478\U0001f3fb" - PRINCESS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f478\U0001f3fc" - PRINCESS_MEDIUM_SKIN_TONE = "\U0001f478\U0001f3fd" - PRINCESS_MEDIUM_DARK_SKIN_TONE = "\U0001f478\U0001f3fe" - PRINCESS_DARK_SKIN_TONE = "\U0001f478\U0001f3ff" - PERSON_WEARING_TURBAN = "\U0001f473" - PERSON_WEARING_TURBAN_LIGHT_SKIN_TONE = "\U0001f473\U0001f3fb" - PERSON_WEARING_TURBAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f473\U0001f3fc" - PERSON_WEARING_TURBAN_MEDIUM_SKIN_TONE = "\U0001f473\U0001f3fd" - PERSON_WEARING_TURBAN_MEDIUM_DARK_SKIN_TONE = "\U0001f473\U0001f3fe" - PERSON_WEARING_TURBAN_DARK_SKIN_TONE = "\U0001f473\U0001f3ff" - MAN_WEARING_TURBAN = "\U0001f473\u200d\u2642\ufe0f" - MAN_WEARING_TURBAN_LIGHT_SKIN_TONE = "\U0001f473\U0001f3fb\u200d\u2642\ufe0f" - MAN_WEARING_TURBAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f473\U0001f3fc\u200d\u2642\ufe0f" - MAN_WEARING_TURBAN_MEDIUM_SKIN_TONE = "\U0001f473\U0001f3fd\u200d\u2642\ufe0f" - MAN_WEARING_TURBAN_MEDIUM_DARK_SKIN_TONE = "\U0001f473\U0001f3fe\u200d\u2642\ufe0f" - MAN_WEARING_TURBAN_DARK_SKIN_TONE = "\U0001f473\U0001f3ff\u200d\u2642\ufe0f" - WOMAN_WEARING_TURBAN = "\U0001f473\u200d\u2640\ufe0f" - WOMAN_WEARING_TURBAN_LIGHT_SKIN_TONE = "\U0001f473\U0001f3fb\u200d\u2640\ufe0f" - WOMAN_WEARING_TURBAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f473\U0001f3fc\u200d\u2640\ufe0f" - WOMAN_WEARING_TURBAN_MEDIUM_SKIN_TONE = "\U0001f473\U0001f3fd\u200d\u2640\ufe0f" - WOMAN_WEARING_TURBAN_MEDIUM_DARK_SKIN_TONE = "\U0001f473\U0001f3fe\u200d\u2640\ufe0f" - WOMAN_WEARING_TURBAN_DARK_SKIN_TONE = "\U0001f473\U0001f3ff\u200d\u2640\ufe0f" - MAN_WITH_CHINESE_CAP = "\U0001f472" - MAN_WITH_CHINESE_CAP_LIGHT_SKIN_TONE = "\U0001f472\U0001f3fb" - MAN_WITH_CHINESE_CAP_MEDIUM_LIGHT_SKIN_TONE = "\U0001f472\U0001f3fc" - MAN_WITH_CHINESE_CAP_MEDIUM_SKIN_TONE = "\U0001f472\U0001f3fd" - MAN_WITH_CHINESE_CAP_MEDIUM_DARK_SKIN_TONE = "\U0001f472\U0001f3fe" - MAN_WITH_CHINESE_CAP_DARK_SKIN_TONE = "\U0001f472\U0001f3ff" - WOMAN_WITH_HEADSCARF = "\U0001f9d5" - PERSON_WITH_HEADSCARF_LIGHT_SKIN_TONE = "\U0001f9d5\U0001f3fb" - PERSON_WITH_HEADSCARF_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d5\U0001f3fc" - PERSON_WITH_HEADSCARF_MEDIUM_SKIN_TONE = "\U0001f9d5\U0001f3fd" - PERSON_WITH_HEADSCARF_MEDIUM_DARK_SKIN_TONE = "\U0001f9d5\U0001f3fe" - PERSON_WITH_HEADSCARF_DARK_SKIN_TONE = "\U0001f9d5\U0001f3ff" - MAN_BEARD = "\U0001f9d4" - BEARDED_PERSON_LIGHT_SKIN_TONE = "\U0001f9d4\U0001f3fb" - BEARDED_PERSON_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d4\U0001f3fc" - BEARDED_PERSON_MEDIUM_SKIN_TONE = "\U0001f9d4\U0001f3fd" - BEARDED_PERSON_MEDIUM_DARK_SKIN_TONE = "\U0001f9d4\U0001f3fe" - BEARDED_PERSON_DARK_SKIN_TONE = "\U0001f9d4\U0001f3ff" - PERSON_BLOND_HAIR = "\U0001f471" - BLOND_HAIRED_PERSON_LIGHT_SKIN_TONE = "\U0001f471\U0001f3fb" - BLOND_HAIRED_PERSON_MEDIUM_LIGHT_SKIN_TONE = "\U0001f471\U0001f3fc" - BLOND_HAIRED_PERSON_MEDIUM_SKIN_TONE = "\U0001f471\U0001f3fd" - BLOND_HAIRED_PERSON_MEDIUM_DARK_SKIN_TONE = "\U0001f471\U0001f3fe" - BLOND_HAIRED_PERSON_DARK_SKIN_TONE = "\U0001f471\U0001f3ff" - MAN_BLOND_HAIR = "\U0001f471\u200d\u2642\ufe0f" - BLOND_HAIRED_MAN_LIGHT_SKIN_TONE = "\U0001f471\U0001f3fb\u200d\u2642\ufe0f" - BLOND_HAIRED_MAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f471\U0001f3fc\u200d\u2642\ufe0f" - BLOND_HAIRED_MAN_MEDIUM_SKIN_TONE = "\U0001f471\U0001f3fd\u200d\u2642\ufe0f" - BLOND_HAIRED_MAN_MEDIUM_DARK_SKIN_TONE = "\U0001f471\U0001f3fe\u200d\u2642\ufe0f" - BLOND_HAIRED_MAN_DARK_SKIN_TONE = "\U0001f471\U0001f3ff\u200d\u2642\ufe0f" - WOMAN_BLOND_HAIR = "\U0001f471\u200d\u2640\ufe0f" - BLOND_HAIRED_WOMAN_LIGHT_SKIN_TONE = "\U0001f471\U0001f3fb\u200d\u2640\ufe0f" - BLOND_HAIRED_WOMAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f471\U0001f3fc\u200d\u2640\ufe0f" - BLOND_HAIRED_WOMAN_MEDIUM_SKIN_TONE = "\U0001f471\U0001f3fd\u200d\u2640\ufe0f" - BLOND_HAIRED_WOMAN_MEDIUM_DARK_SKIN_TONE = "\U0001f471\U0001f3fe\u200d\u2640\ufe0f" - BLOND_HAIRED_WOMAN_DARK_SKIN_TONE = "\U0001f471\U0001f3ff\u200d\u2640\ufe0f" - MAN_RED_HAIRED = "\U0001f468\u200d\U0001f9b0" - MAN_RED_HAIRED_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f9b0" - MAN_RED_HAIRED_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f9b0" - MAN_RED_HAIRED_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f9b0" - MAN_RED_HAIRED_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f9b0" - MAN_RED_HAIRED_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f9b0" - WOMAN_RED_HAIRED = "\U0001f469\u200d\U0001f9b0" - WOMAN_RED_HAIRED_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f9b0" - WOMAN_RED_HAIRED_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f9b0" - WOMAN_RED_HAIRED_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f9b0" - WOMAN_RED_HAIRED_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f9b0" - WOMAN_RED_HAIRED_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f9b0" - MAN_CURLY_HAIRED = "\U0001f468\u200d\U0001f9b1" - MAN_CURLY_HAIRED_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f9b1" - MAN_CURLY_HAIRED_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f9b1" - MAN_CURLY_HAIRED_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f9b1" - MAN_CURLY_HAIRED_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f9b1" - MAN_CURLY_HAIRED_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f9b1" - WOMAN_CURLY_HAIRED = "\U0001f469\u200d\U0001f9b1" - WOMAN_CURLY_HAIRED_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f9b1" - WOMAN_CURLY_HAIRED_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f9b1" - WOMAN_CURLY_HAIRED_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f9b1" - WOMAN_CURLY_HAIRED_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f9b1" - WOMAN_CURLY_HAIRED_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f9b1" - MAN_BALD = "\U0001f468\u200d\U0001f9b2" - MAN_BALD_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f9b2" - MAN_BALD_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f9b2" - MAN_BALD_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f9b2" - MAN_BALD_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f9b2" - MAN_BALD_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f9b2" - WOMAN_BALD = "\U0001f469\u200d\U0001f9b2" - WOMAN_BALD_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f9b2" - WOMAN_BALD_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f9b2" - WOMAN_BALD_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f9b2" - WOMAN_BALD_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f9b2" - WOMAN_BALD_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f9b2" - MAN_WHITE_HAIRED = "\U0001f468\u200d\U0001f9b3" - MAN_WHITE_HAIRED_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f9b3" - MAN_WHITE_HAIRED_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f9b3" - MAN_WHITE_HAIRED_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f9b3" - MAN_WHITE_HAIRED_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f9b3" - MAN_WHITE_HAIRED_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f9b3" - WOMAN_WHITE_HAIRED = "\U0001f469\u200d\U0001f9b3" - WOMAN_WHITE_HAIRED_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f9b3" - WOMAN_WHITE_HAIRED_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f9b3" - WOMAN_WHITE_HAIRED_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f9b3" - WOMAN_WHITE_HAIRED_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f9b3" - WOMAN_WHITE_HAIRED_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f9b3" - MAN_IN_TUXEDO = "\U0001f935" - MAN_IN_TUXEDO_LIGHT_SKIN_TONE = "\U0001f935\U0001f3fb" - MAN_IN_TUXEDO_MEDIUM_LIGHT_SKIN_TONE = "\U0001f935\U0001f3fc" - MAN_IN_TUXEDO_MEDIUM_SKIN_TONE = "\U0001f935\U0001f3fd" - MAN_IN_TUXEDO_MEDIUM_DARK_SKIN_TONE = "\U0001f935\U0001f3fe" - MAN_IN_TUXEDO_DARK_SKIN_TONE = "\U0001f935\U0001f3ff" - BRIDE_WITH_VEIL = "\U0001f470" - BRIDE_WITH_VEIL_LIGHT_SKIN_TONE = "\U0001f470\U0001f3fb" - BRIDE_WITH_VEIL_MEDIUM_LIGHT_SKIN_TONE = "\U0001f470\U0001f3fc" - BRIDE_WITH_VEIL_MEDIUM_SKIN_TONE = "\U0001f470\U0001f3fd" - BRIDE_WITH_VEIL_MEDIUM_DARK_SKIN_TONE = "\U0001f470\U0001f3fe" - BRIDE_WITH_VEIL_DARK_SKIN_TONE = "\U0001f470\U0001f3ff" - PREGNANT_WOMAN = "\U0001f930" - PREGNANT_WOMAN_LIGHT_SKIN_TONE = "\U0001f930\U0001f3fb" - PREGNANT_WOMAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f930\U0001f3fc" - PREGNANT_WOMAN_MEDIUM_SKIN_TONE = "\U0001f930\U0001f3fd" - PREGNANT_WOMAN_MEDIUM_DARK_SKIN_TONE = "\U0001f930\U0001f3fe" - PREGNANT_WOMAN_DARK_SKIN_TONE = "\U0001f930\U0001f3ff" - BREAST_FEEDING = "\U0001f931" - BREAST_FEEDING_LIGHT_SKIN_TONE = "\U0001f931\U0001f3fb" - BREAST_FEEDING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f931\U0001f3fc" - BREAST_FEEDING_MEDIUM_SKIN_TONE = "\U0001f931\U0001f3fd" - BREAST_FEEDING_MEDIUM_DARK_SKIN_TONE = "\U0001f931\U0001f3fe" - BREAST_FEEDING_DARK_SKIN_TONE = "\U0001f931\U0001f3ff" - BABY_ANGEL = "\U0001f47c" - BABY_ANGEL_LIGHT_SKIN_TONE = "\U0001f47c\U0001f3fb" - BABY_ANGEL_MEDIUM_LIGHT_SKIN_TONE = "\U0001f47c\U0001f3fc" - BABY_ANGEL_MEDIUM_SKIN_TONE = "\U0001f47c\U0001f3fd" - BABY_ANGEL_MEDIUM_DARK_SKIN_TONE = "\U0001f47c\U0001f3fe" - BABY_ANGEL_DARK_SKIN_TONE = "\U0001f47c\U0001f3ff" - SANTA_CLAUS = "\U0001f385" - SANTA_CLAUS_LIGHT_SKIN_TONE = "\U0001f385\U0001f3fb" - SANTA_CLAUS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f385\U0001f3fc" - SANTA_CLAUS_MEDIUM_SKIN_TONE = "\U0001f385\U0001f3fd" - SANTA_CLAUS_MEDIUM_DARK_SKIN_TONE = "\U0001f385\U0001f3fe" - SANTA_CLAUS_DARK_SKIN_TONE = "\U0001f385\U0001f3ff" - MRS_CLAUS = "\U0001f936" - MRS_CLAUS_LIGHT_SKIN_TONE = "\U0001f936\U0001f3fb" - MRS_CLAUS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f936\U0001f3fc" - MRS_CLAUS_MEDIUM_SKIN_TONE = "\U0001f936\U0001f3fd" - MRS_CLAUS_MEDIUM_DARK_SKIN_TONE = "\U0001f936\U0001f3fe" - MRS_CLAUS_DARK_SKIN_TONE = "\U0001f936\U0001f3ff" - SUPERHERO = "\U0001f9b8" - SUPERHERO_LIGHT_SKIN_TONE = "\U0001f9b8\U0001f3fb" - SUPERHERO_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9b8\U0001f3fc" - SUPERHERO_MEDIUM_SKIN_TONE = "\U0001f9b8\U0001f3fd" - SUPERHERO_MEDIUM_DARK_SKIN_TONE = "\U0001f9b8\U0001f3fe" - SUPERHERO_DARK_SKIN_TONE = "\U0001f9b8\U0001f3ff" - WOMAN_SUPERHERO = "\U0001f9b8\u200d\u2640\ufe0f" - WOMAN_SUPERHERO_LIGHT_SKIN_TONE = "\U0001f9b8\U0001f3fb\u200d\u2640\ufe0f" - WOMAN_SUPERHERO_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9b8\U0001f3fc\u200d\u2640\ufe0f" - WOMAN_SUPERHERO_MEDIUM_SKIN_TONE = "\U0001f9b8\U0001f3fd\u200d\u2640\ufe0f" - WOMAN_SUPERHERO_MEDIUM_DARK_SKIN_TONE = "\U0001f9b8\U0001f3fe\u200d\u2640\ufe0f" - WOMAN_SUPERHERO_DARK_SKIN_TONE = "\U0001f9b8\U0001f3ff\u200d\u2640\ufe0f" - MAN_SUPERHERO = "\U0001f9b8\u200d\u2642\ufe0f" - MAN_SUPERHERO_LIGHT_SKIN_TONE = "\U0001f9b8\U0001f3fb\u200d\u2642\ufe0f" - MAN_SUPERHERO_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9b8\U0001f3fc\u200d\u2642\ufe0f" - MAN_SUPERHERO_MEDIUM_SKIN_TONE = "\U0001f9b8\U0001f3fd\u200d\u2642\ufe0f" - MAN_SUPERHERO_MEDIUM_DARK_SKIN_TONE = "\U0001f9b8\U0001f3fe\u200d\u2642\ufe0f" - WOMAN_WITH_BUNNY_EARS_TYPE_1_2 = "\U0001f46f\U0001f3fb" - MAN_SUPERHERO_DARK_SKIN_TONE = "\U0001f9b8\U0001f3ff\u200d\u2642\ufe0f" - WOMAN_WITH_BUNNY_EARS_TYPE_3 = "\U0001f46f\U0001f3fc" - WOMAN_WITH_BUNNY_EARS_TYPE_4 = "\U0001f46f\U0001f3fd" - SUPERVILLAIN = "\U0001f9b9" - WOMAN_WITH_BUNNY_EARS_TYPE_5 = "\U0001f46f\U0001f3fe" - WOMAN_WITH_BUNNY_EARS_TYPE_6 = "\U0001f46f\U0001f3ff" - SUPERVILLAIN_LIGHT_SKIN_TONE = "\U0001f9b9\U0001f3fb" - SUPERVILLAIN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9b9\U0001f3fc" - MEN_WITH_BUNNY_EARS_PARTYING_TYPE_1_2 = "\U0001f46f\U0001f3fb\u200d\u2642\ufe0f" - SUPERVILLAIN_MEDIUM_SKIN_TONE = "\U0001f9b9\U0001f3fd" - MEN_WITH_BUNNY_EARS_PARTYING_TYPE_3 = "\U0001f46f\U0001f3fc\u200d\u2642\ufe0f" - SUPERVILLAIN_MEDIUM_DARK_SKIN_TONE = "\U0001f9b9\U0001f3fe" - MEN_WITH_BUNNY_EARS_PARTYING_TYPE_4 = "\U0001f46f\U0001f3fd\u200d\u2642\ufe0f" - SUPERVILLAIN_DARK_SKIN_TONE = "\U0001f9b9\U0001f3ff" - MEN_WITH_BUNNY_EARS_PARTYING_TYPE_5 = "\U0001f46f\U0001f3fe\u200d\u2642\ufe0f" - WOMAN_SUPERVILLAIN = "\U0001f9b9\u200d\u2640\ufe0f" - MEN_WITH_BUNNY_EARS_PARTYING_TYPE_6 = "\U0001f46f\U0001f3ff\u200d\u2642\ufe0f" - WOMAN_SUPERVILLAIN_LIGHT_SKIN_TONE = "\U0001f9b9\U0001f3fb\u200d\u2640\ufe0f" - WOMEN_WITH_BUNNY_EARS_PARTYING_TYPE_1_2 = "\U0001f46f\U0001f3fb\u200d\u2640\ufe0f" - WOMAN_SUPERVILLAIN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9b9\U0001f3fc\u200d\u2640\ufe0f" - WOMEN_WITH_BUNNY_EARS_PARTYING_TYPE_3 = "\U0001f46f\U0001f3fc\u200d\u2640\ufe0f" - WOMEN_WITH_BUNNY_EARS_PARTYING_TYPE_4 = "\U0001f46f\U0001f3fd\u200d\u2640\ufe0f" - WOMEN_WITH_BUNNY_EARS_PARTYING_TYPE_5 = "\U0001f46f\U0001f3fe\u200d\u2640\ufe0f" - WOMAN_SUPERVILLAIN_MEDIUM_SKIN_TONE = "\U0001f9b9\U0001f3fd\u200d\u2640\ufe0f" - WOMEN_WITH_BUNNY_EARS_PARTYING_TYPE_6 = "\U0001f46f\U0001f3ff\u200d\u2640\ufe0f" - WOMAN_SUPERVILLAIN_MEDIUM_DARK_SKIN_TONE = "\U0001f9b9\U0001f3fe\u200d\u2640\ufe0f" - WOMAN_SUPERVILLAIN_DARK_SKIN_TONE = "\U0001f9b9\U0001f3ff\u200d\u2640\ufe0f" - MAN_SUPERVILLAIN = "\U0001f9b9\u200d\u2642\ufe0f" - MAN_SUPERVILLAIN_LIGHT_SKIN_TONE = "\U0001f9b9\U0001f3fb\u200d\u2642\ufe0f" - MAN_SUPERVILLAIN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9b9\U0001f3fc\u200d\u2642\ufe0f" - MAN_SUPERVILLAIN_MEDIUM_SKIN_TONE = "\U0001f9b9\U0001f3fd\u200d\u2642\ufe0f" - MAN_AND_WOMAN_HOLDING_HANDS_TYPE_1_2 = "\U0001f46b\U0001f3fb" - MAN_AND_WOMAN_HOLDING_HANDS_TYPE_3 = "\U0001f46b\U0001f3fc" - MAN_SUPERVILLAIN_MEDIUM_DARK_SKIN_TONE = "\U0001f9b9\U0001f3fe\u200d\u2642\ufe0f" - MAN_AND_WOMAN_HOLDING_HANDS_TYPE_4 = "\U0001f46b\U0001f3fd" - MAN_AND_WOMAN_HOLDING_HANDS_TYPE_5 = "\U0001f46b\U0001f3fe" - MAN_SUPERVILLAIN_DARK_SKIN_TONE = "\U0001f9b9\U0001f3ff\u200d\u2642\ufe0f" - MAN_AND_WOMAN_HOLDING_HANDS_TYPE_6 = "\U0001f46b\U0001f3ff" - MAGE = "\U0001f9d9" - TWO_MEN_HOLDING_HANDS_TYPE_1_2 = "\U0001f46c\U0001f3fb" - TWO_MEN_HOLDING_HANDS_TYPE_3 = "\U0001f46c\U0001f3fc" - MAGE_LIGHT_SKIN_TONE = "\U0001f9d9\U0001f3fb" - TWO_MEN_HOLDING_HANDS_TYPE_4 = "\U0001f46c\U0001f3fd" - MAGE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d9\U0001f3fc" - TWO_MEN_HOLDING_HANDS_TYPE_5 = "\U0001f46c\U0001f3fe" - MAGE_MEDIUM_SKIN_TONE = "\U0001f9d9\U0001f3fd" - TWO_MEN_HOLDING_HANDS_TYPE_6 = "\U0001f46c\U0001f3ff" - MAGE_MEDIUM_DARK_SKIN_TONE = "\U0001f9d9\U0001f3fe" - MAGE_DARK_SKIN_TONE = "\U0001f9d9\U0001f3ff" - WOMAN_MAGE = "\U0001f9d9\u200d\u2640\ufe0f" - TWO_WOMEN_HOLDING_HANDS_TYPE_1_2 = "\U0001f46d\U0001f3fb" - TWO_WOMEN_HOLDING_HANDS_TYPE_3 = "\U0001f46d\U0001f3fc" - WOMAN_MAGE_LIGHT_SKIN_TONE = "\U0001f9d9\U0001f3fb\u200d\u2640\ufe0f" - TWO_WOMEN_HOLDING_HANDS_TYPE_4 = "\U0001f46d\U0001f3fd" - TWO_WOMEN_HOLDING_HANDS_TYPE_5 = "\U0001f46d\U0001f3fe" - WOMAN_MAGE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d9\U0001f3fc\u200d\u2640\ufe0f" - TWO_WOMEN_HOLDING_HANDS_TYPE_6 = "\U0001f46d\U0001f3ff" - WOMAN_MAGE_MEDIUM_SKIN_TONE = "\U0001f9d9\U0001f3fd\u200d\u2640\ufe0f" - WOMAN_MAGE_MEDIUM_DARK_SKIN_TONE = "\U0001f9d9\U0001f3fe\u200d\u2640\ufe0f" - WOMAN_MAGE_DARK_SKIN_TONE = "\U0001f9d9\U0001f3ff\u200d\u2640\ufe0f" - MAN_MAGE = "\U0001f9d9\u200d\u2642\ufe0f" - MAN_MAGE_LIGHT_SKIN_TONE = "\U0001f9d9\U0001f3fb\u200d\u2642\ufe0f" - FAMILY_TYPE_1_2 = "\U0001f46a\U0001f3fb" - FAMILY_TYPE_3 = "\U0001f46a\U0001f3fc" - MAN_MAGE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d9\U0001f3fc\u200d\u2642\ufe0f" - FAMILY_TYPE_4 = "\U0001f46a\U0001f3fd" - FAMILY_TYPE_5 = "\U0001f46a\U0001f3fe" - MAN_MAGE_MEDIUM_SKIN_TONE = "\U0001f9d9\U0001f3fd\u200d\u2642\ufe0f" - FAMILY_TYPE_6 = "\U0001f46a\U0001f3ff" - MAN_MAGE_MEDIUM_DARK_SKIN_TONE = "\U0001f9d9\U0001f3fe\u200d\u2642\ufe0f" - MAN_MAGE_DARK_SKIN_TONE = "\U0001f9d9\U0001f3ff\u200d\u2642\ufe0f" - FAIRY = "\U0001f9da" - FAIRY_LIGHT_SKIN_TONE = "\U0001f9da\U0001f3fb" - FAIRY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9da\U0001f3fc" - FAIRY_MEDIUM_SKIN_TONE = "\U0001f9da\U0001f3fd" - FAIRY_MEDIUM_DARK_SKIN_TONE = "\U0001f9da\U0001f3fe" - FAIRY_DARK_SKIN_TONE = "\U0001f9da\U0001f3ff" - WOMAN_FAIRY = "\U0001f9da\u200d\u2640\ufe0f" - WOMAN_FAIRY_LIGHT_SKIN_TONE = "\U0001f9da\U0001f3fb\u200d\u2640\ufe0f" - WOMAN_FAIRY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9da\U0001f3fc\u200d\u2640\ufe0f" - WOMAN_FAIRY_MEDIUM_SKIN_TONE = "\U0001f9da\U0001f3fd\u200d\u2640\ufe0f" - WOMAN_FAIRY_MEDIUM_DARK_SKIN_TONE = "\U0001f9da\U0001f3fe\u200d\u2640\ufe0f" - WOMAN_FAIRY_DARK_SKIN_TONE = "\U0001f9da\U0001f3ff\u200d\u2640\ufe0f" - MAN_FAIRY = "\U0001f9da\u200d\u2642\ufe0f" - MAN_FAIRY_LIGHT_SKIN_TONE = "\U0001f9da\U0001f3fb\u200d\u2642\ufe0f" - MAN_FAIRY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9da\U0001f3fc\u200d\u2642\ufe0f" - MAN_FAIRY_MEDIUM_SKIN_TONE = "\U0001f9da\U0001f3fd\u200d\u2642\ufe0f" - MAN_FAIRY_MEDIUM_DARK_SKIN_TONE = "\U0001f9da\U0001f3fe\u200d\u2642\ufe0f" - MAN_FAIRY_DARK_SKIN_TONE = "\U0001f9da\U0001f3ff\u200d\u2642\ufe0f" - VAMPIRE = "\U0001f9db" - VAMPIRE_LIGHT_SKIN_TONE = "\U0001f9db\U0001f3fb" - VAMPIRE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9db\U0001f3fc" - VAMPIRE_MEDIUM_SKIN_TONE = "\U0001f9db\U0001f3fd" - VAMPIRE_MEDIUM_DARK_SKIN_TONE = "\U0001f9db\U0001f3fe" - VAMPIRE_DARK_SKIN_TONE = "\U0001f9db\U0001f3ff" - WOMAN_VAMPIRE = "\U0001f9db\u200d\u2640\ufe0f" - WOMAN_VAMPIRE_LIGHT_SKIN_TONE = "\U0001f9db\U0001f3fb\u200d\u2640\ufe0f" - WOMAN_VAMPIRE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9db\U0001f3fc\u200d\u2640\ufe0f" - WOMAN_VAMPIRE_MEDIUM_SKIN_TONE = "\U0001f9db\U0001f3fd\u200d\u2640\ufe0f" - WOMAN_VAMPIRE_MEDIUM_DARK_SKIN_TONE = "\U0001f9db\U0001f3fe\u200d\u2640\ufe0f" - WOMAN_VAMPIRE_DARK_SKIN_TONE = "\U0001f9db\U0001f3ff\u200d\u2640\ufe0f" - MAN_VAMPIRE = "\U0001f9db\u200d\u2642\ufe0f" - MAN_VAMPIRE_LIGHT_SKIN_TONE = "\U0001f9db\U0001f3fb\u200d\u2642\ufe0f" - MAN_VAMPIRE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9db\U0001f3fc\u200d\u2642\ufe0f" - MAN_VAMPIRE_MEDIUM_SKIN_TONE = "\U0001f9db\U0001f3fd\u200d\u2642\ufe0f" - MAN_VAMPIRE_MEDIUM_DARK_SKIN_TONE = "\U0001f9db\U0001f3fe\u200d\u2642\ufe0f" - MAN_VAMPIRE_DARK_SKIN_TONE = "\U0001f9db\U0001f3ff\u200d\u2642\ufe0f" - MERPERSON = "\U0001f9dc" - MERPERSON_LIGHT_SKIN_TONE = "\U0001f9dc\U0001f3fb" - MERPERSON_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9dc\U0001f3fc" - MERPERSON_MEDIUM_SKIN_TONE = "\U0001f9dc\U0001f3fd" - MERPERSON_MEDIUM_DARK_SKIN_TONE = "\U0001f9dc\U0001f3fe" - MERPERSON_DARK_SKIN_TONE = "\U0001f9dc\U0001f3ff" - MERMAID = "\U0001f9dc\u200d\u2640\ufe0f" - MERMAID_LIGHT_SKIN_TONE = "\U0001f9dc\U0001f3fb\u200d\u2640\ufe0f" - MERMAID_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9dc\U0001f3fc\u200d\u2640\ufe0f" - MERMAID_MEDIUM_SKIN_TONE = "\U0001f9dc\U0001f3fd\u200d\u2640\ufe0f" - MERMAID_MEDIUM_DARK_SKIN_TONE = "\U0001f9dc\U0001f3fe\u200d\u2640\ufe0f" - MERMAID_DARK_SKIN_TONE = "\U0001f9dc\U0001f3ff\u200d\u2640\ufe0f" - MERMAN = "\U0001f9dc\u200d\u2642\ufe0f" - MERMAN_LIGHT_SKIN_TONE = "\U0001f9dc\U0001f3fb\u200d\u2642\ufe0f" - MERMAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9dc\U0001f3fc\u200d\u2642\ufe0f" - MERMAN_MEDIUM_SKIN_TONE = "\U0001f9dc\U0001f3fd\u200d\u2642\ufe0f" - MERMAN_MEDIUM_DARK_SKIN_TONE = "\U0001f9dc\U0001f3fe\u200d\u2642\ufe0f" - MERMAN_DARK_SKIN_TONE = "\U0001f9dc\U0001f3ff\u200d\u2642\ufe0f" - ELF = "\U0001f9dd" - ELF_LIGHT_SKIN_TONE = "\U0001f9dd\U0001f3fb" - ELF_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9dd\U0001f3fc" - ELF_MEDIUM_SKIN_TONE = "\U0001f9dd\U0001f3fd" - ELF_MEDIUM_DARK_SKIN_TONE = "\U0001f9dd\U0001f3fe" - ELF_DARK_SKIN_TONE = "\U0001f9dd\U0001f3ff" - WOMAN_ELF = "\U0001f9dd\u200d\u2640\ufe0f" - WOMAN_ELF_LIGHT_SKIN_TONE = "\U0001f9dd\U0001f3fb\u200d\u2640\ufe0f" - WOMAN_ELF_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9dd\U0001f3fc\u200d\u2640\ufe0f" - WOMAN_ELF_MEDIUM_SKIN_TONE = "\U0001f9dd\U0001f3fd\u200d\u2640\ufe0f" - WOMAN_ELF_MEDIUM_DARK_SKIN_TONE = "\U0001f9dd\U0001f3fe\u200d\u2640\ufe0f" - WOMAN_ELF_DARK_SKIN_TONE = "\U0001f9dd\U0001f3ff\u200d\u2640\ufe0f" - MAN_ELF = "\U0001f9dd\u200d\u2642\ufe0f" - MAN_ELF_LIGHT_SKIN_TONE = "\U0001f9dd\U0001f3fb\u200d\u2642\ufe0f" - MAN_ELF_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9dd\U0001f3fc\u200d\u2642\ufe0f" - MAN_ELF_MEDIUM_SKIN_TONE = "\U0001f9dd\U0001f3fd\u200d\u2642\ufe0f" - MAN_ELF_MEDIUM_DARK_SKIN_TONE = "\U0001f9dd\U0001f3fe\u200d\u2642\ufe0f" - MAN_ELF_DARK_SKIN_TONE = "\U0001f9dd\U0001f3ff\u200d\u2642\ufe0f" - GENIE = "\U0001f9de" - WOMAN_GENIE = "\U0001f9de\u200d\u2640\ufe0f" - MAN_GENIE = "\U0001f9de\u200d\u2642\ufe0f" - ZOMBIE = "\U0001f9df" - ZOMBIE_LIGHT_SKIN_TONE = "\U0001f9df\U0001f3fb" - ZOMBIE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9df\U0001f3fc" - ZOMBIE_MEDIUM_SKIN_TONE = "\U0001f9df\U0001f3fd" - ZOMBIE_MEDIUM_DARK_SKIN_TONE = "\U0001f9df\U0001f3fe" - ZOMBIE_DARK_SKIN_TONE = "\U0001f9df\U0001f3ff" - WOMAN_ZOMBIE = "\U0001f9df\u200d\u2640\ufe0f" - MAN_ZOMBIE = "\U0001f9df\u200d\u2642\ufe0f" - PERSON_FROWNING = "\U0001f64d" - PERSON_FROWNING_LIGHT_SKIN_TONE = "\U0001f64d\U0001f3fb" - PERSON_FROWNING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f64d\U0001f3fc" - PERSON_FROWNING_MEDIUM_SKIN_TONE = "\U0001f64d\U0001f3fd" - PERSON_FROWNING_MEDIUM_DARK_SKIN_TONE = "\U0001f64d\U0001f3fe" - PERSON_FROWNING_DARK_SKIN_TONE = "\U0001f64d\U0001f3ff" - MAN_FROWNING = "\U0001f64d\u200d\u2642\ufe0f" - MAN_FROWNING_LIGHT_SKIN_TONE = "\U0001f64d\U0001f3fb\u200d\u2642\ufe0f" - MAN_FROWNING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f64d\U0001f3fc\u200d\u2642\ufe0f" - MAN_FROWNING_MEDIUM_SKIN_TONE = "\U0001f64d\U0001f3fd\u200d\u2642\ufe0f" - MAN_FROWNING_MEDIUM_DARK_SKIN_TONE = "\U0001f64d\U0001f3fe\u200d\u2642\ufe0f" - MAN_FROWNING_DARK_SKIN_TONE = "\U0001f64d\U0001f3ff\u200d\u2642\ufe0f" - WOMAN_FROWNING = "\U0001f64d\u200d\u2640\ufe0f" - WOMAN_FROWNING_LIGHT_SKIN_TONE = "\U0001f64d\U0001f3fb\u200d\u2640\ufe0f" - WOMAN_FROWNING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f64d\U0001f3fc\u200d\u2640\ufe0f" - WOMAN_FROWNING_MEDIUM_SKIN_TONE = "\U0001f64d\U0001f3fd\u200d\u2640\ufe0f" - WOMAN_FROWNING_MEDIUM_DARK_SKIN_TONE = "\U0001f64d\U0001f3fe\u200d\u2640\ufe0f" - WOMAN_FROWNING_DARK_SKIN_TONE = "\U0001f64d\U0001f3ff\u200d\u2640\ufe0f" - PERSON_POUTING = "\U0001f64e" - PERSON_POUTING_LIGHT_SKIN_TONE = "\U0001f64e\U0001f3fb" - PERSON_POUTING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f64e\U0001f3fc" - PERSON_POUTING_MEDIUM_SKIN_TONE = "\U0001f64e\U0001f3fd" - PERSON_POUTING_MEDIUM_DARK_SKIN_TONE = "\U0001f64e\U0001f3fe" - PERSON_POUTING_DARK_SKIN_TONE = "\U0001f64e\U0001f3ff" - MAN_POUTING = "\U0001f64e\u200d\u2642\ufe0f" - MAN_POUTING_LIGHT_SKIN_TONE = "\U0001f64e\U0001f3fb\u200d\u2642\ufe0f" - MAN_POUTING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f64e\U0001f3fc\u200d\u2642\ufe0f" - MAN_POUTING_MEDIUM_SKIN_TONE = "\U0001f64e\U0001f3fd\u200d\u2642\ufe0f" - MAN_POUTING_MEDIUM_DARK_SKIN_TONE = "\U0001f64e\U0001f3fe\u200d\u2642\ufe0f" - MAN_POUTING_DARK_SKIN_TONE = "\U0001f64e\U0001f3ff\u200d\u2642\ufe0f" - WOMAN_POUTING = "\U0001f64e\u200d\u2640\ufe0f" - WOMAN_POUTING_LIGHT_SKIN_TONE = "\U0001f64e\U0001f3fb\u200d\u2640\ufe0f" - WOMAN_POUTING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f64e\U0001f3fc\u200d\u2640\ufe0f" - WOMAN_POUTING_MEDIUM_SKIN_TONE = "\U0001f64e\U0001f3fd\u200d\u2640\ufe0f" - WOMAN_POUTING_MEDIUM_DARK_SKIN_TONE = "\U0001f64e\U0001f3fe\u200d\u2640\ufe0f" - WOMAN_POUTING_DARK_SKIN_TONE = "\U0001f64e\U0001f3ff\u200d\u2640\ufe0f" - PERSON_GESTURING_NO = "\U0001f645" - PERSON_GESTURING_NO_LIGHT_SKIN_TONE = "\U0001f645\U0001f3fb" - PERSON_GESTURING_NO_MEDIUM_LIGHT_SKIN_TONE = "\U0001f645\U0001f3fc" - PERSON_GESTURING_NO_MEDIUM_SKIN_TONE = "\U0001f645\U0001f3fd" - PERSON_GESTURING_NO_MEDIUM_DARK_SKIN_TONE = "\U0001f645\U0001f3fe" - PERSON_GESTURING_NO_DARK_SKIN_TONE = "\U0001f645\U0001f3ff" - MAN_GESTURING_NO = "\U0001f645\u200d\u2642\ufe0f" - MAN_GESTURING_NO_LIGHT_SKIN_TONE = "\U0001f645\U0001f3fb\u200d\u2642\ufe0f" - MAN_GESTURING_NO_MEDIUM_LIGHT_SKIN_TONE = "\U0001f645\U0001f3fc\u200d\u2642\ufe0f" - HANDSHAKE_TYPE_1_2 = "\U0001f91d\U0001f3fb" - HANDSHAKE_TYPE_3 = "\U0001f91d\U0001f3fc" - MAN_GESTURING_NO_MEDIUM_SKIN_TONE = "\U0001f645\U0001f3fd\u200d\u2642\ufe0f" - HANDSHAKE_TYPE_4 = "\U0001f91d\U0001f3fd" - HANDSHAKE_TYPE_5 = "\U0001f91d\U0001f3fe" - MAN_GESTURING_NO_MEDIUM_DARK_SKIN_TONE = "\U0001f645\U0001f3fe\u200d\u2642\ufe0f" - HANDSHAKE_TYPE_6 = "\U0001f91d\U0001f3ff" - MAN_GESTURING_NO_DARK_SKIN_TONE = "\U0001f645\U0001f3ff\u200d\u2642\ufe0f" - WOMAN_GESTURING_NO = "\U0001f645\u200d\u2640\ufe0f" - WOMAN_GESTURING_NO_LIGHT_SKIN_TONE = "\U0001f645\U0001f3fb\u200d\u2640\ufe0f" - WOMAN_GESTURING_NO_MEDIUM_LIGHT_SKIN_TONE = "\U0001f645\U0001f3fc\u200d\u2640\ufe0f" - WOMAN_GESTURING_NO_MEDIUM_SKIN_TONE = "\U0001f645\U0001f3fd\u200d\u2640\ufe0f" - WOMAN_GESTURING_NO_MEDIUM_DARK_SKIN_TONE = "\U0001f645\U0001f3fe\u200d\u2640\ufe0f" - WOMAN_GESTURING_NO_DARK_SKIN_TONE = "\U0001f645\U0001f3ff\u200d\u2640\ufe0f" - PERSON_GESTURING_OK = "\U0001f646" - PERSON_GESTURING_OK_LIGHT_SKIN_TONE = "\U0001f646\U0001f3fb" - PERSON_GESTURING_OK_MEDIUM_LIGHT_SKIN_TONE = "\U0001f646\U0001f3fc" - PERSON_GESTURING_OK_MEDIUM_SKIN_TONE = "\U0001f646\U0001f3fd" - PERSON_GESTURING_OK_MEDIUM_DARK_SKIN_TONE = "\U0001f646\U0001f3fe" - PERSON_GESTURING_OK_DARK_SKIN_TONE = "\U0001f646\U0001f3ff" - MAN_GESTURING_OK = "\U0001f646\u200d\u2642\ufe0f" - MAN_GESTURING_OK_LIGHT_SKIN_TONE = "\U0001f646\U0001f3fb\u200d\u2642\ufe0f" - MAN_GESTURING_OK_MEDIUM_LIGHT_SKIN_TONE = "\U0001f646\U0001f3fc\u200d\u2642\ufe0f" - MAN_GESTURING_OK_MEDIUM_SKIN_TONE = "\U0001f646\U0001f3fd\u200d\u2642\ufe0f" - MAN_GESTURING_OK_MEDIUM_DARK_SKIN_TONE = "\U0001f646\U0001f3fe\u200d\u2642\ufe0f" - MAN_GESTURING_OK_DARK_SKIN_TONE = "\U0001f646\U0001f3ff\u200d\u2642\ufe0f" - WOMAN_GESTURING_OK = "\U0001f646\u200d\u2640\ufe0f" - WOMAN_GESTURING_OK_LIGHT_SKIN_TONE = "\U0001f646\U0001f3fb\u200d\u2640\ufe0f" - WOMAN_GESTURING_OK_MEDIUM_LIGHT_SKIN_TONE = "\U0001f646\U0001f3fc\u200d\u2640\ufe0f" - WOMAN_GESTURING_OK_MEDIUM_SKIN_TONE = "\U0001f646\U0001f3fd\u200d\u2640\ufe0f" - WOMAN_GESTURING_OK_MEDIUM_DARK_SKIN_TONE = "\U0001f646\U0001f3fe\u200d\u2640\ufe0f" - WOMAN_GESTURING_OK_DARK_SKIN_TONE = "\U0001f646\U0001f3ff\u200d\u2640\ufe0f" - PERSON_TIPPING_HAND = "\U0001f481" - PERSON_TIPPING_HAND_LIGHT_SKIN_TONE = "\U0001f481\U0001f3fb" - PERSON_TIPPING_HAND_MEDIUM_LIGHT_SKIN_TONE = "\U0001f481\U0001f3fc" - PERSON_TIPPING_HAND_MEDIUM_SKIN_TONE = "\U0001f481\U0001f3fd" - PERSON_TIPPING_HAND_MEDIUM_DARK_SKIN_TONE = "\U0001f481\U0001f3fe" - PERSON_TIPPING_HAND_DARK_SKIN_TONE = "\U0001f481\U0001f3ff" - MAN_TIPPING_HAND = "\U0001f481\u200d\u2642\ufe0f" - MAN_TIPPING_HAND_LIGHT_SKIN_TONE = "\U0001f481\U0001f3fb\u200d\u2642\ufe0f" - MAN_TIPPING_HAND_MEDIUM_LIGHT_SKIN_TONE = "\U0001f481\U0001f3fc\u200d\u2642\ufe0f" - MAN_TIPPING_HAND_MEDIUM_SKIN_TONE = "\U0001f481\U0001f3fd\u200d\u2642\ufe0f" - MAN_TIPPING_HAND_MEDIUM_DARK_SKIN_TONE = "\U0001f481\U0001f3fe\u200d\u2642\ufe0f" - MAN_TIPPING_HAND_DARK_SKIN_TONE = "\U0001f481\U0001f3ff\u200d\u2642\ufe0f" - WOMAN_TIPPING_HAND = "\U0001f481\u200d\u2640\ufe0f" - WOMAN_TIPPING_HAND_LIGHT_SKIN_TONE = "\U0001f481\U0001f3fb\u200d\u2640\ufe0f" - WOMAN_TIPPING_HAND_MEDIUM_LIGHT_SKIN_TONE = "\U0001f481\U0001f3fc\u200d\u2640\ufe0f" - WOMAN_TIPPING_HAND_MEDIUM_SKIN_TONE = "\U0001f481\U0001f3fd\u200d\u2640\ufe0f" - WOMAN_TIPPING_HAND_MEDIUM_DARK_SKIN_TONE = "\U0001f481\U0001f3fe\u200d\u2640\ufe0f" - WOMAN_TIPPING_HAND_DARK_SKIN_TONE = "\U0001f481\U0001f3ff\u200d\u2640\ufe0f" - PERSON_RAISING_HAND = "\U0001f64b" - PERSON_RAISING_HAND_LIGHT_SKIN_TONE = "\U0001f64b\U0001f3fb" - PERSON_RAISING_HAND_MEDIUM_LIGHT_SKIN_TONE = "\U0001f64b\U0001f3fc" - PERSON_RAISING_HAND_MEDIUM_SKIN_TONE = "\U0001f64b\U0001f3fd" - PERSON_RAISING_HAND_MEDIUM_DARK_SKIN_TONE = "\U0001f64b\U0001f3fe" - PERSON_RAISING_HAND_DARK_SKIN_TONE = "\U0001f64b\U0001f3ff" - MAN_RAISING_HAND = "\U0001f64b\u200d\u2642\ufe0f" - MAN_RAISING_HAND_LIGHT_SKIN_TONE = "\U0001f64b\U0001f3fb\u200d\u2642\ufe0f" - MAN_RAISING_HAND_MEDIUM_LIGHT_SKIN_TONE = "\U0001f64b\U0001f3fc\u200d\u2642\ufe0f" - MAN_RAISING_HAND_MEDIUM_SKIN_TONE = "\U0001f64b\U0001f3fd\u200d\u2642\ufe0f" - MAN_RAISING_HAND_MEDIUM_DARK_SKIN_TONE = "\U0001f64b\U0001f3fe\u200d\u2642\ufe0f" - MAN_RAISING_HAND_DARK_SKIN_TONE = "\U0001f64b\U0001f3ff\u200d\u2642\ufe0f" - WOMAN_RAISING_HAND = "\U0001f64b\u200d\u2640\ufe0f" - WOMAN_RAISING_HAND_LIGHT_SKIN_TONE = "\U0001f64b\U0001f3fb\u200d\u2640\ufe0f" - WOMAN_RAISING_HAND_MEDIUM_LIGHT_SKIN_TONE = "\U0001f64b\U0001f3fc\u200d\u2640\ufe0f" - WOMAN_RAISING_HAND_MEDIUM_SKIN_TONE = "\U0001f64b\U0001f3fd\u200d\u2640\ufe0f" - WOMAN_RAISING_HAND_MEDIUM_DARK_SKIN_TONE = "\U0001f64b\U0001f3fe\u200d\u2640\ufe0f" - WOMAN_RAISING_HAND_DARK_SKIN_TONE = "\U0001f64b\U0001f3ff\u200d\u2640\ufe0f" - PERSON_BOWING = "\U0001f647" - PERSON_BOWING_LIGHT_SKIN_TONE = "\U0001f647\U0001f3fb" - PERSON_BOWING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f647\U0001f3fc" - PERSON_BOWING_MEDIUM_SKIN_TONE = "\U0001f647\U0001f3fd" - PERSON_BOWING_MEDIUM_DARK_SKIN_TONE = "\U0001f647\U0001f3fe" - PERSON_BOWING_DARK_SKIN_TONE = "\U0001f647\U0001f3ff" - MAN_BOWING = "\U0001f647\u200d\u2642\ufe0f" - MAN_BOWING_LIGHT_SKIN_TONE = "\U0001f647\U0001f3fb\u200d\u2642\ufe0f" - MAN_BOWING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f647\U0001f3fc\u200d\u2642\ufe0f" - MAN_BOWING_MEDIUM_SKIN_TONE = "\U0001f647\U0001f3fd\u200d\u2642\ufe0f" - MAN_BOWING_MEDIUM_DARK_SKIN_TONE = "\U0001f647\U0001f3fe\u200d\u2642\ufe0f" - MAN_BOWING_DARK_SKIN_TONE = "\U0001f647\U0001f3ff\u200d\u2642\ufe0f" - WOMAN_BOWING = "\U0001f647\u200d\u2640\ufe0f" - WOMAN_BOWING_LIGHT_SKIN_TONE = "\U0001f647\U0001f3fb\u200d\u2640\ufe0f" - WOMAN_BOWING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f647\U0001f3fc\u200d\u2640\ufe0f" - WOMAN_BOWING_MEDIUM_SKIN_TONE = "\U0001f647\U0001f3fd\u200d\u2640\ufe0f" - WOMAN_BOWING_MEDIUM_DARK_SKIN_TONE = "\U0001f647\U0001f3fe\u200d\u2640\ufe0f" - WOMAN_BOWING_DARK_SKIN_TONE = "\U0001f647\U0001f3ff\u200d\u2640\ufe0f" - PERSON_FACEPALMING = "\U0001f926" - PERSON_FACEPALMING_LIGHT_SKIN_TONE = "\U0001f926\U0001f3fb" - PERSON_FACEPALMING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f926\U0001f3fc" - PERSON_FACEPALMING_MEDIUM_SKIN_TONE = "\U0001f926\U0001f3fd" - PERSON_FACEPALMING_MEDIUM_DARK_SKIN_TONE = "\U0001f926\U0001f3fe" - PERSON_FACEPALMING_DARK_SKIN_TONE = "\U0001f926\U0001f3ff" - MAN_FACEPALMING = "\U0001f926\u200d\u2642\ufe0f" - MAN_FACEPALMING_LIGHT_SKIN_TONE = "\U0001f926\U0001f3fb\u200d\u2642\ufe0f" - MAN_FACEPALMING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f926\U0001f3fc\u200d\u2642\ufe0f" - MAN_FACEPALMING_MEDIUM_SKIN_TONE = "\U0001f926\U0001f3fd\u200d\u2642\ufe0f" - MAN_FACEPALMING_MEDIUM_DARK_SKIN_TONE = "\U0001f926\U0001f3fe\u200d\u2642\ufe0f" - MAN_FACEPALMING_DARK_SKIN_TONE = "\U0001f926\U0001f3ff\u200d\u2642\ufe0f" - WOMAN_FACEPALMING = "\U0001f926\u200d\u2640\ufe0f" - WOMAN_FACEPALMING_LIGHT_SKIN_TONE = "\U0001f926\U0001f3fb\u200d\u2640\ufe0f" - WOMAN_FACEPALMING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f926\U0001f3fc\u200d\u2640\ufe0f" - WOMAN_FACEPALMING_MEDIUM_SKIN_TONE = "\U0001f926\U0001f3fd\u200d\u2640\ufe0f" - WOMAN_FACEPALMING_MEDIUM_DARK_SKIN_TONE = "\U0001f926\U0001f3fe\u200d\u2640\ufe0f" - WOMAN_FACEPALMING_DARK_SKIN_TONE = "\U0001f926\U0001f3ff\u200d\u2640\ufe0f" - PERSON_SHRUGGING = "\U0001f937" - PERSON_SHRUGGING_LIGHT_SKIN_TONE = "\U0001f937\U0001f3fb" - PERSON_SHRUGGING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f937\U0001f3fc" - PERSON_SHRUGGING_MEDIUM_SKIN_TONE = "\U0001f937\U0001f3fd" - PERSON_SHRUGGING_MEDIUM_DARK_SKIN_TONE = "\U0001f937\U0001f3fe" - PERSON_SHRUGGING_DARK_SKIN_TONE = "\U0001f937\U0001f3ff" - MAN_SHRUGGING = "\U0001f937\u200d\u2642\ufe0f" - MAN_SHRUGGING_LIGHT_SKIN_TONE = "\U0001f937\U0001f3fb\u200d\u2642\ufe0f" - MAN_SHRUGGING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f937\U0001f3fc\u200d\u2642\ufe0f" - MAN_SHRUGGING_MEDIUM_SKIN_TONE = "\U0001f937\U0001f3fd\u200d\u2642\ufe0f" - MAN_SHRUGGING_MEDIUM_DARK_SKIN_TONE = "\U0001f937\U0001f3fe\u200d\u2642\ufe0f" - MAN_SHRUGGING_DARK_SKIN_TONE = "\U0001f937\U0001f3ff\u200d\u2642\ufe0f" - WOMAN_SHRUGGING = "\U0001f937\u200d\u2640\ufe0f" - WOMAN_SHRUGGING_LIGHT_SKIN_TONE = "\U0001f937\U0001f3fb\u200d\u2640\ufe0f" - WOMAN_SHRUGGING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f937\U0001f3fc\u200d\u2640\ufe0f" - WOMAN_SHRUGGING_MEDIUM_SKIN_TONE = "\U0001f937\U0001f3fd\u200d\u2640\ufe0f" - WOMAN_SHRUGGING_MEDIUM_DARK_SKIN_TONE = "\U0001f937\U0001f3fe\u200d\u2640\ufe0f" - WOMAN_SHRUGGING_DARK_SKIN_TONE = "\U0001f937\U0001f3ff\u200d\u2640\ufe0f" - PERSON_GETTING_MASSAGE = "\U0001f486" - PERSON_GETTING_MASSAGE_LIGHT_SKIN_TONE = "\U0001f486\U0001f3fb" - PERSON_GETTING_MASSAGE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f486\U0001f3fc" - PERSON_GETTING_MASSAGE_MEDIUM_SKIN_TONE = "\U0001f486\U0001f3fd" - PERSON_GETTING_MASSAGE_MEDIUM_DARK_SKIN_TONE = "\U0001f486\U0001f3fe" - PERSON_GETTING_MASSAGE_DARK_SKIN_TONE = "\U0001f486\U0001f3ff" - MAN_GETTING_MASSAGE = "\U0001f486\u200d\u2642\ufe0f" - MAN_GETTING_MASSAGE_LIGHT_SKIN_TONE = "\U0001f486\U0001f3fb\u200d\u2642\ufe0f" - MAN_GETTING_MASSAGE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f486\U0001f3fc\u200d\u2642\ufe0f" - MAN_GETTING_MASSAGE_MEDIUM_SKIN_TONE = "\U0001f486\U0001f3fd\u200d\u2642\ufe0f" - MAN_GETTING_MASSAGE_MEDIUM_DARK_SKIN_TONE = "\U0001f486\U0001f3fe\u200d\u2642\ufe0f" - MAN_GETTING_MASSAGE_DARK_SKIN_TONE = "\U0001f486\U0001f3ff\u200d\u2642\ufe0f" - WOMAN_GETTING_MASSAGE = "\U0001f486\u200d\u2640\ufe0f" - WOMAN_GETTING_MASSAGE_LIGHT_SKIN_TONE = "\U0001f486\U0001f3fb\u200d\u2640\ufe0f" - WOMAN_GETTING_MASSAGE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f486\U0001f3fc\u200d\u2640\ufe0f" - WOMAN_GETTING_MASSAGE_MEDIUM_SKIN_TONE = "\U0001f486\U0001f3fd\u200d\u2640\ufe0f" - WOMAN_GETTING_MASSAGE_MEDIUM_DARK_SKIN_TONE = "\U0001f486\U0001f3fe\u200d\u2640\ufe0f" - WOMAN_GETTING_MASSAGE_DARK_SKIN_TONE = "\U0001f486\U0001f3ff\u200d\u2640\ufe0f" - PERSON_GETTING_HAIRCUT = "\U0001f487" - PERSON_GETTING_HAIRCUT_LIGHT_SKIN_TONE = "\U0001f487\U0001f3fb" - PERSON_GETTING_HAIRCUT_MEDIUM_LIGHT_SKIN_TONE = "\U0001f487\U0001f3fc" - PERSON_GETTING_HAIRCUT_MEDIUM_SKIN_TONE = "\U0001f487\U0001f3fd" - PERSON_GETTING_HAIRCUT_MEDIUM_DARK_SKIN_TONE = "\U0001f487\U0001f3fe" - PERSON_GETTING_HAIRCUT_DARK_SKIN_TONE = "\U0001f487\U0001f3ff" - MAN_GETTING_HAIRCUT = "\U0001f487\u200d\u2642\ufe0f" - MAN_GETTING_HAIRCUT_LIGHT_SKIN_TONE = "\U0001f487\U0001f3fb\u200d\u2642\ufe0f" - MAN_GETTING_HAIRCUT_MEDIUM_LIGHT_SKIN_TONE = "\U0001f487\U0001f3fc\u200d\u2642\ufe0f" - MAN_GETTING_HAIRCUT_MEDIUM_SKIN_TONE = "\U0001f487\U0001f3fd\u200d\u2642\ufe0f" - MAN_GETTING_HAIRCUT_MEDIUM_DARK_SKIN_TONE = "\U0001f487\U0001f3fe\u200d\u2642\ufe0f" - MAN_GETTING_HAIRCUT_DARK_SKIN_TONE = "\U0001f487\U0001f3ff\u200d\u2642\ufe0f" - WOMAN_GETTING_HAIRCUT = "\U0001f487\u200d\u2640\ufe0f" - WOMAN_GETTING_HAIRCUT_LIGHT_SKIN_TONE = "\U0001f487\U0001f3fb\u200d\u2640\ufe0f" - WOMAN_GETTING_HAIRCUT_MEDIUM_LIGHT_SKIN_TONE = "\U0001f487\U0001f3fc\u200d\u2640\ufe0f" - WOMAN_GETTING_HAIRCUT_MEDIUM_SKIN_TONE = "\U0001f487\U0001f3fd\u200d\u2640\ufe0f" - WOMAN_GETTING_HAIRCUT_MEDIUM_DARK_SKIN_TONE = "\U0001f487\U0001f3fe\u200d\u2640\ufe0f" - WOMAN_GETTING_HAIRCUT_DARK_SKIN_TONE = "\U0001f487\U0001f3ff\u200d\u2640\ufe0f" - PERSON_WALKING = "\U0001f6b6" - PERSON_WALKING_LIGHT_SKIN_TONE = "\U0001f6b6\U0001f3fb" - PERSON_WALKING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f6b6\U0001f3fc" - PERSON_WALKING_MEDIUM_SKIN_TONE = "\U0001f6b6\U0001f3fd" - PERSON_WALKING_MEDIUM_DARK_SKIN_TONE = "\U0001f6b6\U0001f3fe" - PERSON_WALKING_DARK_SKIN_TONE = "\U0001f6b6\U0001f3ff" - MAN_WALKING = "\U0001f6b6\u200d\u2642\ufe0f" - MAN_WALKING_LIGHT_SKIN_TONE = "\U0001f6b6\U0001f3fb\u200d\u2642\ufe0f" - MAN_WALKING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f6b6\U0001f3fc\u200d\u2642\ufe0f" - MAN_WALKING_MEDIUM_SKIN_TONE = "\U0001f6b6\U0001f3fd\u200d\u2642\ufe0f" - MAN_WALKING_MEDIUM_DARK_SKIN_TONE = "\U0001f6b6\U0001f3fe\u200d\u2642\ufe0f" - MAN_WALKING_DARK_SKIN_TONE = "\U0001f6b6\U0001f3ff\u200d\u2642\ufe0f" - WOMAN_WALKING = "\U0001f6b6\u200d\u2640\ufe0f" - WOMAN_WALKING_LIGHT_SKIN_TONE = "\U0001f6b6\U0001f3fb\u200d\u2640\ufe0f" - WOMAN_WALKING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f6b6\U0001f3fc\u200d\u2640\ufe0f" - WOMAN_WALKING_MEDIUM_SKIN_TONE = "\U0001f6b6\U0001f3fd\u200d\u2640\ufe0f" - WOMAN_WALKING_MEDIUM_DARK_SKIN_TONE = "\U0001f6b6\U0001f3fe\u200d\u2640\ufe0f" - WOMAN_WALKING_DARK_SKIN_TONE = "\U0001f6b6\U0001f3ff\u200d\u2640\ufe0f" - PERSON_RUNNING = "\U0001f3c3" - PERSON_RUNNING_LIGHT_SKIN_TONE = "\U0001f3c3\U0001f3fb" - PERSON_RUNNING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f3c3\U0001f3fc" - PERSON_RUNNING_MEDIUM_SKIN_TONE = "\U0001f3c3\U0001f3fd" - PERSON_RUNNING_MEDIUM_DARK_SKIN_TONE = "\U0001f3c3\U0001f3fe" - PERSON_RUNNING_DARK_SKIN_TONE = "\U0001f3c3\U0001f3ff" - MAN_RUNNING = "\U0001f3c3\u200d\u2642\ufe0f" - MAN_RUNNING_LIGHT_SKIN_TONE = "\U0001f3c3\U0001f3fb\u200d\u2642\ufe0f" - MAN_RUNNING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f3c3\U0001f3fc\u200d\u2642\ufe0f" - MAN_RUNNING_MEDIUM_SKIN_TONE = "\U0001f3c3\U0001f3fd\u200d\u2642\ufe0f" - MAN_RUNNING_MEDIUM_DARK_SKIN_TONE = "\U0001f3c3\U0001f3fe\u200d\u2642\ufe0f" - MAN_RUNNING_DARK_SKIN_TONE = "\U0001f3c3\U0001f3ff\u200d\u2642\ufe0f" - WOMAN_RUNNING = "\U0001f3c3\u200d\u2640\ufe0f" - WOMAN_RUNNING_LIGHT_SKIN_TONE = "\U0001f3c3\U0001f3fb\u200d\u2640\ufe0f" - WOMAN_RUNNING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f3c3\U0001f3fc\u200d\u2640\ufe0f" - WOMAN_RUNNING_MEDIUM_SKIN_TONE = "\U0001f3c3\U0001f3fd\u200d\u2640\ufe0f" - WOMAN_RUNNING_MEDIUM_DARK_SKIN_TONE = "\U0001f3c3\U0001f3fe\u200d\u2640\ufe0f" - WOMAN_RUNNING_DARK_SKIN_TONE = "\U0001f3c3\U0001f3ff\u200d\u2640\ufe0f" - WOMAN_DANCING = "\U0001f483" - WOMAN_DANCING_LIGHT_SKIN_TONE = "\U0001f483\U0001f3fb" - WOMAN_DANCING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f483\U0001f3fc" - WOMAN_DANCING_MEDIUM_SKIN_TONE = "\U0001f483\U0001f3fd" - WOMAN_DANCING_MEDIUM_DARK_SKIN_TONE = "\U0001f483\U0001f3fe" - WOMAN_DANCING_DARK_SKIN_TONE = "\U0001f483\U0001f3ff" - MAN_DANCING = "\U0001f57a" - MAN_DANCING_LIGHT_SKIN_TONE = "\U0001f57a\U0001f3fb" - MAN_DANCING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f57a\U0001f3fc" - MAN_DANCING_MEDIUM_SKIN_TONE = "\U0001f57a\U0001f3fd" - MAN_DANCING_MEDIUM_DARK_SKIN_TONE = "\U0001f57a\U0001f3fe" - MAN_DANCING_DARK_SKIN_TONE = "\U0001f57a\U0001f3ff" - PEOPLE_WITH_BUNNY_EARS = "\U0001f46f" - MEN_WITH_BUNNY_EARS = "\U0001f46f\u200d\u2642\ufe0f" - WOMEN_WITH_BUNNY_EARS = "\U0001f46f\u200d\u2640\ufe0f" - PERSON_IN_STEAMY_ROOM = "\U0001f9d6" - PERSON_IN_STEAMY_ROOM_LIGHT_SKIN_TONE = "\U0001f9d6\U0001f3fb" - PERSON_IN_STEAMY_ROOM_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d6\U0001f3fc" - PERSON_IN_STEAMY_ROOM_MEDIUM_SKIN_TONE = "\U0001f9d6\U0001f3fd" - PERSON_IN_STEAMY_ROOM_MEDIUM_DARK_SKIN_TONE = "\U0001f9d6\U0001f3fe" - PERSON_IN_STEAMY_ROOM_DARK_SKIN_TONE = "\U0001f9d6\U0001f3ff" - WOMAN_IN_STEAMY_ROOM = "\U0001f9d6\u200d\u2640\ufe0f" - WOMAN_IN_STEAMY_ROOM_LIGHT_SKIN_TONE = "\U0001f9d6\U0001f3fb\u200d\u2640\ufe0f" - WOMAN_IN_STEAMY_ROOM_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d6\U0001f3fc\u200d\u2640\ufe0f" - WOMAN_IN_STEAMY_ROOM_MEDIUM_SKIN_TONE = "\U0001f9d6\U0001f3fd\u200d\u2640\ufe0f" - WOMAN_IN_STEAMY_ROOM_MEDIUM_DARK_SKIN_TONE = "\U0001f9d6\U0001f3fe\u200d\u2640\ufe0f" - WOMAN_IN_STEAMY_ROOM_DARK_SKIN_TONE = "\U0001f9d6\U0001f3ff\u200d\u2640\ufe0f" - MAN_IN_STEAMY_ROOM = "\U0001f9d6\u200d\u2642\ufe0f" - MAN_IN_STEAMY_ROOM_LIGHT_SKIN_TONE = "\U0001f9d6\U0001f3fb\u200d\u2642\ufe0f" - MAN_IN_STEAMY_ROOM_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d6\U0001f3fc\u200d\u2642\ufe0f" - MAN_IN_STEAMY_ROOM_MEDIUM_SKIN_TONE = "\U0001f9d6\U0001f3fd\u200d\u2642\ufe0f" - MAN_IN_STEAMY_ROOM_MEDIUM_DARK_SKIN_TONE = "\U0001f9d6\U0001f3fe\u200d\u2642\ufe0f" - MAN_IN_STEAMY_ROOM_DARK_SKIN_TONE = "\U0001f9d6\U0001f3ff\u200d\u2642\ufe0f" - PERSON_CLIMBING = "\U0001f9d7" - PERSON_CLIMBING_LIGHT_SKIN_TONE = "\U0001f9d7\U0001f3fb" - PERSON_CLIMBING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d7\U0001f3fc" - PERSON_CLIMBING_MEDIUM_SKIN_TONE = "\U0001f9d7\U0001f3fd" - PERSON_CLIMBING_MEDIUM_DARK_SKIN_TONE = "\U0001f9d7\U0001f3fe" - PERSON_CLIMBING_DARK_SKIN_TONE = "\U0001f9d7\U0001f3ff" - WOMAN_CLIMBING = "\U0001f9d7\u200d\u2640\ufe0f" - WOMAN_CLIMBING_LIGHT_SKIN_TONE = "\U0001f9d7\U0001f3fb\u200d\u2640\ufe0f" - WOMAN_CLIMBING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d7\U0001f3fc\u200d\u2640\ufe0f" - WOMAN_CLIMBING_MEDIUM_SKIN_TONE = "\U0001f9d7\U0001f3fd\u200d\u2640\ufe0f" - WOMAN_CLIMBING_MEDIUM_DARK_SKIN_TONE = "\U0001f9d7\U0001f3fe\u200d\u2640\ufe0f" - WOMAN_CLIMBING_DARK_SKIN_TONE = "\U0001f9d7\U0001f3ff\u200d\u2640\ufe0f" - MAN_CLIMBING = "\U0001f9d7\u200d\u2642\ufe0f" - MAN_CLIMBING_LIGHT_SKIN_TONE = "\U0001f9d7\U0001f3fb\u200d\u2642\ufe0f" - MAN_CLIMBING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d7\U0001f3fc\u200d\u2642\ufe0f" - MAN_CLIMBING_MEDIUM_SKIN_TONE = "\U0001f9d7\U0001f3fd\u200d\u2642\ufe0f" - MAN_CLIMBING_MEDIUM_DARK_SKIN_TONE = "\U0001f9d7\U0001f3fe\u200d\u2642\ufe0f" - MAN_CLIMBING_DARK_SKIN_TONE = "\U0001f9d7\U0001f3ff\u200d\u2642\ufe0f" - PERSON_IN_LOTUS_POSITION = "\U0001f9d8" - PERSON_IN_LOTUS_POSITION_LIGHT_SKIN_TONE = "\U0001f9d8\U0001f3fb" - PERSON_IN_LOTUS_POSITION_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d8\U0001f3fc" - PERSON_IN_LOTUS_POSITION_MEDIUM_SKIN_TONE = "\U0001f9d8\U0001f3fd" - PERSON_IN_LOTUS_POSITION_MEDIUM_DARK_SKIN_TONE = "\U0001f9d8\U0001f3fe" - PERSON_IN_LOTUS_POSITION_DARK_SKIN_TONE = "\U0001f9d8\U0001f3ff" - WOMAN_IN_LOTUS_POSITION = "\U0001f9d8\u200d\u2640\ufe0f" - WOMAN_IN_LOTUS_POSITION_LIGHT_SKIN_TONE = "\U0001f9d8\U0001f3fb\u200d\u2640\ufe0f" - WOMAN_IN_LOTUS_POSITION_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d8\U0001f3fc\u200d\u2640\ufe0f" - WOMAN_IN_LOTUS_POSITION_MEDIUM_SKIN_TONE = "\U0001f9d8\U0001f3fd\u200d\u2640\ufe0f" - WOMAN_IN_LOTUS_POSITION_MEDIUM_DARK_SKIN_TONE = "\U0001f9d8\U0001f3fe\u200d\u2640\ufe0f" - WOMAN_IN_LOTUS_POSITION_DARK_SKIN_TONE = "\U0001f9d8\U0001f3ff\u200d\u2640\ufe0f" - MAN_IN_LOTUS_POSITION = "\U0001f9d8\u200d\u2642\ufe0f" - MAN_IN_LOTUS_POSITION_LIGHT_SKIN_TONE = "\U0001f9d8\U0001f3fb\u200d\u2642\ufe0f" - MAN_IN_LOTUS_POSITION_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d8\U0001f3fc\u200d\u2642\ufe0f" - MAN_IN_LOTUS_POSITION_MEDIUM_SKIN_TONE = "\U0001f9d8\U0001f3fd\u200d\u2642\ufe0f" - MAN_IN_LOTUS_POSITION_MEDIUM_DARK_SKIN_TONE = "\U0001f9d8\U0001f3fe\u200d\u2642\ufe0f" - MAN_IN_LOTUS_POSITION_DARK_SKIN_TONE = "\U0001f9d8\U0001f3ff\u200d\u2642\ufe0f" - PERSON_TAKING_BATH = "\U0001f6c0" - PERSON_TAKING_BATH_LIGHT_SKIN_TONE = "\U0001f6c0\U0001f3fb" - PERSON_TAKING_BATH_MEDIUM_LIGHT_SKIN_TONE = "\U0001f6c0\U0001f3fc" - PERSON_TAKING_BATH_MEDIUM_SKIN_TONE = "\U0001f6c0\U0001f3fd" - PERSON_TAKING_BATH_MEDIUM_DARK_SKIN_TONE = "\U0001f6c0\U0001f3fe" - PERSON_TAKING_BATH_DARK_SKIN_TONE = "\U0001f6c0\U0001f3ff" - PERSON_IN_BED = "\U0001f6cc" - PERSON_IN_BED_LIGHT_SKIN_TONE = "\U0001f6cc\U0001f3fb" - PERSON_IN_BED_MEDIUM_LIGHT_SKIN_TONE = "\U0001f6cc\U0001f3fc" - PERSON_IN_BED_MEDIUM_SKIN_TONE = "\U0001f6cc\U0001f3fd" - PERSON_IN_BED_MEDIUM_DARK_SKIN_TONE = "\U0001f6cc\U0001f3fe" - PERSON_IN_BED_DARK_SKIN_TONE = "\U0001f6cc\U0001f3ff" - MAN_IN_SUIT_LEVITATING = "\U0001f574\ufe0f" - MAN_IN_SUIT_LEVITATING_LIGHT_SKIN_TONE = "\U0001f574\U0001f3fb" - MAN_IN_SUIT_LEVITATING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f574\U0001f3fc" - MAN_IN_SUIT_LEVITATING_MEDIUM_SKIN_TONE = "\U0001f574\U0001f3fd" - MAN_IN_SUIT_LEVITATING_MEDIUM_DARK_SKIN_TONE = "\U0001f574\U0001f3fe" - MAN_IN_SUIT_LEVITATING_DARK_SKIN_TONE = "\U0001f574\U0001f3ff" - SPEAKING_HEAD = "\U0001f5e3\ufe0f" - BUST_IN_SILHOUETTE = "\U0001f464" - BUSTS_IN_SILHOUETTE = "\U0001f465" - PERSON_FENCING = "\U0001f93a" - HORSE_RACING = "\U0001f3c7" - HORSE_RACING_LIGHT_SKIN_TONE = "\U0001f3c7\U0001f3fb" - HORSE_RACING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f3c7\U0001f3fc" - HORSE_RACING_MEDIUM_SKIN_TONE = "\U0001f3c7\U0001f3fd" - HORSE_RACING_MEDIUM_DARK_SKIN_TONE = "\U0001f3c7\U0001f3fe" - HORSE_RACING_DARK_SKIN_TONE = "\U0001f3c7\U0001f3ff" - SKIER = "\u26f7\ufe0f" - SNOWBOARDER = "\U0001f3c2" - SNOWBOARDER_LIGHT_SKIN_TONE = "\U0001f3c2\U0001f3fb" - SNOWBOARDER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f3c2\U0001f3fc" - SNOWBOARDER_MEDIUM_SKIN_TONE = "\U0001f3c2\U0001f3fd" - SNOWBOARDER_MEDIUM_DARK_SKIN_TONE = "\U0001f3c2\U0001f3fe" - SNOWBOARDER_DARK_SKIN_TONE = "\U0001f3c2\U0001f3ff" - PERSON_GOLFING = "\U0001f3cc\ufe0f" - PERSON_GOLFING_LIGHT_SKIN_TONE = "\U0001f3cc\U0001f3fb" - PERSON_GOLFING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f3cc\U0001f3fc" - PERSON_GOLFING_MEDIUM_SKIN_TONE = "\U0001f3cc\U0001f3fd" - PERSON_GOLFING_MEDIUM_DARK_SKIN_TONE = "\U0001f3cc\U0001f3fe" - PERSON_GOLFING_DARK_SKIN_TONE = "\U0001f3cc\U0001f3ff" - MAN_GOLFING = "\U0001f3cc\ufe0f\u200d\u2642\ufe0f" - MAN_GOLFING_LIGHT_SKIN_TONE = "\U0001f3cc\U0001f3fb\u200d\u2642\ufe0f" - MAN_GOLFING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f3cc\U0001f3fc\u200d\u2642\ufe0f" - MAN_GOLFING_MEDIUM_SKIN_TONE = "\U0001f3cc\U0001f3fd\u200d\u2642\ufe0f" - MAN_GOLFING_MEDIUM_DARK_SKIN_TONE = "\U0001f3cc\U0001f3fe\u200d\u2642\ufe0f" - MAN_GOLFING_DARK_SKIN_TONE = "\U0001f3cc\U0001f3ff\u200d\u2642\ufe0f" - WOMAN_GOLFING = "\U0001f3cc\ufe0f\u200d\u2640\ufe0f" - WOMAN_GOLFING_LIGHT_SKIN_TONE = "\U0001f3cc\U0001f3fb\u200d\u2640\ufe0f" - WOMAN_GOLFING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f3cc\U0001f3fc\u200d\u2640\ufe0f" - WOMAN_GOLFING_MEDIUM_SKIN_TONE = "\U0001f3cc\U0001f3fd\u200d\u2640\ufe0f" - WOMAN_GOLFING_MEDIUM_DARK_SKIN_TONE = "\U0001f3cc\U0001f3fe\u200d\u2640\ufe0f" - WOMAN_GOLFING_DARK_SKIN_TONE = "\U0001f3cc\U0001f3ff\u200d\u2640\ufe0f" - PERSON_SURFING = "\U0001f3c4" - PERSON_SURFING_LIGHT_SKIN_TONE = "\U0001f3c4\U0001f3fb" - PERSON_SURFING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f3c4\U0001f3fc" - PERSON_SURFING_MEDIUM_SKIN_TONE = "\U0001f3c4\U0001f3fd" - PERSON_SURFING_MEDIUM_DARK_SKIN_TONE = "\U0001f3c4\U0001f3fe" - PERSON_SURFING_DARK_SKIN_TONE = "\U0001f3c4\U0001f3ff" - MAN_SURFING = "\U0001f3c4\u200d\u2642\ufe0f" - MAN_SURFING_LIGHT_SKIN_TONE = "\U0001f3c4\U0001f3fb\u200d\u2642\ufe0f" - MAN_SURFING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f3c4\U0001f3fc\u200d\u2642\ufe0f" - MAN_SURFING_MEDIUM_SKIN_TONE = "\U0001f3c4\U0001f3fd\u200d\u2642\ufe0f" - MAN_SURFING_MEDIUM_DARK_SKIN_TONE = "\U0001f3c4\U0001f3fe\u200d\u2642\ufe0f" - MAN_SURFING_DARK_SKIN_TONE = "\U0001f3c4\U0001f3ff\u200d\u2642\ufe0f" - WOMAN_SURFING = "\U0001f3c4\u200d\u2640\ufe0f" - WOMAN_SURFING_LIGHT_SKIN_TONE = "\U0001f3c4\U0001f3fb\u200d\u2640\ufe0f" - WOMAN_SURFING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f3c4\U0001f3fc\u200d\u2640\ufe0f" - WOMAN_SURFING_MEDIUM_SKIN_TONE = "\U0001f3c4\U0001f3fd\u200d\u2640\ufe0f" - WOMAN_SURFING_MEDIUM_DARK_SKIN_TONE = "\U0001f3c4\U0001f3fe\u200d\u2640\ufe0f" - WOMAN_SURFING_DARK_SKIN_TONE = "\U0001f3c4\U0001f3ff\u200d\u2640\ufe0f" - PERSON_ROWING_BOAT = "\U0001f6a3" - PERSON_ROWING_BOAT_LIGHT_SKIN_TONE = "\U0001f6a3\U0001f3fb" - PERSON_ROWING_BOAT_MEDIUM_LIGHT_SKIN_TONE = "\U0001f6a3\U0001f3fc" - PERSON_ROWING_BOAT_MEDIUM_SKIN_TONE = "\U0001f6a3\U0001f3fd" - PERSON_ROWING_BOAT_MEDIUM_DARK_SKIN_TONE = "\U0001f6a3\U0001f3fe" - PERSON_ROWING_BOAT_DARK_SKIN_TONE = "\U0001f6a3\U0001f3ff" - MAN_ROWING_BOAT = "\U0001f6a3\u200d\u2642\ufe0f" - MAN_ROWING_BOAT_LIGHT_SKIN_TONE = "\U0001f6a3\U0001f3fb\u200d\u2642\ufe0f" - MAN_ROWING_BOAT_MEDIUM_LIGHT_SKIN_TONE = "\U0001f6a3\U0001f3fc\u200d\u2642\ufe0f" - MAN_ROWING_BOAT_MEDIUM_SKIN_TONE = "\U0001f6a3\U0001f3fd\u200d\u2642\ufe0f" - MAN_ROWING_BOAT_MEDIUM_DARK_SKIN_TONE = "\U0001f6a3\U0001f3fe\u200d\u2642\ufe0f" - MAN_ROWING_BOAT_DARK_SKIN_TONE = "\U0001f6a3\U0001f3ff\u200d\u2642\ufe0f" - WOMAN_ROWING_BOAT = "\U0001f6a3\u200d\u2640\ufe0f" - WOMAN_ROWING_BOAT_LIGHT_SKIN_TONE = "\U0001f6a3\U0001f3fb\u200d\u2640\ufe0f" - WOMAN_ROWING_BOAT_MEDIUM_LIGHT_SKIN_TONE = "\U0001f6a3\U0001f3fc\u200d\u2640\ufe0f" - WOMAN_ROWING_BOAT_MEDIUM_SKIN_TONE = "\U0001f6a3\U0001f3fd\u200d\u2640\ufe0f" - WOMAN_ROWING_BOAT_MEDIUM_DARK_SKIN_TONE = "\U0001f6a3\U0001f3fe\u200d\u2640\ufe0f" - WOMAN_ROWING_BOAT_DARK_SKIN_TONE = "\U0001f6a3\U0001f3ff\u200d\u2640\ufe0f" - PERSON_SWIMMING = "\U0001f3ca" - PERSON_SWIMMING_LIGHT_SKIN_TONE = "\U0001f3ca\U0001f3fb" - PERSON_SWIMMING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f3ca\U0001f3fc" - PERSON_SWIMMING_MEDIUM_SKIN_TONE = "\U0001f3ca\U0001f3fd" - PERSON_SWIMMING_MEDIUM_DARK_SKIN_TONE = "\U0001f3ca\U0001f3fe" - PERSON_SWIMMING_DARK_SKIN_TONE = "\U0001f3ca\U0001f3ff" - MAN_SWIMMING = "\U0001f3ca\u200d\u2642\ufe0f" - MAN_SWIMMING_LIGHT_SKIN_TONE = "\U0001f3ca\U0001f3fb\u200d\u2642\ufe0f" - MAN_SWIMMING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f3ca\U0001f3fc\u200d\u2642\ufe0f" - MAN_SWIMMING_MEDIUM_SKIN_TONE = "\U0001f3ca\U0001f3fd\u200d\u2642\ufe0f" - MAN_SWIMMING_MEDIUM_DARK_SKIN_TONE = "\U0001f3ca\U0001f3fe\u200d\u2642\ufe0f" - MAN_SWIMMING_DARK_SKIN_TONE = "\U0001f3ca\U0001f3ff\u200d\u2642\ufe0f" - WOMAN_SWIMMING = "\U0001f3ca\u200d\u2640\ufe0f" - WOMAN_SWIMMING_LIGHT_SKIN_TONE = "\U0001f3ca\U0001f3fb\u200d\u2640\ufe0f" - WOMAN_SWIMMING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f3ca\U0001f3fc\u200d\u2640\ufe0f" - WOMAN_SWIMMING_MEDIUM_SKIN_TONE = "\U0001f3ca\U0001f3fd\u200d\u2640\ufe0f" - WOMAN_SWIMMING_MEDIUM_DARK_SKIN_TONE = "\U0001f3ca\U0001f3fe\u200d\u2640\ufe0f" - WOMAN_SWIMMING_DARK_SKIN_TONE = "\U0001f3ca\U0001f3ff\u200d\u2640\ufe0f" - PERSON_BOUNCING_BALL = "\u26f9\ufe0f" - PERSON_BOUNCING_BALL_LIGHT_SKIN_TONE = "\u26f9\U0001f3fb" - PERSON_BOUNCING_BALL_MEDIUM_LIGHT_SKIN_TONE = "\u26f9\U0001f3fc" - PERSON_BOUNCING_BALL_MEDIUM_SKIN_TONE = "\u26f9\U0001f3fd" - PERSON_BOUNCING_BALL_MEDIUM_DARK_SKIN_TONE = "\u26f9\U0001f3fe" - PERSON_BOUNCING_BALL_DARK_SKIN_TONE = "\u26f9\U0001f3ff" - MAN_BOUNCING_BALL = "\u26f9\ufe0f\u200d\u2642\ufe0f" - MAN_BOUNCING_BALL_LIGHT_SKIN_TONE = "\u26f9\U0001f3fb\u200d\u2642\ufe0f" - MAN_BOUNCING_BALL_MEDIUM_LIGHT_SKIN_TONE = "\u26f9\U0001f3fc\u200d\u2642\ufe0f" - MAN_BOUNCING_BALL_MEDIUM_SKIN_TONE = "\u26f9\U0001f3fd\u200d\u2642\ufe0f" - MAN_BOUNCING_BALL_MEDIUM_DARK_SKIN_TONE = "\u26f9\U0001f3fe\u200d\u2642\ufe0f" - MAN_BOUNCING_BALL_DARK_SKIN_TONE = "\u26f9\U0001f3ff\u200d\u2642\ufe0f" - WOMAN_BOUNCING_BALL = "\u26f9\ufe0f\u200d\u2640\ufe0f" - WOMAN_BOUNCING_BALL_LIGHT_SKIN_TONE = "\u26f9\U0001f3fb\u200d\u2640\ufe0f" - WOMAN_BOUNCING_BALL_MEDIUM_LIGHT_SKIN_TONE = "\u26f9\U0001f3fc\u200d\u2640\ufe0f" - WOMAN_BOUNCING_BALL_MEDIUM_SKIN_TONE = "\u26f9\U0001f3fd\u200d\u2640\ufe0f" - WOMAN_BOUNCING_BALL_MEDIUM_DARK_SKIN_TONE = "\u26f9\U0001f3fe\u200d\u2640\ufe0f" - WOMAN_BOUNCING_BALL_DARK_SKIN_TONE = "\u26f9\U0001f3ff\u200d\u2640\ufe0f" - PERSON_LIFTING_WEIGHTS = "\U0001f3cb\ufe0f" - PERSON_LIFTING_WEIGHTS_LIGHT_SKIN_TONE = "\U0001f3cb\U0001f3fb" - PERSON_LIFTING_WEIGHTS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f3cb\U0001f3fc" - PERSON_LIFTING_WEIGHTS_MEDIUM_SKIN_TONE = "\U0001f3cb\U0001f3fd" - PERSON_LIFTING_WEIGHTS_MEDIUM_DARK_SKIN_TONE = "\U0001f3cb\U0001f3fe" - PERSON_LIFTING_WEIGHTS_DARK_SKIN_TONE = "\U0001f3cb\U0001f3ff" - MAN_LIFTING_WEIGHTS = "\U0001f3cb\ufe0f\u200d\u2642\ufe0f" - MAN_LIFTING_WEIGHTS_LIGHT_SKIN_TONE = "\U0001f3cb\U0001f3fb\u200d\u2642\ufe0f" - MAN_LIFTING_WEIGHTS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f3cb\U0001f3fc\u200d\u2642\ufe0f" - MAN_LIFTING_WEIGHTS_MEDIUM_SKIN_TONE = "\U0001f3cb\U0001f3fd\u200d\u2642\ufe0f" - MAN_LIFTING_WEIGHTS_MEDIUM_DARK_SKIN_TONE = "\U0001f3cb\U0001f3fe\u200d\u2642\ufe0f" - MAN_LIFTING_WEIGHTS_DARK_SKIN_TONE = "\U0001f3cb\U0001f3ff\u200d\u2642\ufe0f" - WOMAN_LIFTING_WEIGHTS = "\U0001f3cb\ufe0f\u200d\u2640\ufe0f" - WOMAN_LIFTING_WEIGHTS_LIGHT_SKIN_TONE = "\U0001f3cb\U0001f3fb\u200d\u2640\ufe0f" - WOMAN_LIFTING_WEIGHTS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f3cb\U0001f3fc\u200d\u2640\ufe0f" - WOMAN_LIFTING_WEIGHTS_MEDIUM_SKIN_TONE = "\U0001f3cb\U0001f3fd\u200d\u2640\ufe0f" - WOMAN_LIFTING_WEIGHTS_MEDIUM_DARK_SKIN_TONE = "\U0001f3cb\U0001f3fe\u200d\u2640\ufe0f" - WOMAN_LIFTING_WEIGHTS_DARK_SKIN_TONE = "\U0001f3cb\U0001f3ff\u200d\u2640\ufe0f" - PERSON_BIKING = "\U0001f6b4" - PERSON_BIKING_LIGHT_SKIN_TONE = "\U0001f6b4\U0001f3fb" - PERSON_BIKING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f6b4\U0001f3fc" - PERSON_BIKING_MEDIUM_SKIN_TONE = "\U0001f6b4\U0001f3fd" - PERSON_BIKING_MEDIUM_DARK_SKIN_TONE = "\U0001f6b4\U0001f3fe" - PERSON_BIKING_DARK_SKIN_TONE = "\U0001f6b4\U0001f3ff" - MAN_BIKING = "\U0001f6b4\u200d\u2642\ufe0f" - MAN_BIKING_LIGHT_SKIN_TONE = "\U0001f6b4\U0001f3fb\u200d\u2642\ufe0f" - MAN_BIKING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f6b4\U0001f3fc\u200d\u2642\ufe0f" - MAN_BIKING_MEDIUM_SKIN_TONE = "\U0001f6b4\U0001f3fd\u200d\u2642\ufe0f" - MAN_BIKING_MEDIUM_DARK_SKIN_TONE = "\U0001f6b4\U0001f3fe\u200d\u2642\ufe0f" - MAN_BIKING_DARK_SKIN_TONE = "\U0001f6b4\U0001f3ff\u200d\u2642\ufe0f" - WOMAN_BIKING = "\U0001f6b4\u200d\u2640\ufe0f" - WOMAN_BIKING_LIGHT_SKIN_TONE = "\U0001f6b4\U0001f3fb\u200d\u2640\ufe0f" - WOMAN_BIKING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f6b4\U0001f3fc\u200d\u2640\ufe0f" - WOMAN_BIKING_MEDIUM_SKIN_TONE = "\U0001f6b4\U0001f3fd\u200d\u2640\ufe0f" - WOMAN_BIKING_MEDIUM_DARK_SKIN_TONE = "\U0001f6b4\U0001f3fe\u200d\u2640\ufe0f" - WOMAN_BIKING_DARK_SKIN_TONE = "\U0001f6b4\U0001f3ff\u200d\u2640\ufe0f" - PERSON_MOUNTAIN_BIKING = "\U0001f6b5" - PERSON_MOUNTAIN_BIKING_LIGHT_SKIN_TONE = "\U0001f6b5\U0001f3fb" - PERSON_MOUNTAIN_BIKING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f6b5\U0001f3fc" - PERSON_MOUNTAIN_BIKING_MEDIUM_SKIN_TONE = "\U0001f6b5\U0001f3fd" - PERSON_MOUNTAIN_BIKING_MEDIUM_DARK_SKIN_TONE = "\U0001f6b5\U0001f3fe" - PERSON_MOUNTAIN_BIKING_DARK_SKIN_TONE = "\U0001f6b5\U0001f3ff" - MAN_MOUNTAIN_BIKING = "\U0001f6b5\u200d\u2642\ufe0f" - MAN_MOUNTAIN_BIKING_LIGHT_SKIN_TONE = "\U0001f6b5\U0001f3fb\u200d\u2642\ufe0f" - MAN_MOUNTAIN_BIKING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f6b5\U0001f3fc\u200d\u2642\ufe0f" - MAN_MOUNTAIN_BIKING_MEDIUM_SKIN_TONE = "\U0001f6b5\U0001f3fd\u200d\u2642\ufe0f" - MAN_MOUNTAIN_BIKING_MEDIUM_DARK_SKIN_TONE = "\U0001f6b5\U0001f3fe\u200d\u2642\ufe0f" - MAN_MOUNTAIN_BIKING_DARK_SKIN_TONE = "\U0001f6b5\U0001f3ff\u200d\u2642\ufe0f" - WOMAN_MOUNTAIN_BIKING = "\U0001f6b5\u200d\u2640\ufe0f" - WOMAN_MOUNTAIN_BIKING_LIGHT_SKIN_TONE = "\U0001f6b5\U0001f3fb\u200d\u2640\ufe0f" - WOMAN_MOUNTAIN_BIKING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f6b5\U0001f3fc\u200d\u2640\ufe0f" - WOMAN_MOUNTAIN_BIKING_MEDIUM_SKIN_TONE = "\U0001f6b5\U0001f3fd\u200d\u2640\ufe0f" - WOMAN_MOUNTAIN_BIKING_MEDIUM_DARK_SKIN_TONE = "\U0001f6b5\U0001f3fe\u200d\u2640\ufe0f" - WOMAN_MOUNTAIN_BIKING_DARK_SKIN_TONE = "\U0001f6b5\U0001f3ff\u200d\u2640\ufe0f" - RACING_CAR = "\U0001f3ce\ufe0f" - MOTORCYCLE = "\U0001f3cd\ufe0f" - PERSON_CARTWHEELING = "\U0001f938" - PERSON_CARTWHEELING_LIGHT_SKIN_TONE = "\U0001f938\U0001f3fb" - PERSON_CARTWHEELING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f938\U0001f3fc" - PERSON_CARTWHEELING_MEDIUM_SKIN_TONE = "\U0001f938\U0001f3fd" - WRESTLERS_TYPE_1_2 = "\U0001f93c\U0001f3fb" - PERSON_CARTWHEELING_MEDIUM_DARK_SKIN_TONE = "\U0001f938\U0001f3fe" - WRESTLERS_TYPE_3 = "\U0001f93c\U0001f3fc" - PERSON_CARTWHEELING_DARK_SKIN_TONE = "\U0001f938\U0001f3ff" - MAN_CARTWHEELING = "\U0001f938\u200d\u2642\ufe0f" - WRESTLERS_TYPE_4 = "\U0001f93c\U0001f3fd" - WRESTLERS_TYPE_5 = "\U0001f93c\U0001f3fe" - MAN_CARTWHEELING_LIGHT_SKIN_TONE = "\U0001f938\U0001f3fb\u200d\u2642\ufe0f" - WRESTLERS_TYPE_6 = "\U0001f93c\U0001f3ff" - MAN_CARTWHEELING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f938\U0001f3fc\u200d\u2642\ufe0f" - MEN_WRESTLING_TYPE_1_2 = "\U0001f93c\U0001f3fb\u200d\u2642\ufe0f" - MEN_WRESTLING_TYPE_3 = "\U0001f93c\U0001f3fc\u200d\u2642\ufe0f" - MEN_WRESTLING_TYPE_4 = "\U0001f93c\U0001f3fd\u200d\u2642\ufe0f" - MAN_CARTWHEELING_MEDIUM_SKIN_TONE = "\U0001f938\U0001f3fd\u200d\u2642\ufe0f" - MEN_WRESTLING_TYPE_5 = "\U0001f93c\U0001f3fe\u200d\u2642\ufe0f" - MEN_WRESTLING_TYPE_6 = "\U0001f93c\U0001f3ff\u200d\u2642\ufe0f" - MAN_CARTWHEELING_MEDIUM_DARK_SKIN_TONE = "\U0001f938\U0001f3fe\u200d\u2642\ufe0f" - WOMEN_WRESTLING_TYPE_1_2 = "\U0001f93c\U0001f3fb\u200d\u2640\ufe0f" - MAN_CARTWHEELING_DARK_SKIN_TONE = "\U0001f938\U0001f3ff\u200d\u2642\ufe0f" - WOMEN_WRESTLING_TYPE_3 = "\U0001f93c\U0001f3fc\u200d\u2640\ufe0f" - WOMEN_WRESTLING_TYPE_4 = "\U0001f93c\U0001f3fd\u200d\u2640\ufe0f" - WOMAN_CARTWHEELING = "\U0001f938\u200d\u2640\ufe0f" - WOMEN_WRESTLING_TYPE_5 = "\U0001f93c\U0001f3fe\u200d\u2640\ufe0f" - WOMEN_WRESTLING_TYPE_6 = "\U0001f93c\U0001f3ff\u200d\u2640\ufe0f" - WOMAN_CARTWHEELING_LIGHT_SKIN_TONE = "\U0001f938\U0001f3fb\u200d\u2640\ufe0f" - WOMAN_CARTWHEELING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f938\U0001f3fc\u200d\u2640\ufe0f" - WOMAN_CARTWHEELING_MEDIUM_SKIN_TONE = "\U0001f938\U0001f3fd\u200d\u2640\ufe0f" - WOMAN_CARTWHEELING_MEDIUM_DARK_SKIN_TONE = "\U0001f938\U0001f3fe\u200d\u2640\ufe0f" - WOMAN_CARTWHEELING_DARK_SKIN_TONE = "\U0001f938\U0001f3ff\u200d\u2640\ufe0f" - PEOPLE_WRESTLING = "\U0001f93c" - MEN_WRESTLING = "\U0001f93c\u200d\u2642\ufe0f" - WOMEN_WRESTLING = "\U0001f93c\u200d\u2640\ufe0f" - PERSON_PLAYING_WATER_POLO = "\U0001f93d" - PERSON_PLAYING_WATER_POLO_LIGHT_SKIN_TONE = "\U0001f93d\U0001f3fb" - PERSON_PLAYING_WATER_POLO_MEDIUM_LIGHT_SKIN_TONE = "\U0001f93d\U0001f3fc" - PERSON_PLAYING_WATER_POLO_MEDIUM_SKIN_TONE = "\U0001f93d\U0001f3fd" - PERSON_PLAYING_WATER_POLO_MEDIUM_DARK_SKIN_TONE = "\U0001f93d\U0001f3fe" - PERSON_PLAYING_WATER_POLO_DARK_SKIN_TONE = "\U0001f93d\U0001f3ff" - MAN_PLAYING_WATER_POLO = "\U0001f93d\u200d\u2642\ufe0f" - MAN_PLAYING_WATER_POLO_LIGHT_SKIN_TONE = "\U0001f93d\U0001f3fb\u200d\u2642\ufe0f" - MAN_PLAYING_WATER_POLO_MEDIUM_LIGHT_SKIN_TONE = "\U0001f93d\U0001f3fc\u200d\u2642\ufe0f" - MAN_PLAYING_WATER_POLO_MEDIUM_SKIN_TONE = "\U0001f93d\U0001f3fd\u200d\u2642\ufe0f" - MAN_PLAYING_WATER_POLO_MEDIUM_DARK_SKIN_TONE = "\U0001f93d\U0001f3fe\u200d\u2642\ufe0f" - MAN_PLAYING_WATER_POLO_DARK_SKIN_TONE = "\U0001f93d\U0001f3ff\u200d\u2642\ufe0f" - WOMAN_PLAYING_WATER_POLO = "\U0001f93d\u200d\u2640\ufe0f" - WOMAN_PLAYING_WATER_POLO_LIGHT_SKIN_TONE = "\U0001f93d\U0001f3fb\u200d\u2640\ufe0f" - WOMAN_PLAYING_WATER_POLO_MEDIUM_LIGHT_SKIN_TONE = "\U0001f93d\U0001f3fc\u200d\u2640\ufe0f" - WOMAN_PLAYING_WATER_POLO_MEDIUM_SKIN_TONE = "\U0001f93d\U0001f3fd\u200d\u2640\ufe0f" - WOMAN_PLAYING_WATER_POLO_MEDIUM_DARK_SKIN_TONE = "\U0001f93d\U0001f3fe\u200d\u2640\ufe0f" - WOMAN_PLAYING_WATER_POLO_DARK_SKIN_TONE = "\U0001f93d\U0001f3ff\u200d\u2640\ufe0f" - PERSON_PLAYING_HANDBALL = "\U0001f93e" - PERSON_PLAYING_HANDBALL_LIGHT_SKIN_TONE = "\U0001f93e\U0001f3fb" - PERSON_PLAYING_HANDBALL_MEDIUM_LIGHT_SKIN_TONE = "\U0001f93e\U0001f3fc" - PERSON_PLAYING_HANDBALL_MEDIUM_SKIN_TONE = "\U0001f93e\U0001f3fd" - PERSON_PLAYING_HANDBALL_MEDIUM_DARK_SKIN_TONE = "\U0001f93e\U0001f3fe" - PERSON_PLAYING_HANDBALL_DARK_SKIN_TONE = "\U0001f93e\U0001f3ff" - MAN_PLAYING_HANDBALL = "\U0001f93e\u200d\u2642\ufe0f" - MAN_PLAYING_HANDBALL_LIGHT_SKIN_TONE = "\U0001f93e\U0001f3fb\u200d\u2642\ufe0f" - MAN_PLAYING_HANDBALL_MEDIUM_LIGHT_SKIN_TONE = "\U0001f93e\U0001f3fc\u200d\u2642\ufe0f" - MAN_PLAYING_HANDBALL_MEDIUM_SKIN_TONE = "\U0001f93e\U0001f3fd\u200d\u2642\ufe0f" - MAN_PLAYING_HANDBALL_MEDIUM_DARK_SKIN_TONE = "\U0001f93e\U0001f3fe\u200d\u2642\ufe0f" - MAN_PLAYING_HANDBALL_DARK_SKIN_TONE = "\U0001f93e\U0001f3ff\u200d\u2642\ufe0f" - WOMAN_PLAYING_HANDBALL = "\U0001f93e\u200d\u2640\ufe0f" - WOMAN_PLAYING_HANDBALL_LIGHT_SKIN_TONE = "\U0001f93e\U0001f3fb\u200d\u2640\ufe0f" - WOMAN_PLAYING_HANDBALL_MEDIUM_LIGHT_SKIN_TONE = "\U0001f93e\U0001f3fc\u200d\u2640\ufe0f" - WOMAN_PLAYING_HANDBALL_MEDIUM_SKIN_TONE = "\U0001f93e\U0001f3fd\u200d\u2640\ufe0f" - WOMAN_PLAYING_HANDBALL_MEDIUM_DARK_SKIN_TONE = "\U0001f93e\U0001f3fe\u200d\u2640\ufe0f" - WOMAN_PLAYING_HANDBALL_DARK_SKIN_TONE = "\U0001f93e\U0001f3ff\u200d\u2640\ufe0f" - PERSON_JUGGLING = "\U0001f939" - PERSON_JUGGLING_LIGHT_SKIN_TONE = "\U0001f939\U0001f3fb" - PERSON_JUGGLING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f939\U0001f3fc" - PERSON_JUGGLING_MEDIUM_SKIN_TONE = "\U0001f939\U0001f3fd" - PERSON_JUGGLING_MEDIUM_DARK_SKIN_TONE = "\U0001f939\U0001f3fe" - PERSON_JUGGLING_DARK_SKIN_TONE = "\U0001f939\U0001f3ff" - MAN_JUGGLING = "\U0001f939\u200d\u2642\ufe0f" - MAN_JUGGLING_LIGHT_SKIN_TONE = "\U0001f939\U0001f3fb\u200d\u2642\ufe0f" - MAN_JUGGLING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f939\U0001f3fc\u200d\u2642\ufe0f" - MAN_JUGGLING_MEDIUM_SKIN_TONE = "\U0001f939\U0001f3fd\u200d\u2642\ufe0f" - MAN_JUGGLING_MEDIUM_DARK_SKIN_TONE = "\U0001f939\U0001f3fe\u200d\u2642\ufe0f" - MAN_JUGGLING_DARK_SKIN_TONE = "\U0001f939\U0001f3ff\u200d\u2642\ufe0f" - WOMAN_JUGGLING = "\U0001f939\u200d\u2640\ufe0f" - WOMAN_JUGGLING_LIGHT_SKIN_TONE = "\U0001f939\U0001f3fb\u200d\u2640\ufe0f" - WOMAN_JUGGLING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f939\U0001f3fc\u200d\u2640\ufe0f" - WOMAN_JUGGLING_MEDIUM_SKIN_TONE = "\U0001f939\U0001f3fd\u200d\u2640\ufe0f" - WOMAN_JUGGLING_MEDIUM_DARK_SKIN_TONE = "\U0001f939\U0001f3fe\u200d\u2640\ufe0f" - WOMAN_JUGGLING_DARK_SKIN_TONE = "\U0001f939\U0001f3ff\u200d\u2640\ufe0f" - MAN_AND_WOMAN_HOLDING_HANDS = "\U0001f46b" - TWO_MEN_HOLDING_HANDS = "\U0001f46c" - TWO_WOMEN_HOLDING_HANDS = "\U0001f46d" - KISS = "\U0001f48f" - KISS_WOMAN_MAN = "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468" - KISS_MAN_MAN = "\U0001f468\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468" - KISS_WOMAN_WOMAN = "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469" - COUPLE_WITH_HEART = "\U0001f491" - COUPLE_WITH_HEART_WOMAN_MAN = "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f468" - COUPLE_WITH_HEART_MAN_MAN = "\U0001f468\u200d\u2764\ufe0f\u200d\U0001f468" - COUPLE_WITH_HEART_WOMAN_WOMAN = "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f469" - FAMILY = "\U0001f46a" - FAMILY_MAN_WOMAN_BOY = "\U0001f468\u200d\U0001f469\u200d\U0001f466" - FAMILY_MAN_WOMAN_GIRL = "\U0001f468\u200d\U0001f469\u200d\U0001f467" - FAMILY_MAN_WOMAN_GIRL_BOY = "\U0001f468\u200d\U0001f469\u200d\U0001f467\u200d\U0001f466" - FAMILY_MAN_WOMAN_BOY_BOY = "\U0001f468\u200d\U0001f469\u200d\U0001f466\u200d\U0001f466" - FAMILY_MAN_WOMAN_GIRL_GIRL = "\U0001f468\u200d\U0001f469\u200d\U0001f467\u200d\U0001f467" - FAMILY_MAN_MAN_BOY = "\U0001f468\u200d\U0001f468\u200d\U0001f466" - FAMILY_MAN_MAN_GIRL = "\U0001f468\u200d\U0001f468\u200d\U0001f467" - FAMILY_MAN_MAN_GIRL_BOY = "\U0001f468\u200d\U0001f468\u200d\U0001f467\u200d\U0001f466" - FAMILY_MAN_MAN_BOY_BOY = "\U0001f468\u200d\U0001f468\u200d\U0001f466\u200d\U0001f466" - FAMILY_MAN_MAN_GIRL_GIRL = "\U0001f468\u200d\U0001f468\u200d\U0001f467\u200d\U0001f467" - FAMILY_WOMAN_WOMAN_BOY = "\U0001f469\u200d\U0001f469\u200d\U0001f466" - FAMILY_WOMAN_WOMAN_GIRL = "\U0001f469\u200d\U0001f469\u200d\U0001f467" - FAMILY_WOMAN_WOMAN_GIRL_BOY = "\U0001f469\u200d\U0001f469\u200d\U0001f467\u200d\U0001f466" - FAMILY_WOMAN_WOMAN_BOY_BOY = "\U0001f469\u200d\U0001f469\u200d\U0001f466\u200d\U0001f466" - FAMILY_WOMAN_WOMAN_GIRL_GIRL = "\U0001f469\u200d\U0001f469\u200d\U0001f467\u200d\U0001f467" - FAMILY_MAN_BOY = "\U0001f468\u200d\U0001f466" - FAMILY_MAN_BOY_BOY = "\U0001f468\u200d\U0001f466\u200d\U0001f466" - FAMILY_MAN_GIRL = "\U0001f468\u200d\U0001f467" - FAMILY_MAN_GIRL_BOY = "\U0001f468\u200d\U0001f467\u200d\U0001f466" - FAMILY_MAN_GIRL_GIRL = "\U0001f468\u200d\U0001f467\u200d\U0001f467" - FAMILY_WOMAN_BOY = "\U0001f469\u200d\U0001f466" - FAMILY_WOMAN_BOY_BOY = "\U0001f469\u200d\U0001f466\u200d\U0001f466" - FAMILY_WOMAN_GIRL = "\U0001f469\u200d\U0001f467" - FAMILY_WOMAN_GIRL_BOY = "\U0001f469\u200d\U0001f467\u200d\U0001f466" - FAMILY_WOMAN_GIRL_GIRL = "\U0001f469\u200d\U0001f467\u200d\U0001f467" - SELFIE = "\U0001f933" - SELFIE_LIGHT_SKIN_TONE = "\U0001f933\U0001f3fb" - SELFIE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f933\U0001f3fc" - SELFIE_MEDIUM_SKIN_TONE = "\U0001f933\U0001f3fd" - SELFIE_MEDIUM_DARK_SKIN_TONE = "\U0001f933\U0001f3fe" - SELFIE_DARK_SKIN_TONE = "\U0001f933\U0001f3ff" - FLEXED_BICEPS = "\U0001f4aa" - FLEXED_BICEPS_LIGHT_SKIN_TONE = "\U0001f4aa\U0001f3fb" - FLEXED_BICEPS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f4aa\U0001f3fc" - FLEXED_BICEPS_MEDIUM_SKIN_TONE = "\U0001f4aa\U0001f3fd" - FLEXED_BICEPS_MEDIUM_DARK_SKIN_TONE = "\U0001f4aa\U0001f3fe" - FLEXED_BICEPS_DARK_SKIN_TONE = "\U0001f4aa\U0001f3ff" - LEG = "\U0001f9b5" - LEG_LIGHT_SKIN_TONE = "\U0001f9b5\U0001f3fb" - LEG_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9b5\U0001f3fc" - LEG_MEDIUM_SKIN_TONE = "\U0001f9b5\U0001f3fd" - LEG_MEDIUM_DARK_SKIN_TONE = "\U0001f9b5\U0001f3fe" - LEG_DARK_SKIN_TONE = "\U0001f9b5\U0001f3ff" - FOOT = "\U0001f9b6" - FOOT_LIGHT_SKIN_TONE = "\U0001f9b6\U0001f3fb" - FOOT_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9b6\U0001f3fc" - FOOT_MEDIUM_SKIN_TONE = "\U0001f9b6\U0001f3fd" - FOOT_MEDIUM_DARK_SKIN_TONE = "\U0001f9b6\U0001f3fe" - FOOT_DARK_SKIN_TONE = "\U0001f9b6\U0001f3ff" - BACKHAND_INDEX_POINTING_LEFT = "\U0001f448" - BACKHAND_INDEX_POINTING_LEFT_LIGHT_SKIN_TONE = "\U0001f448\U0001f3fb" - BACKHAND_INDEX_POINTING_LEFT_MEDIUM_LIGHT_SKIN_TONE = "\U0001f448\U0001f3fc" - BACKHAND_INDEX_POINTING_LEFT_MEDIUM_SKIN_TONE = "\U0001f448\U0001f3fd" - BACKHAND_INDEX_POINTING_LEFT_MEDIUM_DARK_SKIN_TONE = "\U0001f448\U0001f3fe" - BACKHAND_INDEX_POINTING_LEFT_DARK_SKIN_TONE = "\U0001f448\U0001f3ff" - BACKHAND_INDEX_POINTING_RIGHT = "\U0001f449" - BACKHAND_INDEX_POINTING_RIGHT_LIGHT_SKIN_TONE = "\U0001f449\U0001f3fb" - BACKHAND_INDEX_POINTING_RIGHT_MEDIUM_LIGHT_SKIN_TONE = "\U0001f449\U0001f3fc" - BACKHAND_INDEX_POINTING_RIGHT_MEDIUM_SKIN_TONE = "\U0001f449\U0001f3fd" - BACKHAND_INDEX_POINTING_RIGHT_MEDIUM_DARK_SKIN_TONE = "\U0001f449\U0001f3fe" - BACKHAND_INDEX_POINTING_RIGHT_DARK_SKIN_TONE = "\U0001f449\U0001f3ff" - INDEX_POINTING_UP = "\u261d\ufe0f" - INDEX_POINTING_UP_LIGHT_SKIN_TONE = "\u261d\U0001f3fb" - INDEX_POINTING_UP_MEDIUM_LIGHT_SKIN_TONE = "\u261d\U0001f3fc" - INDEX_POINTING_UP_MEDIUM_SKIN_TONE = "\u261d\U0001f3fd" - INDEX_POINTING_UP_MEDIUM_DARK_SKIN_TONE = "\u261d\U0001f3fe" - INDEX_POINTING_UP_DARK_SKIN_TONE = "\u261d\U0001f3ff" - BACKHAND_INDEX_POINTING_UP = "\U0001f446" - BACKHAND_INDEX_POINTING_UP_LIGHT_SKIN_TONE = "\U0001f446\U0001f3fb" - BACKHAND_INDEX_POINTING_UP_MEDIUM_LIGHT_SKIN_TONE = "\U0001f446\U0001f3fc" - BACKHAND_INDEX_POINTING_UP_MEDIUM_SKIN_TONE = "\U0001f446\U0001f3fd" - BACKHAND_INDEX_POINTING_UP_MEDIUM_DARK_SKIN_TONE = "\U0001f446\U0001f3fe" - BACKHAND_INDEX_POINTING_UP_DARK_SKIN_TONE = "\U0001f446\U0001f3ff" - MIDDLE_FINGER = "\U0001f595" - MIDDLE_FINGER_LIGHT_SKIN_TONE = "\U0001f595\U0001f3fb" - MIDDLE_FINGER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f595\U0001f3fc" - MIDDLE_FINGER_MEDIUM_SKIN_TONE = "\U0001f595\U0001f3fd" - MIDDLE_FINGER_MEDIUM_DARK_SKIN_TONE = "\U0001f595\U0001f3fe" - MIDDLE_FINGER_DARK_SKIN_TONE = "\U0001f595\U0001f3ff" - BACKHAND_INDEX_POINTING_DOWN = "\U0001f447" - BACKHAND_INDEX_POINTING_DOWN_LIGHT_SKIN_TONE = "\U0001f447\U0001f3fb" - BACKHAND_INDEX_POINTING_DOWN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f447\U0001f3fc" - BACKHAND_INDEX_POINTING_DOWN_MEDIUM_SKIN_TONE = "\U0001f447\U0001f3fd" - BACKHAND_INDEX_POINTING_DOWN_MEDIUM_DARK_SKIN_TONE = "\U0001f447\U0001f3fe" - BACKHAND_INDEX_POINTING_DOWN_DARK_SKIN_TONE = "\U0001f447\U0001f3ff" - VICTORY_HAND = "\u270c\ufe0f" - VICTORY_HAND_LIGHT_SKIN_TONE = "\u270c\U0001f3fb" - VICTORY_HAND_MEDIUM_LIGHT_SKIN_TONE = "\u270c\U0001f3fc" - VICTORY_HAND_MEDIUM_SKIN_TONE = "\u270c\U0001f3fd" - VICTORY_HAND_MEDIUM_DARK_SKIN_TONE = "\u270c\U0001f3fe" - VICTORY_HAND_DARK_SKIN_TONE = "\u270c\U0001f3ff" - CROSSED_FINGERS = "\U0001f91e" - CROSSED_FINGERS_LIGHT_SKIN_TONE = "\U0001f91e\U0001f3fb" - CROSSED_FINGERS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f91e\U0001f3fc" - CROSSED_FINGERS_MEDIUM_SKIN_TONE = "\U0001f91e\U0001f3fd" - CROSSED_FINGERS_MEDIUM_DARK_SKIN_TONE = "\U0001f91e\U0001f3fe" - CROSSED_FINGERS_DARK_SKIN_TONE = "\U0001f91e\U0001f3ff" - VULCAN_SALUTE = "\U0001f596" - VULCAN_SALUTE_LIGHT_SKIN_TONE = "\U0001f596\U0001f3fb" - VULCAN_SALUTE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f596\U0001f3fc" - VULCAN_SALUTE_MEDIUM_SKIN_TONE = "\U0001f596\U0001f3fd" - VULCAN_SALUTE_MEDIUM_DARK_SKIN_TONE = "\U0001f596\U0001f3fe" - VULCAN_SALUTE_DARK_SKIN_TONE = "\U0001f596\U0001f3ff" - SIGN_OF_THE_HORNS = "\U0001f918" - SIGN_OF_THE_HORNS_LIGHT_SKIN_TONE = "\U0001f918\U0001f3fb" - SIGN_OF_THE_HORNS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f918\U0001f3fc" - SIGN_OF_THE_HORNS_MEDIUM_SKIN_TONE = "\U0001f918\U0001f3fd" - SIGN_OF_THE_HORNS_MEDIUM_DARK_SKIN_TONE = "\U0001f918\U0001f3fe" - SIGN_OF_THE_HORNS_DARK_SKIN_TONE = "\U0001f918\U0001f3ff" - CALL_ME_HAND = "\U0001f919" - CALL_ME_HAND_LIGHT_SKIN_TONE = "\U0001f919\U0001f3fb" - CALL_ME_HAND_MEDIUM_LIGHT_SKIN_TONE = "\U0001f919\U0001f3fc" - CALL_ME_HAND_MEDIUM_SKIN_TONE = "\U0001f919\U0001f3fd" - CALL_ME_HAND_MEDIUM_DARK_SKIN_TONE = "\U0001f919\U0001f3fe" - CALL_ME_HAND_DARK_SKIN_TONE = "\U0001f919\U0001f3ff" - HAND_WITH_FINGERS_SPLAYED = "\U0001f590\ufe0f" - HAND_WITH_FINGERS_SPLAYED_LIGHT_SKIN_TONE = "\U0001f590\U0001f3fb" - HAND_WITH_FINGERS_SPLAYED_MEDIUM_LIGHT_SKIN_TONE = "\U0001f590\U0001f3fc" - HAND_WITH_FINGERS_SPLAYED_MEDIUM_SKIN_TONE = "\U0001f590\U0001f3fd" - HAND_WITH_FINGERS_SPLAYED_MEDIUM_DARK_SKIN_TONE = "\U0001f590\U0001f3fe" - HAND_WITH_FINGERS_SPLAYED_DARK_SKIN_TONE = "\U0001f590\U0001f3ff" - RAISED_HAND = "\u270b" - RAISED_HAND_LIGHT_SKIN_TONE = "\u270b\U0001f3fb" - RAISED_HAND_MEDIUM_LIGHT_SKIN_TONE = "\u270b\U0001f3fc" - RAISED_HAND_MEDIUM_SKIN_TONE = "\u270b\U0001f3fd" - RAISED_HAND_MEDIUM_DARK_SKIN_TONE = "\u270b\U0001f3fe" - RAISED_HAND_DARK_SKIN_TONE = "\u270b\U0001f3ff" - OK_HAND = "\U0001f44c" - OK_HAND_LIGHT_SKIN_TONE = "\U0001f44c\U0001f3fb" - OK_HAND_MEDIUM_LIGHT_SKIN_TONE = "\U0001f44c\U0001f3fc" - OK_HAND_MEDIUM_SKIN_TONE = "\U0001f44c\U0001f3fd" - OK_HAND_MEDIUM_DARK_SKIN_TONE = "\U0001f44c\U0001f3fe" - OK_HAND_DARK_SKIN_TONE = "\U0001f44c\U0001f3ff" - THUMBS_UP = "\U0001f44d" - THUMBS_UP_LIGHT_SKIN_TONE = "\U0001f44d\U0001f3fb" - THUMBS_UP_MEDIUM_LIGHT_SKIN_TONE = "\U0001f44d\U0001f3fc" - THUMBS_UP_MEDIUM_SKIN_TONE = "\U0001f44d\U0001f3fd" - THUMBS_UP_MEDIUM_DARK_SKIN_TONE = "\U0001f44d\U0001f3fe" - THUMBS_UP_DARK_SKIN_TONE = "\U0001f44d\U0001f3ff" - THUMBS_DOWN = "\U0001f44e" - THUMBS_DOWN_LIGHT_SKIN_TONE = "\U0001f44e\U0001f3fb" - THUMBS_DOWN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f44e\U0001f3fc" - THUMBS_DOWN_MEDIUM_SKIN_TONE = "\U0001f44e\U0001f3fd" - THUMBS_DOWN_MEDIUM_DARK_SKIN_TONE = "\U0001f44e\U0001f3fe" - THUMBS_DOWN_DARK_SKIN_TONE = "\U0001f44e\U0001f3ff" - RAISED_FIST = "\u270a" - RAISED_FIST_LIGHT_SKIN_TONE = "\u270a\U0001f3fb" - RAISED_FIST_MEDIUM_LIGHT_SKIN_TONE = "\u270a\U0001f3fc" - RAISED_FIST_MEDIUM_SKIN_TONE = "\u270a\U0001f3fd" - RAISED_FIST_MEDIUM_DARK_SKIN_TONE = "\u270a\U0001f3fe" - RAISED_FIST_DARK_SKIN_TONE = "\u270a\U0001f3ff" - ONCOMING_FIST = "\U0001f44a" - ONCOMING_FIST_LIGHT_SKIN_TONE = "\U0001f44a\U0001f3fb" - ONCOMING_FIST_MEDIUM_LIGHT_SKIN_TONE = "\U0001f44a\U0001f3fc" - ONCOMING_FIST_MEDIUM_SKIN_TONE = "\U0001f44a\U0001f3fd" - ONCOMING_FIST_MEDIUM_DARK_SKIN_TONE = "\U0001f44a\U0001f3fe" - ONCOMING_FIST_DARK_SKIN_TONE = "\U0001f44a\U0001f3ff" - LEFT_FACING_FIST = "\U0001f91b" - LEFT_FACING_FIST_LIGHT_SKIN_TONE = "\U0001f91b\U0001f3fb" - LEFT_FACING_FIST_MEDIUM_LIGHT_SKIN_TONE = "\U0001f91b\U0001f3fc" - LEFT_FACING_FIST_MEDIUM_SKIN_TONE = "\U0001f91b\U0001f3fd" - LEFT_FACING_FIST_MEDIUM_DARK_SKIN_TONE = "\U0001f91b\U0001f3fe" - LEFT_FACING_FIST_DARK_SKIN_TONE = "\U0001f91b\U0001f3ff" - RIGHT_FACING_FIST = "\U0001f91c" - RIGHT_FACING_FIST_LIGHT_SKIN_TONE = "\U0001f91c\U0001f3fb" - RIGHT_FACING_FIST_MEDIUM_LIGHT_SKIN_TONE = "\U0001f91c\U0001f3fc" - RIGHT_FACING_FIST_MEDIUM_SKIN_TONE = "\U0001f91c\U0001f3fd" - RIGHT_FACING_FIST_MEDIUM_DARK_SKIN_TONE = "\U0001f91c\U0001f3fe" - RIGHT_FACING_FIST_DARK_SKIN_TONE = "\U0001f91c\U0001f3ff" - RAISED_BACK_OF_HAND = "\U0001f91a" - RAISED_BACK_OF_HAND_LIGHT_SKIN_TONE = "\U0001f91a\U0001f3fb" - RAISED_BACK_OF_HAND_MEDIUM_LIGHT_SKIN_TONE = "\U0001f91a\U0001f3fc" - RAISED_BACK_OF_HAND_MEDIUM_SKIN_TONE = "\U0001f91a\U0001f3fd" - RAISED_BACK_OF_HAND_MEDIUM_DARK_SKIN_TONE = "\U0001f91a\U0001f3fe" - RAISED_BACK_OF_HAND_DARK_SKIN_TONE = "\U0001f91a\U0001f3ff" - WAVING_HAND = "\U0001f44b" - WAVING_HAND_LIGHT_SKIN_TONE = "\U0001f44b\U0001f3fb" - WAVING_HAND_MEDIUM_LIGHT_SKIN_TONE = "\U0001f44b\U0001f3fc" - WAVING_HAND_MEDIUM_SKIN_TONE = "\U0001f44b\U0001f3fd" - WAVING_HAND_MEDIUM_DARK_SKIN_TONE = "\U0001f44b\U0001f3fe" - WAVING_HAND_DARK_SKIN_TONE = "\U0001f44b\U0001f3ff" - LOVE_YOU_GESTURE = "\U0001f91f" - LOVE_YOU_GESTURE_LIGHT_SKIN_TONE = "\U0001f91f\U0001f3fb" - LOVE_YOU_GESTURE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f91f\U0001f3fc" - LOVE_YOU_GESTURE_MEDIUM_SKIN_TONE = "\U0001f91f\U0001f3fd" - LOVE_YOU_GESTURE_MEDIUM_DARK_SKIN_TONE = "\U0001f91f\U0001f3fe" - LOVE_YOU_GESTURE_DARK_SKIN_TONE = "\U0001f91f\U0001f3ff" - WRITING_HAND = "\u270d\ufe0f" - WRITING_HAND_LIGHT_SKIN_TONE = "\u270d\U0001f3fb" - WRITING_HAND_MEDIUM_LIGHT_SKIN_TONE = "\u270d\U0001f3fc" - WRITING_HAND_MEDIUM_SKIN_TONE = "\u270d\U0001f3fd" - WRITING_HAND_MEDIUM_DARK_SKIN_TONE = "\u270d\U0001f3fe" - WRITING_HAND_DARK_SKIN_TONE = "\u270d\U0001f3ff" - CLAPPING_HANDS = "\U0001f44f" - CLAPPING_HANDS_LIGHT_SKIN_TONE = "\U0001f44f\U0001f3fb" - CLAPPING_HANDS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f44f\U0001f3fc" - CLAPPING_HANDS_MEDIUM_SKIN_TONE = "\U0001f44f\U0001f3fd" - CLAPPING_HANDS_MEDIUM_DARK_SKIN_TONE = "\U0001f44f\U0001f3fe" - CLAPPING_HANDS_DARK_SKIN_TONE = "\U0001f44f\U0001f3ff" - OPEN_HANDS = "\U0001f450" - OPEN_HANDS_LIGHT_SKIN_TONE = "\U0001f450\U0001f3fb" - OPEN_HANDS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f450\U0001f3fc" - OPEN_HANDS_MEDIUM_SKIN_TONE = "\U0001f450\U0001f3fd" - OPEN_HANDS_MEDIUM_DARK_SKIN_TONE = "\U0001f450\U0001f3fe" - OPEN_HANDS_DARK_SKIN_TONE = "\U0001f450\U0001f3ff" - RAISING_HANDS = "\U0001f64c" - RAISING_HANDS_LIGHT_SKIN_TONE = "\U0001f64c\U0001f3fb" - RAISING_HANDS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f64c\U0001f3fc" - RAISING_HANDS_MEDIUM_SKIN_TONE = "\U0001f64c\U0001f3fd" - RAISING_HANDS_MEDIUM_DARK_SKIN_TONE = "\U0001f64c\U0001f3fe" - RAISING_HANDS_DARK_SKIN_TONE = "\U0001f64c\U0001f3ff" - PALMS_UP_TOGETHER = "\U0001f932" - PALMS_UP_TOGETHER_LIGHT_SKIN_TONE = "\U0001f932\U0001f3fb" - PALMS_UP_TOGETHER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f932\U0001f3fc" - PALMS_UP_TOGETHER_MEDIUM_SKIN_TONE = "\U0001f932\U0001f3fd" - PALMS_UP_TOGETHER_MEDIUM_DARK_SKIN_TONE = "\U0001f932\U0001f3fe" - PALMS_UP_TOGETHER_DARK_SKIN_TONE = "\U0001f932\U0001f3ff" - FOLDED_HANDS = "\U0001f64f" - FOLDED_HANDS_LIGHT_SKIN_TONE = "\U0001f64f\U0001f3fb" - FOLDED_HANDS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f64f\U0001f3fc" - FOLDED_HANDS_MEDIUM_SKIN_TONE = "\U0001f64f\U0001f3fd" - FOLDED_HANDS_MEDIUM_DARK_SKIN_TONE = "\U0001f64f\U0001f3fe" - FOLDED_HANDS_DARK_SKIN_TONE = "\U0001f64f\U0001f3ff" - HANDSHAKE = "\U0001f91d" - NAIL_POLISH = "\U0001f485" - NAIL_POLISH_LIGHT_SKIN_TONE = "\U0001f485\U0001f3fb" - NAIL_POLISH_MEDIUM_LIGHT_SKIN_TONE = "\U0001f485\U0001f3fc" - NAIL_POLISH_MEDIUM_SKIN_TONE = "\U0001f485\U0001f3fd" - NAIL_POLISH_MEDIUM_DARK_SKIN_TONE = "\U0001f485\U0001f3fe" - NAIL_POLISH_DARK_SKIN_TONE = "\U0001f485\U0001f3ff" - EAR = "\U0001f442" - EAR_LIGHT_SKIN_TONE = "\U0001f442\U0001f3fb" - EAR_MEDIUM_LIGHT_SKIN_TONE = "\U0001f442\U0001f3fc" - EAR_MEDIUM_SKIN_TONE = "\U0001f442\U0001f3fd" - EAR_MEDIUM_DARK_SKIN_TONE = "\U0001f442\U0001f3fe" - EAR_DARK_SKIN_TONE = "\U0001f442\U0001f3ff" - NOSE = "\U0001f443" - NOSE_LIGHT_SKIN_TONE = "\U0001f443\U0001f3fb" - NOSE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f443\U0001f3fc" - NOSE_MEDIUM_SKIN_TONE = "\U0001f443\U0001f3fd" - NOSE_MEDIUM_DARK_SKIN_TONE = "\U0001f443\U0001f3fe" - NOSE_DARK_SKIN_TONE = "\U0001f443\U0001f3ff" - RED_HAIR = "\U0001f9b0" - CURLY_HAIR = "\U0001f9b1" - BALD = "\U0001f9b2" - WHITE_HAIR = "\U0001f9b3" - FOOTPRINTS = "\U0001f463" - EYES = "\U0001f440" - EYE = "\U0001f441\ufe0f" - EYE_IN_SPEECH_BUBBLE = "\U0001f441\u200d\U0001f5e8" - BRAIN = "\U0001f9e0" - BONE = "\U0001f9b4" - TOOTH = "\U0001f9b7" - TONGUE = "\U0001f445" - MOUTH = "\U0001f444" - KISS_MARK = "\U0001f48b" - HEART_WITH_ARROW = "\U0001f498" - RED_HEART = "\u2764\ufe0f" - BEATING_HEART = "\U0001f493" - BROKEN_HEART = "\U0001f494" - TWO_HEARTS = "\U0001f495" - SPARKLING_HEART = "\U0001f496" - GROWING_HEART = "\U0001f497" - BLUE_HEART = "\U0001f499" - GREEN_HEART = "\U0001f49a" - YELLOW_HEART = "\U0001f49b" - ORANGE_HEART = "\U0001f9e1" - PURPLE_HEART = "\U0001f49c" - BLACK_HEART = "\U0001f5a4" - HEART_WITH_RIBBON = "\U0001f49d" - REVOLVING_HEARTS = "\U0001f49e" - HEART_DECORATION = "\U0001f49f" - HEAVY_HEART_EXCLAMATION = "\u2763\ufe0f" - LOVE_LETTER = "\U0001f48c" - ZZZ = "\U0001f4a4" - ANGER_SYMBOL = "\U0001f4a2" - BOMB = "\U0001f4a3" - COLLISION = "\U0001f4a5" - SWEAT_DROPLETS = "\U0001f4a6" - DASHING_AWAY = "\U0001f4a8" - DIZZY = "\U0001f4ab" - SPEECH_BALLOON = "\U0001f4ac" - LEFT_SPEECH_BUBBLE = "\U0001f5e8\ufe0f" - RIGHT_ANGER_BUBBLE = "\U0001f5ef\ufe0f" - THOUGHT_BALLOON = "\U0001f4ad" - HOLE = "\U0001f573\ufe0f" - GLASSES = "\U0001f453" - SUNGLASSES = "\U0001f576\ufe0f" - GOGGLES = "\U0001f97d" - LAB_COAT = "\U0001f97c" - NECKTIE = "\U0001f454" - T_SHIRT = "\U0001f455" - JEANS = "\U0001f456" - SCARF = "\U0001f9e3" - GLOVES = "\U0001f9e4" - COAT = "\U0001f9e5" - SOCKS = "\U0001f9e6" - DRESS = "\U0001f457" - KIMONO = "\U0001f458" - BIKINI = "\U0001f459" - WOMAN_S_CLOTHES = "\U0001f45a" - PURSE = "\U0001f45b" - HANDBAG = "\U0001f45c" - CLUTCH_BAG = "\U0001f45d" - SHOPPING_BAGS = "\U0001f6cd\ufe0f" - BACKPACK = "\U0001f392" - MAN_S_SHOE = "\U0001f45e" - RUNNING_SHOE = "\U0001f45f" - HIKING_BOOT = "\U0001f97e" - FLAT_SHOE = "\U0001f97f" - HIGH_HEELED_SHOE = "\U0001f460" - WOMAN_S_SANDAL = "\U0001f461" - WOMAN_S_BOOT = "\U0001f462" - CROWN = "\U0001f451" - WOMAN_S_HAT = "\U0001f452" - TOP_HAT = "\U0001f3a9" - GRADUATION_CAP = "\U0001f393" - BILLED_CAP = "\U0001f9e2" - RESCUE_WORKER_S_HELMET = "\u26d1\ufe0f" - PRAYER_BEADS = "\U0001f4ff" - LIPSTICK = "\U0001f484" - RING = "\U0001f48d" - GEM_STONE = "\U0001f48e" - MONKEY_FACE = "\U0001f435" - MONKEY = "\U0001f412" - GORILLA = "\U0001f98d" - DOG_FACE = "\U0001f436" - DOG = "\U0001f415" - POODLE = "\U0001f429" - WOLF_FACE = "\U0001f43a" - FOX_FACE = "\U0001f98a" - RACCOON = "\U0001f99d" - CAT_FACE = "\U0001f431" - CAT = "\U0001f408" - LION_FACE = "\U0001f981" - TIGER_FACE = "\U0001f42f" - TIGER = "\U0001f405" - LEOPARD = "\U0001f406" - HORSE_FACE = "\U0001f434" - HORSE = "\U0001f40e" - UNICORN_FACE = "\U0001f984" - ZEBRA = "\U0001f993" - DEER = "\U0001f98c" - COW_FACE = "\U0001f42e" - OX = "\U0001f402" - WATER_BUFFALO = "\U0001f403" - COW = "\U0001f404" - PIG_FACE = "\U0001f437" - PIG = "\U0001f416" - BOAR = "\U0001f417" - PIG_NOSE = "\U0001f43d" - RAM = "\U0001f40f" - EWE = "\U0001f411" - GOAT = "\U0001f410" - CAMEL = "\U0001f42a" - TWO_HUMP_CAMEL = "\U0001f42b" - LLAMA = "\U0001f999" - GIRAFFE = "\U0001f992" - ELEPHANT = "\U0001f418" - RHINOCEROS = "\U0001f98f" - HIPPOPOTAMUS = "\U0001f99b" - MOUSE_FACE = "\U0001f42d" - MOUSE = "\U0001f401" - RAT = "\U0001f400" - HAMSTER_FACE = "\U0001f439" - RABBIT_FACE = "\U0001f430" - RABBIT = "\U0001f407" - CHIPMUNK = "\U0001f43f\ufe0f" - HEDGEHOG = "\U0001f994" - BAT = "\U0001f987" - BEAR_FACE = "\U0001f43b" - KOALA = "\U0001f428" - PANDA_FACE = "\U0001f43c" - KANGAROO = "\U0001f998" - BADGER = "\U0001f9a1" - PAW_PRINTS = "\U0001f43e" - TURKEY_2 = "\U0001f983" - CHICKEN = "\U0001f414" - ROOSTER = "\U0001f413" - HATCHING_CHICK = "\U0001f423" - BABY_CHICK = "\U0001f424" - FRONT_FACING_BABY_CHICK = "\U0001f425" - BIRD = "\U0001f426" - PENGUIN = "\U0001f427" - DOVE = "\U0001f54a\ufe0f" - EAGLE = "\U0001f985" - DUCK = "\U0001f986" - SWAN = "\U0001f9a2" - OWL = "\U0001f989" - PEACOCK = "\U0001f99a" - PARROT = "\U0001f99c" - FROG_FACE = "\U0001f438" - CROCODILE = "\U0001f40a" - TURTLE = "\U0001f422" - LIZARD = "\U0001f98e" - SNAKE = "\U0001f40d" - DRAGON_FACE = "\U0001f432" - DRAGON = "\U0001f409" - SAUROPOD = "\U0001f995" - T_REX = "\U0001f996" - SPOUTING_WHALE = "\U0001f433" - WHALE = "\U0001f40b" - DOLPHIN = "\U0001f42c" - FISH = "\U0001f41f" - TROPICAL_FISH = "\U0001f420" - BLOWFISH = "\U0001f421" - SHARK = "\U0001f988" - OCTOPUS = "\U0001f419" - SPIRAL_SHELL = "\U0001f41a" - CRAB = "\U0001f980" - LOBSTER = "\U0001f99e" - SHRIMP = "\U0001f990" - SQUID = "\U0001f991" - SNAIL = "\U0001f40c" - BUTTERFLY = "\U0001f98b" - BUG = "\U0001f41b" - ANT = "\U0001f41c" - HONEYBEE = "\U0001f41d" - LADY_BEETLE = "\U0001f41e" - CRICKET = "\U0001f997" - SPIDER = "\U0001f577\ufe0f" - SPIDER_WEB = "\U0001f578\ufe0f" - SCORPION = "\U0001f982" - MOSQUITO = "\U0001f99f" - MICROBE = "\U0001f9a0" - BOUQUET = "\U0001f490" - CHERRY_BLOSSOM = "\U0001f338" - WHITE_FLOWER = "\U0001f4ae" - ROSETTE = "\U0001f3f5\ufe0f" - ROSE = "\U0001f339" - WILTED_FLOWER = "\U0001f940" - HIBISCUS = "\U0001f33a" - SUNFLOWER = "\U0001f33b" - BLOSSOM = "\U0001f33c" - TULIP = "\U0001f337" - SEEDLING = "\U0001f331" - EVERGREEN_TREE = "\U0001f332" - DECIDUOUS_TREE = "\U0001f333" - PALM_TREE = "\U0001f334" - CACTUS = "\U0001f335" - SHEAF_OF_RICE = "\U0001f33e" - HERB = "\U0001f33f" - SHAMROCK = "\u2618\ufe0f" - FOUR_LEAF_CLOVER = "\U0001f340" - MAPLE_LEAF = "\U0001f341" - FALLEN_LEAF = "\U0001f342" - LEAF_FLUTTERING_IN_WIND = "\U0001f343" - GRAPES = "\U0001f347" - MELON = "\U0001f348" - WATERMELON = "\U0001f349" - TANGERINE = "\U0001f34a" - LEMON = "\U0001f34b" - BANANA = "\U0001f34c" - PINEAPPLE = "\U0001f34d" - MANGO = "\U0001f96d" - RED_APPLE = "\U0001f34e" - GREEN_APPLE = "\U0001f34f" - PEAR = "\U0001f350" - PEACH = "\U0001f351" - CHERRIES = "\U0001f352" - STRAWBERRY = "\U0001f353" - KIWI_FRUIT = "\U0001f95d" - TOMATO = "\U0001f345" - COCONUT = "\U0001f965" - AVOCADO = "\U0001f951" - EGGPLANT = "\U0001f346" - POTATO = "\U0001f954" - CARROT = "\U0001f955" - EAR_OF_CORN = "\U0001f33d" - HOT_PEPPER = "\U0001f336\ufe0f" - CUCUMBER = "\U0001f952" - LEAFY_GREEN = "\U0001f96c" - BROCCOLI = "\U0001f966" - MUSHROOM = "\U0001f344" - PEANUTS = "\U0001f95c" - CHESTNUT = "\U0001f330" - BREAD = "\U0001f35e" - CROISSANT = "\U0001f950" - BAGUETTE_BREAD = "\U0001f956" - PRETZEL = "\U0001f968" - BAGEL = "\U0001f96f" - PANCAKES = "\U0001f95e" - CHEESE_WEDGE = "\U0001f9c0" - MEAT_ON_BONE = "\U0001f356" - POULTRY_LEG = "\U0001f357" - CUT_OF_MEAT = "\U0001f969" - BACON = "\U0001f953" - HAMBURGER = "\U0001f354" - FRENCH_FRIES = "\U0001f35f" - PIZZA = "\U0001f355" - HOT_DOG = "\U0001f32d" - SANDWICH = "\U0001f96a" - TACO = "\U0001f32e" - BURRITO = "\U0001f32f" - STUFFED_FLATBREAD = "\U0001f959" - EGG = "\U0001f95a" - COOKING = "\U0001f373" - SHALLOW_PAN_OF_FOOD = "\U0001f958" - POT_OF_FOOD = "\U0001f372" - BOWL_WITH_SPOON = "\U0001f963" - GREEN_SALAD = "\U0001f957" - POPCORN = "\U0001f37f" - SALT = "\U0001f9c2" - CANNED_FOOD = "\U0001f96b" - BENTO_BOX = "\U0001f371" - RICE_CRACKER = "\U0001f358" - RICE_BALL = "\U0001f359" - COOKED_RICE = "\U0001f35a" - CURRY_RICE = "\U0001f35b" - STEAMING_BOWL = "\U0001f35c" - SPAGHETTI = "\U0001f35d" - ROASTED_SWEET_POTATO = "\U0001f360" - ODEN = "\U0001f362" - SUSHI = "\U0001f363" - FRIED_SHRIMP = "\U0001f364" - FISH_CAKE_WITH_SWIRL = "\U0001f365" - MOON_CAKE = "\U0001f96e" - DANGO = "\U0001f361" - DUMPLING = "\U0001f95f" - FORTUNE_COOKIE = "\U0001f960" - TAKEOUT_BOX = "\U0001f961" - SOFT_ICE_CREAM = "\U0001f366" - SHAVED_ICE = "\U0001f367" - ICE_CREAM = "\U0001f368" - DOUGHNUT = "\U0001f369" - COOKIE = "\U0001f36a" - BIRTHDAY_CAKE = "\U0001f382" - SHORTCAKE = "\U0001f370" - CUPCAKE = "\U0001f9c1" - PIE = "\U0001f967" - CHOCOLATE_BAR = "\U0001f36b" - CANDY = "\U0001f36c" - LOLLIPOP = "\U0001f36d" - CUSTARD = "\U0001f36e" - HONEY_POT = "\U0001f36f" - BABY_BOTTLE = "\U0001f37c" - GLASS_OF_MILK = "\U0001f95b" - HOT_BEVERAGE = "\u2615" - TEACUP_WITHOUT_HANDLE = "\U0001f375" - SAKE = "\U0001f376" - BOTTLE_WITH_POPPING_CORK = "\U0001f37e" - WINE_GLASS = "\U0001f377" - COCKTAIL_GLASS = "\U0001f378" - TROPICAL_DRINK = "\U0001f379" - BEER_MUG = "\U0001f37a" - CLINKING_BEER_MUGS = "\U0001f37b" - CLINKING_GLASSES = "\U0001f942" - TUMBLER_GLASS = "\U0001f943" - CUP_WITH_STRAW = "\U0001f964" - CHOPSTICKS = "\U0001f962" - FORK_AND_KNIFE_WITH_PLATE = "\U0001f37d\ufe0f" - FORK_AND_KNIFE = "\U0001f374" - SPOON = "\U0001f944" - KITCHEN_KNIFE = "\U0001f52a" - AMPHORA = "\U0001f3fa" - GLOBE_SHOWING_EUROPE_AFRICA = "\U0001f30d" - GLOBE_SHOWING_AMERICAS = "\U0001f30e" - GLOBE_SHOWING_ASIA_AUSTRALIA = "\U0001f30f" - GLOBE_WITH_MERIDIANS = "\U0001f310" - WORLD_MAP = "\U0001f5fa\ufe0f" - MAP_OF_JAPAN = "\U0001f5fe" - COMPASS = "\U0001f9ed" - SNOW_CAPPED_MOUNTAIN = "\U0001f3d4\ufe0f" - MOUNTAIN = "\u26f0\ufe0f" - VOLCANO = "\U0001f30b" - MOUNT_FUJI = "\U0001f5fb" - CAMPING = "\U0001f3d5\ufe0f" - BEACH_WITH_UMBRELLA = "\U0001f3d6\ufe0f" - DESERT = "\U0001f3dc\ufe0f" - DESERT_ISLAND = "\U0001f3dd\ufe0f" - NATIONAL_PARK = "\U0001f3de\ufe0f" - STADIUM = "\U0001f3df\ufe0f" - CLASSICAL_BUILDING = "\U0001f3db\ufe0f" - BUILDING_CONSTRUCTION = "\U0001f3d7\ufe0f" - HOUSES = "\U0001f3d8\ufe0f" - DERELICT_HOUSE = "\U0001f3da\ufe0f" - HOUSE = "\U0001f3e0" - HOUSE_WITH_GARDEN = "\U0001f3e1" - BRICK = "\U0001f9f1" - OFFICE_BUILDING = "\U0001f3e2" - JAPANESE_POST_OFFICE = "\U0001f3e3" - POST_OFFICE = "\U0001f3e4" - HOSPITAL = "\U0001f3e5" - BANK = "\U0001f3e6" - HOTEL = "\U0001f3e8" - LOVE_HOTEL = "\U0001f3e9" - CONVENIENCE_STORE = "\U0001f3ea" - SCHOOL = "\U0001f3eb" - DEPARTMENT_STORE = "\U0001f3ec" - FACTORY = "\U0001f3ed" - JAPANESE_CASTLE = "\U0001f3ef" - CASTLE = "\U0001f3f0" - WEDDING = "\U0001f492" - TOKYO_TOWER = "\U0001f5fc" - STATUE_OF_LIBERTY = "\U0001f5fd" - CHURCH = "\u26ea" - MOSQUE = "\U0001f54c" - SYNAGOGUE = "\U0001f54d" - SHINTO_SHRINE = "\u26e9\ufe0f" - KAABA = "\U0001f54b" - FOUNTAIN = "\u26f2" - TENT = "\u26fa" - FOGGY = "\U0001f301" - NIGHT_WITH_STARS = "\U0001f303" - CITYSCAPE = "\U0001f3d9\ufe0f" - SUNRISE_OVER_MOUNTAINS = "\U0001f304" - SUNRISE = "\U0001f305" - CITYSCAPE_AT_DUSK = "\U0001f306" - SUNSET = "\U0001f307" - BRIDGE_AT_NIGHT = "\U0001f309" - HOT_SPRINGS = "\u2668\ufe0f" - MILKY_WAY = "\U0001f30c" - CAROUSEL_HORSE = "\U0001f3a0" - FERRIS_WHEEL = "\U0001f3a1" - ROLLER_COASTER = "\U0001f3a2" - BARBER_POLE = "\U0001f488" - CIRCUS_TENT = "\U0001f3aa" - LOCOMOTIVE = "\U0001f682" - RAILWAY_CAR = "\U0001f683" - HIGH_SPEED_TRAIN = "\U0001f684" - BULLET_TRAIN = "\U0001f685" - TRAIN = "\U0001f686" - METRO = "\U0001f687" - LIGHT_RAIL = "\U0001f688" - STATION = "\U0001f689" - TRAM = "\U0001f68a" - MONORAIL = "\U0001f69d" - MOUNTAIN_RAILWAY = "\U0001f69e" - TRAM_CAR = "\U0001f68b" - BUS = "\U0001f68c" - ONCOMING_BUS = "\U0001f68d" - TROLLEYBUS = "\U0001f68e" - MINIBUS = "\U0001f690" - AMBULANCE = "\U0001f691" - FIRE_ENGINE = "\U0001f692" - POLICE_CAR = "\U0001f693" - ONCOMING_POLICE_CAR = "\U0001f694" - TAXI = "\U0001f695" - ONCOMING_TAXI = "\U0001f696" - AUTOMOBILE = "\U0001f697" - ONCOMING_AUTOMOBILE = "\U0001f698" - SPORT_UTILITY_VEHICLE = "\U0001f699" - DELIVERY_TRUCK = "\U0001f69a" - ARTICULATED_LORRY = "\U0001f69b" - TRACTOR = "\U0001f69c" - BICYCLE = "\U0001f6b2" - KICK_SCOOTER = "\U0001f6f4" - SKATEBOARD = "\U0001f6f9" - MOTOR_SCOOTER = "\U0001f6f5" - BUS_STOP = "\U0001f68f" - MOTORWAY = "\U0001f6e3\ufe0f" - RAILWAY_TRACK = "\U0001f6e4\ufe0f" - OIL_DRUM = "\U0001f6e2\ufe0f" - FUEL_PUMP = "\u26fd" - POLICE_CAR_LIGHT = "\U0001f6a8" - HORIZONTAL_TRAFFIC_LIGHT = "\U0001f6a5" - VERTICAL_TRAFFIC_LIGHT = "\U0001f6a6" - STOP_SIGN = "\U0001f6d1" - CONSTRUCTION = "\U0001f6a7" - ANCHOR = "\u2693" - SAILBOAT = "\u26f5" - CANOE = "\U0001f6f6" - SPEEDBOAT = "\U0001f6a4" - PASSENGER_SHIP = "\U0001f6f3\ufe0f" - FERRY = "\u26f4\ufe0f" - MOTOR_BOAT = "\U0001f6e5\ufe0f" - SHIP = "\U0001f6a2" - AIRPLANE = "\u2708\ufe0f" - SMALL_AIRPLANE = "\U0001f6e9\ufe0f" - AIRPLANE_DEPARTURE = "\U0001f6eb" - AIRPLANE_ARRIVAL = "\U0001f6ec" - SEAT = "\U0001f4ba" - HELICOPTER = "\U0001f681" - SUSPENSION_RAILWAY = "\U0001f69f" - MOUNTAIN_CABLEWAY = "\U0001f6a0" - AERIAL_TRAMWAY = "\U0001f6a1" - SATELLITE = "\U0001f6f0\ufe0f" - ROCKET = "\U0001f680" - FLYING_SAUCER = "\U0001f6f8" - BELLHOP_BELL = "\U0001f6ce\ufe0f" - LUGGAGE = "\U0001f9f3" - HOURGLASS_DONE = "\u231b" - HOURGLASS_NOT_DONE = "\u23f3" - WATCH = "\u231a" - ALARM_CLOCK = "\u23f0" - STOPWATCH = "\u23f1\ufe0f" - TIMER_CLOCK = "\u23f2\ufe0f" - MANTELPIECE_CLOCK = "\U0001f570\ufe0f" - TWELVE_O_CLOCK = "\U0001f55b" - TWELVE_THIRTY = "\U0001f567" - ONE_O_CLOCK = "\U0001f550" - ONE_THIRTY = "\U0001f55c" - TWO_O_CLOCK = "\U0001f551" - TWO_THIRTY = "\U0001f55d" - THREE_O_CLOCK = "\U0001f552" - THREE_THIRTY = "\U0001f55e" - FOUR_O_CLOCK = "\U0001f553" - FOUR_THIRTY = "\U0001f55f" - FIVE_O_CLOCK = "\U0001f554" - FIVE_THIRTY = "\U0001f560" - SIX_O_CLOCK = "\U0001f555" - SIX_THIRTY = "\U0001f561" - SEVEN_O_CLOCK = "\U0001f556" - SEVEN_THIRTY = "\U0001f562" - EIGHT_O_CLOCK = "\U0001f557" - EIGHT_THIRTY = "\U0001f563" - NINE_O_CLOCK = "\U0001f558" - NINE_THIRTY = "\U0001f564" - TEN_O_CLOCK = "\U0001f559" - TEN_THIRTY = "\U0001f565" - ELEVEN_O_CLOCK = "\U0001f55a" - ELEVEN_THIRTY = "\U0001f566" - NEW_MOON = "\U0001f311" - WAXING_CRESCENT_MOON = "\U0001f312" - FIRST_QUARTER_MOON = "\U0001f313" - WAXING_GIBBOUS_MOON = "\U0001f314" - FULL_MOON = "\U0001f315" - WANING_GIBBOUS_MOON = "\U0001f316" - LAST_QUARTER_MOON = "\U0001f317" - WANING_CRESCENT_MOON = "\U0001f318" - CRESCENT_MOON = "\U0001f319" - NEW_MOON_FACE = "\U0001f31a" - FIRST_QUARTER_MOON_FACE = "\U0001f31b" - LAST_QUARTER_MOON_FACE = "\U0001f31c" - THERMOMETER = "\U0001f321\ufe0f" - SUN = "\u2600\ufe0f" - FULL_MOON_FACE = "\U0001f31d" - SUN_WITH_FACE = "\U0001f31e" - STAR = "\u2b50" - GLOWING_STAR = "\U0001f31f" - SHOOTING_STAR = "\U0001f320" - CLOUD = "\u2601\ufe0f" - SUN_BEHIND_CLOUD = "\u26c5" - CLOUD_WITH_LIGHTNING_AND_RAIN = "\u26c8\ufe0f" - SUN_BEHIND_SMALL_CLOUD = "\U0001f324\ufe0f" - SUN_BEHIND_LARGE_CLOUD = "\U0001f325\ufe0f" - SUN_BEHIND_RAIN_CLOUD = "\U0001f326\ufe0f" - CLOUD_WITH_RAIN = "\U0001f327\ufe0f" - CLOUD_WITH_SNOW = "\U0001f328\ufe0f" - CLOUD_WITH_LIGHTNING = "\U0001f329\ufe0f" - TORNADO = "\U0001f32a\ufe0f" - FOG = "\U0001f32b\ufe0f" - WIND_FACE = "\U0001f32c\ufe0f" - CYCLONE = "\U0001f300" - RAINBOW = "\U0001f308" - CLOSED_UMBRELLA = "\U0001f302" - UMBRELLA = "\u2602\ufe0f" - UMBRELLA_WITH_RAIN_DROPS = "\u2614" - UMBRELLA_ON_GROUND = "\u26f1\ufe0f" - HIGH_VOLTAGE = "\u26a1" - SNOWFLAKE = "\u2744\ufe0f" - SNOWMAN = "\u2603\ufe0f" - SNOWMAN_WITHOUT_SNOW = "\u26c4" - COMET = "\u2604\ufe0f" - FIRE = "\U0001f525" - DROPLET = "\U0001f4a7" - WATER_WAVE = "\U0001f30a" - JACK_O_LANTERN = "\U0001f383" - CHRISTMAS_TREE = "\U0001f384" - FIREWORKS = "\U0001f386" - SPARKLER = "\U0001f387" - FIRECRACKER = "\U0001f9e8" - SPARKLES = "\u2728" - BALLOON = "\U0001f388" - PARTY_POPPER = "\U0001f389" - CONFETTI_BALL = "\U0001f38a" - TANABATA_TREE = "\U0001f38b" - PINE_DECORATION = "\U0001f38d" - JAPANESE_DOLLS = "\U0001f38e" - CARP_STREAMER = "\U0001f38f" - WIND_CHIME = "\U0001f390" - MOON_VIEWING_CEREMONY = "\U0001f391" - RED_ENVELOPE = "\U0001f9e7" - RIBBON = "\U0001f380" - WRAPPED_GIFT = "\U0001f381" - REMINDER_RIBBON = "\U0001f397\ufe0f" - ADMISSION_TICKETS = "\U0001f39f\ufe0f" - TICKET = "\U0001f3ab" - MILITARY_MEDAL = "\U0001f396\ufe0f" - TROPHY = "\U0001f3c6" - SPORTS_MEDAL = "\U0001f3c5" - FIRST_PLACE_MEDAL = "\U0001f947" - SECOND_PLACE_MEDAL = "\U0001f948" - THIRD_PLACE_MEDAL = "\U0001f949" - SOCCER_BALL = "\u26bd" - BASEBALL = "\u26be" - SOFTBALL = "\U0001f94e" - BASKETBALL = "\U0001f3c0" - VOLLEYBALL = "\U0001f3d0" - AMERICAN_FOOTBALL = "\U0001f3c8" - RUGBY_FOOTBALL = "\U0001f3c9" - TENNIS = "\U0001f3be" - FLYING_DISC = "\U0001f94f" - BOWLING = "\U0001f3b3" - CRICKET_GAME = "\U0001f3cf" - FIELD_HOCKEY = "\U0001f3d1" - ICE_HOCKEY = "\U0001f3d2" - LACROSSE = "\U0001f94d" - PING_PONG = "\U0001f3d3" - BADMINTON = "\U0001f3f8" - BOXING_GLOVE = "\U0001f94a" - MARTIAL_ARTS_UNIFORM = "\U0001f94b" - GOAL_NET = "\U0001f945" - FLAG_IN_HOLE = "\u26f3" - ICE_SKATE = "\u26f8\ufe0f" - FISHING_POLE = "\U0001f3a3" - RUNNING_SHIRT = "\U0001f3bd" - SKIS = "\U0001f3bf" - SLED = "\U0001f6f7" - CURLING_STONE = "\U0001f94c" - DIRECT_HIT = "\U0001f3af" - POOL_8_BALL = "\U0001f3b1" - CRYSTAL_BALL = "\U0001f52e" - NAZAR_AMULET = "\U0001f9ff" - VIDEO_GAME = "\U0001f3ae" - JOYSTICK = "\U0001f579\ufe0f" - SLOT_MACHINE = "\U0001f3b0" - GAME_DIE = "\U0001f3b2" - JIGSAW = "\U0001f9e9" - TEDDY_BEAR = "\U0001f9f8" - SPADE_SUIT = "\u2660\ufe0f" - HEART_SUIT = "\u2665\ufe0f" - DIAMOND_SUIT = "\u2666\ufe0f" - CLUB_SUIT = "\u2663\ufe0f" - CHESS_PAWN = "\u265f\ufe0f" - JOKER = "\U0001f0cf" - MAHJONG_RED_DRAGON = "\U0001f004" - FLOWER_PLAYING_CARDS = "\U0001f3b4" - PERFORMING_ARTS = "\U0001f3ad" - FRAMED_PICTURE = "\U0001f5bc\ufe0f" - ARTIST_PALETTE = "\U0001f3a8" - MUTED_SPEAKER = "\U0001f507" - SPEAKER_LOW_VOLUME = "\U0001f508" - SPEAKER_MEDIUM_VOLUME = "\U0001f509" - SPEAKER_HIGH_VOLUME = "\U0001f50a" - LOUDSPEAKER = "\U0001f4e2" - MEGAPHONE = "\U0001f4e3" - POSTAL_HORN = "\U0001f4ef" - BELL = "\U0001f514" - BELL_WITH_SLASH = "\U0001f515" - MUSICAL_SCORE = "\U0001f3bc" - MUSICAL_NOTE = "\U0001f3b5" - MUSICAL_NOTES = "\U0001f3b6" - STUDIO_MICROPHONE = "\U0001f399\ufe0f" - LEVEL_SLIDER = "\U0001f39a\ufe0f" - CONTROL_KNOBS = "\U0001f39b\ufe0f" - MICROPHONE = "\U0001f3a4" - HEADPHONE = "\U0001f3a7" - RADIO = "\U0001f4fb" - SAXOPHONE = "\U0001f3b7" - GUITAR = "\U0001f3b8" - MUSICAL_KEYBOARD = "\U0001f3b9" - TRUMPET = "\U0001f3ba" - VIOLIN = "\U0001f3bb" - DRUM = "\U0001f941" - MOBILE_PHONE = "\U0001f4f1" - MOBILE_PHONE_WITH_ARROW = "\U0001f4f2" - TELEPHONE = "\u260e\ufe0f" - TELEPHONE_RECEIVER = "\U0001f4de" - PAGER = "\U0001f4df" - FAX_MACHINE = "\U0001f4e0" - BATTERY = "\U0001f50b" - ELECTRIC_PLUG = "\U0001f50c" - LAPTOP_COMPUTER = "\U0001f4bb" - DESKTOP_COMPUTER = "\U0001f5a5\ufe0f" - PRINTER = "\U0001f5a8\ufe0f" - KEYBOARD = "\u2328\ufe0f" - COMPUTER_MOUSE = "\U0001f5b1\ufe0f" - TRACKBALL = "\U0001f5b2\ufe0f" - COMPUTER_DISK = "\U0001f4bd" - FLOPPY_DISK = "\U0001f4be" - OPTICAL_DISK = "\U0001f4bf" - DVD = "\U0001f4c0" - ABACUS = "\U0001f9ee" - MOVIE_CAMERA = "\U0001f3a5" - FILM_FRAMES = "\U0001f39e\ufe0f" - FILM_PROJECTOR = "\U0001f4fd\ufe0f" - CLAPPER_BOARD = "\U0001f3ac" - TELEVISION = "\U0001f4fa" - CAMERA = "\U0001f4f7" - CAMERA_WITH_FLASH = "\U0001f4f8" - VIDEO_CAMERA = "\U0001f4f9" - VIDEOCASSETTE = "\U0001f4fc" - MAGNIFYING_GLASS_TILTED_LEFT = "\U0001f50d" - MAGNIFYING_GLASS_TILTED_RIGHT = "\U0001f50e" - CANDLE = "\U0001f56f\ufe0f" - LIGHT_BULB = "\U0001f4a1" - FLASHLIGHT = "\U0001f526" - RED_PAPER_LANTERN = "\U0001f3ee" - NOTEBOOK_WITH_DECORATIVE_COVER = "\U0001f4d4" - CLOSED_BOOK = "\U0001f4d5" - OPEN_BOOK = "\U0001f4d6" - GREEN_BOOK = "\U0001f4d7" - BLUE_BOOK = "\U0001f4d8" - ORANGE_BOOK = "\U0001f4d9" - BOOKS = "\U0001f4da" - NOTEBOOK = "\U0001f4d3" - LEDGER = "\U0001f4d2" - PAGE_WITH_CURL = "\U0001f4c3" - SCROLL = "\U0001f4dc" - PAGE_FACING_UP = "\U0001f4c4" - NEWSPAPER = "\U0001f4f0" - ROLLED_UP_NEWSPAPER = "\U0001f5de\ufe0f" - BOOKMARK_TABS = "\U0001f4d1" - BOOKMARK = "\U0001f516" - LABEL = "\U0001f3f7\ufe0f" - MONEY_BAG = "\U0001f4b0" - YEN_BANKNOTE = "\U0001f4b4" - DOLLAR_BANKNOTE = "\U0001f4b5" - EURO_BANKNOTE = "\U0001f4b6" - POUND_BANKNOTE = "\U0001f4b7" - MONEY_WITH_WINGS = "\U0001f4b8" - CREDIT_CARD = "\U0001f4b3" - RECEIPT = "\U0001f9fe" - CHART_INCREASING_WITH_YEN = "\U0001f4b9" - CURRENCY_EXCHANGE = "\U0001f4b1" - HEAVY_DOLLAR_SIGN = "\U0001f4b2" - ENVELOPE = "\u2709\ufe0f" - E_MAIL = "\U0001f4e7" - INCOMING_ENVELOPE = "\U0001f4e8" - ENVELOPE_WITH_ARROW = "\U0001f4e9" - OUTBOX_TRAY = "\U0001f4e4" - INBOX_TRAY = "\U0001f4e5" - PACKAGE = "\U0001f4e6" - CLOSED_MAILBOX_WITH_RAISED_FLAG = "\U0001f4eb" - CLOSED_MAILBOX_WITH_LOWERED_FLAG = "\U0001f4ea" - OPEN_MAILBOX_WITH_RAISED_FLAG = "\U0001f4ec" - OPEN_MAILBOX_WITH_LOWERED_FLAG = "\U0001f4ed" - POSTBOX = "\U0001f4ee" - BALLOT_BOX_WITH_BALLOT = "\U0001f5f3\ufe0f" - PENCIL = "\u270f\ufe0f" - BLACK_NIB = "\u2712\ufe0f" - FOUNTAIN_PEN = "\U0001f58b\ufe0f" - PEN = "\U0001f58a\ufe0f" - PAINTBRUSH = "\U0001f58c\ufe0f" - CRAYON = "\U0001f58d\ufe0f" - MEMO = "\U0001f4dd" - BRIEFCASE = "\U0001f4bc" - FILE_FOLDER = "\U0001f4c1" - OPEN_FILE_FOLDER = "\U0001f4c2" - CARD_INDEX_DIVIDERS = "\U0001f5c2\ufe0f" - CALENDAR = "\U0001f4c5" - TEAR_OFF_CALENDAR = "\U0001f4c6" - SPIRAL_NOTEPAD = "\U0001f5d2\ufe0f" - SPIRAL_CALENDAR = "\U0001f5d3\ufe0f" - CARD_INDEX = "\U0001f4c7" - CHART_INCREASING = "\U0001f4c8" - CHART_DECREASING = "\U0001f4c9" - BAR_CHART = "\U0001f4ca" - CLIPBOARD = "\U0001f4cb" - PUSHPIN = "\U0001f4cc" - ROUND_PUSHPIN = "\U0001f4cd" - PAPERCLIP = "\U0001f4ce" - LINKED_PAPERCLIPS = "\U0001f587\ufe0f" - STRAIGHT_RULER = "\U0001f4cf" - TRIANGULAR_RULER = "\U0001f4d0" - SCISSORS = "\u2702\ufe0f" - CARD_FILE_BOX = "\U0001f5c3\ufe0f" - FILE_CABINET = "\U0001f5c4\ufe0f" - WASTEBASKET = "\U0001f5d1\ufe0f" - LOCKED = "\U0001f512" - UNLOCKED = "\U0001f513" - LOCKED_WITH_PEN = "\U0001f50f" - LOCKED_WITH_KEY = "\U0001f510" - KEY = "\U0001f511" - OLD_KEY = "\U0001f5dd\ufe0f" - HAMMER = "\U0001f528" - PICK = "\u26cf\ufe0f" - HAMMER_AND_PICK = "\u2692\ufe0f" - HAMMER_AND_WRENCH = "\U0001f6e0\ufe0f" - DAGGER = "\U0001f5e1\ufe0f" - CROSSED_SWORDS = "\u2694\ufe0f" - PISTOL = "\U0001f52b" - BOW_AND_ARROW = "\U0001f3f9" - SHIELD = "\U0001f6e1\ufe0f" - WRENCH = "\U0001f527" - NUT_AND_BOLT = "\U0001f529" - GEAR = "\u2699\ufe0f" - CLAMP = "\U0001f5dc\ufe0f" - BALANCE_SCALE = "\u2696\ufe0f" - LINK = "\U0001f517" - CHAINS = "\u26d3\ufe0f" - TOOLBOX = "\U0001f9f0" - MAGNET = "\U0001f9f2" - ALEMBIC = "\u2697\ufe0f" - TEST_TUBE = "\U0001f9ea" - PETRI_DISH = "\U0001f9eb" - DNA = "\U0001f9ec" - FIRE_EXTINGUISHER = "\U0001f9ef" - MICROSCOPE = "\U0001f52c" - TELESCOPE = "\U0001f52d" - SATELLITE_ANTENNA = "\U0001f4e1" - SYRINGE = "\U0001f489" - PILL = "\U0001f48a" - DOOR = "\U0001f6aa" - BED = "\U0001f6cf\ufe0f" - COUCH_AND_LAMP = "\U0001f6cb\ufe0f" - TOILET = "\U0001f6bd" - SHOWER = "\U0001f6bf" - BATHTUB = "\U0001f6c1" - LOTION_BOTTLE = "\U0001f9f4" - THREAD = "\U0001f9f5" - YARN = "\U0001f9f6" - SAFETY_PIN = "\U0001f9f7" - BROOM = "\U0001f9f9" - BASKET = "\U0001f9fa" - ROLL_OF_PAPER = "\U0001f9fb" - SOAP = "\U0001f9fc" - SPONGE = "\U0001f9fd" - SHOPPING_CART = "\U0001f6d2" - CIGARETTE = "\U0001f6ac" - COFFIN = "\u26b0\ufe0f" - FUNERAL_URN = "\u26b1\ufe0f" - MOAI = "\U0001f5ff" - ATM_SIGN = "\U0001f3e7" - LITTER_IN_BIN_SIGN = "\U0001f6ae" - POTABLE_WATER = "\U0001f6b0" - WHEELCHAIR_SYMBOL = "\u267f" - MEN_S_ROOM = "\U0001f6b9" - WOMEN_S_ROOM = "\U0001f6ba" - RESTROOM = "\U0001f6bb" - BABY_SYMBOL = "\U0001f6bc" - WATER_CLOSET = "\U0001f6be" - PASSPORT_CONTROL = "\U0001f6c2" - CUSTOMS = "\U0001f6c3" - BAGGAGE_CLAIM = "\U0001f6c4" - LEFT_LUGGAGE = "\U0001f6c5" - WARNING = "\u26a0\ufe0f" - CHILDREN_CROSSING = "\U0001f6b8" - NO_ENTRY = "\u26d4" - PROHIBITED = "\U0001f6ab" - NO_BICYCLES = "\U0001f6b3" - NO_SMOKING = "\U0001f6ad" - NO_LITTERING = "\U0001f6af" - NON_POTABLE_WATER = "\U0001f6b1" - NO_PEDESTRIANS = "\U0001f6b7" - NO_MOBILE_PHONES = "\U0001f4f5" - NO_ONE_UNDER_EIGHTEEN = "\U0001f51e" - RADIOACTIVE = "\u2622\ufe0f" - BIOHAZARD = "\u2623\ufe0f" - UP_ARROW = "\u2b06\ufe0f" - UP_RIGHT_ARROW = "\u2197\ufe0f" - RIGHT_ARROW = "\u27a1\ufe0f" - DOWN_RIGHT_ARROW = "\u2198\ufe0f" - DOWN_ARROW = "\u2b07\ufe0f" - DOWN_LEFT_ARROW = "\u2199\ufe0f" - LEFT_ARROW = "\u2b05\ufe0f" - UP_LEFT_ARROW = "\u2196\ufe0f" - UP_DOWN_ARROW = "\u2195\ufe0f" - LEFT_RIGHT_ARROW = "\u2194\ufe0f" - RIGHT_ARROW_CURVING_LEFT = "\u21a9\ufe0f" - LEFT_ARROW_CURVING_RIGHT = "\u21aa\ufe0f" - RIGHT_ARROW_CURVING_UP = "\u2934\ufe0f" - RIGHT_ARROW_CURVING_DOWN = "\u2935\ufe0f" - CLOCKWISE_VERTICAL_ARROWS = "\U0001f503" - COUNTERCLOCKWISE_ARROWS_BUTTON = "\U0001f504" - BACK_ARROW = "\U0001f519" - END_ARROW = "\U0001f51a" - ON_ARROW = "\U0001f51b" - SOON_ARROW = "\U0001f51c" - TOP_ARROW = "\U0001f51d" - PLACE_OF_WORSHIP = "\U0001f6d0" - ATOM_SYMBOL = "\u269b\ufe0f" - INFINITY = "\u267e\ufe0f" - OM = "\U0001f549\ufe0f" - STAR_OF_DAVID = "\u2721\ufe0f" - WHEEL_OF_DHARMA = "\u2638\ufe0f" - YIN_YANG = "\u262f\ufe0f" - LATIN_CROSS = "\u271d\ufe0f" - ORTHODOX_CROSS = "\u2626\ufe0f" - STAR_AND_CRESCENT = "\u262a\ufe0f" - PEACE_SYMBOL = "\u262e\ufe0f" - MENORAH = "\U0001f54e" - DOTTED_SIX_POINTED_STAR = "\U0001f52f" - ARIES = "\u2648" - TAURUS = "\u2649" - GEMINI = "\u264a" - CANCER = "\u264b" - LEO = "\u264c" - VIRGO = "\u264d" - LIBRA = "\u264e" - SCORPIO = "\u264f" - SAGITTARIUS = "\u2650" - CAPRICORN = "\u2651" - AQUARIUS = "\u2652" - PISCES = "\u2653" - OPHIUCHUS = "\u26ce" - SHUFFLE_TRACKS_BUTTON = "\U0001f500" - REPEAT_BUTTON = "\U0001f501" - REPEAT_SINGLE_BUTTON = "\U0001f502" - PLAY_BUTTON = "\u25b6\ufe0f" - FAST_FORWARD_BUTTON = "\u23e9" - NEXT_TRACK_BUTTON = "\u23ed\ufe0f" - PLAY_OR_PAUSE_BUTTON = "\u23ef\ufe0f" - REVERSE_BUTTON = "\u25c0\ufe0f" - FAST_REVERSE_BUTTON = "\u23ea" - LAST_TRACK_BUTTON = "\u23ee\ufe0f" - UPWARDS_BUTTON = "\U0001f53c" - FAST_UP_BUTTON = "\u23eb" - DOWNWARDS_BUTTON = "\U0001f53d" - FAST_DOWN_BUTTON = "\u23ec" - PAUSE_BUTTON = "\u23f8\ufe0f" - STOP_BUTTON = "\u23f9\ufe0f" - RECORD_BUTTON = "\u23fa\ufe0f" - EJECT_BUTTON = "\u23cf\ufe0f" - CINEMA = "\U0001f3a6" - DIM_BUTTON = "\U0001f505" - BRIGHT_BUTTON = "\U0001f506" - ANTENNA_BARS = "\U0001f4f6" - VIBRATION_MODE = "\U0001f4f3" - MOBILE_PHONE_OFF = "\U0001f4f4" - FEMALE_SIGN = "\u2640\ufe0f" - MALE_SIGN = "\u2642\ufe0f" - MEDICAL_SYMBOL = "\u2695\ufe0f" - RECYCLING_SYMBOL = "\u267b\ufe0f" - FLEUR_DE_LIS = "\u269c\ufe0f" - TRIDENT_EMBLEM = "\U0001f531" - NAME_BADGE = "\U0001f4db" - JAPANESE_SYMBOL_FOR_BEGINNER = "\U0001f530" - HEAVY_LARGE_CIRCLE = "\u2b55" - WHITE_HEAVY_CHECK_MARK = "\u2705" - BALLOT_BOX_WITH_CHECK = "\u2611\ufe0f" - HEAVY_CHECK_MARK = "\u2714\ufe0f" - HEAVY_MULTIPLICATION_X = "\u2716\ufe0f" - CROSS_MARK = "\u274c" - CROSS_MARK_BUTTON = "\u274e" - HEAVY_PLUS_SIGN = "\u2795" - HEAVY_MINUS_SIGN = "\u2796" - HEAVY_DIVISION_SIGN = "\u2797" - CURLY_LOOP = "\u27b0" - DOUBLE_CURLY_LOOP = "\u27bf" - PART_ALTERNATION_MARK = "\u303d\ufe0f" - EIGHT_SPOKED_ASTERISK = "\u2733\ufe0f" - EIGHT_POINTED_STAR = "\u2734\ufe0f" - SPARKLE = "\u2747\ufe0f" - DOUBLE_EXCLAMATION_MARK = "\u203c\ufe0f" - EXCLAMATION_QUESTION_MARK = "\u2049\ufe0f" - QUESTION_MARK = "\u2753" - WHITE_QUESTION_MARK = "\u2754" - WHITE_EXCLAMATION_MARK = "\u2755" - EXCLAMATION_MARK = "\u2757" - WAVY_DASH = "\u3030\ufe0f" - COPYRIGHT = "\xa9\ufe0f" - REGISTERED = "\xae\ufe0f" - TRADE_MARK = "\u2122\ufe0f" - KEYCAP_NUMBER_SIGN = "#\ufe0f\u20e3" - KEYCAP_ASTERISK = "*\ufe0f\u20e3" - KEYCAP_DIGIT_ZERO = "0\ufe0f\u20e3" - KEYCAP_DIGIT_ONE = "1\ufe0f\u20e3" - KEYCAP_DIGIT_TWO = "2\ufe0f\u20e3" - KEYCAP_DIGIT_THREE = "3\ufe0f\u20e3" - KEYCAP_DIGIT_FOUR = "4\ufe0f\u20e3" - KEYCAP_DIGIT_FIVE = "5\ufe0f\u20e3" - KEYCAP_DIGIT_SIX = "6\ufe0f\u20e3" - KEYCAP_DIGIT_SEVEN = "7\ufe0f\u20e3" - KEYCAP_DIGIT_EIGHT = "8\ufe0f\u20e3" - KEYCAP_DIGIT_NINE = "9\ufe0f\u20e3" - KEYCAP_10 = "\U0001f51f" - HUNDRED_POINTS = "\U0001f4af" - INPUT_LATIN_UPPERCASE = "\U0001f520" - INPUT_LATIN_LOWERCASE = "\U0001f521" - INPUT_NUMBERS = "\U0001f522" - INPUT_SYMBOLS = "\U0001f523" - INPUT_LATIN_LETTERS = "\U0001f524" - A_BUTTON_BLOOD_TYPE = "\U0001f170\ufe0f" - AB_BUTTON_BLOOD_TYPE = "\U0001f18e" - B_BUTTON_BLOOD_TYPE = "\U0001f171\ufe0f" - CL_BUTTON = "\U0001f191" - COOL_BUTTON = "\U0001f192" - FREE_BUTTON = "\U0001f193" - INFORMATION = "\u2139\ufe0f" - ID_BUTTON = "\U0001f194" - CIRCLED_M = "\u24c2\ufe0f" - NEW_BUTTON = "\U0001f195" - NG_BUTTON = "\U0001f196" - O_BUTTON_BLOOD_TYPE = "\U0001f17e\ufe0f" - OK_BUTTON = "\U0001f197" - P_BUTTON = "\U0001f17f\ufe0f" - SOS_BUTTON = "\U0001f198" - UP_BUTTON = "\U0001f199" - VS_BUTTON = "\U0001f19a" - JAPANESE_HERE_BUTTON = "\U0001f201" - JAPANESE_SERVICE_CHARGE_BUTTON = "\U0001f202\ufe0f" - JAPANESE_MONTHLY_AMOUNT_BUTTON = "\U0001f237\ufe0f" - JAPANESE_NOT_FREE_OF_CHARGE_BUTTON = "\U0001f236" - JAPANESE_RESERVED_BUTTON = "\U0001f22f" - JAPANESE_BARGAIN_BUTTON = "\U0001f250" - JAPANESE_DISCOUNT_BUTTON = "\U0001f239" - JAPANESE_FREE_OF_CHARGE_BUTTON = "\U0001f21a" - JAPANESE_PROHIBITED_BUTTON = "\U0001f232" - JAPANESE_ACCEPTABLE_BUTTON = "\U0001f251" - JAPANESE_APPLICATION_BUTTON = "\U0001f238" - JAPANESE_PASSING_GRADE_BUTTON = "\U0001f234" - JAPANESE_VACANCY_BUTTON = "\U0001f233" - JAPANESE_CONGRATULATIONS_BUTTON = "\u3297\ufe0f" - JAPANESE_SECRET_BUTTON = "\u3299\ufe0f" - JAPANESE_OPEN_FOR_BUSINESS_BUTTON = "\U0001f23a" - JAPANESE_NO_VACANCY_BUTTON = "\U0001f235" - BLACK_SMALL_SQUARE = "\u25aa\ufe0f" - WHITE_SMALL_SQUARE = "\u25ab\ufe0f" - WHITE_MEDIUM_SQUARE = "\u25fb\ufe0f" - BLACK_MEDIUM_SQUARE = "\u25fc\ufe0f" - WHITE_MEDIUM_SMALL_SQUARE = "\u25fd" - BLACK_MEDIUM_SMALL_SQUARE = "\u25fe" - BLACK_LARGE_SQUARE = "\u2b1b" - WHITE_LARGE_SQUARE = "\u2b1c" - LARGE_ORANGE_DIAMOND = "\U0001f536" - LARGE_BLUE_DIAMOND = "\U0001f537" - SMALL_ORANGE_DIAMOND = "\U0001f538" - SMALL_BLUE_DIAMOND = "\U0001f539" - RED_TRIANGLE_POINTED_UP = "\U0001f53a" - RED_TRIANGLE_POINTED_DOWN = "\U0001f53b" - DIAMOND_WITH_A_DOT = "\U0001f4a0" - RADIO_BUTTON = "\U0001f518" - BLACK_SQUARE_BUTTON = "\U0001f532" - WHITE_SQUARE_BUTTON = "\U0001f533" - WHITE_CIRCLE = "\u26aa" - BLACK_CIRCLE = "\u26ab" - RED_CIRCLE = "\U0001f534" - BLUE_CIRCLE = "\U0001f535" - CHEQUERED_FLAG = "\U0001f3c1" - TRIANGULAR_FLAG = "\U0001f6a9" - CROSSED_FLAGS = "\U0001f38c" - BLACK_FLAG = "\U0001f3f4" - WHITE_FLAG = "\U0001f3f3\ufe0f" - RAINBOW_FLAG = "\U0001f3f3\ufe0f\u200d\U0001f308" - PIRATE_FLAG = "\U0001f3f4\u200d\u2620\ufe0f" - ASCENSION_ISLAND = "\U0001f1e6\U0001f1e8" - ANDORRA = "\U0001f1e6\U0001f1e9" - UNITED_ARAB_EMIRATES = "\U0001f1e6\U0001f1ea" - AFGHANISTAN = "\U0001f1e6\U0001f1eb" - ANTIGUA_AND_BARBUDA = "\U0001f1e6\U0001f1ec" - ANGUILLA = "\U0001f1e6\U0001f1ee" - ALBANIA = "\U0001f1e6\U0001f1f1" - ARMENIA = "\U0001f1e6\U0001f1f2" - ANGOLA = "\U0001f1e6\U0001f1f4" - ANTARCTICA = "\U0001f1e6\U0001f1f6" - ARGENTINA = "\U0001f1e6\U0001f1f7" - AMERICAN_SAMOA = "\U0001f1e6\U0001f1f8" - AUSTRIA = "\U0001f1e6\U0001f1f9" - AUSTRALIA = "\U0001f1e6\U0001f1fa" - ARUBA = "\U0001f1e6\U0001f1fc" - ALAND_ISLANDS = "\U0001f1e6\U0001f1fd" - AZERBAIJAN = "\U0001f1e6\U0001f1ff" - BOSNIA_AND_HERZEGOVINA = "\U0001f1e7\U0001f1e6" - BARBADOS = "\U0001f1e7\U0001f1e7" - BANGLADESH = "\U0001f1e7\U0001f1e9" - BELGIUM = "\U0001f1e7\U0001f1ea" - BURKINA_FASO = "\U0001f1e7\U0001f1eb" - BULGARIA = "\U0001f1e7\U0001f1ec" - BAHRAIN = "\U0001f1e7\U0001f1ed" - BURUNDI = "\U0001f1e7\U0001f1ee" - BENIN = "\U0001f1e7\U0001f1ef" - ST_BARTHELEMY = "\U0001f1e7\U0001f1f1" - BERMUDA = "\U0001f1e7\U0001f1f2" - BRUNEI = "\U0001f1e7\U0001f1f3" - BOLIVIA = "\U0001f1e7\U0001f1f4" - CARIBBEAN_NETHERLANDS = "\U0001f1e7\U0001f1f6" - BRAZIL = "\U0001f1e7\U0001f1f7" - BAHAMAS = "\U0001f1e7\U0001f1f8" - BHUTAN = "\U0001f1e7\U0001f1f9" - BOUVET_ISLAND = "\U0001f1e7\U0001f1fb" - BOTSWANA = "\U0001f1e7\U0001f1fc" - BELARUS = "\U0001f1e7\U0001f1fe" - BELIZE = "\U0001f1e7\U0001f1ff" - CANADA = "\U0001f1e8\U0001f1e6" - COCOS_KEELING_ISLANDS = "\U0001f1e8\U0001f1e8" - CONGO_KINSHASA = "\U0001f1e8\U0001f1e9" - CENTRAL_AFRICAN_REPUBLIC = "\U0001f1e8\U0001f1eb" - CONGO_BRAZZAVILLE = "\U0001f1e8\U0001f1ec" - SWITZERLAND = "\U0001f1e8\U0001f1ed" - COTE_D_IVOIRE = "\U0001f1e8\U0001f1ee" - COOK_ISLANDS = "\U0001f1e8\U0001f1f0" - CHILE = "\U0001f1e8\U0001f1f1" - CAMEROON = "\U0001f1e8\U0001f1f2" - CHINA = "\U0001f1e8\U0001f1f3" - COLOMBIA = "\U0001f1e8\U0001f1f4" - CLIPPERTON_ISLAND = "\U0001f1e8\U0001f1f5" - COSTA_RICA = "\U0001f1e8\U0001f1f7" - CUBA = "\U0001f1e8\U0001f1fa" - CAPE_VERDE = "\U0001f1e8\U0001f1fb" - CURACAO = "\U0001f1e8\U0001f1fc" - CHRISTMAS_ISLAND = "\U0001f1e8\U0001f1fd" - CYPRUS = "\U0001f1e8\U0001f1fe" - CZECHIA = "\U0001f1e8\U0001f1ff" - GERMANY = "\U0001f1e9\U0001f1ea" - DIEGO_GARCIA = "\U0001f1e9\U0001f1ec" - DJIBOUTI = "\U0001f1e9\U0001f1ef" - DENMARK = "\U0001f1e9\U0001f1f0" - DOMINICA = "\U0001f1e9\U0001f1f2" - DOMINICAN_REPUBLIC = "\U0001f1e9\U0001f1f4" - ALGERIA = "\U0001f1e9\U0001f1ff" - CEUTA_AND_MELILLA = "\U0001f1ea\U0001f1e6" - ECUADOR = "\U0001f1ea\U0001f1e8" - ESTONIA = "\U0001f1ea\U0001f1ea" - EGYPT = "\U0001f1ea\U0001f1ec" - WESTERN_SAHARA = "\U0001f1ea\U0001f1ed" - ERITREA = "\U0001f1ea\U0001f1f7" - SPAIN = "\U0001f1ea\U0001f1f8" - ETHIOPIA = "\U0001f1ea\U0001f1f9" - EUROPEAN_UNION = "\U0001f1ea\U0001f1fa" - FINLAND = "\U0001f1eb\U0001f1ee" - FIJI = "\U0001f1eb\U0001f1ef" - FALKLAND_ISLANDS = "\U0001f1eb\U0001f1f0" - MICRONESIA = "\U0001f1eb\U0001f1f2" - FAROE_ISLANDS = "\U0001f1eb\U0001f1f4" - FRANCE = "\U0001f1eb\U0001f1f7" - GABON = "\U0001f1ec\U0001f1e6" - UNITED_KINGDOM = "\U0001f1ec\U0001f1e7" - GRENADA = "\U0001f1ec\U0001f1e9" - GEORGIA = "\U0001f1ec\U0001f1ea" - FRENCH_GUIANA = "\U0001f1ec\U0001f1eb" - GUERNSEY = "\U0001f1ec\U0001f1ec" - GHANA = "\U0001f1ec\U0001f1ed" - GIBRALTAR = "\U0001f1ec\U0001f1ee" - GREENLAND = "\U0001f1ec\U0001f1f1" - GAMBIA = "\U0001f1ec\U0001f1f2" - GUINEA = "\U0001f1ec\U0001f1f3" - GUADELOUPE = "\U0001f1ec\U0001f1f5" - EQUATORIAL_GUINEA = "\U0001f1ec\U0001f1f6" - GREECE = "\U0001f1ec\U0001f1f7" - SOUTH_GEORGIA_AND_SOUTH_SANDWICH_ISLANDS = "\U0001f1ec\U0001f1f8" - GUATEMALA = "\U0001f1ec\U0001f1f9" - GUAM = "\U0001f1ec\U0001f1fa" - GUINEA_BISSAU = "\U0001f1ec\U0001f1fc" - GUYANA = "\U0001f1ec\U0001f1fe" - HONG_KONG_SAR_CHINA = "\U0001f1ed\U0001f1f0" - HEARD_AND_MCDONALD_ISLANDS = "\U0001f1ed\U0001f1f2" - HONDURAS = "\U0001f1ed\U0001f1f3" - CROATIA = "\U0001f1ed\U0001f1f7" - HAITI = "\U0001f1ed\U0001f1f9" - HUNGARY = "\U0001f1ed\U0001f1fa" - CANARY_ISLANDS = "\U0001f1ee\U0001f1e8" - INDONESIA = "\U0001f1ee\U0001f1e9" - IRELAND = "\U0001f1ee\U0001f1ea" - ISRAEL = "\U0001f1ee\U0001f1f1" - ISLE_OF_MAN = "\U0001f1ee\U0001f1f2" - INDIA = "\U0001f1ee\U0001f1f3" - BRITISH_INDIAN_OCEAN_TERRITORY = "\U0001f1ee\U0001f1f4" - IRAQ = "\U0001f1ee\U0001f1f6" - IRAN = "\U0001f1ee\U0001f1f7" - ICELAND = "\U0001f1ee\U0001f1f8" - ITALY = "\U0001f1ee\U0001f1f9" - JERSEY = "\U0001f1ef\U0001f1ea" - JAMAICA = "\U0001f1ef\U0001f1f2" - JORDAN = "\U0001f1ef\U0001f1f4" - JAPAN = "\U0001f1ef\U0001f1f5" - KENYA = "\U0001f1f0\U0001f1ea" - KYRGYZSTAN = "\U0001f1f0\U0001f1ec" - CAMBODIA = "\U0001f1f0\U0001f1ed" - KIRIBATI = "\U0001f1f0\U0001f1ee" - COMOROS = "\U0001f1f0\U0001f1f2" - ST_KITTS_AND_NEVIS = "\U0001f1f0\U0001f1f3" - NORTH_KOREA = "\U0001f1f0\U0001f1f5" - SOUTH_KOREA = "\U0001f1f0\U0001f1f7" - KUWAIT = "\U0001f1f0\U0001f1fc" - CAYMAN_ISLANDS = "\U0001f1f0\U0001f1fe" - KAZAKHSTAN = "\U0001f1f0\U0001f1ff" - LAOS = "\U0001f1f1\U0001f1e6" - LEBANON = "\U0001f1f1\U0001f1e7" - ST_LUCIA = "\U0001f1f1\U0001f1e8" - LIECHTENSTEIN = "\U0001f1f1\U0001f1ee" - SRI_LANKA = "\U0001f1f1\U0001f1f0" - LIBERIA = "\U0001f1f1\U0001f1f7" - LESOTHO = "\U0001f1f1\U0001f1f8" - LITHUANIA = "\U0001f1f1\U0001f1f9" - LUXEMBOURG = "\U0001f1f1\U0001f1fa" - LATVIA = "\U0001f1f1\U0001f1fb" - LIBYA = "\U0001f1f1\U0001f1fe" - MOROCCO = "\U0001f1f2\U0001f1e6" - MONACO = "\U0001f1f2\U0001f1e8" - MOLDOVA = "\U0001f1f2\U0001f1e9" - MONTENEGRO = "\U0001f1f2\U0001f1ea" - ST_MARTIN = "\U0001f1f2\U0001f1eb" - MADAGASCAR = "\U0001f1f2\U0001f1ec" - MARSHALL_ISLANDS = "\U0001f1f2\U0001f1ed" - MACEDONIA = "\U0001f1f2\U0001f1f0" - MALI = "\U0001f1f2\U0001f1f1" - MYANMAR_BURMA = "\U0001f1f2\U0001f1f2" - MONGOLIA = "\U0001f1f2\U0001f1f3" - MACAU_SAR_CHINA = "\U0001f1f2\U0001f1f4" - NORTHERN_MARIANA_ISLANDS = "\U0001f1f2\U0001f1f5" - MARTINIQUE = "\U0001f1f2\U0001f1f6" - MAURITANIA = "\U0001f1f2\U0001f1f7" - MONTSERRAT = "\U0001f1f2\U0001f1f8" - MALTA = "\U0001f1f2\U0001f1f9" - MAURITIUS = "\U0001f1f2\U0001f1fa" - MALDIVES = "\U0001f1f2\U0001f1fb" - MALAWI = "\U0001f1f2\U0001f1fc" - MEXICO = "\U0001f1f2\U0001f1fd" - MALAYSIA = "\U0001f1f2\U0001f1fe" - MOZAMBIQUE = "\U0001f1f2\U0001f1ff" - NAMIBIA = "\U0001f1f3\U0001f1e6" - NEW_CALEDONIA = "\U0001f1f3\U0001f1e8" - NIGER = "\U0001f1f3\U0001f1ea" - NORFOLK_ISLAND = "\U0001f1f3\U0001f1eb" - NIGERIA = "\U0001f1f3\U0001f1ec" - NICARAGUA = "\U0001f1f3\U0001f1ee" - NETHERLANDS = "\U0001f1f3\U0001f1f1" - NORWAY = "\U0001f1f3\U0001f1f4" - NEPAL = "\U0001f1f3\U0001f1f5" - NAURU = "\U0001f1f3\U0001f1f7" - NIUE = "\U0001f1f3\U0001f1fa" - NEW_ZEALAND = "\U0001f1f3\U0001f1ff" - OMAN = "\U0001f1f4\U0001f1f2" - PANAMA = "\U0001f1f5\U0001f1e6" - PERU = "\U0001f1f5\U0001f1ea" - FRENCH_POLYNESIA = "\U0001f1f5\U0001f1eb" - PAPUA_NEW_GUINEA = "\U0001f1f5\U0001f1ec" - PHILIPPINES = "\U0001f1f5\U0001f1ed" - PAKISTAN = "\U0001f1f5\U0001f1f0" - POLAND = "\U0001f1f5\U0001f1f1" - ST_PIERRE_AND_MIQUELON = "\U0001f1f5\U0001f1f2" - PITCAIRN_ISLANDS = "\U0001f1f5\U0001f1f3" - PUERTO_RICO = "\U0001f1f5\U0001f1f7" - PALESTINIAN_TERRITORIES = "\U0001f1f5\U0001f1f8" - PORTUGAL = "\U0001f1f5\U0001f1f9" - PALAU = "\U0001f1f5\U0001f1fc" - PARAGUAY = "\U0001f1f5\U0001f1fe" - QATAR = "\U0001f1f6\U0001f1e6" - REUNION = "\U0001f1f7\U0001f1ea" - ROMANIA = "\U0001f1f7\U0001f1f4" - SERBIA = "\U0001f1f7\U0001f1f8" - RUSSIA = "\U0001f1f7\U0001f1fa" - RWANDA = "\U0001f1f7\U0001f1fc" - SAUDI_ARABIA = "\U0001f1f8\U0001f1e6" - SOLOMON_ISLANDS = "\U0001f1f8\U0001f1e7" - SEYCHELLES = "\U0001f1f8\U0001f1e8" - SUDAN = "\U0001f1f8\U0001f1e9" - SWEDEN = "\U0001f1f8\U0001f1ea" - SINGAPORE = "\U0001f1f8\U0001f1ec" - ST_HELENA = "\U0001f1f8\U0001f1ed" - SLOVENIA = "\U0001f1f8\U0001f1ee" - SVALBARD_AND_JAN_MAYEN = "\U0001f1f8\U0001f1ef" - SLOVAKIA = "\U0001f1f8\U0001f1f0" - SIERRA_LEONE = "\U0001f1f8\U0001f1f1" - SAN_MARINO = "\U0001f1f8\U0001f1f2" - SENEGAL = "\U0001f1f8\U0001f1f3" - SOMALIA = "\U0001f1f8\U0001f1f4" - SURINAME = "\U0001f1f8\U0001f1f7" - SOUTH_SUDAN = "\U0001f1f8\U0001f1f8" - SAO_TOME_AND_PRINCIPE = "\U0001f1f8\U0001f1f9" - EL_SALVADOR = "\U0001f1f8\U0001f1fb" - SINT_MAARTEN = "\U0001f1f8\U0001f1fd" - SYRIA = "\U0001f1f8\U0001f1fe" - SWAZILAND = "\U0001f1f8\U0001f1ff" - TRISTAN_DA_CUNHA = "\U0001f1f9\U0001f1e6" - TURKS_AND_CAICOS_ISLANDS = "\U0001f1f9\U0001f1e8" - CHAD = "\U0001f1f9\U0001f1e9" - FRENCH_SOUTHERN_TERRITORIES = "\U0001f1f9\U0001f1eb" - TOGO = "\U0001f1f9\U0001f1ec" - THAILAND = "\U0001f1f9\U0001f1ed" - TAJIKISTAN = "\U0001f1f9\U0001f1ef" - TOKELAU = "\U0001f1f9\U0001f1f0" - TIMOR_LESTE = "\U0001f1f9\U0001f1f1" - TURKMENISTAN = "\U0001f1f9\U0001f1f2" - TUNISIA = "\U0001f1f9\U0001f1f3" - TONGA = "\U0001f1f9\U0001f1f4" - TURKEY = "\U0001f1f9\U0001f1f7" - TRINIDAD_AND_TOBAGO = "\U0001f1f9\U0001f1f9" - TUVALU = "\U0001f1f9\U0001f1fb" - TAIWAN = "\U0001f1f9\U0001f1fc" - TANZANIA = "\U0001f1f9\U0001f1ff" - UKRAINE = "\U0001f1fa\U0001f1e6" - UGANDA = "\U0001f1fa\U0001f1ec" - U_S_OUTLYING_ISLANDS = "\U0001f1fa\U0001f1f2" - UNITED_NATIONS = "\U0001f1fa\U0001f1f3" - UNITED_STATES = "\U0001f1fa\U0001f1f8" - URUGUAY = "\U0001f1fa\U0001f1fe" - UZBEKISTAN = "\U0001f1fa\U0001f1ff" - VATICAN_CITY = "\U0001f1fb\U0001f1e6" - ST_VINCENT_AND_GRENADINES = "\U0001f1fb\U0001f1e8" - VENEZUELA = "\U0001f1fb\U0001f1ea" - BRITISH_VIRGIN_ISLANDS = "\U0001f1fb\U0001f1ec" - U_S_VIRGIN_ISLANDS = "\U0001f1fb\U0001f1ee" - VIETNAM = "\U0001f1fb\U0001f1f3" - VANUATU = "\U0001f1fb\U0001f1fa" - WALLIS_AND_FUTUNA = "\U0001f1fc\U0001f1eb" - SAMOA = "\U0001f1fc\U0001f1f8" - KOSOVO = "\U0001f1fd\U0001f1f0" - YEMEN = "\U0001f1fe\U0001f1ea" - MAYOTTE = "\U0001f1fe\U0001f1f9" - SOUTH_AFRICA = "\U0001f1ff\U0001f1e6" - ZAMBIA = "\U0001f1ff\U0001f1f2" - ZIMBABWE = "\U0001f1ff\U0001f1fc" - ENGLAND = "\U0001f3f4\U000e0067\U000e0062\U000e0065\U000e006e\U000e0067\U000e007f" - SCOTLAND = "\U0001f3f4\U000e0067\U000e0062\U000e0073\U000e0063\U000e0074\U000e007f" - WALES = "\U0001f3f4\U000e0067\U000e0062\U000e0077\U000e006c\U000e0073\U000e007f" - MODERN_PENTATHLON = "\U0001f93b" - RIFLE = "\U0001f946" - REGIONAL_INDICATOR_SYMBOL_LETTER_A = "\U0001f1e6" - REGIONAL_INDICATOR_SYMBOL_LETTER_B = "\U0001f1e7" - REGIONAL_INDICATOR_SYMBOL_LETTER_C = "\U0001f1e8" - REGIONAL_INDICATOR_SYMBOL_LETTER_D = "\U0001f1e9" - REGIONAL_INDICATOR_SYMBOL_LETTER_E = "\U0001f1ea" - REGIONAL_INDICATOR_SYMBOL_LETTER_F = "\U0001f1eb" - REGIONAL_INDICATOR_SYMBOL_LETTER_G = "\U0001f1ec" - REGIONAL_INDICATOR_SYMBOL_LETTER_H = "\U0001f1ed" - REGIONAL_INDICATOR_SYMBOL_LETTER_I = "\U0001f1ee" - REGIONAL_INDICATOR_SYMBOL_LETTER_J = "\U0001f1ef" - REGIONAL_INDICATOR_SYMBOL_LETTER_K = "\U0001f1f0" - REGIONAL_INDICATOR_SYMBOL_LETTER_L = "\U0001f1f1" - REGIONAL_INDICATOR_SYMBOL_LETTER_M = "\U0001f1f2" - REGIONAL_INDICATOR_SYMBOL_LETTER_N = "\U0001f1f3" - REGIONAL_INDICATOR_SYMBOL_LETTER_O = "\U0001f1f4" - REGIONAL_INDICATOR_SYMBOL_LETTER_P = "\U0001f1f5" - REGIONAL_INDICATOR_SYMBOL_LETTER_Q = "\U0001f1f6" - REGIONAL_INDICATOR_SYMBOL_LETTER_R = "\U0001f1f7" - REGIONAL_INDICATOR_SYMBOL_LETTER_S = "\U0001f1f8" - REGIONAL_INDICATOR_SYMBOL_LETTER_T = "\U0001f1f9" - REGIONAL_INDICATOR_SYMBOL_LETTER_U = "\U0001f1fa" - REGIONAL_INDICATOR_SYMBOL_LETTER_V = "\U0001f1fb" - REGIONAL_INDICATOR_SYMBOL_LETTER_W = "\U0001f1fc" - REGIONAL_INDICATOR_SYMBOL_LETTER_X = "\U0001f1fd" - REGIONAL_INDICATOR_SYMBOL_LETTER_Y = "\U0001f1fe" - REGIONAL_INDICATOR_SYMBOL_LETTER_Z = "\U0001f1ff" - DINO_CAT = "\U0001f431\u200d\U0001f409" - NINJA_CAT = "\U0001f431\u200d\U0001f464" - STUNT_CAT = "\U0001f431\u200d\U0001f3cd" - ASTRO_CAT = "\U0001f431\u200d\U0001f680" - HACKER_CAT = "\U0001f431\u200d\U0001f4bb" - HIPSTER_CAT = "\U0001f431\u200d\U0001f453" - OLYMPIC_RINGS = "\u25ef\u200d\u25ef\u200d\u25ef\u200d\u25ef\u200d\u25ef" - FLAG_FOR_SUKHBAATAR_MN_051 = "\U0001f3f4\U000e006d\U000e006e\U000e0030\U000e0035\U000e0031\U000e007f" - COUPLE_WITH_HEART_WOMAN_MEDIUM_LIGHT_SKIN_TONE_WOMAN_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fb" - FLAG_FOR_ORKHON_MN_035 = "\U0001f3f4\U000e006d\U000e006e\U000e0030\U000e0033\U000e0035\U000e007f" - FLAG_FOR_TIRIS_ZEMMOUR_MR_11 = "\U0001f3f4\U000e006d\U000e0072\U000e0031\U000e0031\U000e007f" - FLAG_FOR_KHOVD_MN_043 = "\U0001f3f4\U000e006d\U000e006e\U000e0030\U000e0034\U000e0033\U000e007f" - FLAG_FOR_ASSABA_MR_03 = "\U0001f3f4\U000e006d\U000e0072\U000e0030\U000e0033\U000e007f" - FLAG_FOR_BRAKNA_MR_05 = "\U0001f3f4\U000e006d\U000e0072\U000e0030\U000e0035\U000e007f" - FLAG_FOR_GORGOL_MR_04 = "\U0001f3f4\U000e006d\U000e0072\U000e0030\U000e0034\U000e007f" - FLAG_FOR_ARKHANGAI_MN_073 = "\U0001f3f4\U000e006d\U000e006e\U000e0030\U000e0037\U000e0033\U000e007f" - FLAG_FOR_AKMOLA_KZ_AKM = "\U0001f3f4\U000e006b\U000e007a\U000e0061\U000e006b\U000e006d\U000e007f" - FLAG_FOR_BIRKIRKARA_MT_04 = "\U0001f3f4\U000e006d\U000e0074\U000e0030\U000e0034\U000e007f" - FLAG_FOR_IKLIN_MT_19 = "\U0001f3f4\U000e006d\U000e0074\U000e0031\U000e0039\U000e007f" - FLAG_FOR_NAXXAR_MT_38 = "\U0001f3f4\U000e006d\U000e0074\U000e0033\U000e0038\U000e007f" - FLAG_FOR_KALKARA_MT_21 = "\U0001f3f4\U000e006d\U000e0074\U000e0032\U000e0031\U000e007f" - FLAG_FOR_FONTANA_MT_10 = "\U0001f3f4\U000e006d\U000e0074\U000e0031\U000e0030\U000e007f" - FLAG_FOR_LIJA_MT_24 = "\U0001f3f4\U000e006d\U000e0074\U000e0032\U000e0034\U000e007f" - FLAG_FOR_FGURA_MT_08 = "\U0001f3f4\U000e006d\U000e0074\U000e0030\U000e0038\U000e007f" - FLAG_FOR_GUDJA_MT_11 = "\U0001f3f4\U000e006d\U000e0074\U000e0031\U000e0031\U000e007f" - FLAG_FOR_NOUAKCHOTT_SUD_MR_15 = "\U0001f3f4\U000e006d\U000e0072\U000e0031\U000e0035\U000e007f" - FLAG_FOR_MUNXAR_MT_36 = "\U0001f3f4\U000e006d\U000e0074\U000e0033\U000e0036\U000e007f" - FLAG_FOR_G_AJNSIELEM_MT_13 = "\U0001f3f4\U000e006d\U000e0074\U000e0031\U000e0033\U000e007f" - FLAG_FOR_BIRGU_MT_03 = "\U0001f3f4\U000e006d\U000e0074\U000e0030\U000e0033\U000e007f" - FLAG_FOR_ATTARD_MT_01 = "\U0001f3f4\U000e006d\U000e0074\U000e0030\U000e0031\U000e007f" - FLAG_FOR_AMRUN_MT_18 = "\U0001f3f4\U000e006d\U000e0074\U000e0031\U000e0038\U000e007f" - FLAG_FOR_G_ARB_MT_14 = "\U0001f3f4\U000e006d\U000e0074\U000e0031\U000e0034\U000e007f" - FLAG_FOR_MARSAXLOKK_MT_28 = "\U0001f3f4\U000e006d\U000e0074\U000e0032\U000e0038\U000e007f" - FLAG_FOR_G_AXAQ_MT_17 = "\U0001f3f4\U000e006d\U000e0074\U000e0031\U000e0037\U000e007f" - FLAG_FOR_MQABBA_MT_33 = "\U0001f3f4\U000e006d\U000e0074\U000e0033\U000e0033\U000e007f" - FLAG_FOR_GZIRA_MT_12 = "\U0001f3f4\U000e006d\U000e0074\U000e0031\U000e0032\U000e007f" - FLAG_FOR_NADUR_MT_37 = "\U0001f3f4\U000e006d\U000e0074\U000e0033\U000e0037\U000e007f" - FLAG_FOR_MOSTA_MT_32 = "\U0001f3f4\U000e006d\U000e0074\U000e0033\U000e0032\U000e007f" - FLAG_FOR_MELLIE_A_MT_30 = "\U0001f3f4\U000e006d\U000e0074\U000e0033\U000e0030\U000e007f" - FLAG_FOR_PAOLA_MT_39 = "\U0001f3f4\U000e006d\U000e0074\U000e0033\U000e0039\U000e007f" - FLAG_FOR_KERCEM_MT_22 = "\U0001f3f4\U000e006d\U000e0074\U000e0032\U000e0032\U000e007f" - FLAG_FOR_KIRKOP_MT_23 = "\U0001f3f4\U000e006d\U000e0074\U000e0032\U000e0033\U000e007f" - FLAG_FOR_MSIDA_MT_34 = "\U0001f3f4\U000e006d\U000e0074\U000e0033\U000e0034\U000e007f" - FLAG_FOR_DINGLI_MT_07 = "\U0001f3f4\U000e006d\U000e0074\U000e0030\U000e0037\U000e007f" - FLAG_FOR_FLORIANA_MT_09 = "\U0001f3f4\U000e006d\U000e0074\U000e0030\U000e0039\U000e007f" - FLAG_FOR_COSPICUA_MT_06 = "\U0001f3f4\U000e006d\U000e0074\U000e0030\U000e0036\U000e007f" - FLAG_FOR_NOUAKCHOTT_NORD_MR_14 = "\U0001f3f4\U000e006d\U000e0072\U000e0031\U000e0034\U000e007f" - FLAG_FOR_G_ARG_UR_MT_15 = "\U0001f3f4\U000e006d\U000e0074\U000e0031\U000e0035\U000e007f" - COUPLE_WITH_HEART_WOMAN_DARK_SKIN_TONE_WOMAN = "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f469" - FLAG_FOR_IMTARFA_MT_35 = "\U0001f3f4\U000e006d\U000e0074\U000e0033\U000e0035\U000e007f" - FLAG_FOR_BIRZEBBUGA_MT_05 = "\U0001f3f4\U000e006d\U000e0074\U000e0030\U000e0035\U000e007f" - FLAG_FOR_BALZAN_MT_02 = "\U0001f3f4\U000e006d\U000e0074\U000e0030\U000e0032\U000e007f" - FLAG_FOR_REDANGE_LU_RD = "\U0001f3f4\U000e006c\U000e0075\U000e0072\U000e0064\U000e007f" - FLAG_FOR_VALLETTA_MT_60 = "\U0001f3f4\U000e006d\U000e0074\U000e0036\U000e0030\U000e007f" - FLAG_FOR_QRENDI_MT_44 = "\U0001f3f4\U000e006d\U000e0074\U000e0034\U000e0034\U000e007f" - FAMILY_WOMAN_DARK_SKIN_TONE_BABY_DARK_SKIN_TONE_BABY_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f476\U0001f3ff\u200d\U0001f476\U0001f3ff" - FLAG_FOR_FLACQ_MU_FL = "\U0001f3f4\U000e006d\U000e0075\U000e0066\U000e006c\U000e007f" - FLAG_FOR_SANTA_LUCIJA_MT_53 = "\U0001f3f4\U000e006d\U000e0074\U000e0035\U000e0033\U000e007f" - FLAG_FOR_ZEJTUN_MT_67 = "\U0001f3f4\U000e006d\U000e0074\U000e0036\U000e0037\U000e007f" - FLAG_FOR_FUNAFUTI_TV_FUN = "\U0001f3f4\U000e0074\U000e0076\U000e0066\U000e0075\U000e006e\U000e007f" - FLAG_FOR_PEMBROKE_MT_40 = "\U0001f3f4\U000e006d\U000e0074\U000e0034\U000e0030\U000e007f" - FLAG_FOR_CUREPIPE_MU_CU = "\U0001f3f4\U000e006d\U000e0075\U000e0063\U000e0075\U000e007f" - FLAG_FOR_ZURRIEQ_MT_68 = "\U0001f3f4\U000e006d\U000e0074\U000e0036\U000e0038\U000e007f" - FLAG_FOR_QORMI_MT_43 = "\U0001f3f4\U000e006d\U000e0074\U000e0034\U000e0033\U000e007f" - FLAG_FOR_SAINT_LAWRENCE_MT_50 = "\U0001f3f4\U000e006d\U000e0074\U000e0035\U000e0030\U000e007f" - FLAG_FOR_ZABBAR_MT_64 = "\U0001f3f4\U000e006d\U000e0074\U000e0036\U000e0034\U000e007f" - FLAG_FOR_PAMPLEMOUSSES_MU_PA = "\U0001f3f4\U000e006d\U000e0075\U000e0070\U000e0061\U000e007f" - FLAG_FOR_QALA_MT_42 = "\U0001f3f4\U000e006d\U000e0074\U000e0034\U000e0032\U000e007f" - FLAG_FOR_SLIEMA_MT_56 = "\U0001f3f4\U000e006d\U000e0074\U000e0035\U000e0036\U000e007f" - FLAG_FOR_TARXIEN_MT_59 = "\U0001f3f4\U000e006d\U000e0074\U000e0035\U000e0039\U000e007f" - FLAG_FOR_TA_XBIEX_MT_58 = "\U0001f3f4\U000e006d\U000e0074\U000e0035\U000e0038\U000e007f" - KISS_WOMAN_DARK_SKIN_TONE_MAN_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe" - FLAG_FOR_XAG_RA_MT_61 = "\U0001f3f4\U000e006d\U000e0074\U000e0036\U000e0031\U000e007f" - FLAG_FOR_SWIEQI_MT_57 = "\U0001f3f4\U000e006d\U000e0074\U000e0035\U000e0037\U000e007f" - FLAG_FOR_SAN_GWANN_MT_49 = "\U0001f3f4\U000e006d\U000e0074\U000e0034\U000e0039\U000e007f" - FLAG_FOR_ZEBBUG_MT_66 = "\U0001f3f4\U000e006d\U000e0074\U000e0036\U000e0036\U000e007f" - FLAG_FOR_SIGGIEWI_MT_55 = "\U0001f3f4\U000e006d\U000e0074\U000e0035\U000e0035\U000e007f" - FLAG_FOR_PORT_LOUIS_MU_PU = "\U0001f3f4\U000e006d\U000e0075\U000e0070\U000e0075\U000e007f" - FLAG_FOR_SANTA_VENERA_MT_54 = "\U0001f3f4\U000e006d\U000e0074\U000e0035\U000e0034\U000e007f" - FLAG_FOR_XG_AJRA_MT_63 = "\U0001f3f4\U000e006d\U000e0074\U000e0036\U000e0033\U000e007f" - FLAG_FOR_CENTRAL_MW_C = "\U0001f3f4\U000e006d\U000e0077\U000e0063\U000e007f" - FLAG_FOR_SOUTH_PROVINCE_MV_SU = "\U0001f3f4\U000e006d\U000e0076\U000e0073\U000e0075\U000e007f" - FLAG_FOR_TRIESENBERG_LI_10 = "\U0001f3f4\U000e006c\U000e0069\U000e0031\U000e0030\U000e007f" - FLAG_FOR_VACOAS_PHOENIX_MU_VP = "\U0001f3f4\U000e006d\U000e0075\U000e0076\U000e0070\U000e007f" - FLAG_FOR_ANENII_NOI_MD_AN = "\U0001f3f4\U000e006d\U000e0064\U000e0061\U000e006e\U000e007f" - FLAG_FOR_GUANAJUATO_MX_GUA = "\U0001f3f4\U000e006d\U000e0078\U000e0067\U000e0075\U000e0061\U000e007f" - FLAG_FOR_MALE_MV_MLE = "\U0001f3f4\U000e006d\U000e0076\U000e006d\U000e006c\U000e0065\U000e007f" - FLAG_FOR_OAXACA_MX_OAX = "\U0001f3f4\U000e006d\U000e0078\U000e006f\U000e0061\U000e0078\U000e007f" - FLAG_FOR_CENTRAL_PROVINCE_MV_CE = "\U0001f3f4\U000e006d\U000e0076\U000e0063\U000e0065\U000e007f" - FAMILY_MAN_MEDIUM_SKIN_TONE_MAN_MEDIUM_SKIN_TONE_BOY_MEDIUM_SKIN_TONE_GIRL_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f468\U0001f3fd\u200d\U0001f466\U0001f3fd\u200d\U0001f467\U0001f3fd" - FLAG_FOR_MUNSTER_IE_M = "\U0001f3f4\U000e0069\U000e0065\U000e006d\U000e007f" - FLAG_FOR_MANGYSTAU_KZ_MAN = "\U0001f3f4\U000e006b\U000e007a\U000e006d\U000e0061\U000e006e\U000e007f" - FLAG_FOR_BAJA_CALIFORNIA_SUR_MX_BCS = "\U0001f3f4\U000e006d\U000e0078\U000e0062\U000e0063\U000e0073\U000e007f" - FLAG_FOR_CAMPECHE_MX_CAM = "\U0001f3f4\U000e006d\U000e0078\U000e0063\U000e0061\U000e006d\U000e007f" - FLAG_FOR_KYOTO_JP_26 = "\U0001f3f4\U000e006a\U000e0070\U000e0032\U000e0036\U000e007f" - FLAG_FOR_KOHGILUYEH_AND_BOYER_AHMAD_IR_18 = "\U0001f3f4\U000e0069\U000e0072\U000e0031\U000e0038\U000e007f" - FLAG_FOR_HIDALGO_MX_HID = "\U0001f3f4\U000e006d\U000e0078\U000e0068\U000e0069\U000e0064\U000e007f" - FLAG_FOR_CHLEF_DZ_02 = "\U0001f3f4\U000e0064\U000e007a\U000e0030\U000e0032\U000e007f" - FLAG_FOR_QUATRE_BORNES_MU_QB = "\U0001f3f4\U000e006d\U000e0075\U000e0071\U000e0062\U000e007f" - FLAG_FOR_PLAINES_WILHEMS_MU_PW = "\U0001f3f4\U000e006d\U000e0075\U000e0070\U000e0077\U000e007f" - FLAG_FOR_SAVANNE_MU_SA = "\U0001f3f4\U000e006d\U000e0075\U000e0073\U000e0061\U000e007f" - FLAG_FOR_SOUTHERN_MW_S = "\U0001f3f4\U000e006d\U000e0077\U000e0073\U000e007f" - FLAG_FOR_COAHUILA_MX_COA = "\U0001f3f4\U000e006d\U000e0078\U000e0063\U000e006f\U000e0061\U000e007f" - FLAG_FOR_NEGERI_SEMBILAN_MY_05 = "\U0001f3f4\U000e006d\U000e0079\U000e0030\U000e0035\U000e007f" - FLAG_FOR_SOFALA_MZ_S = "\U0001f3f4\U000e006d\U000e007a\U000e0073\U000e007f" - FLAG_FOR_LABUAN_MY_15 = "\U0001f3f4\U000e006d\U000e0079\U000e0031\U000e0035\U000e007f" - FLAG_FOR_NUEVO_LEON_MX_NLE = "\U0001f3f4\U000e006d\U000e0078\U000e006e\U000e006c\U000e0065\U000e007f" - FLAG_FOR_LA_SOURCE_MC_SO = "\U0001f3f4\U000e006d\U000e0063\U000e0073\U000e006f\U000e007f" - FLAG_FOR_SOUTH_GYEONGSANG_KR_48 = "\U0001f3f4\U000e006b\U000e0072\U000e0034\U000e0038\U000e007f" - FAMILY_MAN_LIGHT_SKIN_TONE_MAN_LIGHT_SKIN_TONE_BOY_LIGHT_SKIN_TONE_BOY_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f468\U0001f3fb\u200d\U0001f466\U0001f3fb\u200d\U0001f466\U0001f3fb" - FLAG_FOR_GAZA_MZ_G = "\U0001f3f4\U000e006d\U000e007a\U000e0067\U000e007f" - TAG_GRAVE_ACCENT = "\U000e0060" - FLAG_FOR_MALACCA_MY_04 = "\U0001f3f4\U000e006d\U000e0079\U000e0030\U000e0034\U000e007f" - FLAG_FOR_CABO_DELGADO_MZ_P = "\U0001f3f4\U000e006d\U000e007a\U000e0070\U000e007f" - FLAG_FOR_TLAXCALA_MX_TLA = "\U0001f3f4\U000e006d\U000e0078\U000e0074\U000e006c\U000e0061\U000e007f" - FLAG_FOR_APULIA_IT_75 = "\U0001f3f4\U000e0069\U000e0074\U000e0037\U000e0035\U000e007f" - FLAG_FOR_NAMPULA_MZ_N = "\U0001f3f4\U000e006d\U000e007a\U000e006e\U000e007f" - FLAG_FOR_OGOOUE_LOLO_GA_7 = "\U0001f3f4\U000e0067\U000e0061\U000e0037\U000e007f" - FLAG_FOR_ERONGO_NA_ER = "\U0001f3f4\U000e006e\U000e0061\U000e0065\U000e0072\U000e007f" - FLAG_FOR_TETE_MZ_T = "\U0001f3f4\U000e006d\U000e007a\U000e0074\U000e007f" - FLAG_FOR_UPPER_NORTH_PROVINCE_MV_UN = "\U0001f3f4\U000e006d\U000e0076\U000e0075\U000e006e\U000e007f" - FLAG_FOR_MANICA_MZ_B = "\U0001f3f4\U000e006d\U000e007a\U000e0062\U000e007f" - FLAG_FOR_PUTRAJAYA_MY_16 = "\U0001f3f4\U000e006d\U000e0079\U000e0031\U000e0036\U000e007f" - FLAG_FOR_MAPUTO_MZ_MPM = "\U0001f3f4\U000e006d\U000e007a\U000e006d\U000e0070\U000e006d\U000e007f" - FLAG_FOR_HARDAP_NA_HA = "\U0001f3f4\U000e006e\U000e0061\U000e0068\U000e0061\U000e007f" - FLAG_FOR_NIASSA_MZ_A = "\U0001f3f4\U000e006d\U000e007a\U000e0061\U000e007f" - FLAG_FOR_BAGO_MM_02 = "\U0001f3f4\U000e006d\U000e006d\U000e0030\U000e0032\U000e007f" - FLAG_FOR_INHAMBANE_MZ_I = "\U0001f3f4\U000e006d\U000e007a\U000e0069\U000e007f" - FLAG_FOR_ZAMBEZI_NA_CA = "\U0001f3f4\U000e006e\U000e0061\U000e0063\U000e0061\U000e007f" - FLAG_FOR_PERLIS_MY_09 = "\U0001f3f4\U000e006d\U000e0079\U000e0030\U000e0039\U000e007f" - FLAG_FOR_KAVANGO_WEST_NA_KW = "\U0001f3f4\U000e006e\U000e0061\U000e006b\U000e0077\U000e007f" - FLAG_FOR_RIVIERE_DU_REMPART_MU_RR = "\U0001f3f4\U000e006d\U000e0075\U000e0072\U000e0072\U000e007f" - FLAG_FOR_ZACATECAS_MX_ZAC = "\U0001f3f4\U000e006d\U000e0078\U000e007a\U000e0061\U000e0063\U000e007f" - FLAG_FOR_OSHANA_NA_ON = "\U0001f3f4\U000e006e\U000e0061\U000e006f\U000e006e\U000e007f" - FLAG_FOR_DOSSO_NE_3 = "\U0001f3f4\U000e006e\U000e0065\U000e0033\U000e007f" - FLAG_FOR_KHOMAS_NA_KH = "\U0001f3f4\U000e006e\U000e0061\U000e006b\U000e0068\U000e007f" - FLAG_FOR_TILLABERI_NE_6 = "\U0001f3f4\U000e006e\U000e0065\U000e0036\U000e007f" - FLAG_FOR_KANO_NG_KN = "\U0001f3f4\U000e006e\U000e0067\U000e006b\U000e006e\U000e007f" - FLAG_FOR_ANAMBRA_NG_AN = "\U0001f3f4\U000e006e\U000e0067\U000e0061\U000e006e\U000e007f" - FLAG_FOR_NIAMEY_NE_8 = "\U0001f3f4\U000e006e\U000e0065\U000e0038\U000e007f" - FLAG_FOR_KADUNA_NG_KD = "\U0001f3f4\U000e006e\U000e0067\U000e006b\U000e0064\U000e007f" - FLAG_FOR_JIGAWA_NG_JI = "\U0001f3f4\U000e006e\U000e0067\U000e006a\U000e0069\U000e007f" - FLAG_FOR_MARADI_NE_4 = "\U0001f3f4\U000e006e\U000e0065\U000e0034\U000e007f" - FLAG_FOR_OMUSATI_NA_OS = "\U0001f3f4\U000e006e\U000e0061\U000e006f\U000e0073\U000e007f" - FLAG_FOR_AGADEZ_NE_1 = "\U0001f3f4\U000e006e\U000e0065\U000e0031\U000e007f" - FLAG_FOR_KARAS_NA_KA = "\U0001f3f4\U000e006e\U000e0061\U000e006b\U000e0061\U000e007f" - FLAG_FOR_BENUE_NG_BE = "\U0001f3f4\U000e006e\U000e0067\U000e0062\U000e0065\U000e007f" - FLAG_FOR_KUNENE_NA_KU = "\U0001f3f4\U000e006e\U000e0061\U000e006b\U000e0075\U000e007f" - FLAG_FOR_OHANGWENA_NA_OW = "\U0001f3f4\U000e006e\U000e0061\U000e006f\U000e0077\U000e007f" - FLAG_FOR_EKITI_NG_EK = "\U0001f3f4\U000e006e\U000e0067\U000e0065\U000e006b\U000e007f" - FLAG_FOR_SIDI_BEL_ABBES_DZ_22 = "\U0001f3f4\U000e0064\U000e007a\U000e0032\U000e0032\U000e007f" - FLAG_FOR_EBONYI_NG_EB = "\U0001f3f4\U000e006e\U000e0067\U000e0065\U000e0062\U000e007f" - FLAG_FOR_TAHOUA_NE_5 = "\U0001f3f4\U000e006e\U000e0065\U000e0035\U000e007f" - FLAG_FOR_KATSINA_NG_KT = "\U0001f3f4\U000e006e\U000e0067\U000e006b\U000e0074\U000e007f" - FLAG_FOR_BAYELSA_NG_BY = "\U0001f3f4\U000e006e\U000e0067\U000e0062\U000e0079\U000e007f" - FLAG_FOR_ABIA_NG_AB = "\U0001f3f4\U000e006e\U000e0067\U000e0061\U000e0062\U000e007f" - FLAG_FOR_OTJOZONDJUPA_NA_OD = "\U0001f3f4\U000e006e\U000e0061\U000e006f\U000e0064\U000e007f" - FLAG_FOR_ZINDER_NE_7 = "\U0001f3f4\U000e006e\U000e0065\U000e0037\U000e007f" - MAN_WITH_HEADSCARF_LIGHT_SKIN_TONE = "\U0001f9d5\U0001f3fb\u200d\u2642\ufe0f" - FLAG_FOR_DIFFA_NE_2 = "\U0001f3f4\U000e006e\U000e0065\U000e0032\U000e007f" - FLAG_FOR_AKWA_IBOM_NG_AK = "\U0001f3f4\U000e006e\U000e0067\U000e0061\U000e006b\U000e007f" - FLAG_FOR_BOACO_NI_BO = "\U0001f3f4\U000e006e\U000e0069\U000e0062\U000e006f\U000e007f" - FLAG_FOR_OYO_NG_OY = "\U0001f3f4\U000e006e\U000e0067\U000e006f\U000e0079\U000e007f" - FLAG_FOR_CHONTALES_NI_CO = "\U0001f3f4\U000e006e\U000e0069\U000e0063\U000e006f\U000e007f" - FLAG_FOR_NUEVA_SEGOVIA_NI_NS = "\U0001f3f4\U000e006e\U000e0069\U000e006e\U000e0073\U000e007f" - FLAG_FOR_ROTUMA_FJ_R = "\U0001f3f4\U000e0066\U000e006a\U000e0072\U000e007f" - FLAG_FOR_ESTELI_NI_ES = "\U0001f3f4\U000e006e\U000e0069\U000e0065\U000e0073\U000e007f" - FLAG_FOR_MAYOTTE_FR_MAY = "\U0001f3f4\U000e0066\U000e0072\U000e006d\U000e0061\U000e0079\U000e007f" - FLAG_FOR_TARABA_NG_TA = "\U0001f3f4\U000e006e\U000e0067\U000e0074\U000e0061\U000e007f" - FLAG_FOR_CARAZO_NI_CA = "\U0001f3f4\U000e006e\U000e0069\U000e0063\U000e0061\U000e007f" - FLAG_FOR_JINOTEGA_NI_JI = "\U0001f3f4\U000e006e\U000e0069\U000e006a\U000e0069\U000e007f" - FLAG_FOR_GELDERLAND_NL_GE = "\U0001f3f4\U000e006e\U000e006c\U000e0067\U000e0065\U000e007f" - FLAG_FOR_NORTHERN_DISTRICT_IL_Z = "\U0001f3f4\U000e0069\U000e006c\U000e007a\U000e007f" - FLAG_FOR_NASARAWA_NG_NA = "\U0001f3f4\U000e006e\U000e0067\U000e006e\U000e0061\U000e007f" - FLAG_FOR_MANAGUA_NI_MN = "\U0001f3f4\U000e006e\U000e0069\U000e006d\U000e006e\U000e007f" - FLAG_FOR_SANCTI_SPIRITUS_CU_07 = "\U0001f3f4\U000e0063\U000e0075\U000e0030\U000e0037\U000e007f" - FLAG_FOR_ZAMFARA_NG_ZA = "\U0001f3f4\U000e006e\U000e0067\U000e007a\U000e0061\U000e007f" - FLAG_FOR_CHINANDEGA_NI_CI = "\U0001f3f4\U000e006e\U000e0069\U000e0063\U000e0069\U000e007f" - FLAG_FOR_MATAGALPA_NI_MT = "\U0001f3f4\U000e006e\U000e0069\U000e006d\U000e0074\U000e007f" - FLAG_FOR_MADRIZ_NI_MD = "\U0001f3f4\U000e006e\U000e0069\U000e006d\U000e0064\U000e007f" - FLAG_FOR_RIVAS_NI_RI = "\U0001f3f4\U000e006e\U000e0069\U000e0072\U000e0069\U000e007f" - FLAG_FOR_SAINT_JAMES_JM_08 = "\U0001f3f4\U000e006a\U000e006d\U000e0030\U000e0038\U000e007f" - FLAG_FOR_EL_PROGRESO_GT_PR = "\U0001f3f4\U000e0067\U000e0074\U000e0070\U000e0072\U000e007f" - FLAG_FOR_ATLANTICO_NORTE_NI_AN = "\U0001f3f4\U000e006e\U000e0069\U000e0061\U000e006e\U000e007f" - FLAG_FOR_PLATEAU_NG_PL = "\U0001f3f4\U000e006e\U000e0067\U000e0070\U000e006c\U000e007f" - FLAG_FOR_KABARDINO_BALKAR_RU_KB = "\U0001f3f4\U000e0072\U000e0075\U000e006b\U000e0062\U000e007f" - FLAG_FOR_FLEVOLAND_NL_FL = "\U0001f3f4\U000e006e\U000e006c\U000e0066\U000e006c\U000e007f" - FLAG_FOR_KWARA_NG_KW = "\U0001f3f4\U000e006e\U000e0067\U000e006b\U000e0077\U000e007f" - FLAG_FOR_GRANADA_NI_GR = "\U0001f3f4\U000e006e\U000e0069\U000e0067\U000e0072\U000e007f" - FLAG_FOR_SANTIAGO_DE_CUBA_CU_13 = "\U0001f3f4\U000e0063\U000e0075\U000e0031\U000e0033\U000e007f" - FAMILY_MAN_MEDIUM_LIGHT_SKIN_TONE_WOMAN_MEDIUM_LIGHT_SKIN_TONE_GIRL_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f469\U0001f3fc\u200d\U0001f467\U0001f3fc" - FLAG_FOR_VESTFOLD_NO_07 = "\U0001f3f4\U000e006e\U000e006f\U000e0030\U000e0037\U000e007f" - FLAG_FOR_PURWANCHAL_NP_4 = "\U0001f3f4\U000e006e\U000e0070\U000e0034\U000e007f" - FLAG_FOR_STFOLD_NO_01 = "\U0001f3f4\U000e006e\U000e006f\U000e0030\U000e0031\U000e007f" - FLAG_FOR_S_R_TR_NDELAG_NO_16 = "\U0001f3f4\U000e006e\U000e006f\U000e0031\U000e0036\U000e007f" - FAMILY_MAN_MEDIUM_LIGHT_SKIN_TONE_MAN_MEDIUM_LIGHT_SKIN_TONE_GIRL_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f468\U0001f3fc\u200d\U0001f467\U0001f3fc" - FLAG_FOR_JAN_MAYEN_NO_22 = "\U0001f3f4\U000e006e\U000e006f\U000e0032\U000e0032\U000e007f" - FLAG_FOR_LARVOTTO_MC_LA = "\U0001f3f4\U000e006d\U000e0063\U000e006c\U000e0061\U000e007f" - FLAG_FOR_CENTRAL_NP_1 = "\U0001f3f4\U000e006e\U000e0070\U000e0031\U000e007f" - FLAG_FOR_TELEMARK_NO_08 = "\U0001f3f4\U000e006e\U000e006f\U000e0030\U000e0038\U000e007f" - FLAG_FOR_MADHYA_PASHCHIMANCHAL_NP_2 = "\U0001f3f4\U000e006e\U000e0070\U000e0032\U000e007f" - COUPLE_WITH_HEART_MAN_MEDIUM_LIGHT_SKIN_TONE_MAN_DARK_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff" - FLAG_FOR_AUST_AGDER_NO_09 = "\U0001f3f4\U000e006e\U000e006f\U000e0030\U000e0039\U000e007f" - FLAG_FOR_BALZERS_LI_01 = "\U0001f3f4\U000e006c\U000e0069\U000e0030\U000e0031\U000e007f" - FLAG_FOR_M_RE_OG_ROMSDAL_NO_15 = "\U0001f3f4\U000e006e\U000e006f\U000e0031\U000e0035\U000e007f" - FLAG_FOR_NORD_TR_NDELAG_NO_17 = "\U0001f3f4\U000e006e\U000e006f\U000e0031\U000e0037\U000e007f" - FLAG_FOR_AKERSHUS_NO_02 = "\U0001f3f4\U000e006e\U000e006f\U000e0030\U000e0032\U000e007f" - FLAG_FOR_BUSKERUD_NO_06 = "\U0001f3f4\U000e006e\U000e006f\U000e0030\U000e0036\U000e007f" - FLAG_FOR_BOE_NR_06 = "\U0001f3f4\U000e006e\U000e0072\U000e0030\U000e0036\U000e007f" - FLAG_FOR_NORDLAND_NO_18 = "\U0001f3f4\U000e006e\U000e006f\U000e0031\U000e0038\U000e007f" - FLAG_FOR_PRILEP_MK_62 = "\U0001f3f4\U000e006d\U000e006b\U000e0036\U000e0032\U000e007f" - FLAG_FOR_BAITI_NR_05 = "\U0001f3f4\U000e006e\U000e0072\U000e0030\U000e0035\U000e007f" - FLAG_FOR_ANETAN_NR_03 = "\U0001f3f4\U000e006e\U000e0072\U000e0030\U000e0033\U000e007f" - FLAG_FOR_BUADA_NR_07 = "\U0001f3f4\U000e006e\U000e0072\U000e0030\U000e0037\U000e007f" - FLAG_FOR_NZEREKORE_REGION_GN_N = "\U0001f3f4\U000e0067\U000e006e\U000e006e\U000e007f" - FLAG_FOR_ONDO_NG_ON = "\U0001f3f4\U000e006e\U000e0067\U000e006f\U000e006e\U000e007f" - FLAG_FOR_OPPLAND_NO_05 = "\U0001f3f4\U000e006e\U000e006f\U000e0030\U000e0035\U000e007f" - FLAG_FOR_ANIBARE_NR_04 = "\U0001f3f4\U000e006e\U000e0072\U000e0030\U000e0034\U000e007f" - FLAG_FOR_MUSCAT_OM_MA = "\U0001f3f4\U000e006f\U000e006d\U000e006d\U000e0061\U000e007f" - FLAG_FOR_CHATHAM_ISLANDS_NZ_CIT = "\U0001f3f4\U000e006e\U000e007a\U000e0063\U000e0069\U000e0074\U000e007f" - FLAG_FOR_UNGHENI_MD_UN = "\U0001f3f4\U000e006d\U000e0064\U000e0075\U000e006e\U000e007f" - FLAG_FOR_KANKAN_REGION_GN_K = "\U0001f3f4\U000e0067\U000e006e\U000e006b\U000e007f" - KISS_WOMAN_DARK_SKIN_TONE_MAN_LIGHT_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb" - FLAG_FOR_DAYKUNDI_AF_DAY = "\U0001f3f4\U000e0061\U000e0066\U000e0064\U000e0061\U000e0079\U000e007f" - FLAG_FOR_MUSANDAM_OM_MU = "\U0001f3f4\U000e006f\U000e006d\U000e006d\U000e0075\U000e007f" - FLAG_FOR_KARAK_JO_KA = "\U0001f3f4\U000e006a\U000e006f\U000e006b\U000e0061\U000e007f" - FLAG_FOR_JANUB_ASH_SHARQIYAH_OM_SJ = "\U0001f3f4\U000e006f\U000e006d\U000e0073\U000e006a\U000e007f" - FLAG_FOR_WAIKATO_NZ_WKO = "\U0001f3f4\U000e006e\U000e007a\U000e0077\U000e006b\U000e006f\U000e007f" - FLAG_FOR_TELSIAI_COUNTY_LT_TE = "\U0001f3f4\U000e006c\U000e0074\U000e0074\U000e0065\U000e007f" - FLAG_FOR_SHAMAL_ASH_SHARQIYAH_OM_SS = "\U0001f3f4\U000e006f\U000e006d\U000e0073\U000e0073\U000e007f" - TAG_LATIN_CAPITAL_LETTER_U = "\U000e0055" - FLAG_FOR_IJUW_NR_10 = "\U0001f3f4\U000e006e\U000e0072\U000e0031\U000e0030\U000e007f" - FLAG_FOR_COLON_PA_3 = "\U0001f3f4\U000e0070\U000e0061\U000e0033\U000e007f" - FLAG_FOR_SHAMAL_AL_BATINAH_OM_BS = "\U0001f3f4\U000e006f\U000e006d\U000e0062\U000e0073\U000e007f" - FLAG_FOR_DHOFAR_OM_ZU = "\U0001f3f4\U000e006f\U000e006d\U000e007a\U000e0075\U000e007f" - FLAG_FOR_AD_DHAHIRAH_OM_ZA = "\U0001f3f4\U000e006f\U000e006d\U000e007a\U000e0061\U000e007f" - FLAG_FOR_GISBORNE_NZ_GIS = "\U0001f3f4\U000e006e\U000e007a\U000e0067\U000e0069\U000e0073\U000e007f" - FLAG_FOR_ALOJA_LV_005 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0030\U000e0035\U000e007f" - FLAG_FOR_TROMS_NO_19 = "\U0001f3f4\U000e006e\U000e006f\U000e0031\U000e0039\U000e007f" - KISS_WOMAN_MEDIUM_LIGHT_SKIN_TONE_MAN_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd" - FLAG_FOR_NIBOK_NR_12 = "\U0001f3f4\U000e006e\U000e0072\U000e0031\U000e0032\U000e007f" - FLAG_FOR_JANUB_AL_BATINAH_OM_BJ = "\U0001f3f4\U000e006f\U000e006d\U000e0062\U000e006a\U000e007f" - FLAG_FOR_TUMBES_PE_TUM = "\U0001f3f4\U000e0070\U000e0065\U000e0074\U000e0075\U000e006d\U000e007f" - FLAG_FOR_EL_CALLAO_PE_CAL = "\U0001f3f4\U000e0070\U000e0065\U000e0063\U000e0061\U000e006c\U000e007f" - FLAG_FOR_PUNO_PE_PUN = "\U0001f3f4\U000e0070\U000e0065\U000e0070\U000e0075\U000e006e\U000e007f" - FAMILY_WOMAN_MEDIUM_SKIN_TONE_WOMAN_MEDIUM_SKIN_TONE_GIRL_MEDIUM_SKIN_TONE_BABY_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f469\U0001f3fd\u200d\U0001f467\U0001f3fd\u200d\U0001f476\U0001f3fd" - FLAG_FOR_PASCO_PE_PAS = "\U0001f3f4\U000e0070\U000e0065\U000e0070\U000e0061\U000e0073\U000e007f" - FLAG_FOR_LIMA_PE_LMA = "\U0001f3f4\U000e0070\U000e0065\U000e006c\U000e006d\U000e0061\U000e007f" - FLAG_FOR_MECKLENBURG_VORPOMMERN_DE_MV = "\U0001f3f4\U000e0064\U000e0065\U000e006d\U000e0076\U000e007f" - FLAG_FOR_PIURA_PE_PIU = "\U0001f3f4\U000e0070\U000e0065\U000e0070\U000e0069\U000e0075\U000e007f" - FLAG_FOR_ICA_PE_ICA = "\U0001f3f4\U000e0070\U000e0065\U000e0069\U000e0063\U000e0061\U000e007f" - FLAG_FOR_GITEGA_BI_GI = "\U0001f3f4\U000e0062\U000e0069\U000e0067\U000e0069\U000e007f" - FLAG_FOR_UCAYALI_PE_UCA = "\U0001f3f4\U000e0070\U000e0065\U000e0075\U000e0063\U000e0061\U000e007f" - FLAG_FOR_DENIGOMODU_NR_08 = "\U0001f3f4\U000e006e\U000e0072\U000e0030\U000e0038\U000e007f" - FLAG_FOR_HUANUCO_PE_HUC = "\U0001f3f4\U000e0070\U000e0065\U000e0068\U000e0075\U000e0063\U000e007f" - FLAG_FOR_JUNIN_PE_JUN = "\U0001f3f4\U000e0070\U000e0065\U000e006a\U000e0075\U000e006e\U000e007f" - FLAG_FOR_LA_LIBERTAD_PE_LAL = "\U0001f3f4\U000e0070\U000e0065\U000e006c\U000e0061\U000e006c\U000e007f" - FLAG_FOR_CHIMBU_PG_CPK = "\U0001f3f4\U000e0070\U000e0067\U000e0063\U000e0070\U000e006b\U000e007f" - FLAG_FOR_CAJAMARCA_PE_CAJ = "\U0001f3f4\U000e0070\U000e0065\U000e0063\U000e0061\U000e006a\U000e007f" - FLAG_FOR_BALKAN_TM_B = "\U0001f3f4\U000e0074\U000e006d\U000e0062\U000e007f" - FLAG_FOR_VERAGUAS_PA_9 = "\U0001f3f4\U000e0070\U000e0061\U000e0039\U000e007f" - FLAG_FOR_LAMBAYEQUE_PE_LAM = "\U0001f3f4\U000e0070\U000e0065\U000e006c\U000e0061\U000e006d\U000e007f" - FLAG_FOR_AMAZONAS_PE_AMA = "\U0001f3f4\U000e0070\U000e0065\U000e0061\U000e006d\U000e0061\U000e007f" - FLAG_FOR_NORTH_JEOLLA_KR_45 = "\U0001f3f4\U000e006b\U000e0072\U000e0034\U000e0035\U000e007f" - FLAG_FOR_HUANCAVELICA_PE_HUV = "\U0001f3f4\U000e0070\U000e0065\U000e0068\U000e0075\U000e0076\U000e007f" - FLAG_FOR_DARIEN_PA_5 = "\U0001f3f4\U000e0070\U000e0061\U000e0035\U000e007f" - FLAG_FOR_ROGALAND_NO_11 = "\U0001f3f4\U000e006e\U000e006f\U000e0031\U000e0031\U000e007f" - FLAG_FOR_LORETO_PE_LOR = "\U0001f3f4\U000e0070\U000e0065\U000e006c\U000e006f\U000e0072\U000e007f" - FLAG_FOR_SAN_MARTIN_PE_SAM = "\U0001f3f4\U000e0070\U000e0065\U000e0073\U000e0061\U000e006d\U000e007f" - FLAG_FOR_CHONGQING_CN_50 = "\U0001f3f4\U000e0063\U000e006e\U000e0035\U000e0030\U000e007f" - FLAG_FOR_SANDAUN_PG_SAN = "\U0001f3f4\U000e0070\U000e0067\U000e0073\U000e0061\U000e006e\U000e007f" - FLAG_FOR_CIUDAD_DE_MEXICO_MX_CMX = "\U0001f3f4\U000e006d\U000e0078\U000e0063\U000e006d\U000e0078\U000e007f" - FLAG_FOR_OSUN_NG_OS = "\U0001f3f4\U000e006e\U000e0067\U000e006f\U000e0073\U000e007f" - MAHJONG_TILE_TWO_OF_CIRCLES = "\U0001f01a" - FLAG_FOR_HEDMARK_NO_04 = "\U0001f3f4\U000e006e\U000e006f\U000e0030\U000e0034\U000e007f" - COUPLE_WITH_HEART_MAN_MAN_MEDIUM_SKIN_TONE = "\U0001f468\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd" - FLAG_FOR_NORTHERN_MINDANAO_PH_10 = "\U0001f3f4\U000e0070\U000e0068\U000e0031\U000e0030\U000e007f" - FLAG_FOR_MADANG_PG_MPM = "\U0001f3f4\U000e0070\U000e0067\U000e006d\U000e0070\U000e006d\U000e007f" - FLAG_FOR_WESTERN_PG_WPD = "\U0001f3f4\U000e0070\U000e0067\U000e0077\U000e0070\U000e0064\U000e007f" - FLAG_FOR_SOUTH_PYONGAN_KP_02 = "\U0001f3f4\U000e006b\U000e0070\U000e0030\U000e0032\U000e007f" - FLAG_FOR_EASTERN_VISAYAS_PH_08 = "\U0001f3f4\U000e0070\U000e0068\U000e0030\U000e0038\U000e007f" - FLAG_FOR_WESTERN_FJ_W = "\U0001f3f4\U000e0066\U000e006a\U000e0077\U000e007f" - FLAG_FOR_SOCCSKSARGEN_PH_12 = "\U0001f3f4\U000e0070\U000e0068\U000e0031\U000e0032\U000e007f" - FLAG_FOR_MOROBE_PG_MPL = "\U0001f3f4\U000e0070\U000e0067\U000e006d\U000e0070\U000e006c\U000e007f" - FLAG_FOR_MIMAROPA_PH_41 = "\U0001f3f4\U000e0070\U000e0068\U000e0034\U000e0031\U000e007f" - COUPLE_WITH_HEART_WOMAN_LIGHT_SKIN_TONE_WOMAN_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fe" - FLAG_FOR_HELA_PG_HLA = "\U0001f3f4\U000e0070\U000e0067\U000e0068\U000e006c\U000e0061\U000e007f" - FLAG_FOR_CALABARZON_PH_40 = "\U0001f3f4\U000e0070\U000e0068\U000e0034\U000e0030\U000e007f" - FLAG_FOR_ZAMBOANGA_PENINSULA_PH_09 = "\U0001f3f4\U000e0070\U000e0068\U000e0030\U000e0039\U000e007f" - FLAG_FOR_HERRERA_PA_6 = "\U0001f3f4\U000e0070\U000e0061\U000e0036\U000e007f" - COUPLE_WITH_HEART_MAN_LIGHT_SKIN_TONE_MAN_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb" - FLAG_FOR_EDO_NG_ED = "\U0001f3f4\U000e006e\U000e0067\U000e0065\U000e0064\U000e007f" - FLAG_FOR_MILNE_BAY_PG_MBA = "\U0001f3f4\U000e0070\U000e0067\U000e006d\U000e0062\U000e0061\U000e007f" - FAMILY_WOMAN_LIGHT_SKIN_TONE_GIRL_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f467\U0001f3fb" - FLAG_FOR_WESTERN_VISAYAS_PH_06 = "\U0001f3f4\U000e0070\U000e0068\U000e0030\U000e0036\U000e007f" - FLAG_FOR_ANABAR_NR_02 = "\U0001f3f4\U000e006e\U000e0072\U000e0030\U000e0032\U000e007f" - FLAG_FOR_CARAGA_PH_13 = "\U0001f3f4\U000e0070\U000e0068\U000e0031\U000e0033\U000e007f" - FLAG_FOR_KOGI_NG_KO = "\U0001f3f4\U000e006e\U000e0067\U000e006b\U000e006f\U000e007f" - FLAG_FOR_GUANACASTE_CR_G = "\U0001f3f4\U000e0063\U000e0072\U000e0067\U000e007f" - FLAG_FOR_COROZAL_BZ_CZL = "\U0001f3f4\U000e0062\U000e007a\U000e0063\U000e007a\U000e006c\U000e007f" - FLAG_FOR_ALSUNGA_LV_006 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0030\U000e0036\U000e007f" - FLAG_FOR_TULKARM_PS_TKM = "\U0001f3f4\U000e0070\U000e0073\U000e0074\U000e006b\U000e006d\U000e007f" - FLAG_FOR_OPOLE_PL_OP = "\U0001f3f4\U000e0070\U000e006c\U000e006f\U000e0070\U000e007f" - FLAG_FOR_KHYBER_PAKHTUNKHWA_PK_KP = "\U0001f3f4\U000e0070\U000e006b\U000e006b\U000e0070\U000e007f" - FLAG_FOR_WEST_POMERANIA_PL_ZP = "\U0001f3f4\U000e0070\U000e006c\U000e007a\U000e0070\U000e007f" - FLAG_FOR_KURDAMIR_AZ_KUR = "\U0001f3f4\U000e0061\U000e007a\U000e006b\U000e0075\U000e0072\U000e007f" - FLAG_FOR_LUBUSZ_PL_LB = "\U0001f3f4\U000e0070\U000e006c\U000e006c\U000e0062\U000e007f" - FLAG_FOR_HEBRON_PS_HBN = "\U0001f3f4\U000e0070\U000e0073\U000e0068\U000e0062\U000e006e\U000e007f" - FLAG_FOR_RAFAH_PS_RFH = "\U0001f3f4\U000e0070\U000e0073\U000e0072\U000e0066\U000e0068\U000e007f" - FLAG_FOR_WARMIAN_MASURIA_PL_WN = "\U0001f3f4\U000e0070\U000e006c\U000e0077\U000e006e\U000e007f" - FLAG_FOR_PUNJAB_PK_PB = "\U0001f3f4\U000e0070\U000e006b\U000e0070\U000e0062\U000e007f" - FLAG_FOR_AVEIRO_PT_01 = "\U0001f3f4\U000e0070\U000e0074\U000e0030\U000e0031\U000e007f" - FLAG_FOR_MAZOVIA_PL_MZ = "\U0001f3f4\U000e0070\U000e006c\U000e006d\U000e007a\U000e007f" - FLAG_FOR_LUBLIN_PL_LU = "\U0001f3f4\U000e0070\U000e006c\U000e006c\U000e0075\U000e007f" - FLAG_FOR_NABLUS_PS_NBS = "\U0001f3f4\U000e0070\U000e0073\U000e006e\U000e0062\U000e0073\U000e007f" - FLAG_FOR_QALQILYA_PS_QQA = "\U0001f3f4\U000e0070\U000e0073\U000e0071\U000e0071\U000e0061\U000e007f" - FLAG_FOR_KUYAVIAN_POMERANIA_PL_KP = "\U0001f3f4\U000e0070\U000e006c\U000e006b\U000e0070\U000e007f" - FLAG_FOR_SINDH_PK_SD = "\U0001f3f4\U000e0070\U000e006b\U000e0073\U000e0064\U000e007f" - FLAG_FOR_PODLASKIE_PL_PD = "\U0001f3f4\U000e0070\U000e006c\U000e0070\U000e0064\U000e007f" - FLAG_FOR_DAVAO_PH_11 = "\U0001f3f4\U000e0070\U000e0068\U000e0031\U000e0031\U000e007f" - FLAG_FOR_SUBCARPATHIA_PL_PK = "\U0001f3f4\U000e0070\U000e006c\U000e0070\U000e006b\U000e007f" - FLAG_FOR_ODZ_PL_LD = "\U0001f3f4\U000e0070\U000e006c\U000e006c\U000e0064\U000e007f" - FLAG_FOR_SALFIT_PS_SLT = "\U0001f3f4\U000e0070\U000e0073\U000e0073\U000e006c\U000e0074\U000e007f" - FLAG_FOR_JENIN_PS_JEN = "\U0001f3f4\U000e0070\U000e0073\U000e006a\U000e0065\U000e006e\U000e007f" - FLAG_FOR_SILESIA_PL_SL = "\U0001f3f4\U000e0070\U000e006c\U000e0073\U000e006c\U000e007f" - FLAG_FOR_KHAN_YUNIS_PS_KYS = "\U0001f3f4\U000e0070\U000e0073\U000e006b\U000e0079\U000e0073\U000e007f" - TAG_RIGHT_SQUARE_BRACKET = "\U000e005d" - FLAG_FOR_MOSCOW_PROVINCE_RU_MOS = "\U0001f3f4\U000e0072\U000e0075\U000e006d\U000e006f\U000e0073\U000e007f" - FLAG_FOR_TUSCANY_IT_52 = "\U0001f3f4\U000e0069\U000e0074\U000e0035\U000e0032\U000e007f" - FLAG_FOR_BICOL_PH_05 = "\U0001f3f4\U000e0070\U000e0068\U000e0030\U000e0035\U000e007f" - TAG_LATIN_CAPITAL_LETTER_K = "\U000e004b" - FLAG_FOR_LOWER_SILESIAN_PL_DS = "\U0001f3f4\U000e0070\U000e006c\U000e0064\U000e0073\U000e007f" - FLAG_FOR_CAGAYAN_VALLEY_PH_02 = "\U0001f3f4\U000e0070\U000e0068\U000e0030\U000e0032\U000e007f" - FLAG_FOR_RIO_DE_JANEIRO_BR_RJ = "\U0001f3f4\U000e0062\U000e0072\U000e0072\U000e006a\U000e007f" - FLAG_FOR_EVORA_PT_07 = "\U0001f3f4\U000e0070\U000e0074\U000e0030\U000e0037\U000e007f" - FLAG_FOR_NGARAARD_PW_214 = "\U0001f3f4\U000e0070\U000e0077\U000e0032\U000e0031\U000e0034\U000e007f" - FLAG_FOR_BRAGANCA_PT_04 = "\U0001f3f4\U000e0070\U000e0074\U000e0030\U000e0034\U000e007f" - FLAG_FOR_SHIDA_KARTLI_GE_SK = "\U0001f3f4\U000e0067\U000e0065\U000e0073\U000e006b\U000e007f" - FLAG_FOR_MENENG_NR_11 = "\U0001f3f4\U000e006e\U000e0072\U000e0031\U000e0031\U000e007f" - FLAG_FOR_AIMELIIK_PW_002 = "\U0001f3f4\U000e0070\U000e0077\U000e0030\U000e0030\U000e0032\U000e007f" - FLAG_FOR_AMAMBAY_PY_13 = "\U0001f3f4\U000e0070\U000e0079\U000e0031\U000e0033\U000e007f" - FLAG_FOR_HATOHOBEI_PW_050 = "\U0001f3f4\U000e0070\U000e0077\U000e0030\U000e0035\U000e0030\U000e007f" - FLAG_FOR_CENTRAL_PY_11 = "\U0001f3f4\U000e0070\U000e0079\U000e0031\U000e0031\U000e007f" - FLAG_FOR_COIMBRA_PT_06 = "\U0001f3f4\U000e0070\U000e0074\U000e0030\U000e0036\U000e007f" - FLAG_FOR_CENTRAL_PG_CPM = "\U0001f3f4\U000e0070\U000e0067\U000e0063\U000e0070\U000e006d\U000e007f" - FLAG_FOR_KOROR_PW_150 = "\U0001f3f4\U000e0070\U000e0077\U000e0031\U000e0035\U000e0030\U000e007f" - FLAG_FOR_COCLE_PA_2 = "\U0001f3f4\U000e0070\U000e0061\U000e0032\U000e007f" - FLAG_FOR_NEEMBUCU_PY_12 = "\U0001f3f4\U000e0070\U000e0079\U000e0031\U000e0032\U000e007f" - FLAG_FOR_AIRAI_PW_004 = "\U0001f3f4\U000e0070\U000e0077\U000e0030\U000e0030\U000e0034\U000e007f" - FLAG_FOR_SANTAREM_PT_14 = "\U0001f3f4\U000e0070\U000e0074\U000e0031\U000e0034\U000e007f" - FLAG_FOR_NGATPANG_PW_224 = "\U0001f3f4\U000e0070\U000e0077\U000e0032\U000e0032\U000e0034\U000e007f" - FLAG_FOR_PORTALEGRE_PT_12 = "\U0001f3f4\U000e0070\U000e0074\U000e0031\U000e0032\U000e007f" - FLAG_FOR_LEIRIA_PT_10 = "\U0001f3f4\U000e0070\U000e0074\U000e0031\U000e0030\U000e007f" - FLAG_FOR_NGARDMAU_PW_222 = "\U0001f3f4\U000e0070\U000e0077\U000e0032\U000e0032\U000e0032\U000e007f" - FLAG_FOR_NGCHESAR_PW_226 = "\U0001f3f4\U000e0070\U000e0077\U000e0032\U000e0032\U000e0036\U000e007f" - FLAG_FOR_NORTH_HWANGHAE_KP_06 = "\U0001f3f4\U000e006b\U000e0070\U000e0030\U000e0036\U000e007f" - COUPLE_WITH_HEART_WOMAN_MEDIUM_LIGHT_SKIN_TONE_MAN_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd" - FLAG_FOR_WESTERN_NP_3 = "\U0001f3f4\U000e006e\U000e0070\U000e0033\U000e007f" - FLAG_FOR_VIANA_DO_CASTELO_PT_16 = "\U0001f3f4\U000e0070\U000e0074\U000e0031\U000e0036\U000e007f" - FLAG_FOR_CANINDEYU_PY_14 = "\U0001f3f4\U000e0070\U000e0079\U000e0031\U000e0034\U000e007f" - FLAG_FOR_CORDILLERA_PY_3 = "\U0001f3f4\U000e0070\U000e0079\U000e0033\U000e007f" - FLAG_FOR_CAAZAPA_PY_6 = "\U0001f3f4\U000e0070\U000e0079\U000e0036\U000e007f" - FLAG_FOR_COVASNA_RO_CV = "\U0001f3f4\U000e0072\U000e006f\U000e0063\U000e0076\U000e007f" - FLAG_FOR_GIURGIU_RO_GR = "\U0001f3f4\U000e0072\U000e006f\U000e0067\U000e0072\U000e007f" - FLAG_FOR_DAMBOVITA_RO_DB = "\U0001f3f4\U000e0072\U000e006f\U000e0064\U000e0062\U000e007f" - FLAG_FOR_ITAPUA_PY_7 = "\U0001f3f4\U000e0070\U000e0079\U000e0037\U000e007f" - FLAG_FOR_CAAGUAZU_PY_5 = "\U0001f3f4\U000e0070\U000e0079\U000e0035\U000e007f" - FLAG_FOR_SAN_PEDRO_PY_2 = "\U0001f3f4\U000e0070\U000e0079\U000e0032\U000e007f" - FLAG_FOR_NGEREMLENGUI_PW_227 = "\U0001f3f4\U000e0070\U000e0077\U000e0032\U000e0032\U000e0037\U000e007f" - FLAG_FOR_BIHOR_RO_BH = "\U0001f3f4\U000e0072\U000e006f\U000e0062\U000e0068\U000e007f" - FLAG_FOR_DOLJ_RO_DJ = "\U0001f3f4\U000e0072\U000e006f\U000e0064\U000e006a\U000e007f" - FLAG_FOR_ARGES_RO_AG = "\U0001f3f4\U000e0072\U000e006f\U000e0061\U000e0067\U000e007f" - FLAG_FOR_AL_KHOR_QA_KH = "\U0001f3f4\U000e0071\U000e0061\U000e006b\U000e0068\U000e007f" - FLAG_FOR_GILGIT_BALTISTAN_PK_GB = "\U0001f3f4\U000e0070\U000e006b\U000e0067\U000e0062\U000e007f" - FLAG_FOR_GUAIRA_PY_4 = "\U0001f3f4\U000e0070\U000e0079\U000e0034\U000e007f" - FLAG_FOR_PARAGUARI_PY_9 = "\U0001f3f4\U000e0070\U000e0079\U000e0039\U000e007f" - FLAG_FOR_CAPITAL_IS_1 = "\U0001f3f4\U000e0069\U000e0073\U000e0031\U000e007f" - FLAG_FOR_GALATI_RO_GL = "\U0001f3f4\U000e0072\U000e006f\U000e0067\U000e006c\U000e007f" - FLAG_FOR_ARAD_RO_AR = "\U0001f3f4\U000e0072\U000e006f\U000e0061\U000e0072\U000e007f" - FLAG_FOR_BRAILA_RO_BR = "\U0001f3f4\U000e0072\U000e006f\U000e0062\U000e0072\U000e007f" - FLAG_FOR_TIVAT_ME_19 = "\U0001f3f4\U000e006d\U000e0065\U000e0031\U000e0039\U000e007f" - FLAG_FOR_HARGHITA_RO_HR = "\U0001f3f4\U000e0072\U000e006f\U000e0068\U000e0072\U000e007f" - FLAG_FOR_MISIONES_PY_8 = "\U0001f3f4\U000e0070\U000e0079\U000e0038\U000e007f" - FLAG_FOR_AL_DAAYEN_QA_ZA = "\U0001f3f4\U000e0071\U000e0061\U000e007a\U000e0061\U000e007f" - FLAG_FOR_DIKHIL_DJ_DI = "\U0001f3f4\U000e0064\U000e006a\U000e0064\U000e0069\U000e007f" - FLAG_FOR_OLT_RO_OT = "\U0001f3f4\U000e0072\U000e006f\U000e006f\U000e0074\U000e007f" - FLAG_FOR_BRANICEVO_RS_11 = "\U0001f3f4\U000e0072\U000e0073\U000e0031\U000e0031\U000e007f" - FLAG_FOR_RASINA_RS_19 = "\U0001f3f4\U000e0072\U000e0073\U000e0031\U000e0039\U000e007f" - FLAG_FOR_SUCEAVA_RO_SV = "\U0001f3f4\U000e0072\U000e006f\U000e0073\U000e0076\U000e007f" - FLAG_FOR_ZAJECAR_RS_15 = "\U0001f3f4\U000e0072\U000e0073\U000e0031\U000e0035\U000e007f" - WHITE_HEART_SUIT = "\u2661" - FLAG_FOR_JABLANICA_RS_23 = "\U0001f3f4\U000e0072\U000e0073\U000e0032\U000e0033\U000e007f" - FLAG_FOR_SUMADIJA_RS_12 = "\U0001f3f4\U000e0072\U000e0073\U000e0031\U000e0032\U000e007f" - FLAG_FOR_PIROT_RS_22 = "\U0001f3f4\U000e0072\U000e0073\U000e0032\U000e0032\U000e007f" - FLAG_FOR_MURES_RO_MS = "\U0001f3f4\U000e0072\U000e006f\U000e006d\U000e0073\U000e007f" - FLAG_FOR_TELEORMAN_RO_TR = "\U0001f3f4\U000e0072\U000e006f\U000e0074\U000e0072\U000e007f" - FLAG_FOR_MACVA_RS_08 = "\U0001f3f4\U000e0072\U000e0073\U000e0030\U000e0038\U000e007f" - FLAG_FOR_MORAVICA_RS_17 = "\U0001f3f4\U000e0072\U000e0073\U000e0031\U000e0037\U000e007f" - FLAG_FOR_ILFOV_RO_IF = "\U0001f3f4\U000e0072\U000e006f\U000e0069\U000e0066\U000e007f" - FLAG_FOR_PRAHOVA_RO_PH = "\U0001f3f4\U000e0072\U000e006f\U000e0070\U000e0068\U000e007f" - FLAG_FOR_LOS_LAGOS_CL_LL = "\U0001f3f4\U000e0063\U000e006c\U000e006c\U000e006c\U000e007f" - FLAG_FOR_TULCEA_RO_TL = "\U0001f3f4\U000e0072\U000e006f\U000e0074\U000e006c\U000e007f" - FLAG_FOR_IALOMITA_RO_IL = "\U0001f3f4\U000e0072\U000e006f\U000e0069\U000e006c\U000e007f" - FLAG_FOR_MOULINS_MC_MU = "\U0001f3f4\U000e006d\U000e0063\U000e006d\U000e0075\U000e007f" - FLAG_FOR_KOLUBARA_RS_09 = "\U0001f3f4\U000e0072\U000e0073\U000e0030\U000e0039\U000e007f" - FLAG_FOR_ZLATIBOR_RS_16 = "\U0001f3f4\U000e0072\U000e0073\U000e0031\U000e0036\U000e007f" - FLAG_FOR_SALAJ_RO_SJ = "\U0001f3f4\U000e0072\U000e006f\U000e0073\U000e006a\U000e007f" - FLAG_FOR_VASLUI_RO_VS = "\U0001f3f4\U000e0072\U000e006f\U000e0076\U000e0073\U000e007f" - FLAG_FOR_POMORAVLJE_RS_13 = "\U0001f3f4\U000e0072\U000e0073\U000e0031\U000e0033\U000e007f" - FLAG_FOR_NISAVA_RS_20 = "\U0001f3f4\U000e0072\U000e0073\U000e0032\U000e0030\U000e007f" - FLAG_FOR_MEHEDINTI_RO_MH = "\U0001f3f4\U000e0072\U000e006f\U000e006d\U000e0068\U000e007f" - FLAG_FOR_RASKA_RS_18 = "\U0001f3f4\U000e0072\U000e0073\U000e0031\U000e0038\U000e007f" - FLAG_FOR_PCINJA_RS_24 = "\U0001f3f4\U000e0072\U000e0073\U000e0032\U000e0034\U000e007f" - FLAG_FOR_BEOGRAD_RS_00 = "\U0001f3f4\U000e0072\U000e0073\U000e0030\U000e0030\U000e007f" - FLAG_FOR_TACNA_PE_TAC = "\U0001f3f4\U000e0070\U000e0065\U000e0074\U000e0061\U000e0063\U000e007f" - FLAG_FOR_BELGOROD_RU_BEL = "\U0001f3f4\U000e0072\U000e0075\U000e0062\U000e0065\U000e006c\U000e007f" - FLAG_FOR_KURGAN_RU_KGN = "\U0001f3f4\U000e0072\U000e0075\U000e006b\U000e0067\U000e006e\U000e007f" - FLAG_FOR_CHELYABINSK_RU_CHE = "\U0001f3f4\U000e0072\U000e0075\U000e0063\U000e0068\U000e0065\U000e007f" - FLAG_FOR_KIROV_RU_KIR = "\U0001f3f4\U000e0072\U000e0075\U000e006b\U000e0069\U000e0072\U000e007f" - KISS_MAN_LIGHT_SKIN_TONE_MAN_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb" - FLAG_FOR_UTTAR_PRADESH_IN_UP = "\U0001f3f4\U000e0069\U000e006e\U000e0075\U000e0070\U000e007f" - FLAG_FOR_KOMI_RU_KO = "\U0001f3f4\U000e0072\U000e0075\U000e006b\U000e006f\U000e007f" - FLAG_FOR_IVANOVO_RU_IVA = "\U0001f3f4\U000e0072\U000e0075\U000e0069\U000e0076\U000e0061\U000e007f" - FLAG_FOR_ALBA_RO_AB = "\U0001f3f4\U000e0072\U000e006f\U000e0061\U000e0062\U000e007f" - FLAG_FOR_KHANTY_MANSI_RU_KHM = "\U0001f3f4\U000e0072\U000e0075\U000e006b\U000e0068\U000e006d\U000e007f" - FLAG_FOR_KALUGA_RU_KLU = "\U0001f3f4\U000e0072\U000e0075\U000e006b\U000e006c\U000e0075\U000e007f" - FLAG_FOR_PECS_HU_PS = "\U0001f3f4\U000e0068\U000e0075\U000e0070\U000e0073\U000e007f" - FLAG_FOR_MARI_EL_RU_ME = "\U0001f3f4\U000e0072\U000e0075\U000e006d\U000e0065\U000e007f" - FLAG_FOR_IRKUTSK_RU_IRK = "\U0001f3f4\U000e0072\U000e0075\U000e0069\U000e0072\U000e006b\U000e007f" - FLAG_FOR_PLAV_ME_13 = "\U0001f3f4\U000e006d\U000e0065\U000e0031\U000e0033\U000e007f" - FLAG_FOR_GRAND_KRU_LR_GK = "\U0001f3f4\U000e006c\U000e0072\U000e0067\U000e006b\U000e007f" - FLAG_FOR_SIEM_REAP_KH_17 = "\U0001f3f4\U000e006b\U000e0068\U000e0031\U000e0037\U000e007f" - FLAG_FOR_KAMCHATKA_KRAI_RU_KAM = "\U0001f3f4\U000e0072\U000e0075\U000e006b\U000e0061\U000e006d\U000e007f" - FLAG_FOR_ALTAI_RU_AL = "\U0001f3f4\U000e0072\U000e0075\U000e0061\U000e006c\U000e007f" - FLAG_FOR_MORDOVIA_RU_MO = "\U0001f3f4\U000e0072\U000e0075\U000e006d\U000e006f\U000e007f" - FLAG_FOR_ASTRAKHAN_RU_AST = "\U0001f3f4\U000e0072\U000e0075\U000e0061\U000e0073\U000e0074\U000e007f" - FLAG_FOR_KARACHAY_CHERKESS_RU_KC = "\U0001f3f4\U000e0072\U000e0075\U000e006b\U000e0063\U000e007f" - FLAG_FOR_KEMEROVO_RU_KEM = "\U0001f3f4\U000e0072\U000e0075\U000e006b\U000e0065\U000e006d\U000e007f" - FLAG_FOR_ENGA_PG_EPW = "\U0001f3f4\U000e0070\U000e0067\U000e0065\U000e0070\U000e0077\U000e007f" - FLAG_FOR_VOLOGDA_RU_VLG = "\U0001f3f4\U000e0072\U000e0075\U000e0076\U000e006c\U000e0067\U000e007f" - FLAG_FOR_TOMSK_RU_TOM = "\U0001f3f4\U000e0072\U000e0075\U000e0074\U000e006f\U000e006d\U000e007f" - FLAG_FOR_VLADIMIR_RU_VLA = "\U0001f3f4\U000e0072\U000e0075\U000e0076\U000e006c\U000e0061\U000e007f" - FLAG_FOR_CONCEPCION_PY_1 = "\U0001f3f4\U000e0070\U000e0079\U000e0031\U000e007f" - FLAG_FOR_BAUCHI_NG_BA = "\U0001f3f4\U000e006e\U000e0067\U000e0062\U000e0061\U000e007f" - FLAG_FOR_NIZHNY_NOVGOROD_RU_NIZ = "\U0001f3f4\U000e0072\U000e0075\U000e006e\U000e0069\U000e007a\U000e007f" - FLAG_FOR_ORENBURG_RU_ORE = "\U0001f3f4\U000e0072\U000e0075\U000e006f\U000e0072\U000e0065\U000e007f" - FLAG_FOR_NOVOSIBIRSK_RU_NVS = "\U0001f3f4\U000e0072\U000e0075\U000e006e\U000e0076\U000e0073\U000e007f" - FLAG_FOR_SAINT_JOHN_GD_04 = "\U0001f3f4\U000e0067\U000e0064\U000e0030\U000e0034\U000e007f" - FLAG_FOR_TVER_RU_TVE = "\U0001f3f4\U000e0072\U000e0075\U000e0074\U000e0076\U000e0065\U000e007f" - FLAG_FOR_NOVGOROD_RU_NGR = "\U0001f3f4\U000e0072\U000e0075\U000e006e\U000e0067\U000e0072\U000e007f" - FLAG_FOR_TULA_RU_TUL = "\U0001f3f4\U000e0072\U000e0075\U000e0074\U000e0075\U000e006c\U000e007f" - FLAG_FOR_CASTELO_BRANCO_PT_05 = "\U0001f3f4\U000e0070\U000e0074\U000e0030\U000e0035\U000e007f" - FLAG_FOR_PRESIDENTE_HAYES_PY_15 = "\U0001f3f4\U000e0070\U000e0079\U000e0031\U000e0035\U000e007f" - FLAG_FOR_SMOLENSK_RU_SMO = "\U0001f3f4\U000e0072\U000e0075\U000e0073\U000e006d\U000e006f\U000e007f" - FLAG_FOR_TYUMEN_RU_TYU = "\U0001f3f4\U000e0072\U000e0075\U000e0074\U000e0079\U000e0075\U000e007f" - FLAG_FOR_SVERDLOVSK_RU_SVE = "\U0001f3f4\U000e0072\U000e0075\U000e0073\U000e0076\U000e0065\U000e007f" - FLAG_FOR_DAMAN_AND_DIU_IN_DD = "\U0001f3f4\U000e0069\U000e006e\U000e0064\U000e0064\U000e007f" - FLAG_FOR_SARATOV_RU_SAR = "\U0001f3f4\U000e0072\U000e0075\U000e0073\U000e0061\U000e0072\U000e007f" - FLAG_FOR_NENETS_RU_NEN = "\U0001f3f4\U000e0072\U000e0075\U000e006e\U000e0065\U000e006e\U000e007f" - FLAG_FOR_XIANGKHOUANG_LA_XI = "\U0001f3f4\U000e006c\U000e0061\U000e0078\U000e0069\U000e007f" - FLAG_FOR_PSKOV_RU_PSK = "\U0001f3f4\U000e0072\U000e0075\U000e0070\U000e0073\U000e006b\U000e007f" - FLAG_FOR_OTAGO_NZ_OTA = "\U0001f3f4\U000e006e\U000e007a\U000e006f\U000e0074\U000e0061\U000e007f" - FLAG_FOR_CANTERBURY_NZ_CAN = "\U0001f3f4\U000e006e\U000e007a\U000e0063\U000e0061\U000e006e\U000e007f" - FLAG_FOR_HA_IL_SA_06 = "\U0001f3f4\U000e0073\U000e0061\U000e0030\U000e0036\U000e007f" - FLAG_FOR_EASTERN_RW_02 = "\U0001f3f4\U000e0072\U000e0077\U000e0030\U000e0032\U000e007f" - FLAG_FOR_TUVA_RU_TY = "\U0001f3f4\U000e0072\U000e0075\U000e0074\U000e0079\U000e007f" - FLAG_FOR_ANSE_BOILEAU_SC_02 = "\U0001f3f4\U000e0073\U000e0063\U000e0030\U000e0032\U000e007f" - FLAG_FOR_ZABAYKALSKY_KRAI_RU_ZAB = "\U0001f3f4\U000e0072\U000e0075\U000e007a\U000e0061\U000e0062\U000e007f" - FLAG_FOR_BAIE_SAINTE_ANNE_SC_07 = "\U0001f3f4\U000e0073\U000e0063\U000e0030\U000e0037\U000e007f" - FAMILY_MAN_DARK_SKIN_TONE_MAN_DARK_SKIN_TONE_GIRL_DARK_SKIN_TONE_BOY_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f468\U0001f3ff\u200d\U0001f467\U0001f3ff\u200d\U0001f466\U0001f3ff" - FLAG_FOR_CHOISEUL_SB_CH = "\U0001f3f4\U000e0073\U000e0062\U000e0063\U000e0068\U000e007f" - FLAG_FOR_SOUTHERN_RW_05 = "\U0001f3f4\U000e0072\U000e0077\U000e0030\U000e0035\U000e007f" - FLAG_FOR_VAS_HU_VA = "\U0001f3f4\U000e0068\U000e0075\U000e0076\U000e0061\U000e007f" - FLAG_FOR_ASIR_SA_14 = "\U0001f3f4\U000e0073\U000e0061\U000e0031\U000e0034\U000e007f" - FLAG_FOR_NAJRAN_SA_10 = "\U0001f3f4\U000e0073\U000e0061\U000e0031\U000e0030\U000e007f" - FLAG_FOR_AL_JAWF_SA_12 = "\U0001f3f4\U000e0073\U000e0061\U000e0031\U000e0032\U000e007f" - FLAG_FOR_WESTERN_RW_04 = "\U0001f3f4\U000e0072\U000e0077\U000e0030\U000e0034\U000e007f" - FLAG_FOR_RENNELL_AND_BELLONA_SB_RB = "\U0001f3f4\U000e0073\U000e0062\U000e0072\U000e0062\U000e007f" - FLAG_FOR_EASTERN_SA_04 = "\U0001f3f4\U000e0073\U000e0061\U000e0030\U000e0034\U000e007f" - FLAG_FOR_MALAITA_SB_ML = "\U0001f3f4\U000e0073\U000e0062\U000e006d\U000e006c\U000e007f" - FLAG_FOR_KRASNOYARSK_KRAI_RU_KYA = "\U0001f3f4\U000e0072\U000e0075\U000e006b\U000e0079\U000e0061\U000e007f" - FLAG_FOR_BEAU_VALLON_SC_08 = "\U0001f3f4\U000e0073\U000e0063\U000e0030\U000e0038\U000e007f" - FLAG_FOR_KIGALI_RW_01 = "\U0001f3f4\U000e0072\U000e0077\U000e0030\U000e0031\U000e007f" - FLAG_FOR_ISABEL_SB_IS = "\U0001f3f4\U000e0073\U000e0062\U000e0069\U000e0073\U000e007f" - FLAG_FOR_NORTHERN_BORDERS_SA_08 = "\U0001f3f4\U000e0073\U000e0061\U000e0030\U000e0038\U000e007f" - FLAG_FOR_YAROSLAVL_RU_YAR = "\U0001f3f4\U000e0072\U000e0075\U000e0079\U000e0061\U000e0072\U000e007f" - FLAG_FOR_WILTZ_LU_WI = "\U0001f3f4\U000e006c\U000e0075\U000e0077\U000e0069\U000e007f" - FLAG_FOR_HONIARA_SB_CT = "\U0001f3f4\U000e0073\U000e0062\U000e0063\U000e0074\U000e007f" - FLAG_FOR_NORTHERN_RW_03 = "\U0001f3f4\U000e0072\U000e0077\U000e0030\U000e0033\U000e007f" - FLAG_FOR_SONSOROL_PW_370 = "\U0001f3f4\U000e0070\U000e0077\U000e0033\U000e0037\U000e0030\U000e007f" - FLAG_FOR_TABUK_SA_07 = "\U0001f3f4\U000e0073\U000e0061\U000e0030\U000e0037\U000e007f" - FLAG_FOR_LA_RIVIERE_ANGLAISE_SC_16 = "\U0001f3f4\U000e0073\U000e0063\U000e0031\U000e0036\U000e007f" - FLAG_FOR_GRAND_ANSE_MAHE_SC_13 = "\U0001f3f4\U000e0073\U000e0063\U000e0031\U000e0033\U000e007f" - FLAG_FOR_POINTE_LA_RUE_SC_20 = "\U0001f3f4\U000e0073\U000e0063\U000e0032\U000e0030\U000e007f" - FLAG_FOR_CASCADE_SC_11 = "\U0001f3f4\U000e0073\U000e0063\U000e0031\U000e0031\U000e007f" - FLAG_FOR_NORTH_DARFUR_SD_DN = "\U0001f3f4\U000e0073\U000e0064\U000e0064\U000e006e\U000e007f" - FLAG_FOR_WEST_KURDUFAN_SD_GK = "\U0001f3f4\U000e0073\U000e0064\U000e0067\U000e006b\U000e007f" - FLAG_FOR_RIVER_NILE_SD_NR = "\U0001f3f4\U000e0073\U000e0064\U000e006e\U000e0072\U000e007f" - FLAG_FOR_EAST_DARFUR_SD_DE = "\U0001f3f4\U000e0073\U000e0064\U000e0064\U000e0065\U000e007f" - FLAG_FOR_NORRBOTTEN_SE_BD = "\U0001f3f4\U000e0073\U000e0065\U000e0062\U000e0064\U000e007f" - FLAG_FOR_SODERMANLAND_SE_D = "\U0001f3f4\U000e0073\U000e0065\U000e0064\U000e007f" - FLAG_FOR_TAKAMAKA_SC_23 = "\U0001f3f4\U000e0073\U000e0063\U000e0032\U000e0033\U000e007f" - FLAG_FOR_FRENCH_POLYNESIA_FR_PF = "\U0001f3f4\U000e0066\U000e0072\U000e0070\U000e0066\U000e007f" - FLAG_FOR_AL_QADARIF_SD_GD = "\U0001f3f4\U000e0073\U000e0064\U000e0067\U000e0064\U000e007f" - FLAG_FOR_VASTERBOTTEN_SE_AC = "\U0001f3f4\U000e0073\U000e0065\U000e0061\U000e0063\U000e007f" - FLAG_FOR_NORTHERN_SD_NO = "\U0001f3f4\U000e0073\U000e0064\U000e006e\U000e006f\U000e007f" - FLAG_FOR_MONT_FLEURI_SC_18 = "\U0001f3f4\U000e0073\U000e0063\U000e0031\U000e0038\U000e007f" - FLAG_FOR_CENTRAL_SB_CE = "\U0001f3f4\U000e0073\U000e0062\U000e0063\U000e0065\U000e007f" - FLAG_FOR_CENTRAL_DARFUR_SD_DC = "\U0001f3f4\U000e0073\U000e0064\U000e0064\U000e0063\U000e007f" - FLAG_FOR_VOLGOGRAD_RU_VGG = "\U0001f3f4\U000e0072\U000e0075\U000e0076\U000e0067\U000e0067\U000e007f" - FLAG_FOR_PLAISANCE_SC_19 = "\U0001f3f4\U000e0073\U000e0063\U000e0031\U000e0039\U000e007f" - FLAG_FOR_MONT_BUXTON_SC_17 = "\U0001f3f4\U000e0073\U000e0063\U000e0031\U000e0037\U000e007f" - FLAG_FOR_KASSALA_SD_KA = "\U0001f3f4\U000e0073\U000e0064\U000e006b\U000e0061\U000e007f" - FLAG_FOR_LES_MAMELLES_SC_24 = "\U0001f3f4\U000e0073\U000e0063\U000e0032\U000e0034\U000e007f" - FLAG_FOR_GLACIS_SC_12 = "\U0001f3f4\U000e0073\U000e0063\U000e0031\U000e0032\U000e007f" - FLAG_FOR_ROSTOV_RU_ROS = "\U0001f3f4\U000e0072\U000e0075\U000e0072\U000e006f\U000e0073\U000e007f" - FLAG_FOR_SENNAR_SD_SI = "\U0001f3f4\U000e0073\U000e0064\U000e0073\U000e0069\U000e007f" - FLAG_FOR_LA_DIGUE_SC_15 = "\U0001f3f4\U000e0073\U000e0063\U000e0031\U000e0035\U000e007f" - FLAG_FOR_SOUTH_DARFUR_SD_DS = "\U0001f3f4\U000e0073\U000e0064\U000e0064\U000e0073\U000e007f" - FLAG_FOR_WHITE_NILE_SD_NW = "\U0001f3f4\U000e0073\U000e0064\U000e006e\U000e0077\U000e007f" - FLAG_FOR_AL_JAZIRAH_SD_GZ = "\U0001f3f4\U000e0073\U000e0064\U000e0067\U000e007a\U000e007f" - FLAG_FOR_SOUTH_KURDUFAN_SD_KS = "\U0001f3f4\U000e0073\U000e0064\U000e006b\U000e0073\U000e007f" - FLAG_FOR_NORTH_KURDUFAN_SD_KN = "\U0001f3f4\U000e0073\U000e0064\U000e006b\U000e006e\U000e007f" - FLAG_FOR_PORT_GLAUD_SC_21 = "\U0001f3f4\U000e0073\U000e0063\U000e0032\U000e0031\U000e007f" - FLAG_FOR_OSTERGOTLAND_SE_E = "\U0001f3f4\U000e0073\U000e0065\U000e0065\U000e007f" - FLAG_FOR_GRAND_ANSE_PRASLIN_SC_14 = "\U0001f3f4\U000e0073\U000e0063\U000e0031\U000e0034\U000e007f" - FLAG_FOR_JONKOPING_SE_F = "\U0001f3f4\U000e0073\U000e0065\U000e0066\U000e007f" - FLAG_FOR_VARMLAND_SE_S = "\U0001f3f4\U000e0073\U000e0065\U000e0073\U000e007f" - FLAG_FOR_CERKLJE_NA_GORENJSKEM_SI_012 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0031\U000e0032\U000e007f" - FLAG_FOR_CERKNO_SI_014 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0031\U000e0034\U000e007f" - FLAG_FOR_SVETI_NIKOLE_MK_69 = "\U0001f3f4\U000e006d\U000e006b\U000e0036\U000e0039\U000e007f" - FLAG_FOR_NORTH_EAST_SG_02 = "\U0001f3f4\U000e0073\U000e0067\U000e0030\U000e0032\U000e007f" - FLAG_FOR_SOUTH_EAST_SG_04 = "\U0001f3f4\U000e0073\U000e0067\U000e0030\U000e0034\U000e007f" - FLAG_FOR_NORTH_WEST_SG_03 = "\U0001f3f4\U000e0073\U000e0067\U000e0030\U000e0033\U000e007f" - FLAG_FOR_DESTRNIK_SI_018 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0031\U000e0038\U000e007f" - FLAG_FOR_SOUTH_WEST_SG_05 = "\U0001f3f4\U000e0073\U000e0067\U000e0030\U000e0035\U000e007f" - FLAG_FOR_BRDA_SI_007 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0030\U000e0037\U000e007f" - FLAG_FOR_OREBRO_SE_T = "\U0001f3f4\U000e0073\U000e0065\U000e0074\U000e007f" - FLAG_FOR_KRONOBERG_SE_G = "\U0001f3f4\U000e0073\U000e0065\U000e0067\U000e007f" - FLAG_FOR_ASCENSION_ISLAND_SH_AC = "\U0001f3f4\U000e0073\U000e0068\U000e0061\U000e0063\U000e007f" - FLAG_FOR_VASTMANLAND_SE_U = "\U0001f3f4\U000e0073\U000e0065\U000e0075\U000e007f" - FLAG_FOR_CERKNICA_SI_013 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0031\U000e0033\U000e007f" - FLAG_FOR_CRENSOVCI_SI_015 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0031\U000e0035\U000e007f" - FLAG_FOR_REZINA_MD_RE = "\U0001f3f4\U000e006d\U000e0064\U000e0072\U000e0065\U000e007f" - KISS_WOMAN_LIGHT_SKIN_TONE_MAN_DARK_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff" - FLAG_FOR_BLED_SI_003 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0030\U000e0033\U000e007f" - FLAG_FOR_GOIAS_BR_GO = "\U0001f3f4\U000e0062\U000e0072\U000e0067\U000e006f\U000e007f" - FLAG_FOR_BELTINCI_SI_002 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0030\U000e0032\U000e007f" - FLAG_FOR_CRNOMELJ_SI_017 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0031\U000e0037\U000e007f" - FLAG_FOR_BREZOVICA_SI_008 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0030\U000e0038\U000e007f" - FLAG_FOR_MINYA_EG_MN = "\U0001f3f4\U000e0065\U000e0067\U000e006d\U000e006e\U000e007f" - FLAG_FOR_RIVIERE_NOIRE_MU_BL = "\U0001f3f4\U000e006d\U000e0075\U000e0062\U000e006c\U000e007f" - FLAG_FOR_GOVI_ALTAI_MN_065 = "\U0001f3f4\U000e006d\U000e006e\U000e0030\U000e0036\U000e0035\U000e007f" - FLAG_FOR_AJDOVSCINA_SI_001 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0030\U000e0031\U000e007f" - FLAG_FOR_BOROVNICA_SI_005 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0030\U000e0035\U000e007f" - FLAG_FOR_JURSINCI_SI_042 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0034\U000e0032\U000e007f" - FLAG_FOR_IDRIJA_SI_036 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0033\U000e0036\U000e007f" - FLAG_FOR_KUZMA_SI_056 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0035\U000e0036\U000e007f" - FLAG_FOR_ULYANOVSK_RU_ULY = "\U0001f3f4\U000e0072\U000e0075\U000e0075\U000e006c\U000e0079\U000e007f" - FLAG_FOR_IZOLA_SI_040 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0034\U000e0030\U000e007f" - FLAG_FOR_KOZJE_SI_051 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0035\U000e0031\U000e007f" - FLAG_FOR_KUNGOTA_SI_055 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0035\U000e0035\U000e007f" - FLAG_FOR_JESENICE_SI_041 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0034\U000e0031\U000e007f" - FLAG_FOR_GORNJI_GRAD_SI_030 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0033\U000e0030\U000e007f" - FLAG_FOR_DIVACA_SI_019 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0031\U000e0039\U000e007f" - FLAG_FOR_IVANCNA_GORICA_SI_039 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0033\U000e0039\U000e007f" - FLAG_FOR_KRSKO_SI_054 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0035\U000e0034\U000e007f" - FLAG_FOR_KAMNIK_SI_043 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0034\U000e0033\U000e007f" - FLAG_FOR_DOBREPOLJE_SI_020 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0032\U000e0030\U000e007f" - FLAG_FOR_SALOVCI_SI_033 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0033\U000e0033\U000e007f" - FLAG_FOR_KOBILJE_SI_047 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0034\U000e0037\U000e007f" - FLAG_FOR_HRASTNIK_SI_034 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0033\U000e0034\U000e007f" - FLAG_FOR_DOBROVA_POLHOV_GRADEC_SI_021 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0032\U000e0031\U000e007f" - FLAG_FOR_DRAVOGRAD_SI_025 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0032\U000e0035\U000e007f" - FLAG_FOR_STOCKHOLM_SE_AB = "\U0001f3f4\U000e0073\U000e0065\U000e0061\U000e0062\U000e007f" - FLAG_FOR_DUPLEK_SI_026 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0032\U000e0036\U000e007f" - FLAG_FOR_SALACGRIVA_LV_086 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0038\U000e0036\U000e007f" - FLAG_FOR_KOCEVJE_SI_048 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0034\U000e0038\U000e007f" - FLAG_FOR_KRANJSKA_GORA_SI_053 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0035\U000e0033\U000e007f" - FLAG_FOR_KANAL_SI_044 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0034\U000e0034\U000e007f" - FLAG_FOR_GORNJI_PETROVCI_SI_031 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0033\U000e0031\U000e007f" - FLAG_FOR_GROSUPLJE_SI_032 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0033\U000e0032\U000e007f" - FLAG_FOR_GORISNICA_SI_028 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0032\U000e0038\U000e007f" - FLAG_FOR_TEMOTU_SB_TE = "\U0001f3f4\U000e0073\U000e0062\U000e0074\U000e0065\U000e007f" - FLAG_FOR_DORNAVA_SI_024 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0032\U000e0034\U000e007f" - FLAG_FOR_DOMZALE_SI_023 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0032\U000e0033\U000e007f" - FLAG_FOR_KOBARID_SI_046 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0034\U000e0036\U000e007f" - FLAG_FOR_GORNJA_RADGONA_SI_029 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0032\U000e0039\U000e007f" - FLAG_FOR_ILIRSKA_BISTRICA_SI_038 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0033\U000e0038\U000e007f" - FLAG_FOR_KOMEN_SI_049 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0034\U000e0039\U000e007f" - FLAG_FOR_MARIBOR_SI_070 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0037\U000e0030\U000e007f" - FLAG_FOR_MEZICA_SI_074 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0037\U000e0034\U000e007f" - FLAG_FOR_LUCE_SI_067 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0036\U000e0037\U000e007f" - FLAG_FOR_PODVELKA_SI_093 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0039\U000e0033\U000e007f" - FLAG_FOR_LOSKI_POTOK_SI_066 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0036\U000e0036\U000e007f" - FLAG_FOR_NAZARJE_SI_083 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0038\U000e0033\U000e007f" - FLAG_FOR_LITIJA_SI_060 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0036\U000e0030\U000e007f" - FLAG_FOR_LUKOVICA_SI_068 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0036\U000e0038\U000e007f" - FLAG_FOR_PESNICA_SI_089 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0038\U000e0039\U000e007f" - FLAG_FOR_OSILNICA_SI_088 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0038\U000e0038\U000e007f" - FLAG_FOR_LENDAVA_SI_059 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0035\U000e0039\U000e007f" - FLAG_FOR_MIREN_KOSTANJEVICA_SI_075 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0037\U000e0035\U000e007f" - FLAG_FOR_MORAVSKE_TOPLICE_SI_078 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0037\U000e0038\U000e007f" - FLAG_FOR_PODCETRTEK_SI_092 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0039\U000e0032\U000e007f" - FLAG_FOR_LJUTOMER_SI_063 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0036\U000e0033\U000e007f" - FLAG_FOR_LASKO_SI_057 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0035\U000e0037\U000e007f" - FLAG_FOR_PIVKA_SI_091 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0039\U000e0031\U000e007f" - FLAG_FOR_METLIKA_SI_073 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0037\U000e0033\U000e007f" - FLAG_FOR_HERCEG_NOVI_ME_08 = "\U0001f3f4\U000e006d\U000e0065\U000e0030\U000e0038\U000e007f" - FLAG_FOR_LOSKA_DOLINA_SI_065 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0036\U000e0035\U000e007f" - FLAG_FOR_ANSE_ETOILE_SC_03 = "\U0001f3f4\U000e0073\U000e0063\U000e0030\U000e0033\U000e007f" - FLAG_FOR_ORMOZ_SI_087 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0038\U000e0037\U000e007f" - FLAG_FOR_LOGATEC_SI_064 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0036\U000e0034\U000e007f" - FLAG_FOR_NAKLO_SI_082 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0038\U000e0032\U000e007f" - FLAG_FOR_NOVA_GORICA_SI_084 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0038\U000e0034\U000e007f" - FLAG_FOR_LJUBNO_SI_062 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0036\U000e0032\U000e007f" - FLAG_FOR_MISLINJA_SI_076 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0037\U000e0036\U000e007f" - FLAG_FOR_MEDVODE_SI_071 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0037\U000e0031\U000e007f" - FLAG_FOR_POSTOJNA_SI_094 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0039\U000e0034\U000e007f" - FLAG_FOR_MORAVCE_SI_077 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0037\U000e0037\U000e007f" - FLAG_FOR_LJUBLJANA_SI_061 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0036\U000e0031\U000e007f" - FLAG_FOR_MAJSPERK_SI_069 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0036\U000e0039\U000e007f" - FLAG_FOR_MENGES_SI_072 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0037\U000e0032\U000e007f" - FLAG_FOR_MUTA_SI_081 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0038\U000e0031\U000e007f" - FLAG_FOR_MOZIRJE_SI_079 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0037\U000e0039\U000e007f" - FLAG_FOR_MURSKA_SOBOTA_SI_080 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0038\U000e0030\U000e007f" - FLAG_FOR_RIBNICA_SI_104 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0030\U000e0034\U000e007f" - FLAG_FOR_SKOFLJICA_SI_123 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0032\U000e0033\U000e007f" - FLAG_FOR_SEZANA_SI_111 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0031\U000e0031\U000e007f" - FLAG_FOR_TRBOVLJE_SI_129 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0032\U000e0039\U000e007f" - FLAG_FOR_SLOVENSKE_KONJICE_SI_114 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0031\U000e0034\U000e007f" - FLAG_FOR_STORE_SI_127 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0032\U000e0037\U000e007f" - FLAG_FOR_PUCONCI_SI_097 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0039\U000e0037\U000e007f" - FLAG_FOR_TREBNJE_SI_130 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0033\U000e0030\U000e007f" - FLAG_FOR_ROGATEC_SI_107 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0030\U000e0037\U000e007f" - FLAG_FOR_TOLMIN_SI_128 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0032\U000e0038\U000e007f" - FLAG_FOR_RADOVLJICA_SI_102 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0030\U000e0032\U000e007f" - FLAG_FOR_SEVNICA_SI_110 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0031\U000e0030\U000e007f" - FLAG_FOR_STARSE_SI_115 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0031\U000e0035\U000e007f" - FLAG_FOR_RADLJE_OB_DRAVI_SI_101 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0030\U000e0031\U000e007f" - FLAG_FOR_PTUJ_SI_096 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0039\U000e0036\U000e007f" - FLAG_FOR_SLOVENSKA_BISTRICA_SI_113 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0031\U000e0033\U000e007f" - FLAG_FOR_SEMIC_SI_109 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0030\U000e0039\U000e007f" - FLAG_FOR_PREDDVOR_SI_095 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0039\U000e0035\U000e007f" - FLAG_FOR_RADECE_SI_099 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0039\U000e0039\U000e007f" - FLAG_FOR_SOSTANJ_SI_126 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0032\U000e0036\U000e007f" - FLAG_FOR_ROGASKA_SLATINA_SI_106 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0030\U000e0036\U000e007f" - FLAG_FOR_TRZIC_SI_131 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0033\U000e0031\U000e007f" - FLAG_FOR_RUSE_SI_108 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0030\U000e0038\U000e007f" - FLAG_FOR_SMARJE_PRI_JELSAH_SI_124 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0032\U000e0034\U000e007f" - FLAG_FOR_TURNISCE_SI_132 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0033\U000e0032\U000e007f" - FLAG_FOR_SKOCJAN_SI_121 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0032\U000e0031\U000e007f" - FLAG_FOR_SVETI_JURIJ_SI_116 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0031\U000e0036\U000e007f" - FLAG_FOR_RACE_FRAM_SI_098 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0039\U000e0038\U000e007f" - FLAG_FOR_SLOVENJ_GRADEC_SI_112 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0031\U000e0032\U000e007f" - FLAG_FOR_SENTILJ_SI_118 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0031\U000e0038\U000e007f" - FLAG_FOR_ROGASOVCI_SI_105 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0030\U000e0035\U000e007f" - FLAG_FOR_SENTJERNEJ_SI_119 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0031\U000e0039\U000e007f" - FLAG_FOR_SENCUR_SI_117 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0031\U000e0037\U000e007f" - FLAG_FOR_SENTJUR_SI_120 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0032\U000e0030\U000e007f" - FLAG_FOR_RAVNE_NA_KOROSKEM_SI_103 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0030\U000e0033\U000e007f" - FLAG_FOR_DOLENJSKE_TOPLICE_SI_157 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0035\U000e0037\U000e007f" - FLAG_FOR_MIKLAVZ_NA_DRAVSKEM_POLJU_SI_169 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0036\U000e0039\U000e007f" - FLAG_FOR_VIDEM_SI_135 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0033\U000e0035\U000e007f" - FLAG_FOR_ZRECE_SI_144 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0034\U000e0034\U000e007f" - FLAG_FOR_WEST_DARFUR_SD_DW = "\U0001f3f4\U000e0073\U000e0064\U000e0064\U000e0077\U000e007f" - FLAG_FOR_KOSTEL_SI_165 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0036\U000e0035\U000e007f" - FLAG_FOR_VOJNIK_SI_139 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0033\U000e0039\U000e007f" - FLAG_FOR_BENEDIKT_SI_148 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0034\U000e0038\U000e007f" - FLAG_FOR_HORJUL_SI_162 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0036\U000e0032\U000e007f" - FLAG_FOR_OPLOTNICA_SI_171 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0037\U000e0031\U000e007f" - FLAG_FOR_JEZERSKO_SI_163 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0036\U000e0033\U000e007f" - FLAG_FOR_VITANJE_SI_137 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0033\U000e0037\U000e007f" - FLAG_FOR_ZAGORJE_OB_SAVI_SI_142 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0034\U000e0032\U000e007f" - FLAG_FOR_VUZENICA_SI_141 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0034\U000e0031\U000e007f" - FLAG_FOR_VIPAVA_SI_136 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0033\U000e0036\U000e007f" - FLAG_FOR_HAJDINA_SI_159 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0035\U000e0039\U000e007f" - FLAG_FOR_ZELEZNIKI_SI_146 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0034\U000e0036\U000e007f" - FLAG_FOR_ZIRI_SI_147 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0034\U000e0037\U000e007f" - FLAG_FOR_KRIZEVCI_SI_166 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0036\U000e0036\U000e007f" - FLAG_FOR_CERKVENJAK_SI_153 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0035\U000e0033\U000e007f" - FLAG_FOR_VRHNIKA_SI_140 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0034\U000e0030\U000e007f" - FLAG_FOR_HOCE_SLIVNICA_SI_160 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0036\U000e0030\U000e007f" - FLAG_FOR_VODICE_SI_138 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0033\U000e0038\U000e007f" - FLAG_FOR_MIRNA_PEC_SI_170 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0037\U000e0030\U000e007f" - FLAG_FOR_MARKOVCI_SI_168 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0036\U000e0038\U000e007f" - FLAG_FOR_HODOS_SI_161 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0036\U000e0031\U000e007f" - FLAG_FOR_DOBRNA_SI_155 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0035\U000e0035\U000e007f" - FAMILY_WOMAN_LIGHT_SKIN_TONE_GIRL_LIGHT_SKIN_TONE_BABY_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f467\U0001f3fb\u200d\U0001f476\U0001f3fb" - FLAG_FOR_KOMENDA_SI_164 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0036\U000e0034\U000e007f" - FLAG_FOR_ZALEC_SI_190 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0039\U000e0030\U000e007f" - FLAG_FOR_TRNOVSKA_VAS_SI_185 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0038\U000e0035\U000e007f" - FLAG_FOR_SMARTNO_PRI_LITIJI_SI_194 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0039\U000e0034\U000e007f" - FLAG_FOR_GORJE_SI_207 = "\U0001f3f4\U000e0073\U000e0069\U000e0032\U000e0030\U000e0037\U000e007f" - FLAG_FOR_PREBOLD_SI_174 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0037\U000e0034\U000e007f" - FLAG_FOR_SELNICA_OB_DRAVI_SI_178 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0037\U000e0038\U000e007f" - FLAG_FOR_APACE_SI_195 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0039\U000e0035\U000e007f" - FLAG_FOR_CIRKULANE_SI_196 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0039\U000e0036\U000e007f" - FLAG_FOR_SOLCAVA_SI_180 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0038\U000e0030\U000e007f" - FLAG_FOR_SEMPETER_VRTOJBA_SI_183 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0038\U000e0033\U000e007f" - FLAG_FOR_VALLON_DE_LA_ROUSSE_MC_VR = "\U0001f3f4\U000e006d\U000e0063\U000e0076\U000e0072\U000e007f" - FLAG_FOR_SODRAZICA_SI_179 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0037\U000e0039\U000e007f" - FLAG_FOR_RENCE_VOGRSKO_SI_201 = "\U0001f3f4\U000e0073\U000e0069\U000e0032\U000e0030\U000e0031\U000e007f" - FLAG_FOR_PREVALJE_SI_175 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0037\U000e0035\U000e007f" - FLAG_FOR_POLJCANE_SI_200 = "\U0001f3f4\U000e0073\U000e0069\U000e0032\U000e0030\U000e0030\U000e007f" - FLAG_FOR_RIBNICA_NA_POHORJU_SI_177 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0037\U000e0037\U000e007f" - FLAG_FOR_VERZEJ_SI_188 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0038\U000e0038\U000e007f" - FLAG_FOR_MAKOLE_SI_198 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0039\U000e0038\U000e007f" - FLAG_FOR_PODLEHNIK_SI_172 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0037\U000e0032\U000e007f" - FLAG_FOR_POLZELA_SI_173 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0037\U000e0033\U000e007f" - FLAG_FOR_RAZKRIZJE_SI_176 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0037\U000e0036\U000e007f" - FLAG_FOR_TABOR_SI_184 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0038\U000e0034\U000e007f" - FLAG_FOR_KOSTANJEVICA_NA_KRKI_SI_197 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0039\U000e0037\U000e007f" - FLAG_FOR_ZETALE_SI_191 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0039\U000e0031\U000e007f" - FLAG_FOR_ZUZEMBERK_SI_193 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0039\U000e0033\U000e007f" - FLAG_FOR_STRAZA_SI_203 = "\U0001f3f4\U000e0073\U000e0069\U000e0032\U000e0030\U000e0033\U000e007f" - FLAG_FOR_VRANSKO_SI_189 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0038\U000e0039\U000e007f" - FLAG_FOR_TUBAS_PS_TBS = "\U0001f3f4\U000e0070\U000e0073\U000e0074\U000e0062\U000e0073\U000e007f" - FLAG_FOR_TRZIN_SI_186 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0038\U000e0036\U000e007f" - FLAG_FOR_SVETI_TOMAZ_SI_205 = "\U0001f3f4\U000e0073\U000e0069\U000e0032\U000e0030\U000e0035\U000e007f" - FLAG_FOR_MOKRONOG_TREBELNO_SI_199 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0039\U000e0039\U000e007f" - FLAG_FOR_WESTERN_AREA_SL_W = "\U0001f3f4\U000e0073\U000e006c\U000e0077\U000e007f" - FLAG_FOR_CALABRIA_IT_78 = "\U0001f3f4\U000e0069\U000e0074\U000e0037\U000e0038\U000e007f" - FLAG_FOR_SERRAVALLE_SM_09 = "\U0001f3f4\U000e0073\U000e006d\U000e0030\U000e0039\U000e007f" - FLAG_FOR_FAETANO_SM_04 = "\U0001f3f4\U000e0073\U000e006d\U000e0030\U000e0034\U000e007f" - FLAG_FOR_SENTRUPERT_SI_211 = "\U0001f3f4\U000e0073\U000e0069\U000e0032\U000e0031\U000e0031\U000e007f" - FLAG_FOR_TRNAVA_SK_TA = "\U0001f3f4\U000e0073\U000e006b\U000e0074\U000e0061\U000e007f" - FLAG_FOR_ACQUAVIVA_SM_01 = "\U0001f3f4\U000e0073\U000e006d\U000e0030\U000e0031\U000e007f" - FLAG_FOR_SOUTHERN_SL_S = "\U0001f3f4\U000e0073\U000e006c\U000e0073\U000e007f" - FLAG_FOR_PRESOV_SK_PV = "\U0001f3f4\U000e0073\U000e006b\U000e0070\U000e0076\U000e007f" - FLAG_FOR_KOSICE_SK_KI = "\U0001f3f4\U000e0073\U000e006b\U000e006b\U000e0069\U000e007f" - FLAG_FOR_NORTHERN_SL_N = "\U0001f3f4\U000e0073\U000e006c\U000e006e\U000e007f" - FLAG_FOR_KAOLACK_SN_KL = "\U0001f3f4\U000e0073\U000e006e\U000e006b\U000e006c\U000e007f" - FLAG_FOR_SAN_MARINO_SM_07 = "\U0001f3f4\U000e0073\U000e006d\U000e0030\U000e0037\U000e007f" - FLAG_FOR_FIORENTINO_SM_05 = "\U0001f3f4\U000e0073\U000e006d\U000e0030\U000e0035\U000e007f" - FLAG_FOR_TAMBACOUNDA_SN_TC = "\U0001f3f4\U000e0073\U000e006e\U000e0074\U000e0063\U000e007f" - FLAG_FOR_SEOUL_KR_11 = "\U0001f3f4\U000e006b\U000e0072\U000e0031\U000e0031\U000e007f" - FLAG_FOR_CHIESANUOVA_SM_02 = "\U0001f3f4\U000e0073\U000e006d\U000e0030\U000e0032\U000e007f" - FLAG_FOR_KOLDA_SN_KD = "\U0001f3f4\U000e0073\U000e006e\U000e006b\U000e0064\U000e007f" - FLAG_FOR_KAFFRINE_SN_KA = "\U0001f3f4\U000e0073\U000e006e\U000e006b\U000e0061\U000e007f" - FLAG_FOR_SEDHIOU_SN_SE = "\U0001f3f4\U000e0073\U000e006e\U000e0073\U000e0065\U000e007f" - FLAG_FOR_FATICK_SN_FK = "\U0001f3f4\U000e0073\U000e006e\U000e0066\U000e006b\U000e007f" - FLAG_FOR_TRENCIN_SK_TC = "\U0001f3f4\U000e0073\U000e006b\U000e0074\U000e0063\U000e007f" - FLAG_FOR_BANSKA_BYSTRICA_SK_BC = "\U0001f3f4\U000e0073\U000e006b\U000e0062\U000e0063\U000e007f" - FLAG_FOR_LOUGA_SN_LG = "\U0001f3f4\U000e0073\U000e006e\U000e006c\U000e0067\U000e007f" - FLAG_FOR_NITRA_SK_NI = "\U0001f3f4\U000e0073\U000e006b\U000e006e\U000e0069\U000e007f" - FLAG_FOR_RECICA_OB_SAVINJI_SI_209 = "\U0001f3f4\U000e0073\U000e0069\U000e0032\U000e0030\U000e0039\U000e007f" - FLAG_FOR_RAJASTHAN_IN_RJ = "\U0001f3f4\U000e0069\U000e006e\U000e0072\U000e006a\U000e007f" - FLAG_FOR_LENINGRAD_RU_LEN = "\U0001f3f4\U000e0072\U000e0075\U000e006c\U000e0065\U000e006e\U000e007f" - FLAG_FOR_ZILINA_SK_ZI = "\U0001f3f4\U000e0073\U000e006b\U000e007a\U000e0069\U000e007f" - FLAG_FOR_BORGO_MAGGIORE_SM_06 = "\U0001f3f4\U000e0073\U000e006d\U000e0030\U000e0036\U000e007f" - FLAG_FOR_MATAM_SN_MT = "\U0001f3f4\U000e0073\U000e006e\U000e006d\U000e0074\U000e007f" - FLAG_FOR_DOMAGNANO_SM_03 = "\U0001f3f4\U000e0073\U000e006d\U000e0030\U000e0033\U000e007f" - FLAG_FOR_KEDOUGOU_SN_KE = "\U0001f3f4\U000e0073\U000e006e\U000e006b\U000e0065\U000e007f" - FLAG_FOR_MIRNA_SI_212 = "\U0001f3f4\U000e0073\U000e0069\U000e0032\U000e0031\U000e0032\U000e007f" - FLAG_FOR_THIES_SN_TH = "\U0001f3f4\U000e0073\U000e006e\U000e0074\U000e0068\U000e007f" - FLAG_FOR_MONTEGIARDINO_SM_08 = "\U0001f3f4\U000e0073\U000e006d\U000e0030\U000e0038\U000e007f" - FLAG_FOR_VALCEA_RO_VL = "\U0001f3f4\U000e0072\U000e006f\U000e0076\U000e006c\U000e007f" - FLAG_FOR_CLUJ_RO_CJ = "\U0001f3f4\U000e0072\U000e006f\U000e0063\U000e006a\U000e007f" - FLAG_FOR_TOGDHEER_SO_TO = "\U0001f3f4\U000e0073\U000e006f\U000e0074\U000e006f\U000e007f" - FLAG_FOR_UPPER_NILE_SS_NU = "\U0001f3f4\U000e0073\U000e0073\U000e006e\U000e0075\U000e007f" - FLAG_FOR_SARAMACCA_SR_SA = "\U0001f3f4\U000e0073\U000e0072\U000e0073\U000e0061\U000e007f" - FLAG_FOR_WESTERN_BAHR_EL_GHAZAL_SS_BW = "\U0001f3f4\U000e0073\U000e0073\U000e0062\U000e0077\U000e007f" - FLAG_FOR_WARRAP_SS_WR = "\U0001f3f4\U000e0073\U000e0073\U000e0077\U000e0072\U000e007f" - DIE_FACE_6 = "\u2685" - FLAG_FOR_NICKERIE_SR_NI = "\U0001f3f4\U000e0073\U000e0072\U000e006e\U000e0069\U000e007f" - FLAG_FOR_EASTERN_EQUATORIA_SS_EE = "\U0001f3f4\U000e0073\U000e0073\U000e0065\U000e0065\U000e007f" - FLAG_FOR_BAKOOL_SO_BK = "\U0001f3f4\U000e0073\U000e006f\U000e0062\U000e006b\U000e007f" - FLAG_FOR_PARAMARIBO_SR_PM = "\U0001f3f4\U000e0073\U000e0072\U000e0070\U000e006d\U000e007f" - FLAG_FOR_WANICA_SR_WA = "\U0001f3f4\U000e0073\U000e0072\U000e0077\U000e0061\U000e007f" - FLAG_FOR_ANGAUR_PW_010 = "\U0001f3f4\U000e0070\U000e0077\U000e0030\U000e0031\U000e0030\U000e007f" - FLAG_FOR_MUDUG_SO_MU = "\U0001f3f4\U000e0073\U000e006f\U000e006d\U000e0075\U000e007f" - FLAG_FOR_MIDDLE_JUBA_SO_JD = "\U0001f3f4\U000e0073\U000e006f\U000e006a\U000e0064\U000e007f" - FLAG_FOR_WESTERN_EQUATORIA_SS_EW = "\U0001f3f4\U000e0073\U000e0073\U000e0065\U000e0077\U000e007f" - FLAG_FOR_PRINCIPE_ST_P = "\U0001f3f4\U000e0073\U000e0074\U000e0070\U000e007f" - FLAG_FOR_CORONIE_SR_CR = "\U0001f3f4\U000e0073\U000e0072\U000e0063\U000e0072\U000e007f" - FLAG_FOR_CONSTANTA_RO_CT = "\U0001f3f4\U000e0072\U000e006f\U000e0063\U000e0074\U000e007f" - FLAG_FOR_HIRAN_SO_HI = "\U0001f3f4\U000e0073\U000e006f\U000e0068\U000e0069\U000e007f" - FLAG_FOR_KARELIA_RU_KR = "\U0001f3f4\U000e0072\U000e0075\U000e006b\U000e0072\U000e007f" - FLAG_FOR_LOWER_SHEBELLE_SO_SH = "\U0001f3f4\U000e0073\U000e006f\U000e0073\U000e0068\U000e007f" - FLAG_FOR_GEDO_SO_GE = "\U0001f3f4\U000e0073\U000e006f\U000e0067\U000e0065\U000e007f" - FLAG_FOR_ZIGUINCHOR_SN_ZG = "\U0001f3f4\U000e0073\U000e006e\U000e007a\U000e0067\U000e007f" - FLAG_FOR_BOQUERON_PY_19 = "\U0001f3f4\U000e0070\U000e0079\U000e0031\U000e0039\U000e007f" - FLAG_FOR_MIDDLE_SHEBELLE_SO_SD = "\U0001f3f4\U000e0073\U000e006f\U000e0073\U000e0064\U000e007f" - FLAG_FOR_SANAAG_SO_SA = "\U0001f3f4\U000e0073\U000e006f\U000e0073\U000e0061\U000e007f" - FLAG_FOR_BROKOPONDO_SR_BR = "\U0001f3f4\U000e0073\U000e0072\U000e0062\U000e0072\U000e007f" - FLAG_FOR_BREZICE_SI_009 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0030\U000e0039\U000e007f" - FLAG_FOR_HRPELJE_KOZINA_SI_035 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0033\U000e0035\U000e007f" - FLAG_FOR_AWDAL_SO_AW = "\U0001f3f4\U000e0073\U000e006f\U000e0061\U000e0077\U000e007f" - FLAG_FOR_LATAKIA_SY_LA = "\U0001f3f4\U000e0073\U000e0079\U000e006c\U000e0061\U000e007f" - FLAG_FOR_AS_SUWAYDA_SY_SU = "\U0001f3f4\U000e0073\U000e0079\U000e0073\U000e0075\U000e007f" - FLAG_FOR_AR_RAQQAH_SY_RA = "\U0001f3f4\U000e0073\U000e0079\U000e0072\U000e0061\U000e007f" - FLAG_FOR_CUSCATLAN_SV_CU = "\U0001f3f4\U000e0073\U000e0076\U000e0063\U000e0075\U000e007f" - FLAG_FOR_CHALATENANGO_SV_CH = "\U0001f3f4\U000e0073\U000e0076\U000e0063\U000e0068\U000e007f" - FLAG_FOR_GALGUDUUD_SO_GA = "\U0001f3f4\U000e0073\U000e006f\U000e0067\U000e0061\U000e007f" - FLAG_FOR_DOBROVNIK_SI_156 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0035\U000e0036\U000e007f" - FLAG_FOR_BORKOU_TD_BO = "\U0001f3f4\U000e0074\U000e0064\U000e0062\U000e006f\U000e007f" - FLAG_FOR_LUBOMBO_SZ_LU = "\U0001f3f4\U000e0073\U000e007a\U000e006c\U000e0075\U000e007f" - FLAG_FOR_MANZINI_SZ_MA = "\U0001f3f4\U000e0073\U000e007a\U000e006d\U000e0061\U000e007f" - FLAG_FOR_MORAZAN_SV_MO = "\U0001f3f4\U000e0073\U000e0076\U000e006d\U000e006f\U000e007f" - FLAG_FOR_DARAA_SY_DR = "\U0001f3f4\U000e0073\U000e0079\U000e0064\U000e0072\U000e007f" - FLAG_FOR_SANTA_ANA_SV_SA = "\U0001f3f4\U000e0073\U000e0076\U000e0073\U000e0061\U000e007f" - FLAG_FOR_TARTUS_SY_TA = "\U0001f3f4\U000e0073\U000e0079\U000e0074\U000e0061\U000e007f" - FLAG_FOR_SHISELWENI_SZ_SH = "\U0001f3f4\U000e0073\U000e007a\U000e0073\U000e0068\U000e007f" - FLAG_FOR_LA_UNION_SV_UN = "\U0001f3f4\U000e0073\U000e0076\U000e0075\U000e006e\U000e007f" - FLAG_FOR_BATHA_TD_BA = "\U0001f3f4\U000e0074\U000e0064\U000e0062\U000e0061\U000e007f" - FLAG_FOR_KANEM_TD_KA = "\U0001f3f4\U000e0074\U000e0064\U000e006b\U000e0061\U000e007f" - FLAG_FOR_GUADELOUPE_FR_GUA = "\U0001f3f4\U000e0066\U000e0072\U000e0067\U000e0075\U000e0061\U000e007f" - FLAG_FOR_QUNEITRA_SY_QU = "\U0001f3f4\U000e0073\U000e0079\U000e0071\U000e0075\U000e007f" - FLAG_FOR_HOMS_SY_HI = "\U0001f3f4\U000e0073\U000e0079\U000e0068\U000e0069\U000e007f" - FLAG_FOR_AL_HASAKAH_SY_HA = "\U0001f3f4\U000e0073\U000e0079\U000e0068\U000e0061\U000e007f" - FLAG_FOR_HAMA_SY_HM = "\U0001f3f4\U000e0073\U000e0079\U000e0068\U000e006d\U000e007f" - FLAG_FOR_AHUACHAPAN_SV_AH = "\U0001f3f4\U000e0073\U000e0076\U000e0061\U000e0068\U000e007f" - FLAG_FOR_SONSONATE_SV_SO = "\U0001f3f4\U000e0073\U000e0076\U000e0073\U000e006f\U000e007f" - FLAG_FOR_CABANAS_SV_CA = "\U0001f3f4\U000e0073\U000e0076\U000e0063\U000e0061\U000e007f" - FLAG_FOR_ENNEDI_EST_TD_EE = "\U0001f3f4\U000e0074\U000e0064\U000e0065\U000e0065\U000e007f" - FLAG_FOR_ALEPPO_SY_HL = "\U0001f3f4\U000e0073\U000e0079\U000e0068\U000e006c\U000e007f" - FLAG_FOR_GOVISUMBER_MN_064 = "\U0001f3f4\U000e006d\U000e006e\U000e0030\U000e0036\U000e0034\U000e007f" - FLAG_FOR_LA_LIBERTAD_SV_LI = "\U0001f3f4\U000e0073\U000e0076\U000e006c\U000e0069\U000e007f" - FLAG_FOR_BAHR_EL_GAZEL_TD_BG = "\U0001f3f4\U000e0074\U000e0064\U000e0062\U000e0067\U000e007f" - FLAG_FOR_SIPALIWINI_SR_SI = "\U0001f3f4\U000e0073\U000e0072\U000e0073\U000e0069\U000e007f" - FLAG_FOR_FINNMARK_NO_20 = "\U0001f3f4\U000e006e\U000e006f\U000e0032\U000e0030\U000e007f" - FLAG_FOR_NONTHABURI_TH_12 = "\U0001f3f4\U000e0074\U000e0068\U000e0031\U000e0032\U000e007f" - FLAG_FOR_PATHUM_THANI_TH_13 = "\U0001f3f4\U000e0074\U000e0068\U000e0031\U000e0033\U000e007f" - FAMILY_MAN_MEDIUM_SKIN_TONE_WOMAN_MEDIUM_SKIN_TONE_BABY_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f469\U0001f3fd\u200d\U0001f476\U0001f3fd" - FLAG_FOR_WADI_FIRA_TD_WF = "\U0001f3f4\U000e0074\U000e0064\U000e0077\U000e0066\U000e007f" - FLAG_FOR_PRACHIN_BURI_TH_25 = "\U0001f3f4\U000e0074\U000e0068\U000e0032\U000e0035\U000e007f" - FLAG_FOR_BURI_RAM_TH_31 = "\U0001f3f4\U000e0074\U000e0068\U000e0033\U000e0031\U000e007f" - FLAG_FOR_NAKHON_RATCHASIMA_TH_30 = "\U0001f3f4\U000e0074\U000e0068\U000e0033\U000e0030\U000e007f" - FLAG_FOR_N_DJAMENA_TD_ND = "\U0001f3f4\U000e0074\U000e0064\U000e006e\U000e0064\U000e007f" - FLAG_FOR_PHRA_NAKHON_SI_AYUTTHAYA_TH_14 = "\U0001f3f4\U000e0074\U000e0068\U000e0031\U000e0034\U000e007f" - FLAG_FOR_MARSASKALA_MT_27 = "\U0001f3f4\U000e006d\U000e0074\U000e0032\U000e0037\U000e007f" - FLAG_FOR_LOGONE_OCCIDENTAL_TD_LO = "\U0001f3f4\U000e0074\U000e0064\U000e006c\U000e006f\U000e007f" - FLAG_FOR_CHACHOENGSAO_TH_24 = "\U0001f3f4\U000e0074\U000e0068\U000e0032\U000e0034\U000e007f" - FLAG_FOR_TIBESTI_TD_TI = "\U0001f3f4\U000e0074\U000e0064\U000e0074\U000e0069\U000e007f" - FLAG_FOR_CHAI_NAT_TH_18 = "\U0001f3f4\U000e0074\U000e0068\U000e0031\U000e0038\U000e007f" - FLAG_FOR_MOYEN_CHARI_TD_MC = "\U0001f3f4\U000e0074\U000e0064\U000e006d\U000e0063\U000e007f" - FLAG_FOR_TANDJILE_TD_TA = "\U0001f3f4\U000e0074\U000e0064\U000e0074\U000e0061\U000e007f" - FLAG_FOR_SAINT_HELENA_SH_HL = "\U0001f3f4\U000e0073\U000e0068\U000e0068\U000e006c\U000e007f" - FLAG_FOR_PLATEAUX_TG_P = "\U0001f3f4\U000e0074\U000e0067\U000e0070\U000e007f" - FLAG_FOR_SALAMAT_TD_SA = "\U0001f3f4\U000e0074\U000e0064\U000e0073\U000e0061\U000e007f" - FLAG_FOR_CHON_BURI_TH_20 = "\U0001f3f4\U000e0074\U000e0068\U000e0032\U000e0030\U000e007f" - FLAG_FOR_MANDOUL_TD_MA = "\U0001f3f4\U000e0074\U000e0064\U000e006d\U000e0061\U000e007f" - FLAG_FOR_MAYO_KEBBI_EST_TD_ME = "\U0001f3f4\U000e0074\U000e0064\U000e006d\U000e0065\U000e007f" - FLAG_FOR_SURIN_TH_32 = "\U0001f3f4\U000e0074\U000e0068\U000e0033\U000e0032\U000e007f" - FLAG_FOR_ARDAHAN_TR_75 = "\U0001f3f4\U000e0074\U000e0072\U000e0037\U000e0035\U000e007f" - FLAG_FOR_NAKHON_NAYOK_TH_26 = "\U0001f3f4\U000e0074\U000e0068\U000e0032\U000e0036\U000e007f" - FLAG_FOR_SARABURI_TH_19 = "\U0001f3f4\U000e0074\U000e0068\U000e0031\U000e0039\U000e007f" - FLAG_FOR_RIF_DIMASHQ_SY_RD = "\U0001f3f4\U000e0073\U000e0079\U000e0072\U000e0064\U000e007f" - FLAG_FOR_SAMUT_PRAKAN_TH_11 = "\U0001f3f4\U000e0074\U000e0068\U000e0031\U000e0031\U000e007f" - FLAG_FOR_SA_KAEO_TH_27 = "\U0001f3f4\U000e0074\U000e0068\U000e0032\U000e0037\U000e007f" - FLAG_FOR_GUERA_TD_GR = "\U0001f3f4\U000e0074\U000e0064\U000e0067\U000e0072\U000e007f" - FLAG_FOR_OUADDAI_TD_OD = "\U0001f3f4\U000e0074\U000e0064\U000e006f\U000e0064\U000e007f" - FLAG_FOR_SAVANES_TG_S = "\U0001f3f4\U000e0074\U000e0067\U000e0073\U000e007f" - FLAG_FOR_PHRAE_TH_54 = "\U0001f3f4\U000e0074\U000e0068\U000e0035\U000e0034\U000e007f" - FLAG_FOR_KHON_KAEN_TH_40 = "\U0001f3f4\U000e0074\U000e0068\U000e0034\U000e0030\U000e007f" - FLAG_FOR_UTHAI_THANI_TH_61 = "\U0001f3f4\U000e0074\U000e0068\U000e0036\U000e0031\U000e007f" - FLAG_FOR_MUKDAHAN_TH_49 = "\U0001f3f4\U000e0074\U000e0068\U000e0034\U000e0039\U000e007f" - FLAG_FOR_PHITSANULOK_TH_65 = "\U0001f3f4\U000e0074\U000e0068\U000e0036\U000e0035\U000e007f" - FLAG_FOR_LAMPANG_TH_52 = "\U0001f3f4\U000e0074\U000e0068\U000e0035\U000e0032\U000e007f" - FLAG_FOR_SUPHANBURI_TH_72 = "\U0001f3f4\U000e0074\U000e0068\U000e0037\U000e0032\U000e007f" - FLAG_FOR_RATCHABURI_TH_70 = "\U0001f3f4\U000e0074\U000e0068\U000e0037\U000e0030\U000e007f" - FLAG_FOR_UDON_THANI_TH_41 = "\U0001f3f4\U000e0074\U000e0068\U000e0034\U000e0031\U000e007f" - FLAG_FOR_LOEI_TH_42 = "\U0001f3f4\U000e0074\U000e0068\U000e0034\U000e0032\U000e007f" - FLAG_FOR_NONG_KHAI_TH_43 = "\U0001f3f4\U000e0074\U000e0068\U000e0034\U000e0033\U000e007f" - FLAG_FOR_NAKHON_SAWAN_TH_60 = "\U0001f3f4\U000e0074\U000e0068\U000e0036\U000e0030\U000e007f" - FLAG_FOR_PHETCHABUN_TH_67 = "\U0001f3f4\U000e0074\U000e0068\U000e0036\U000e0037\U000e007f" - FLAG_FOR_SAKON_NAKHON_TH_47 = "\U0001f3f4\U000e0074\U000e0068\U000e0034\U000e0037\U000e007f" - FLAG_FOR_KALASIN_TH_46 = "\U0001f3f4\U000e0074\U000e0068\U000e0034\U000e0036\U000e007f" - FLAG_FOR_KANCHANABURI_TH_71 = "\U0001f3f4\U000e0074\U000e0068\U000e0037\U000e0031\U000e007f" - FLAG_FOR_TOTONICAPAN_GT_TO = "\U0001f3f4\U000e0067\U000e0074\U000e0074\U000e006f\U000e007f" - FLAG_FOR_SAMUT_SAKHON_TH_74 = "\U0001f3f4\U000e0074\U000e0068\U000e0037\U000e0034\U000e007f" - FLAG_FOR_TIGRAY_ET_TI = "\U0001f3f4\U000e0065\U000e0074\U000e0074\U000e0069\U000e007f" - FLAG_FOR_NAKHON_PHANOM_TH_48 = "\U0001f3f4\U000e0074\U000e0068\U000e0034\U000e0038\U000e007f" - FLAG_FOR_NAKHON_PATHOM_TH_73 = "\U0001f3f4\U000e0074\U000e0068\U000e0037\U000e0033\U000e007f" - FLAG_FOR_UBON_RATCHATHANI_TH_34 = "\U0001f3f4\U000e0074\U000e0068\U000e0033\U000e0034\U000e007f" - FLAG_FOR_KAMPHAENG_PHET_TH_62 = "\U0001f3f4\U000e0074\U000e0068\U000e0036\U000e0032\U000e007f" - FLAG_FOR_YASOTHON_TH_35 = "\U0001f3f4\U000e0074\U000e0068\U000e0033\U000e0035\U000e007f" - FLAG_FOR_CHIANG_RAI_TH_57 = "\U0001f3f4\U000e0074\U000e0068\U000e0035\U000e0037\U000e007f" - FLAG_FOR_CHIANG_MAI_TH_50 = "\U0001f3f4\U000e0074\U000e0068\U000e0035\U000e0030\U000e007f" - FLAG_FOR_NONG_BUA_LAM_PHU_TH_39 = "\U0001f3f4\U000e0074\U000e0068\U000e0033\U000e0039\U000e007f" - FLAG_FOR_TAK_TH_63 = "\U0001f3f4\U000e0074\U000e0068\U000e0036\U000e0033\U000e007f" - FLAG_FOR_BUENG_KAN_TH_38 = "\U0001f3f4\U000e0074\U000e0068\U000e0033\U000e0038\U000e007f" - FLAG_FOR_LAMPHUN_TH_51 = "\U0001f3f4\U000e0074\U000e0068\U000e0035\U000e0031\U000e007f" - FLAG_FOR_PHAYAO_TH_56 = "\U0001f3f4\U000e0074\U000e0068\U000e0035\U000e0036\U000e007f" - FLAG_FOR_ROI_ET_TH_45 = "\U0001f3f4\U000e0074\U000e0068\U000e0034\U000e0035\U000e007f" - FLAG_FOR_PHICHIT_TH_66 = "\U0001f3f4\U000e0074\U000e0068\U000e0036\U000e0036\U000e007f" - FLAG_FOR_MAHA_SARAKHAM_TH_44 = "\U0001f3f4\U000e0074\U000e0068\U000e0034\U000e0034\U000e007f" - FLAG_FOR_SI_SA_KET_TH_33 = "\U0001f3f4\U000e0074\U000e0068\U000e0033\U000e0033\U000e007f" - FLAG_FOR_NAN_TH_55 = "\U0001f3f4\U000e0074\U000e0068\U000e0035\U000e0035\U000e007f" - FLAG_FOR_SUKHOTHAI_TH_64 = "\U0001f3f4\U000e0074\U000e0068\U000e0036\U000e0034\U000e007f" - FLAG_FOR_AILEU_TL_AL = "\U0001f3f4\U000e0074\U000e006c\U000e0061\U000e006c\U000e007f" - FLAG_FOR_MOQUEGUA_PE_MOQ = "\U0001f3f4\U000e0070\U000e0065\U000e006d\U000e006f\U000e0071\U000e007f" - FLAG_FOR_BAUCAU_TL_BA = "\U0001f3f4\U000e0074\U000e006c\U000e0062\U000e0061\U000e007f" - FLAG_FOR_AINARO_TL_AN = "\U0001f3f4\U000e0074\U000e006c\U000e0061\U000e006e\U000e007f" - FLAG_FOR_YAP_FM_YAP = "\U0001f3f4\U000e0066\U000e006d\U000e0079\U000e0061\U000e0070\U000e007f" - FLAG_FOR_SATUN_TH_91 = "\U0001f3f4\U000e0074\U000e0068\U000e0039\U000e0031\U000e007f" - FLAG_FOR_PHANG_NGA_TH_82 = "\U0001f3f4\U000e0074\U000e0068\U000e0038\U000e0032\U000e007f" - FLAG_FOR_TRANG_TH_92 = "\U0001f3f4\U000e0074\U000e0068\U000e0039\U000e0032\U000e007f" - FLAG_FOR_SONGKHLA_TH_90 = "\U0001f3f4\U000e0074\U000e0068\U000e0039\U000e0030\U000e007f" - FLAG_FOR_DASOGUZ_TM_D = "\U0001f3f4\U000e0074\U000e006d\U000e0064\U000e007f" - KISS_WOMAN_DARK_SKIN_TONE_WOMAN_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3ff" - FLAG_FOR_PATTAYA_TH_S = "\U0001f3f4\U000e0074\U000e0068\U000e0073\U000e007f" - FLAG_FOR_BLEKINGE_SE_K = "\U0001f3f4\U000e0073\U000e0065\U000e006b\U000e007f" - FLAG_FOR_MANATUTO_TL_MT = "\U0001f3f4\U000e0074\U000e006c\U000e006d\U000e0074\U000e007f" - FLAG_FOR_PATTANI_TH_94 = "\U0001f3f4\U000e0074\U000e0068\U000e0039\U000e0034\U000e007f" - FLAG_FOR_HIROSHIMA_JP_34 = "\U0001f3f4\U000e006a\U000e0070\U000e0033\U000e0034\U000e007f" - FLAG_FOR_JIWAKA_PG_JWK = "\U0001f3f4\U000e0070\U000e0067\U000e006a\U000e0077\U000e006b\U000e007f" - FLAG_FOR_LIQUICA_TL_LI = "\U0001f3f4\U000e0074\U000e006c\U000e006c\U000e0069\U000e007f" - FLAG_FOR_KHATLON_TJ_KT = "\U0001f3f4\U000e0074\U000e006a\U000e006b\U000e0074\U000e007f" - FLAG_FOR_SUGHD_TJ_SU = "\U0001f3f4\U000e0074\U000e006a\U000e0073\U000e0075\U000e007f" - FLAG_FOR_VIQUEQUE_TL_VI = "\U0001f3f4\U000e0074\U000e006c\U000e0076\U000e0069\U000e007f" - FLAG_FOR_BOBONARO_TL_BO = "\U0001f3f4\U000e0074\U000e006c\U000e0062\U000e006f\U000e007f" - FLAG_FOR_SURAT_THANI_TH_84 = "\U0001f3f4\U000e0074\U000e0068\U000e0038\U000e0034\U000e007f" - FLAG_FOR_RANONG_TH_85 = "\U0001f3f4\U000e0074\U000e0068\U000e0038\U000e0035\U000e007f" - FLAG_FOR_NORD_EST_HT_NE = "\U0001f3f4\U000e0068\U000e0074\U000e006e\U000e0065\U000e007f" - FLAG_FOR_PRACHUAP_KHIRI_KHAN_TH_77 = "\U0001f3f4\U000e0074\U000e0068\U000e0037\U000e0037\U000e007f" - FLAG_FOR_PHETCHABURI_TH_76 = "\U0001f3f4\U000e0074\U000e0068\U000e0037\U000e0036\U000e007f" - FLAG_FOR_NARATHIWAT_TH_96 = "\U0001f3f4\U000e0074\U000e0068\U000e0039\U000e0036\U000e007f" - FLAG_FOR_MAHDIA_TN_53 = "\U0001f3f4\U000e0074\U000e006e\U000e0035\U000e0033\U000e007f" - FLAG_FOR_TONGATAPU_TO_04 = "\U0001f3f4\U000e0074\U000e006f\U000e0030\U000e0034\U000e007f" - FLAG_FOR_VAVA_U_TO_05 = "\U0001f3f4\U000e0074\U000e006f\U000e0030\U000e0035\U000e007f" - FLAG_FOR_KAIROUAN_TN_41 = "\U0001f3f4\U000e0074\U000e006e\U000e0034\U000e0031\U000e007f" - FLAG_FOR_BOLU_TR_14 = "\U0001f3f4\U000e0074\U000e0072\U000e0031\U000e0034\U000e007f" - FLAG_FOR_BINGOL_TR_12 = "\U0001f3f4\U000e0074\U000e0072\U000e0031\U000e0032\U000e007f" - FLAG_FOR_AMASYA_TR_05 = "\U0001f3f4\U000e0074\U000e0072\U000e0030\U000e0035\U000e007f" - FLAG_FOR_BEJA_TN_31 = "\U0001f3f4\U000e0074\U000e006e\U000e0033\U000e0031\U000e007f" - FLAG_FOR_ZAGHOUAN_TN_22 = "\U0001f3f4\U000e0074\U000e006e\U000e0032\U000e0032\U000e007f" - FLAG_FOR_BILECIK_TR_11 = "\U0001f3f4\U000e0074\U000e0072\U000e0031\U000e0031\U000e007f" - FLAG_FOR_AGRI_TR_04 = "\U0001f3f4\U000e0074\U000e0072\U000e0030\U000e0034\U000e007f" - FLAG_FOR_KEF_TN_33 = "\U0001f3f4\U000e0074\U000e006e\U000e0033\U000e0033\U000e007f" - FLAG_FOR_MANOUBA_TN_14 = "\U0001f3f4\U000e0074\U000e006e\U000e0031\U000e0034\U000e007f" - FLAG_FOR_MEDENINE_TN_82 = "\U0001f3f4\U000e0074\U000e006e\U000e0038\U000e0032\U000e007f" - FLAG_FOR_BALIKESIR_TR_10 = "\U0001f3f4\U000e0074\U000e0072\U000e0031\U000e0030\U000e007f" - FLAG_FOR_EWA_NR_09 = "\U0001f3f4\U000e006e\U000e0072\U000e0030\U000e0039\U000e007f" - FLAG_FOR_TOZEUR_TN_72 = "\U0001f3f4\U000e0074\U000e006e\U000e0037\U000e0032\U000e007f" - FLAG_FOR_EUA_TO_01 = "\U0001f3f4\U000e0074\U000e006f\U000e0030\U000e0031\U000e007f" - FLAG_FOR_KASSERINE_TN_42 = "\U0001f3f4\U000e0074\U000e006e\U000e0034\U000e0032\U000e007f" - FLAG_FOR_GABES_TN_81 = "\U0001f3f4\U000e0074\U000e006e\U000e0038\U000e0031\U000e007f" - FLAG_FOR_KAUNAS_LT_16 = "\U0001f3f4\U000e006c\U000e0074\U000e0031\U000e0036\U000e007f" - FLAG_FOR_SILIANA_TN_34 = "\U0001f3f4\U000e0074\U000e006e\U000e0033\U000e0034\U000e007f" - FLAG_FOR_JENDOUBA_TN_32 = "\U0001f3f4\U000e0074\U000e006e\U000e0033\U000e0032\U000e007f" - FAMILY_WOMAN_MEDIUM_SKIN_TONE_BOY_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f466\U0001f3fd" - FLAG_FOR_BIZERTE_TN_23 = "\U0001f3f4\U000e0074\U000e006e\U000e0032\U000e0033\U000e007f" - COUPLE_WITH_HEART_MAN_MEDIUM_SKIN_TONE_WOMAN = "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f469" - FLAG_FOR_NIUAS_TO_03 = "\U0001f3f4\U000e0074\U000e006f\U000e0030\U000e0033\U000e007f" - FLAG_FOR_AYDIN_TR_09 = "\U0001f3f4\U000e0074\U000e0072\U000e0030\U000e0039\U000e007f" - FLAG_FOR_SIDI_BOUZID_TN_43 = "\U0001f3f4\U000e0074\U000e006e\U000e0034\U000e0033\U000e007f" - FLAG_FOR_JOHOR_MY_01 = "\U0001f3f4\U000e006d\U000e0079\U000e0030\U000e0031\U000e007f" - FLAG_FOR_KEBILI_TN_73 = "\U0001f3f4\U000e0074\U000e006e\U000e0037\U000e0033\U000e007f" - FLAG_FOR_TATAOUINE_TN_83 = "\U0001f3f4\U000e0074\U000e006e\U000e0038\U000e0033\U000e007f" - FLAG_FOR_MONASTIR_TN_52 = "\U0001f3f4\U000e0074\U000e006e\U000e0035\U000e0032\U000e007f" - FLAG_FOR_ARTVIN_TR_08 = "\U0001f3f4\U000e0074\U000e0072\U000e0030\U000e0038\U000e007f" - FLAG_FOR_GRAUBUNDEN_CH_GR = "\U0001f3f4\U000e0063\U000e0068\U000e0067\U000e0072\U000e007f" - FLAG_FOR_MALATYA_TR_44 = "\U0001f3f4\U000e0074\U000e0072\U000e0034\U000e0034\U000e007f" - FLAG_FOR_DENIZLI_TR_20 = "\U0001f3f4\U000e0074\U000e0072\U000e0032\U000e0030\U000e007f" - FLAG_FOR_RIZE_TR_53 = "\U0001f3f4\U000e0074\U000e0072\U000e0035\U000e0033\U000e007f" - FLAG_FOR_DIYARBAKIR_TR_21 = "\U0001f3f4\U000e0074\U000e0072\U000e0032\U000e0031\U000e007f" - FLAG_FOR_EDIRNE_TR_22 = "\U0001f3f4\U000e0074\U000e0072\U000e0032\U000e0032\U000e007f" - FLAG_FOR_BURSA_TR_16 = "\U0001f3f4\U000e0074\U000e0072\U000e0031\U000e0036\U000e007f" - FLAG_FOR_SIIRT_TR_56 = "\U0001f3f4\U000e0074\U000e0072\U000e0035\U000e0036\U000e007f" - FLAG_FOR_BOKE_REGION_GN_B = "\U0001f3f4\U000e0067\U000e006e\U000e0062\U000e007f" - FLAG_FOR_KAHRAMANMARAS_TR_46 = "\U0001f3f4\U000e0074\U000e0072\U000e0034\U000e0036\U000e007f" - FLAG_FOR_MARDIN_TR_47 = "\U0001f3f4\U000e0074\U000e0072\U000e0034\U000e0037\U000e007f" - FLAG_FOR_ERZURUM_TR_25 = "\U0001f3f4\U000e0074\U000e0072\U000e0032\U000e0035\U000e007f" - FLAG_FOR_ORO_PG_NPP = "\U0001f3f4\U000e0070\U000e0067\U000e006e\U000e0070\U000e0070\U000e007f" - FLAG_FOR_NIGDE_TR_51 = "\U0001f3f4\U000e0074\U000e0072\U000e0035\U000e0031\U000e007f" - FLAG_FOR_ORDU_TR_52 = "\U0001f3f4\U000e0074\U000e0072\U000e0035\U000e0032\U000e007f" - FLAG_FOR_NGOBE_BUGLE_PA_NB = "\U0001f3f4\U000e0070\U000e0061\U000e006e\U000e0062\U000e007f" - FLAG_FOR_HAKKARI_TR_30 = "\U0001f3f4\U000e0074\U000e0072\U000e0033\U000e0030\U000e007f" - FLAG_FOR_SAMSUN_TR_55 = "\U0001f3f4\U000e0074\U000e0072\U000e0035\U000e0035\U000e007f" - FLAG_FOR_HATAY_TR_31 = "\U0001f3f4\U000e0074\U000e0072\U000e0033\U000e0031\U000e007f" - FLAG_FOR_SAKARYA_TR_54 = "\U0001f3f4\U000e0074\U000e0072\U000e0035\U000e0034\U000e007f" - FLAG_FOR_KIRKLARELI_TR_39 = "\U0001f3f4\U000e0074\U000e0072\U000e0033\U000e0039\U000e007f" - FLAG_FOR_VICTORIA_MT_45 = "\U0001f3f4\U000e006d\U000e0074\U000e0034\U000e0035\U000e007f" - FLAG_FOR_KIRSEHIR_TR_40 = "\U0001f3f4\U000e0074\U000e0072\U000e0034\U000e0030\U000e007f" - FLAG_FOR_ISPARTA_TR_32 = "\U0001f3f4\U000e0074\U000e0072\U000e0033\U000e0032\U000e007f" - FLAG_FOR_ESKISEHIR_TR_26 = "\U0001f3f4\U000e0074\U000e0072\U000e0032\U000e0036\U000e007f" - FLAG_FOR_BURDUR_TR_15 = "\U0001f3f4\U000e0074\U000e0072\U000e0031\U000e0035\U000e007f" - FLAG_FOR_MANISA_TR_45 = "\U0001f3f4\U000e0074\U000e0072\U000e0034\U000e0035\U000e007f" - FLAG_FOR_PRAHA_HLAVNI_MESTO_CZ_10 = "\U0001f3f4\U000e0063\U000e007a\U000e0031\U000e0030\U000e007f" - FLAG_FOR_ELAZIG_TR_23 = "\U0001f3f4\U000e0074\U000e0072\U000e0032\U000e0033\U000e007f" - FLAG_FOR_CANAKKALE_TR_17 = "\U0001f3f4\U000e0074\U000e0072\U000e0031\U000e0037\U000e007f" - FLAG_FOR_MAYARO_RIO_CLARO_TT_MRC = "\U0001f3f4\U000e0074\U000e0074\U000e006d\U000e0072\U000e0063\U000e007f" - FLAG_FOR_TOKAT_TR_60 = "\U0001f3f4\U000e0074\U000e0072\U000e0036\U000e0030\U000e007f" - FLAG_FOR_TOBAGO_TT_TOB = "\U0001f3f4\U000e0074\U000e0074\U000e0074\U000e006f\U000e0062\U000e007f" - FLAG_FOR_DIEGO_MARTIN_TT_DMN = "\U0001f3f4\U000e0074\U000e0074\U000e0064\U000e006d\U000e006e\U000e007f" - FLAG_FOR_SAN_FERNANDO_TT_SFO = "\U0001f3f4\U000e0074\U000e0074\U000e0073\U000e0066\U000e006f\U000e007f" - FLAG_FOR_KILIS_TR_79 = "\U0001f3f4\U000e0074\U000e0072\U000e0037\U000e0039\U000e007f" - KISS_WOMAN_MEDIUM_LIGHT_SKIN_TONE_WOMAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fc" - FLAG_FOR_PENZA_RU_PNZ = "\U0001f3f4\U000e0072\U000e0075\U000e0070\U000e006e\U000e007a\U000e007f" - FLAG_FOR_SANLIURFA_TR_63 = "\U0001f3f4\U000e0074\U000e0072\U000e0036\U000e0033\U000e007f" - FLAG_FOR_ERZINCAN_TR_24 = "\U0001f3f4\U000e0074\U000e0072\U000e0032\U000e0034\U000e007f" - FLAG_FOR_TEKIRDAG_TR_59 = "\U0001f3f4\U000e0074\U000e0072\U000e0035\U000e0039\U000e007f" - FLAG_FOR_KIRIKKALE_TR_71 = "\U0001f3f4\U000e0074\U000e0072\U000e0037\U000e0031\U000e007f" - FLAG_FOR_IGDIR_TR_76 = "\U0001f3f4\U000e0074\U000e0072\U000e0037\U000e0036\U000e007f" - FLAG_FOR_COUVA_TABAQUITE_TALPARO_TT_CTT = "\U0001f3f4\U000e0074\U000e0074\U000e0063\U000e0074\U000e0074\U000e007f" - FLAG_FOR_KARAMAN_TR_70 = "\U0001f3f4\U000e0074\U000e0072\U000e0037\U000e0030\U000e007f" - FLAG_FOR_ZONGULDAK_TR_67 = "\U0001f3f4\U000e0074\U000e0072\U000e0036\U000e0037\U000e007f" - FLAG_FOR_TUNCELI_TR_62 = "\U0001f3f4\U000e0074\U000e0072\U000e0036\U000e0032\U000e007f" - FLAG_FOR_POINT_FORTIN_TT_PTF = "\U0001f3f4\U000e0074\U000e0074\U000e0070\U000e0074\U000e0066\U000e007f" - FLAG_FOR_PENAL_DEBE_TT_PED = "\U0001f3f4\U000e0074\U000e0074\U000e0070\U000e0065\U000e0064\U000e007f" - FLAG_FOR_SAN_JUAN_LAVENTILLE_TT_SJL = "\U0001f3f4\U000e0074\U000e0074\U000e0073\U000e006a\U000e006c\U000e007f" - FLAG_FOR_PRINCES_TOWN_TT_PRT = "\U0001f3f4\U000e0074\U000e0074\U000e0070\U000e0072\U000e0074\U000e007f" - FLAG_FOR_YALOVA_TR_77 = "\U0001f3f4\U000e0074\U000e0072\U000e0037\U000e0037\U000e007f" - FLAG_FOR_BAYBURT_TR_69 = "\U0001f3f4\U000e0074\U000e0072\U000e0036\U000e0039\U000e007f" - FLAG_FOR_BAIE_LAZARE_SC_06 = "\U0001f3f4\U000e0073\U000e0063\U000e0030\U000e0036\U000e007f" - FLAG_FOR_CHAGUANAS_TT_CHA = "\U0001f3f4\U000e0074\U000e0074\U000e0063\U000e0068\U000e0061\U000e007f" - FLAG_FOR_BARTIN_TR_74 = "\U0001f3f4\U000e0074\U000e0072\U000e0037\U000e0034\U000e007f" - FLAG_FOR_SIPARIA_TT_SIP = "\U0001f3f4\U000e0074\U000e0074\U000e0073\U000e0069\U000e0070\U000e007f" - FLAG_FOR_SINOP_TR_57 = "\U0001f3f4\U000e0074\U000e0072\U000e0035\U000e0037\U000e007f" - FLAG_FOR_GAZIANTEP_TR_27 = "\U0001f3f4\U000e0074\U000e0072\U000e0032\U000e0037\U000e007f" - FLAG_FOR_KARABUK_TR_78 = "\U0001f3f4\U000e0074\U000e0072\U000e0037\U000e0038\U000e007f" - FLAG_FOR_SANTA_BARBARA_HN_SB = "\U0001f3f4\U000e0068\U000e006e\U000e0073\U000e0062\U000e007f" - FLAG_FOR_CHIAYI_COUNTY_TW_CYI = "\U0001f3f4\U000e0074\U000e0077\U000e0063\U000e0079\U000e0069\U000e007f" - FLAG_FOR_KAGERA_TZ_05 = "\U0001f3f4\U000e0074\U000e007a\U000e0030\U000e0035\U000e007f" - FLAG_FOR_NANUMEA_TV_NMA = "\U0001f3f4\U000e0074\U000e0076\U000e006e\U000e006d\U000e0061\U000e007f" - FLAG_FOR_TAINAN_TW_TNN = "\U0001f3f4\U000e0074\U000e0077\U000e0074\U000e006e\U000e006e\U000e007f" - FLAG_FOR_DODOMA_TZ_03 = "\U0001f3f4\U000e0074\U000e007a\U000e0030\U000e0033\U000e007f" - FLAG_FOR_CHIAYI_TW_CYQ = "\U0001f3f4\U000e0074\U000e0077\U000e0063\U000e0079\U000e0071\U000e007f" - FLAG_FOR_YUNLIN_TW_YUN = "\U0001f3f4\U000e0074\U000e0077\U000e0079\U000e0075\U000e006e\U000e007f" - FLAG_FOR_NEW_TAIPEI_TW_NWT = "\U0001f3f4\U000e0074\U000e0077\U000e006e\U000e0077\U000e0074\U000e007f" - FLAG_FOR_KINMEN_TW_KIN = "\U0001f3f4\U000e0074\U000e0077\U000e006b\U000e0069\U000e006e\U000e007f" - FLAG_FOR_HUALIEN_TW_HUA = "\U0001f3f4\U000e0074\U000e0077\U000e0068\U000e0075\U000e0061\U000e007f" - FLAG_FOR_TAICHUNG_TW_TXG = "\U0001f3f4\U000e0074\U000e0077\U000e0074\U000e0078\U000e0067\U000e007f" - FAMILY_MAN_MEDIUM_DARK_SKIN_TONE_WOMAN_MEDIUM_DARK_SKIN_TONE_BOY_MEDIUM_DARK_SKIN_TONE_BABY_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f469\U0001f3fe\u200d\U0001f466\U0001f3fe\u200d\U0001f476\U0001f3fe" - FLAG_FOR_PINGTUNG_TW_PIF = "\U0001f3f4\U000e0074\U000e0077\U000e0070\U000e0069\U000e0066\U000e007f" - FLAG_FOR_IRINGA_TZ_04 = "\U0001f3f4\U000e0074\U000e007a\U000e0030\U000e0034\U000e007f" - FLAG_FOR_KEELUNG_TW_KEE = "\U0001f3f4\U000e0074\U000e0077\U000e006b\U000e0065\U000e0065\U000e007f" - KISS_MAN_MEDIUM_DARK_SKIN_TONE_MAN = "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468" - FLAG_FOR_AGLONA_LV_001 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0030\U000e0031\U000e007f" - FLAG_FOR_CANKOVA_SI_152 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0035\U000e0032\U000e007f" - FLAG_FOR_ARUSHA_TZ_01 = "\U0001f3f4\U000e0074\U000e007a\U000e0030\U000e0031\U000e007f" - FLAG_FOR_NUI_TV_NUI = "\U0001f3f4\U000e0074\U000e0076\U000e006e\U000e0075\U000e0069\U000e007f" - FLAG_FOR_YILAN_TW_ILA = "\U0001f3f4\U000e0074\U000e0077\U000e0069\U000e006c\U000e0061\U000e007f" - FLAG_FOR_VERACRUZ_MX_VER = "\U0001f3f4\U000e006d\U000e0078\U000e0076\U000e0065\U000e0072\U000e007f" - FAMILY_MAN_MEDIUM_DARK_SKIN_TONE_WOMAN_MEDIUM_DARK_SKIN_TONE_GIRL_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f469\U0001f3fe\u200d\U0001f467\U0001f3fe" - FLAG_FOR_LIENCHIANG_TW_LIE = "\U0001f3f4\U000e0074\U000e0077\U000e006c\U000e0069\U000e0065\U000e007f" - FLAG_FOR_NUKUFETAU_TV_NKF = "\U0001f3f4\U000e0074\U000e0076\U000e006e\U000e006b\U000e0066\U000e007f" - FLAG_FOR_VAITUPU_TV_VAI = "\U0001f3f4\U000e0074\U000e0076\U000e0076\U000e0061\U000e0069\U000e007f" - FLAG_FOR_VOLYN_UA_07 = "\U0001f3f4\U000e0075\U000e0061\U000e0030\U000e0037\U000e007f" - FLAG_FOR_MTWARA_TZ_17 = "\U0001f3f4\U000e0074\U000e007a\U000e0031\U000e0037\U000e007f" - FLAG_FOR_TAMBOV_RU_TAM = "\U0001f3f4\U000e0072\U000e0075\U000e0074\U000e0061\U000e006d\U000e007f" - FLAG_FOR_KATAVI_TZ_28 = "\U0001f3f4\U000e0074\U000e007a\U000e0032\U000e0038\U000e007f" - FLAG_FOR_SOUTH_PEMBA_TZ_10 = "\U0001f3f4\U000e0074\U000e007a\U000e0031\U000e0030\U000e007f" - FLAG_FOR_RUKWA_TZ_20 = "\U0001f3f4\U000e0074\U000e007a\U000e0032\U000e0030\U000e007f" - FLAG_FOR_PORT_LOUIS_DISTRICT_MU_PL = "\U0001f3f4\U000e006d\U000e0075\U000e0070\U000e006c\U000e007f" - FLAG_FOR_MANYARA_TZ_26 = "\U0001f3f4\U000e0074\U000e007a\U000e0032\U000e0036\U000e007f" - FLAG_FOR_SEVASTOPOL_UA_40 = "\U0001f3f4\U000e0075\U000e0061\U000e0034\U000e0030\U000e007f" - FLAG_FOR_NJOMBE_TZ_29 = "\U0001f3f4\U000e0074\U000e007a\U000e0032\U000e0039\U000e007f" - FLAG_FOR_ZAPORIZHZHYA_UA_23 = "\U0001f3f4\U000e0075\U000e0061\U000e0032\U000e0033\U000e007f" - FLAG_FOR_MBEYA_TZ_14 = "\U0001f3f4\U000e0074\U000e007a\U000e0031\U000e0034\U000e007f" - FLAG_FOR_KYIVSHCHYNA_UA_32 = "\U0001f3f4\U000e0075\U000e0061\U000e0033\U000e0032\U000e007f" - FLAG_FOR_RUVUMA_TZ_21 = "\U0001f3f4\U000e0074\U000e007a\U000e0032\U000e0031\U000e007f" - FLAG_FOR_SHINYANGA_TZ_22 = "\U0001f3f4\U000e0074\U000e007a\U000e0032\U000e0032\U000e007f" - FLAG_FOR_ZAKARPATTIA_UA_21 = "\U0001f3f4\U000e0075\U000e0061\U000e0032\U000e0031\U000e007f" - FLAG_FOR_NORTE_GW_N = "\U0001f3f4\U000e0067\U000e0077\U000e006e\U000e007f" - FLAG_FOR_RIVNENSHCHYNA_UA_56 = "\U0001f3f4\U000e0075\U000e0061\U000e0035\U000e0036\U000e007f" - FLAG_FOR_SAINT_GEORGE_DM_04 = "\U0001f3f4\U000e0064\U000e006d\U000e0030\U000e0034\U000e007f" - FLAG_FOR_ODESHCHYNA_UA_51 = "\U0001f3f4\U000e0075\U000e0061\U000e0035\U000e0031\U000e007f" - FLAG_FOR_SIMIYU_TZ_30 = "\U0001f3f4\U000e0074\U000e007a\U000e0033\U000e0030\U000e007f" - FLAG_FOR_GEITA_TZ_27 = "\U0001f3f4\U000e0074\U000e007a\U000e0032\U000e0037\U000e007f" - FLAG_FOR_POLTAVSHCHYNA_UA_53 = "\U0001f3f4\U000e0075\U000e0061\U000e0035\U000e0033\U000e007f" - FLAG_FOR_TABORA_TZ_24 = "\U0001f3f4\U000e0074\U000e007a\U000e0032\U000e0034\U000e007f" - FLAG_FOR_SINGIDA_TZ_23 = "\U0001f3f4\U000e0074\U000e007a\U000e0032\U000e0033\U000e007f" - FLAG_FOR_MWANZA_TZ_18 = "\U0001f3f4\U000e0074\U000e007a\U000e0031\U000e0038\U000e007f" - FLAG_FOR_SELENGE_MN_049 = "\U0001f3f4\U000e006d\U000e006e\U000e0030\U000e0034\U000e0039\U000e007f" - FLAG_FOR_VINNYCHCHYNA_UA_05 = "\U0001f3f4\U000e0075\U000e0061\U000e0030\U000e0035\U000e007f" - FLAG_FOR_FLORIDA_US_FL = "\U0001f3f4\U000e0075\U000e0073\U000e0066\U000e006c\U000e007f" - FLAG_FOR_LOFA_LR_LO = "\U0001f3f4\U000e006c\U000e0072\U000e006c\U000e006f\U000e007f" - RECYCLING_SYMBOL_FOR_TYPE_7_PLASTICS = "\u2679" - FLAG_FOR_BAKER_ISLAND_UM_81 = "\U0001f3f4\U000e0075\U000e006d\U000e0038\U000e0031\U000e007f" - FLAG_FOR_NORTHERN_UG_N = "\U0001f3f4\U000e0075\U000e0067\U000e006e\U000e007f" - FLAG_FOR_INGUSHETIA_RU_IN = "\U0001f3f4\U000e0072\U000e0075\U000e0069\U000e006e\U000e007f" - FLAG_FOR_KHERSONSHCHYNA_UA_65 = "\U0001f3f4\U000e0075\U000e0061\U000e0036\U000e0035\U000e007f" - FLAG_FOR_WAKE_ISLAND_UM_79 = "\U0001f3f4\U000e0075\U000e006d\U000e0037\U000e0039\U000e007f" - FLAG_FOR_PALMYRA_ATOLL_UM_95 = "\U0001f3f4\U000e0075\U000e006d\U000e0039\U000e0035\U000e007f" - FLAG_FOR_KHARKIVSHCHYNA_UA_63 = "\U0001f3f4\U000e0075\U000e0061\U000e0036\U000e0033\U000e007f" - FLAG_FOR_KHMELNYCHCHYNA_UA_68 = "\U0001f3f4\U000e0075\U000e0061\U000e0036\U000e0038\U000e007f" - FLAG_FOR_WESTERN_UG_W = "\U0001f3f4\U000e0075\U000e0067\U000e0077\U000e007f" - FLAG_FOR_EASTERN_UG_E = "\U0001f3f4\U000e0075\U000e0067\U000e0065\U000e007f" - FLAG_FOR_IDLIB_SY_ID = "\U0001f3f4\U000e0073\U000e0079\U000e0069\U000e0064\U000e007f" - FLAG_FOR_TERNOPILSHCHYNA_UA_61 = "\U0001f3f4\U000e0075\U000e0061\U000e0036\U000e0031\U000e007f" - FLAG_FOR_CHERNIVTSI_OBLAST_UA_77 = "\U0001f3f4\U000e0075\U000e0061\U000e0037\U000e0037\U000e007f" - FLAG_FOR_PARA_SR_PR = "\U0001f3f4\U000e0073\U000e0072\U000e0070\U000e0072\U000e007f" - FLAG_FOR_SUMSHCHYNA_UA_59 = "\U0001f3f4\U000e0075\U000e0061\U000e0035\U000e0039\U000e007f" - FAMILY_WOMAN_WOMAN_BABY_BOY = "\U0001f469\u200d\U0001f469\u200d\U0001f476\u200d\U0001f466" - FLAG_FOR_JERICHO_PS_JRH = "\U0001f3f4\U000e0070\U000e0073\U000e006a\U000e0072\U000e0068\U000e007f" - FLAG_FOR_MIDWAY_ATOLL_UM_71 = "\U0001f3f4\U000e0075\U000e006d\U000e0037\U000e0031\U000e007f" - FLAG_FOR_JOHNSTON_ATOLL_UM_67 = "\U0001f3f4\U000e0075\U000e006d\U000e0036\U000e0037\U000e007f" - FAMILY_WOMAN_MEDIUM_LIGHT_SKIN_TONE_BOY_MEDIUM_LIGHT_SKIN_TONE_BABY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f466\U0001f3fc\u200d\U0001f476\U0001f3fc" - FLAG_FOR_CANELONES_UY_CA = "\U0001f3f4\U000e0075\U000e0079\U000e0063\U000e0061\U000e007f" - FLAG_FOR_KOSTROMA_RU_KOS = "\U0001f3f4\U000e0072\U000e0075\U000e006b\U000e006f\U000e0073\U000e007f" - FLAG_FOR_JARVIS_ISLAND_UM_86 = "\U0001f3f4\U000e0075\U000e006d\U000e0038\U000e0036\U000e007f" - FLAG_FOR_PENNSYLVANIA_US_PA = "\U0001f3f4\U000e0075\U000e0073\U000e0070\U000e0061\U000e007f" - FLAG_FOR_PUEBLA_MX_PUE = "\U0001f3f4\U000e006d\U000e0078\U000e0070\U000e0075\U000e0065\U000e007f" - FLAG_FOR_SVETA_TROJICA_V_SLOVENSKIH_GORICAH_SI_204 = "\U0001f3f4\U000e0073\U000e0069\U000e0032\U000e0030\U000e0034\U000e007f" - FAMILY_MAN_MEDIUM_LIGHT_SKIN_TONE_MAN_MEDIUM_LIGHT_SKIN_TONE_BOY_MEDIUM_LIGHT_SKIN_TONE_BOY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f468\U0001f3fc\u200d\U0001f466\U0001f3fc\u200d\U0001f466\U0001f3fc" - FLAG_FOR_CHERKASHCHYNA_UA_71 = "\U0001f3f4\U000e0075\U000e0061\U000e0037\U000e0031\U000e007f" - FLAG_FOR_ALEXANDRIA_EG_ALX = "\U0001f3f4\U000e0065\U000e0067\U000e0061\U000e006c\U000e0078\U000e007f" - FLAG_FOR_CENTRAL_MACEDONIA_GR_B = "\U0001f3f4\U000e0067\U000e0072\U000e0062\U000e007f" - FLAG_FOR_ARTIGAS_UY_AR = "\U0001f3f4\U000e0075\U000e0079\U000e0061\U000e0072\U000e007f" - FLAG_FOR_OSLO_NO_03 = "\U0001f3f4\U000e006e\U000e006f\U000e0030\U000e0033\U000e007f" - FLAG_FOR_LOGONE_ORIENTAL_TD_LR = "\U0001f3f4\U000e0074\U000e0064\U000e006c\U000e0072\U000e007f" - FLAG_FOR_SINT_EUSTATIUS_NL_BQ3 = "\U0001f3f4\U000e006e\U000e006c\U000e0062\U000e0071\U000e0033\U000e007f" - FAMILY_MAN_MEDIUM_DARK_SKIN_TONE_MAN_MEDIUM_DARK_SKIN_TONE_BOY_MEDIUM_DARK_SKIN_TONE_BABY_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f468\U0001f3fe\u200d\U0001f466\U0001f3fe\u200d\U0001f476\U0001f3fe" - FLAG_FOR_KINGMAN_REEF_UM_89 = "\U0001f3f4\U000e0075\U000e006d\U000e0038\U000e0039\U000e007f" - FLAG_FOR_DNIPROPETROVSHCHYNA_UA_12 = "\U0001f3f4\U000e0075\U000e0061\U000e0031\U000e0032\U000e007f" - FLAG_FOR_VRANCEA_RO_VN = "\U0001f3f4\U000e0072\U000e006f\U000e0076\U000e006e\U000e007f" - FLAG_FOR_NORTH_PEMBA_TZ_06 = "\U0001f3f4\U000e0074\U000e007a\U000e0030\U000e0036\U000e007f" - FLAG_FOR_MONTEVIDEO_UY_MO = "\U0001f3f4\U000e0075\U000e0079\U000e006d\U000e006f\U000e007f" - FLAG_FOR_COLONIA_UY_CO = "\U0001f3f4\U000e0075\U000e0079\U000e0063\U000e006f\U000e007f" - FLAG_FOR_JIZZAKH_UZ_JI = "\U0001f3f4\U000e0075\U000e007a\U000e006a\U000e0069\U000e007f" - FLAG_FOR_OKLAHOMA_US_OK = "\U0001f3f4\U000e0075\U000e0073\U000e006f\U000e006b\U000e007f" - FLAG_FOR_TREINTA_Y_TRES_UY_TT = "\U0001f3f4\U000e0075\U000e0079\U000e0074\U000e0074\U000e007f" - FLAG_FOR_SOUTH_CAROLINA_US_SC = "\U0001f3f4\U000e0075\U000e0073\U000e0073\U000e0063\U000e007f" - FLAG_FOR_PAYSANDU_UY_PA = "\U0001f3f4\U000e0075\U000e0079\U000e0070\U000e0061\U000e007f" - FLAG_FOR_TASHKENT_PROVINCE_UZ_TO = "\U0001f3f4\U000e0075\U000e007a\U000e0074\U000e006f\U000e007f" - FLAG_FOR_NAMANGAN_UZ_NG = "\U0001f3f4\U000e0075\U000e007a\U000e006e\U000e0067\U000e007f" - FLAG_FOR_DURAZNO_UY_DU = "\U0001f3f4\U000e0075\U000e0079\U000e0064\U000e0075\U000e007f" - FLAG_FOR_ARAGUA_VE_D = "\U0001f3f4\U000e0076\U000e0065\U000e0064\U000e007f" - FLAG_FOR_KARAKALPAKSTAN_UZ_QR = "\U0001f3f4\U000e0075\U000e007a\U000e0071\U000e0072\U000e007f" - FLAG_FOR_CERRO_LARGO_UY_CL = "\U0001f3f4\U000e0075\U000e0079\U000e0063\U000e006c\U000e007f" - FLAG_FOR_KELANTAN_MY_03 = "\U0001f3f4\U000e006d\U000e0079\U000e0030\U000e0033\U000e007f" - FLAG_FOR_FLORES_UY_FS = "\U0001f3f4\U000e0075\U000e0079\U000e0066\U000e0073\U000e007f" - FLAG_FOR_FERGANA_UZ_FA = "\U0001f3f4\U000e0075\U000e007a\U000e0066\U000e0061\U000e007f" - FLAG_FOR_MARYLAND_US_MD = "\U0001f3f4\U000e0075\U000e0073\U000e006d\U000e0064\U000e007f" - FLAG_FOR_ANZOATEGUI_VE_B = "\U0001f3f4\U000e0076\U000e0065\U000e0062\U000e007f" - FLAG_FOR_SAINT_ANDREW_VC_02 = "\U0001f3f4\U000e0076\U000e0063\U000e0030\U000e0032\U000e007f" - FLAG_FOR_BUKHARA_UZ_BU = "\U0001f3f4\U000e0075\U000e007a\U000e0062\U000e0075\U000e007f" - FLAG_FOR_MELEKEOK_PW_212 = "\U0001f3f4\U000e0070\U000e0077\U000e0032\U000e0031\U000e0032\U000e007f" - FLAG_FOR_TELENESTI_MD_TE = "\U0001f3f4\U000e006d\U000e0064\U000e0074\U000e0065\U000e007f" - FLAG_FOR_CHARLOTTE_VC_01 = "\U0001f3f4\U000e0076\U000e0063\U000e0030\U000e0031\U000e007f" - FLAG_FOR_ANDIJAN_UZ_AN = "\U0001f3f4\U000e0075\U000e007a\U000e0061\U000e006e\U000e007f" - FLAG_FOR_SALTO_UY_SA = "\U0001f3f4\U000e0075\U000e0079\U000e0073\U000e0061\U000e007f" - FLAG_FOR_SORIANO_UY_SO = "\U0001f3f4\U000e0075\U000e0079\U000e0073\U000e006f\U000e007f" - FLAG_FOR_SIRDARYO_UZ_SI = "\U0001f3f4\U000e0075\U000e007a\U000e0073\U000e0069\U000e007f" - FLAG_FOR_TASHKENT_UZ_TK = "\U0001f3f4\U000e0075\U000e007a\U000e0074\U000e006b\U000e007f" - FLAG_FOR_CAPITAL_VE_A = "\U0001f3f4\U000e0076\U000e0065\U000e0061\U000e007f" - FLAG_FOR_SAINT_DAVID_VC_03 = "\U0001f3f4\U000e0076\U000e0063\U000e0030\U000e0033\U000e007f" - FLAG_FOR_MALDONADO_UY_MA = "\U0001f3f4\U000e0075\U000e0079\U000e006d\U000e0061\U000e007f" - FLAG_FOR_RIO_NEGRO_UY_RN = "\U0001f3f4\U000e0075\U000e0079\U000e0072\U000e006e\U000e007f" - FLAG_FOR_LAVALLEJA_UY_LA = "\U0001f3f4\U000e0075\U000e0079\U000e006c\U000e0061\U000e007f" - FLAG_FOR_NAVOIY_UZ_NW = "\U0001f3f4\U000e0075\U000e007a\U000e006e\U000e0077\U000e007f" - FLAG_FOR_SAMARQAND_UZ_SA = "\U0001f3f4\U000e0075\U000e007a\U000e0073\U000e0061\U000e007f" - FLAG_FOR_SURXONDARYO_UZ_SU = "\U0001f3f4\U000e0075\U000e007a\U000e0073\U000e0075\U000e007f" - FLAG_FOR_QASHQADARYO_UZ_QA = "\U0001f3f4\U000e0075\U000e007a\U000e0071\U000e0061\U000e007f" - FLAG_FOR_TACUAREMBO_UY_TA = "\U0001f3f4\U000e0075\U000e0079\U000e0074\U000e0061\U000e007f" - FLAG_FOR_SON_LA_VN_05 = "\U0001f3f4\U000e0076\U000e006e\U000e0030\U000e0035\U000e007f" - FLAG_FOR_LANG_SON_VN_09 = "\U0001f3f4\U000e0076\U000e006e\U000e0030\U000e0039\U000e007f" - FLAG_FOR_HA_GIANG_VN_03 = "\U0001f3f4\U000e0076\U000e006e\U000e0030\U000e0033\U000e007f" - FLAG_FOR_MIRANDA_VE_M = "\U0001f3f4\U000e0076\U000e0065\U000e006d\U000e007f" - FLAG_FOR_TUYEN_QUANG_VN_07 = "\U0001f3f4\U000e0076\U000e006e\U000e0030\U000e0037\U000e007f" - FLAG_FOR_THANH_HOA_VN_21 = "\U0001f3f4\U000e0076\U000e006e\U000e0032\U000e0031\U000e007f" - FLAG_FOR_NGHE_AN_VN_22 = "\U0001f3f4\U000e0076\U000e006e\U000e0032\U000e0032\U000e007f" - FLAG_FOR_COJEDES_VE_H = "\U0001f3f4\U000e0076\U000e0065\U000e0068\U000e007f" - FLAG_FOR_CAO_BANG_VN_04 = "\U0001f3f4\U000e0076\U000e006e\U000e0030\U000e0034\U000e007f" - FLAG_FOR_QUANG_NGAI_VN_29 = "\U0001f3f4\U000e0076\U000e006e\U000e0032\U000e0039\U000e007f" - FLAG_FOR_SANTANDER_CO_SAN = "\U0001f3f4\U000e0063\U000e006f\U000e0073\U000e0061\U000e006e\U000e007f" - FLAG_FOR_NINH_BINH_VN_18 = "\U0001f3f4\U000e0076\U000e006e\U000e0031\U000e0038\U000e007f" - FLAG_FOR_YEN_BAI_VN_06 = "\U0001f3f4\U000e0076\U000e006e\U000e0030\U000e0036\U000e007f" - FLAG_FOR_THAI_BINH_VN_20 = "\U0001f3f4\U000e0076\U000e006e\U000e0032\U000e0030\U000e007f" - FLAG_FOR_BARINAS_VE_E = "\U0001f3f4\U000e0076\U000e0065\U000e0065\U000e007f" - FLAG_FOR_HA_TINH_VN_23 = "\U0001f3f4\U000e0076\U000e006e\U000e0032\U000e0033\U000e007f" - FLAG_FOR_LOWER_RIVER_DIVISION_GM_L = "\U0001f3f4\U000e0067\U000e006d\U000e006c\U000e007f" - FLAG_FOR_IG_SI_037 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0033\U000e0037\U000e007f" - FAMILY_WOMAN_MEDIUM_LIGHT_SKIN_TONE_MAN_MEDIUM_LIGHT_SKIN_TONE_GIRL_MEDIUM_LIGHT_SKIN_TONE_GIRL_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f468\U0001f3fc\u200d\U0001f467\U0001f3fc\u200d\U0001f467\U0001f3fc" - FLAG_FOR_LARA_VE_K = "\U0001f3f4\U000e0076\U000e0065\U000e006b\U000e007f" - FLAG_FOR_QUANG_NINH_VN_13 = "\U0001f3f4\U000e0076\U000e006e\U000e0031\U000e0033\U000e007f" - FLAG_FOR_AU_CAP_SC_04 = "\U0001f3f4\U000e0073\U000e0063\U000e0030\U000e0034\U000e007f" - FLAG_FOR_HOA_BINH_VN_14 = "\U0001f3f4\U000e0076\U000e006e\U000e0031\U000e0034\U000e007f" - FLAG_FOR_KON_TUM_VN_28 = "\U0001f3f4\U000e0076\U000e006e\U000e0032\U000e0038\U000e007f" - FAMILY_WOMAN_MEDIUM_LIGHT_SKIN_TONE_BOY_MEDIUM_LIGHT_SKIN_TONE_BOY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f466\U0001f3fc\u200d\U0001f466\U0001f3fc" - FLAG_FOR_ZANZIBAR_URBAN_WEST_TZ_15 = "\U0001f3f4\U000e0074\U000e007a\U000e0031\U000e0035\U000e007f" - FLAG_FOR_OECUSSE_TL_OE = "\U0001f3f4\U000e0074\U000e006c\U000e006f\U000e0065\U000e007f" - FLAG_FOR_YARACUY_VE_U = "\U0001f3f4\U000e0076\U000e0065\U000e0075\U000e007f" - FLAG_FOR_BINH_PHUOC_VN_58 = "\U0001f3f4\U000e0076\U000e006e\U000e0035\U000e0038\U000e007f" - FLAG_FOR_NINH_THUAN_VN_36 = "\U0001f3f4\U000e0076\U000e006e\U000e0033\U000e0036\U000e007f" - FLAG_FOR_OSHIKOTO_NA_OT = "\U0001f3f4\U000e006e\U000e0061\U000e006f\U000e0074\U000e007f" - FLAG_FOR_CARGADOS_CARAJOS_MU_CC = "\U0001f3f4\U000e006d\U000e0075\U000e0063\U000e0063\U000e007f" - FLAG_FOR_PHU_YEN_VN_32 = "\U0001f3f4\U000e0076\U000e006e\U000e0033\U000e0032\U000e007f" - FLAG_FOR_NAM_INH_VN_67 = "\U0001f3f4\U000e0076\U000e006e\U000e0036\U000e0037\U000e007f" - FLAG_FOR_BAC_GIANG_VN_54 = "\U0001f3f4\U000e0076\U000e006e\U000e0035\U000e0034\U000e007f" - FLAG_FOR_TIEN_GIANG_VN_46 = "\U0001f3f4\U000e0076\U000e006e\U000e0034\U000e0036\U000e007f" - FLAG_FOR_NORTHERN_MW_N = "\U0001f3f4\U000e006d\U000e0077\U000e006e\U000e007f" - FLAG_FOR_CAN_THO_VN_CT = "\U0001f3f4\U000e0076\U000e006e\U000e0063\U000e0074\U000e007f" - FLAG_FOR_BINH_THUAN_VN_40 = "\U0001f3f4\U000e0076\U000e006e\U000e0034\U000e0030\U000e007f" - FLAG_FOR_AK_LAK_VN_33 = "\U0001f3f4\U000e0076\U000e006e\U000e0033\U000e0033\U000e007f" - FLAG_FOR_LAM_ONG_VN_35 = "\U0001f3f4\U000e0076\U000e006e\U000e0033\U000e0035\U000e007f" - FLAG_FOR_BEN_TRE_VN_50 = "\U0001f3f4\U000e0076\U000e006e\U000e0035\U000e0030\U000e007f" - FLAG_FOR_TAY_NINH_VN_37 = "\U0001f3f4\U000e0076\U000e006e\U000e0033\U000e0037\U000e007f" - FLAG_FOR_BINH_INH_VN_31 = "\U0001f3f4\U000e0076\U000e006e\U000e0033\U000e0031\U000e007f" - FLAG_FOR_BAC_LIEU_VN_55 = "\U0001f3f4\U000e0076\U000e006e\U000e0035\U000e0035\U000e007f" - FLAG_FOR_ONG_THAP_VN_45 = "\U0001f3f4\U000e0076\U000e006e\U000e0034\U000e0035\U000e007f" - FLAG_FOR_KIEN_GIANG_VN_47 = "\U0001f3f4\U000e0076\U000e006e\U000e0034\U000e0037\U000e007f" - FLAG_FOR_ONG_NAI_VN_39 = "\U0001f3f4\U000e0076\U000e006e\U000e0033\U000e0039\U000e007f" - FLAG_FOR_BA_RIA_VUNG_TAU_VN_43 = "\U0001f3f4\U000e0076\U000e006e\U000e0034\U000e0033\U000e007f" - FLAG_FOR_UTAH_US_UT = "\U0001f3f4\U000e0075\U000e0073\U000e0075\U000e0074\U000e007f" - FLAG_FOR_VINH_PHUC_VN_70 = "\U0001f3f4\U000e0076\U000e006e\U000e0037\U000e0030\U000e007f" - FLAG_FOR_IEN_BIEN_VN_71 = "\U0001f3f4\U000e0076\U000e006e\U000e0037\U000e0031\U000e007f" - FLAG_FOR_BAC_KAN_VN_53 = "\U0001f3f4\U000e0076\U000e006e\U000e0035\U000e0033\U000e007f" - FLAG_FOR_DA_NANG_VN_DN = "\U0001f3f4\U000e0076\U000e006e\U000e0064\U000e006e\U000e007f" - FLAG_FOR_HA_NAM_VN_63 = "\U0001f3f4\U000e0076\U000e006e\U000e0036\U000e0033\U000e007f" - FLAG_FOR_AK_NONG_VN_72 = "\U0001f3f4\U000e0076\U000e006e\U000e0037\U000e0032\U000e007f" - FLAG_FOR_SOC_TRANG_VN_52 = "\U0001f3f4\U000e0076\U000e006e\U000e0035\U000e0032\U000e007f" - FLAG_FOR_GIA_LAI_VN_30 = "\U0001f3f4\U000e0076\U000e006e\U000e0033\U000e0030\U000e007f" - FLAG_FOR_CA_MAU_VN_59 = "\U0001f3f4\U000e0076\U000e006e\U000e0035\U000e0039\U000e007f" - FLAG_FOR_BOLIVAR_VE_F = "\U0001f3f4\U000e0076\U000e0065\U000e0066\U000e007f" - FLAG_FOR_KHANH_HOA_VN_34 = "\U0001f3f4\U000e0076\U000e006e\U000e0033\U000e0034\U000e007f" - FLAG_FOR_LONG_AN_VN_41 = "\U0001f3f4\U000e0076\U000e006e\U000e0034\U000e0031\U000e007f" - FLAG_FOR_SHEFA_VU_SEE = "\U0001f3f4\U000e0076\U000e0075\U000e0073\U000e0065\U000e0065\U000e007f" - FLAG_FOR_AMRAN_YE_AM = "\U0001f3f4\U000e0079\U000e0065\U000e0061\U000e006d\U000e007f" - FLAG_FOR_RAYMAH_YE_RA = "\U0001f3f4\U000e0079\U000e0065\U000e0072\U000e0061\U000e007f" - FLAG_FOR_MAE_HONG_SON_TH_58 = "\U0001f3f4\U000e0074\U000e0068\U000e0035\U000e0038\U000e007f" - FLAG_FOR_AL_MAHRAH_YE_MR = "\U0001f3f4\U000e0079\U000e0065\U000e006d\U000e0072\U000e007f" - FLAG_FOR_AL_MAHWIT_YE_MW = "\U0001f3f4\U000e0079\U000e0065\U000e006d\U000e0077\U000e007f" - FLAG_FOR_SATUPA_ITEA_WS_SA = "\U0001f3f4\U000e0077\U000e0073\U000e0073\U000e0061\U000e007f" - FLAG_FOR_GAGA_EMAUGA_WS_GE = "\U0001f3f4\U000e0077\U000e0073\U000e0067\U000e0065\U000e007f" - FLAG_FOR_HAIPHONG_VN_HP = "\U0001f3f4\U000e0076\U000e006e\U000e0068\U000e0070\U000e007f" - FAMILY_MAN_LIGHT_SKIN_TONE_MAN_LIGHT_SKIN_TONE_GIRL_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f468\U0001f3fb\u200d\U0001f467\U0001f3fb" - FLAG_FOR_DHALE_YE_DA = "\U0001f3f4\U000e0079\U000e0065\U000e0064\U000e0061\U000e007f" - FLAG_FOR_DHAMAR_YE_DH = "\U0001f3f4\U000e0079\U000e0065\U000e0064\U000e0068\U000e007f" - FLAG_FOR_TUAMASAGA_WS_TU = "\U0001f3f4\U000e0077\U000e0073\U000e0074\U000e0075\U000e007f" - FLAG_FOR_AL_BAYDA_YE_BA = "\U0001f3f4\U000e0079\U000e0065\U000e0062\U000e0061\U000e007f" - FLAG_FOR_HAJJAH_YE_HJ = "\U0001f3f4\U000e0079\U000e0065\U000e0068\U000e006a\U000e007f" - FAMILY_MAN_MEDIUM_SKIN_TONE_WOMAN_MEDIUM_SKIN_TONE_BABY_MEDIUM_SKIN_TONE_BABY_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f469\U0001f3fd\u200d\U0001f476\U0001f3fd\u200d\U0001f476\U0001f3fd" - FLAG_FOR_SHABWAH_YE_SH = "\U0001f3f4\U000e0079\U000e0065\U000e0073\U000e0068\U000e007f" - FAMILY_WOMAN_MAN_GIRL_BOY = "\U0001f469\u200d\U0001f468\u200d\U0001f467\u200d\U0001f466" - FLAG_FOR_ATUA_WS_AT = "\U0001f3f4\U000e0077\U000e0073\U000e0061\U000e0074\U000e007f" - FLAG_FOR_UVEA_WF_UV = "\U0001f3f4\U000e0077\U000e0066\U000e0075\U000e0076\U000e007f" - FLAG_FOR_PENAMA_VU_PAM = "\U0001f3f4\U000e0076\U000e0075\U000e0070\U000e0061\U000e006d\U000e007f" - FLAG_FOR_CRNA_NA_KOROSKEM_SI_016 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0031\U000e0036\U000e007f" - FLAG_FOR_ADAN_YE_AD = "\U0001f3f4\U000e0079\U000e0065\U000e0061\U000e0064\U000e007f" - FLAG_FOR_ABYAN_YE_AB = "\U0001f3f4\U000e0079\U000e0065\U000e0061\U000e0062\U000e007f" - FLAG_FOR_PALAULI_WS_PA = "\U0001f3f4\U000e0077\U000e0073\U000e0070\U000e0061\U000e007f" - FLAG_FOR_CENTAR_ZUPA_MK_78 = "\U0001f3f4\U000e006d\U000e006b\U000e0037\U000e0038\U000e007f" - FLAG_FOR_VA_A_O_FONOTI_WS_VF = "\U0001f3f4\U000e0077\U000e0073\U000e0076\U000e0066\U000e007f" - FLAG_FOR_BULAWAYO_ZW_BU = "\U0001f3f4\U000e007a\U000e0077\U000e0062\U000e0075\U000e007f" - FLAG_FOR_HARARE_ZW_HA = "\U0001f3f4\U000e007a\U000e0077\U000e0068\U000e0061\U000e007f" - FLAG_FOR_GAUTENG_ZA_GT = "\U0001f3f4\U000e007a\U000e0061\U000e0067\U000e0074\U000e007f" - FLAG_FOR_TAIZ_YE_TA = "\U0001f3f4\U000e0079\U000e0065\U000e0074\U000e0061\U000e007f" - FLAG_FOR_NEAMT_RO_NT = "\U0001f3f4\U000e0072\U000e006f\U000e006e\U000e0074\U000e007f" - FLAG_FOR_NORTHERN_ZM_05 = "\U0001f3f4\U000e007a\U000e006d\U000e0030\U000e0035\U000e007f" - COUPLE_WITH_HEART_MAN_MEDIUM_LIGHT_SKIN_TONE_MAN_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd" - FLAG_FOR_MUCHINGA_ZM_10 = "\U0001f3f4\U000e007a\U000e006d\U000e0031\U000e0030\U000e007f" - FLAG_FOR_COPPERBELT_ZM_08 = "\U0001f3f4\U000e007a\U000e006d\U000e0030\U000e0038\U000e007f" - FLAG_FOR_NORTH_WESTERN_ZM_06 = "\U0001f3f4\U000e007a\U000e006d\U000e0030\U000e0036\U000e007f" - KISS_MAN_MEDIUM_DARK_SKIN_TONE_WOMAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fc" - FLAG_FOR_LUAPULA_ZM_04 = "\U0001f3f4\U000e007a\U000e006d\U000e0030\U000e0034\U000e007f" - FLAG_FOR_LIMPOPO_ZA_LP = "\U0001f3f4\U000e007a\U000e0061\U000e006c\U000e0070\U000e007f" - FLAG_FOR_KOCAELI_TR_41 = "\U0001f3f4\U000e0074\U000e0072\U000e0034\U000e0031\U000e007f" - FLAG_FOR_LUSAKA_ZM_09 = "\U0001f3f4\U000e007a\U000e006d\U000e0030\U000e0039\U000e007f" - FLAG_FOR_WESTERN_ZM_01 = "\U0001f3f4\U000e007a\U000e006d\U000e0030\U000e0031\U000e007f" - FLAG_FOR_REPUBLIKA_SRPSKA_BA_SRP = "\U0001f3f4\U000e0062\U000e0061\U000e0073\U000e0072\U000e0070\U000e007f" - FLAG_FOR_NORTHERN_CAPE_ZA_NC = "\U0001f3f4\U000e007a\U000e0061\U000e006e\U000e0063\U000e007f" - FLAG_FOR_SOUTHLAND_NZ_STL = "\U0001f3f4\U000e006e\U000e007a\U000e0073\U000e0074\U000e006c\U000e007f" - FLAG_FOR_MPUMALANGA_ZA_MP = "\U0001f3f4\U000e007a\U000e0061\U000e006d\U000e0070\U000e007f" - FLAG_FOR_EASTERN_ZM_03 = "\U0001f3f4\U000e007a\U000e006d\U000e0030\U000e0033\U000e007f" - FLAG_FOR_MATABELELAND_NORTH_ZW_MN = "\U0001f3f4\U000e007a\U000e0077\U000e006d\U000e006e\U000e007f" - FLAG_FOR_MASHONALAND_EAST_ZW_ME = "\U0001f3f4\U000e007a\U000e0077\U000e006d\U000e0065\U000e007f" - FLAG_FOR_CENTRAL_ZM_02 = "\U0001f3f4\U000e007a\U000e006d\U000e0030\U000e0032\U000e007f" - FLAG_FOR_MASHONALAND_WEST_ZW_MW = "\U0001f3f4\U000e007a\U000e0077\U000e006d\U000e0077\U000e007f" - FLAG_FOR_SOUTHERN_ZM_07 = "\U0001f3f4\U000e007a\U000e006d\U000e0030\U000e0037\U000e007f" - FLAG_FOR_MASVINGO_ZW_MV = "\U0001f3f4\U000e007a\U000e0077\U000e006d\U000e0076\U000e007f" - FLAG_FOR_MATABELELAND_SOUTH_ZW_MS = "\U0001f3f4\U000e007a\U000e0077\U000e006d\U000e0073\U000e007f" - FLAG_FOR_EASTERN_CAPE_ZA_EC = "\U0001f3f4\U000e007a\U000e0061\U000e0065\U000e0063\U000e007f" - FLAG_FOR_NEW_VALLEY_EG_WAD = "\U0001f3f4\U000e0065\U000e0067\U000e0077\U000e0061\U000e0064\U000e007f" - FLAG_FOR_BOLIVAR_EC_B = "\U0001f3f4\U000e0065\U000e0063\U000e0062\U000e007f" - FLAG_FOR_CHANTHABURI_TH_22 = "\U0001f3f4\U000e0074\U000e0068\U000e0032\U000e0032\U000e007f" - FLAG_FOR_ARTA_DJ_AR = "\U0001f3f4\U000e0064\U000e006a\U000e0061\U000e0072\U000e007f" - TAG_COLON = "\U000e003a" - FAMILY_WOMAN_MEDIUM_SKIN_TONE_MAN_MEDIUM_SKIN_TONE_BABY_MEDIUM_SKIN_TONE_BOY_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f468\U0001f3fd\u200d\U0001f476\U0001f3fd\u200d\U0001f466\U0001f3fd" - FAMILY_MAN_MEDIUM_SKIN_TONE_MAN_MEDIUM_SKIN_TONE_GIRL_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f468\U0001f3fd\u200d\U0001f467\U0001f3fd" - FLAG_FOR_NIUTAO_TV_NIT = "\U0001f3f4\U000e0074\U000e0076\U000e006e\U000e0069\U000e0074\U000e007f" - TAG_HYPHEN_MINUS = "\U000e002d" - FLAG_FOR_PRIMORSKY_KRAI_RU_PRI = "\U0001f3f4\U000e0072\U000e0075\U000e0070\U000e0072\U000e0069\U000e007f" - FLAG_FOR_MANICALAND_ZW_MA = "\U0001f3f4\U000e007a\U000e0077\U000e006d\U000e0061\U000e007f" - FLAG_FOR_CHUY_KG_C = "\U0001f3f4\U000e006b\U000e0067\U000e0063\U000e007f" - FLAG_FOR_MAINE_US_ME = "\U0001f3f4\U000e0075\U000e0073\U000e006d\U000e0065\U000e007f" - FLAG_FOR_LOWER_JUBA_SO_JH = "\U0001f3f4\U000e0073\U000e006f\U000e006a\U000e0068\U000e007f" - FLAG_FOR_FUKUSHIMA_JP_07 = "\U0001f3f4\U000e006a\U000e0070\U000e0030\U000e0037\U000e007f" - FLAG_FOR_PUDUCHERRY_IN_PY = "\U0001f3f4\U000e0069\U000e006e\U000e0070\U000e0079\U000e007f" - FLAG_FOR_KRABI_TH_81 = "\U0001f3f4\U000e0074\U000e0068\U000e0038\U000e0031\U000e007f" - FLAG_FOR_UTRECHT_NL_UT = "\U0001f3f4\U000e006e\U000e006c\U000e0075\U000e0074\U000e007f" - FLAG_FOR_MANITOBA_CA_MB = "\U0001f3f4\U000e0063\U000e0061\U000e006d\U000e0062\U000e007f" - FLAG_FOR_GAGA_IFOMAUGA_WS_GI = "\U0001f3f4\U000e0077\U000e0073\U000e0067\U000e0069\U000e007f" - FLAG_FOR_NORTH_KHORASAN_IR_31 = "\U0001f3f4\U000e0069\U000e0072\U000e0033\U000e0031\U000e007f" - KISS_WOMAN_MEDIUM_DARK_SKIN_TONE_WOMAN_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fe" - FLAG_FOR_IOWA_US_IA = "\U0001f3f4\U000e0075\U000e0073\U000e0069\U000e0061\U000e007f" - FLAG_FOR_LARNACA_CY_03 = "\U0001f3f4\U000e0063\U000e0079\U000e0030\U000e0033\U000e007f" - RESTRICTED_LEFT_ENTRY_2 = "\u26e1" - FLAG_FOR_NORMANDIE_FR_NOR = "\U0001f3f4\U000e0066\U000e0072\U000e006e\U000e006f\U000e0072\U000e007f" - FLAG_FOR_HAWAII_US_HI = "\U0001f3f4\U000e0075\U000e0073\U000e0068\U000e0069\U000e007f" - FLAG_FOR_ZAMBEZIA_MZ_Q = "\U0001f3f4\U000e006d\U000e007a\U000e0071\U000e007f" - FLAG_FOR_MURMANSK_RU_MUR = "\U0001f3f4\U000e0072\U000e0075\U000e006d\U000e0075\U000e0072\U000e007f" - FLAG_FOR_CANARIES_LC_12 = "\U0001f3f4\U000e006c\U000e0063\U000e0031\U000e0032\U000e007f" - FLAG_FOR_NEW_HAMPSHIRE_US_NH = "\U0001f3f4\U000e0075\U000e0073\U000e006e\U000e0068\U000e007f" - FLAG_FOR_DJIBOUTI_DJ_DJ = "\U0001f3f4\U000e0064\U000e006a\U000e0064\U000e006a\U000e007f" - FLAG_FOR_JIZAN_SA_09 = "\U0001f3f4\U000e0073\U000e0061\U000e0030\U000e0039\U000e007f" - FLAG_FOR_MARSA_MT_26 = "\U0001f3f4\U000e006d\U000e0074\U000e0032\U000e0036\U000e007f" - FLAG_FOR_MUGLA_TR_48 = "\U0001f3f4\U000e0074\U000e0072\U000e0034\U000e0038\U000e007f" - TAG_LATIN_CAPITAL_LETTER_G = "\U000e0047" - FLAG_FOR_LAANE_VIRU_EE_59 = "\U0001f3f4\U000e0065\U000e0065\U000e0035\U000e0039\U000e007f" - FLAG_FOR_SAN_JOSE_UY_SJ = "\U0001f3f4\U000e0075\U000e0079\U000e0073\U000e006a\U000e007f" - FLAG_FOR_NORTH_GAZA_PS_NGZ = "\U0001f3f4\U000e0070\U000e0073\U000e006e\U000e0067\U000e007a\U000e007f" - FLAG_FOR_MANUS_PG_MRL = "\U0001f3f4\U000e0070\U000e0067\U000e006d\U000e0072\U000e006c\U000e007f" - FLAG_FOR_SIVAS_TR_58 = "\U0001f3f4\U000e0074\U000e0072\U000e0035\U000e0038\U000e007f" - WOMAN_IN_TUXEDO_MEDIUM_LIGHT_SKIN_TONE = "\U0001f935\U0001f3fc\u200d\u2640\ufe0f" - KISS_WOMAN_MEDIUM_LIGHT_SKIN_TONE_MAN = "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468" - FLAG_FOR_LESSER_POLAND_PL_MA = "\U0001f3f4\U000e0070\U000e006c\U000e006d\U000e0061\U000e007f" - FLAG_FOR_WEST_MACEDONIA_GR_C = "\U0001f3f4\U000e0067\U000e0072\U000e0063\U000e007f" - FLAG_FOR_DEMIR_HISAR_MK_25 = "\U0001f3f4\U000e006d\U000e006b\U000e0032\U000e0035\U000e007f" - FLAG_FOR_ARKHABIL_SUQUTRA_YE_SU = "\U0001f3f4\U000e0079\U000e0065\U000e0073\U000e0075\U000e007f" - FLAG_FOR_COMMEWIJNE_SR_CM = "\U0001f3f4\U000e0073\U000e0072\U000e0063\U000e006d\U000e007f" - FAMILY_MAN_DARK_SKIN_TONE_WOMAN_DARK_SKIN_TONE_BOY_DARK_SKIN_TONE_GIRL_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f469\U0001f3ff\u200d\U0001f466\U0001f3ff\u200d\U0001f467\U0001f3ff" - COUPLE_WITH_HEART_WOMAN_WOMAN_LIGHT_SKIN_TONE = "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fb" - FLAG_FOR_PRYKARPATTIA_UA_26 = "\U0001f3f4\U000e0075\U000e0061\U000e0032\U000e0036\U000e007f" - FLAG_FOR_NUEVA_ESPARTA_VE_O = "\U0001f3f4\U000e0076\U000e0065\U000e006f\U000e007f" - FLAG_FOR_HARJU_EE_37 = "\U0001f3f4\U000e0065\U000e0065\U000e0033\U000e0037\U000e007f" - FLAG_FOR_GUJARAT_IN_GJ = "\U0001f3f4\U000e0069\U000e006e\U000e0067\U000e006a\U000e007f" - FLAG_FOR_JEKABPILS_MUNICIPALITY_LV_042 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0034\U000e0032\U000e007f" - FLAG_FOR_NAVASSA_ISLAND_UM_76 = "\U0001f3f4\U000e0075\U000e006d\U000e0037\U000e0036\U000e007f" - KISS_MAN_DARK_SKIN_TONE_WOMAN_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fd" - FLAG_FOR_OGRE_LV_067 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0036\U000e0037\U000e007f" - FLAG_FOR_YAREN_NR_14 = "\U0001f3f4\U000e006e\U000e0072\U000e0031\U000e0034\U000e007f" - FLAG_FOR_SCHELLENBERG_LI_08 = "\U0001f3f4\U000e006c\U000e0069\U000e0030\U000e0038\U000e007f" - FAMILY_WOMAN_MEDIUM_DARK_SKIN_TONE_BABY_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f476\U0001f3fe" - FLAG_FOR_TELANGANA_IN_TG = "\U0001f3f4\U000e0069\U000e006e\U000e0074\U000e0067\U000e007f" - FLAG_FOR_OROMIA_ET_OR = "\U0001f3f4\U000e0065\U000e0074\U000e006f\U000e0072\U000e007f" - FLAG_FOR_UUSIMAA_FI_18 = "\U0001f3f4\U000e0066\U000e0069\U000e0031\U000e0038\U000e007f" - FLAG_FOR_SASSANDRA_MARAHOUE_CI_SM = "\U0001f3f4\U000e0063\U000e0069\U000e0073\U000e006d\U000e007f" - FLAG_FOR_HA_APAI_TO_02 = "\U0001f3f4\U000e0074\U000e006f\U000e0030\U000e0032\U000e007f" - FLAG_FOR_GUAYAS_EC_G = "\U0001f3f4\U000e0065\U000e0063\U000e0067\U000e007f" - FLAG_FOR_DUZCE_TR_81 = "\U0001f3f4\U000e0074\U000e0072\U000e0038\U000e0031\U000e007f" - FLAG_FOR_APURIMAC_PE_APU = "\U0001f3f4\U000e0070\U000e0065\U000e0061\U000e0070\U000e0075\U000e007f" - FAMILY_MAN_LIGHT_SKIN_TONE_WOMAN_LIGHT_SKIN_TONE_BABY_LIGHT_SKIN_TONE_BOY_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f469\U0001f3fb\u200d\U0001f476\U0001f3fb\u200d\U0001f466\U0001f3fb" - FAMILY_WOMAN_MAN_BABY_BOY = "\U0001f469\u200d\U0001f468\u200d\U0001f476\u200d\U0001f466" - FLAG_FOR_SOUTHERN_NATIONS_NATIONALITIES_AND_PEOPLES_ET_SN = "\U0001f3f4\U000e0065\U000e0074\U000e0073\U000e006e\U000e007f" - FLAG_FOR_CHOCO_CO_CHO = "\U0001f3f4\U000e0063\U000e006f\U000e0063\U000e0068\U000e006f\U000e007f" - FAMILY_MAN_LIGHT_SKIN_TONE_BOY_LIGHT_SKIN_TONE_BOY_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f466\U0001f3fb\u200d\U0001f466\U0001f3fb" - FLAG_FOR_SAINT_PATRICK_VC_05 = "\U0001f3f4\U000e0076\U000e0063\U000e0030\U000e0035\U000e007f" - FLAG_FOR_HAWALLI_KW_HA = "\U0001f3f4\U000e006b\U000e0077\U000e0068\U000e0061\U000e007f" - FLAG_FOR_ADRAR_MR_07 = "\U0001f3f4\U000e006d\U000e0072\U000e0030\U000e0037\U000e007f" - FLAG_FOR_BOUENZA_CG_11 = "\U0001f3f4\U000e0063\U000e0067\U000e0031\U000e0031\U000e007f" - FLAG_FOR_WYOMING_US_WY = "\U0001f3f4\U000e0075\U000e0073\U000e0077\U000e0079\U000e007f" - FLAG_FOR_BAY_ISLANDS_HN_IB = "\U0001f3f4\U000e0068\U000e006e\U000e0069\U000e0062\U000e007f" - FLAG_FOR_HSINCHU_TW_HSZ = "\U0001f3f4\U000e0074\U000e0077\U000e0068\U000e0073\U000e007a\U000e007f" - TAG_FULL_STOP = "\U000e002e" - FLAG_FOR_VAKAGA_CF_VK = "\U0001f3f4\U000e0063\U000e0066\U000e0076\U000e006b\U000e007f" - FLAG_FOR_ALBERTA_CA_AB = "\U0001f3f4\U000e0063\U000e0061\U000e0061\U000e0062\U000e007f" - FAMILY_MAN_LIGHT_SKIN_TONE_MAN_LIGHT_SKIN_TONE_BOY_LIGHT_SKIN_TONE_GIRL_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f468\U0001f3fb\u200d\U0001f466\U0001f3fb\u200d\U0001f467\U0001f3fb" - FLAG_FOR_PORTUGUESA_VE_P = "\U0001f3f4\U000e0076\U000e0065\U000e0070\U000e007f" - FLAG_FOR_ILE_DE_FRANCE_FR_IDF = "\U0001f3f4\U000e0066\U000e0072\U000e0069\U000e0064\U000e0066\U000e007f" - FLAG_FOR_MISSISSIPPI_US_MS = "\U0001f3f4\U000e0075\U000e0073\U000e006d\U000e0073\U000e007f" - FLAG_FOR_SKOFJA_LOKA_SI_122 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0032\U000e0032\U000e007f" - FLAG_FOR_VAINODE_LV_100 = "\U0001f3f4\U000e006c\U000e0076\U000e0031\U000e0030\U000e0030\U000e007f" - FLAG_FOR_AUSTRALIAN_CAPITAL_TERRITORY_AU_ACT = "\U0001f3f4\U000e0061\U000e0075\U000e0061\U000e0063\U000e0074\U000e007f" - FLAG_FOR_GUARICO_VE_J = "\U0001f3f4\U000e0076\U000e0065\U000e006a\U000e007f" - FLAG_FOR_SAINT_CATHERINE_JM_14 = "\U0001f3f4\U000e006a\U000e006d\U000e0031\U000e0034\U000e007f" - FLAG_FOR_SOKOTO_NG_SO = "\U0001f3f4\U000e006e\U000e0067\U000e0073\U000e006f\U000e007f" - FLAG_FOR_AREQUIPA_PE_ARE = "\U0001f3f4\U000e0070\U000e0065\U000e0061\U000e0072\U000e0065\U000e007f" - FLAG_FOR_SONORA_MX_SON = "\U0001f3f4\U000e006d\U000e0078\U000e0073\U000e006f\U000e006e\U000e007f" - FLAG_FOR_CHANGHUA_TW_CHA = "\U0001f3f4\U000e0074\U000e0077\U000e0063\U000e0068\U000e0061\U000e007f" - FLAG_FOR_LERIBE_LS_C = "\U0001f3f4\U000e006c\U000e0073\U000e0063\U000e007f" - FLAG_FOR_SAINTE_DEVOTE_CHAPEL_MC_SD = "\U0001f3f4\U000e006d\U000e0063\U000e0073\U000e0064\U000e007f" - FLAG_FOR_ISSYK_KUL_KG_Y = "\U0001f3f4\U000e006b\U000e0067\U000e0079\U000e007f" - FLAG_FOR_CHANDIGARH_IN_CH = "\U0001f3f4\U000e0069\U000e006e\U000e0063\U000e0068\U000e007f" - FAMILY_MAN_MAN_BOY_BABY = "\U0001f468\u200d\U0001f468\u200d\U0001f466\u200d\U0001f476" - FLAG_FOR_ARAGON_ES_AR = "\U0001f3f4\U000e0065\U000e0073\U000e0061\U000e0072\U000e007f" - KISS_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc" - FLAG_FOR_PLASNICA_MK_61 = "\U0001f3f4\U000e006d\U000e006b\U000e0036\U000e0031\U000e007f" - FLAG_FOR_ANTALYA_TR_07 = "\U0001f3f4\U000e0074\U000e0072\U000e0030\U000e0037\U000e007f" - FLAG_FOR_VARAZDIN_HR_05 = "\U0001f3f4\U000e0068\U000e0072\U000e0030\U000e0035\U000e007f" - FAMILY_WOMAN_DARK_SKIN_TONE_BOY_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f466\U0001f3ff" - FLAG_FOR_TAFEA_VU_TAE = "\U0001f3f4\U000e0076\U000e0075\U000e0074\U000e0061\U000e0065\U000e007f" - FLAG_FOR_VELIKE_LASCE_SI_134 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0033\U000e0034\U000e007f" - FLAG_FOR_QUERETARO_MX_QUE = "\U0001f3f4\U000e006d\U000e0078\U000e0071\U000e0075\U000e0065\U000e007f" - FLAG_FOR_DEIR_AL_BALAH_PS_DEB = "\U0001f3f4\U000e0070\U000e0073\U000e0064\U000e0065\U000e0062\U000e007f" - FLAG_FOR_MEXICO_STATE_MX_MEX = "\U0001f3f4\U000e006d\U000e0078\U000e006d\U000e0065\U000e0078\U000e007f" - FLAG_FOR_LEON_NI_LE = "\U0001f3f4\U000e006e\U000e0069\U000e006c\U000e0065\U000e007f" - FLAG_FOR_NORTH_PROVINCE_MV_NO = "\U0001f3f4\U000e006d\U000e0076\U000e006e\U000e006f\U000e007f" - FLAG_FOR_ST_BARTHELEMY_FR_BL = "\U0001f3f4\U000e0066\U000e0072\U000e0062\U000e006c\U000e007f" - FLAG_FOR_HORDALAND_NO_12 = "\U0001f3f4\U000e006e\U000e006f\U000e0031\U000e0032\U000e007f" - FLAG_FOR_PLZENSKY_KRAJ_CZ_32 = "\U0001f3f4\U000e0063\U000e007a\U000e0033\U000e0032\U000e007f" - FLAG_FOR_EGER_HU_EG = "\U0001f3f4\U000e0068\U000e0075\U000e0065\U000e0067\U000e007f" - FLAG_FOR_TABASCO_MX_TAB = "\U0001f3f4\U000e006d\U000e0078\U000e0074\U000e0061\U000e0062\U000e007f" - FLAG_FOR_FLANDERS_BE_VLG = "\U0001f3f4\U000e0062\U000e0065\U000e0076\U000e006c\U000e0067\U000e007f" - FLAG_FOR_KERMANSHAH_IR_17 = "\U0001f3f4\U000e0069\U000e0072\U000e0031\U000e0037\U000e007f" - FLAG_FOR_APURE_VE_C = "\U0001f3f4\U000e0076\U000e0065\U000e0063\U000e007f" - FLAG_FOR_SILA_TD_SI = "\U0001f3f4\U000e0074\U000e0064\U000e0073\U000e0069\U000e007f" - FLAG_FOR_DURANGO_MX_DUR = "\U0001f3f4\U000e006d\U000e0078\U000e0064\U000e0075\U000e0072\U000e007f" - FLAG_FOR_MARAMURES_RO_MM = "\U0001f3f4\U000e0072\U000e006f\U000e006d\U000e006d\U000e007f" - FLAG_FOR_VELENJE_SI_133 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0033\U000e0033\U000e007f" - FLAG_FOR_SAINT_JOHN_DM_05 = "\U0001f3f4\U000e0064\U000e006d\U000e0030\U000e0035\U000e007f" - FLAG_FOR_NORTH_GYEONGSANG_KR_47 = "\U0001f3f4\U000e006b\U000e0072\U000e0034\U000e0037\U000e007f" - FLAG_FOR_MOROGORO_TZ_16 = "\U0001f3f4\U000e0074\U000e007a\U000e0031\U000e0036\U000e007f" - FLAG_FOR_NORTH_HOLLAND_NL_NH = "\U0001f3f4\U000e006e\U000e006c\U000e006e\U000e0068\U000e007f" - FLAG_FOR_MERIDA_VE_L = "\U0001f3f4\U000e0076\U000e0065\U000e006c\U000e007f" - FLAG_FOR_AUCKLAND_NZ_AUK = "\U0001f3f4\U000e006e\U000e007a\U000e0061\U000e0075\U000e006b\U000e007f" - FLAG_FOR_RIO_GRANDE_DO_SUL_BR_RS = "\U0001f3f4\U000e0062\U000e0072\U000e0072\U000e0073\U000e007f" - FLAG_FOR_RYAZAN_RU_RYA = "\U0001f3f4\U000e0072\U000e0075\U000e0072\U000e0079\U000e0061\U000e007f" - FLAG_FOR_DAR_ES_SALAAM_TZ_02 = "\U0001f3f4\U000e0074\U000e007a\U000e0030\U000e0032\U000e007f" - FAMILY_MAN_LIGHT_SKIN_TONE_MAN_LIGHT_SKIN_TONE_BABY_LIGHT_SKIN_TONE_BABY_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f468\U0001f3fb\u200d\U0001f476\U0001f3fb\u200d\U0001f476\U0001f3fb" - FLAG_FOR_GAFSA_TN_71 = "\U0001f3f4\U000e0074\U000e006e\U000e0037\U000e0031\U000e007f" - FLAG_FOR_MASHONALAND_CENTRAL_ZW_MC = "\U0001f3f4\U000e007a\U000e0077\U000e006d\U000e0063\U000e007f" - FLAG_FOR_BOVEC_SI_006 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0030\U000e0036\U000e007f" - FLAG_FOR_ANKARAN_SI_213 = "\U0001f3f4\U000e0073\U000e0069\U000e0032\U000e0031\U000e0033\U000e007f" - KISS_MAN_MEDIUM_SKIN_TONE_MAN_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb" - TAG_LATIN_CAPITAL_LETTER_O = "\U000e004f" - FLAG_FOR_GUERRERO_MX_GRO = "\U0001f3f4\U000e006d\U000e0078\U000e0067\U000e0072\U000e006f\U000e007f" - FLAG_FOR_DOBJE_SI_154 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0035\U000e0034\U000e007f" - FAMILY_MAN_DARK_SKIN_TONE_WOMAN_DARK_SKIN_TONE_GIRL_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f469\U0001f3ff\u200d\U0001f467\U0001f3ff" - FLAG_FOR_RETALHULEU_GT_RE = "\U0001f3f4\U000e0067\U000e0074\U000e0072\U000e0065\U000e007f" - FLAG_FOR_NARINO_CO_NAR = "\U0001f3f4\U000e0063\U000e006f\U000e006e\U000e0061\U000e0072\U000e007f" - FLAG_FOR_TUNIS_TN_11 = "\U0001f3f4\U000e0074\U000e006e\U000e0031\U000e0031\U000e007f" - FLAG_FOR_TIBET_CN_54 = "\U0001f3f4\U000e0063\U000e006e\U000e0035\U000e0034\U000e007f" - FLAG_FOR_MALAMPA_VU_MAP = "\U0001f3f4\U000e0076\U000e0075\U000e006d\U000e0061\U000e0070\U000e007f" - FLAG_FOR_PIEDMONT_IT_21 = "\U0001f3f4\U000e0069\U000e0074\U000e0032\U000e0031\U000e007f" - FLAG_FOR_KAYSERI_TR_38 = "\U0001f3f4\U000e0074\U000e0072\U000e0033\U000e0038\U000e007f" - FLAG_FOR_EQUATEUR_CD_EQ = "\U0001f3f4\U000e0063\U000e0064\U000e0065\U000e0071\U000e007f" - FLAG_FOR_SABAH_MY_12 = "\U0001f3f4\U000e006d\U000e0079\U000e0031\U000e0032\U000e007f" - COUPLE_WITH_HEART_WOMAN_LIGHT_SKIN_TONE_WOMAN_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fb" - FLAG_FOR_NYANGA_GA_5 = "\U0001f3f4\U000e0067\U000e0061\U000e0035\U000e007f" - FLAG_FOR_TAVASTIA_PROPER_FI_06 = "\U0001f3f4\U000e0066\U000e0069\U000e0030\U000e0036\U000e007f" - FLAG_FOR_CARAS_SEVERIN_RO_CS = "\U0001f3f4\U000e0072\U000e006f\U000e0063\U000e0073\U000e007f" - FLAG_FOR_CENTRAL_VISAYAS_PH_07 = "\U0001f3f4\U000e0070\U000e0068\U000e0030\U000e0037\U000e007f" - FLAG_FOR_MASERU_LS_A = "\U0001f3f4\U000e006c\U000e0073\U000e0061\U000e007f" - FLAG_FOR_SAINT_ANDREW_GD_01 = "\U0001f3f4\U000e0067\U000e0064\U000e0030\U000e0031\U000e007f" - FLAG_FOR_LAI_CHAU_VN_01 = "\U0001f3f4\U000e0076\U000e006e\U000e0030\U000e0031\U000e007f" - FLAG_FOR_CHAIYAPHUM_TH_36 = "\U0001f3f4\U000e0074\U000e0068\U000e0033\U000e0036\U000e007f" - FLAG_FOR_ESMERALDAS_EC_E = "\U0001f3f4\U000e0065\U000e0063\U000e0065\U000e007f" - FLAG_FOR_DILI_TL_DI = "\U0001f3f4\U000e0074\U000e006c\U000e0064\U000e0069\U000e007f" - FLAG_FOR_USULUTAN_SV_US = "\U0001f3f4\U000e0073\U000e0076\U000e0075\U000e0073\U000e007f" - FLAG_FOR_MARSABIT_KE_25 = "\U0001f3f4\U000e006b\U000e0065\U000e0032\U000e0035\U000e007f" - FLAG_FOR_FALCON_VE_I = "\U0001f3f4\U000e0076\U000e0065\U000e0069\U000e007f" - FLAG_FOR_ZANZAN_CI_ZZ = "\U0001f3f4\U000e0063\U000e0069\U000e007a\U000e007a\U000e007f" - FLAG_FOR_NORD_UBANGI_CD_NU = "\U0001f3f4\U000e0063\U000e0064\U000e006e\U000e0075\U000e007f" - FLAG_FOR_PAILIN_KH_24 = "\U0001f3f4\U000e006b\U000e0068\U000e0032\U000e0034\U000e007f" - FLAG_FOR_CASTILE_AND_LEON_ES_CL = "\U0001f3f4\U000e0065\U000e0073\U000e0063\U000e006c\U000e007f" - FAMILY_WOMAN_MEDIUM_SKIN_TONE_WOMAN_MEDIUM_SKIN_TONE_BABY_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f469\U0001f3fd\u200d\U0001f476\U0001f3fd" - FLAG_FOR_SAMUT_SONGKHRAM_TH_75 = "\U0001f3f4\U000e0074\U000e0068\U000e0037\U000e0035\U000e007f" - FLAG_FOR_ADRAR_DZ_01 = "\U0001f3f4\U000e0064\U000e007a\U000e0030\U000e0031\U000e007f" - FLAG_FOR_EAST_SEPIK_PG_ESW = "\U0001f3f4\U000e0070\U000e0067\U000e0065\U000e0073\U000e0077\U000e007f" - FLAG_FOR_EMILIA_ROMAGNA_IT_45 = "\U0001f3f4\U000e0069\U000e0074\U000e0034\U000e0035\U000e007f" - FLAG_FOR_CHUMPHON_TH_86 = "\U0001f3f4\U000e0074\U000e0068\U000e0038\U000e0036\U000e007f" - FLAG_FOR_MAZSALACA_LV_060 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0036\U000e0030\U000e007f" - FLAG_FOR_BARI_SO_BR = "\U0001f3f4\U000e0073\U000e006f\U000e0062\U000e0072\U000e007f" - FLAG_FOR_ALAND_ISLANDS_FI_01 = "\U0001f3f4\U000e0066\U000e0069\U000e0030\U000e0031\U000e007f" - FLAG_FOR_QUANG_NAM_VN_27 = "\U0001f3f4\U000e0076\U000e006e\U000e0032\U000e0037\U000e007f" - FLAG_FOR_LINE_ISLANDS_KI_L = "\U0001f3f4\U000e006b\U000e0069\U000e006c\U000e007f" - FLAG_FOR_KAGOSHIMA_JP_46 = "\U0001f3f4\U000e006a\U000e0070\U000e0034\U000e0036\U000e007f" - FLAG_FOR_CAIRO_EG_C = "\U0001f3f4\U000e0065\U000e0067\U000e0063\U000e007f" - FLAG_FOR_KUTAHYA_TR_43 = "\U0001f3f4\U000e0074\U000e0072\U000e0034\U000e0033\U000e007f" - FAMILY_MAN_WOMAN_BOY_BABY = "\U0001f468\u200d\U0001f469\u200d\U0001f466\u200d\U0001f476" - FLAG_FOR_AL_BAHAH_SA_11 = "\U0001f3f4\U000e0073\U000e0061\U000e0031\U000e0031\U000e007f" - FLAG_FOR_GREATER_POLAND_PL_WP = "\U0001f3f4\U000e0070\U000e006c\U000e0077\U000e0070\U000e007f" - FLAG_FOR_WEST_AZARBAIJAN_IR_02 = "\U0001f3f4\U000e0069\U000e0072\U000e0030\U000e0032\U000e007f" - FLAG_FOR_YAMALO_NENETS_OKRUG_RU_YAN = "\U0001f3f4\U000e0072\U000e0075\U000e0079\U000e0061\U000e006e\U000e007f" - FLAG_FOR_MONTANA_US_MT = "\U0001f3f4\U000e0075\U000e0073\U000e006d\U000e0074\U000e007f" - KISS_WOMAN_WOMAN_MEDIUM_SKIN_TONE = "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fd" - FLAG_FOR_POMEROON_SUPENAAM_GY_PM = "\U0001f3f4\U000e0067\U000e0079\U000e0070\U000e006d\U000e007f" - FLAG_FOR_GWANGJU_CITY_KR_29 = "\U0001f3f4\U000e006b\U000e0072\U000e0032\U000e0039\U000e007f" - FLAG_FOR_DONECHCHYNA_UA_14 = "\U0001f3f4\U000e0075\U000e0061\U000e0031\U000e0034\U000e007f" - FLAG_FOR_SAFI_MT_47 = "\U0001f3f4\U000e006d\U000e0074\U000e0034\U000e0037\U000e007f" - COUPLE_WITH_HEART_MAN_WOMAN_LIGHT_SKIN_TONE = "\U0001f468\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fb" - KISS_MAN_DARK_SKIN_TONE_WOMAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fc" - FLAG_FOR_ANTIOQUIA_CO_ANT = "\U0001f3f4\U000e0063\U000e006f\U000e0061\U000e006e\U000e0074\U000e007f" - FAMILY_WOMAN_MEDIUM_SKIN_TONE_MAN_MEDIUM_SKIN_TONE_GIRL_MEDIUM_SKIN_TONE_GIRL_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f468\U0001f3fd\u200d\U0001f467\U0001f3fd\u200d\U0001f467\U0001f3fd" - FLAG_FOR_KRANJ_SI_052 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0035\U000e0032\U000e007f" - FLAG_FOR_MIYAGI_JP_04 = "\U0001f3f4\U000e006a\U000e0070\U000e0030\U000e0034\U000e007f" - FLAG_FOR_MARGIBI_LR_MG = "\U0001f3f4\U000e006c\U000e0072\U000e006d\U000e0067\U000e007f" - FLAG_FOR_ZULIA_VE_V = "\U0001f3f4\U000e0076\U000e0065\U000e0076\U000e007f" - FLAG_FOR_VAISIGANO_WS_VS = "\U0001f3f4\U000e0077\U000e0073\U000e0076\U000e0073\U000e007f" - FLAG_FOR_GLARUS_CH_GL = "\U0001f3f4\U000e0063\U000e0068\U000e0067\U000e006c\U000e007f" - FLAG_FOR_BECHAR_DZ_08 = "\U0001f3f4\U000e0064\U000e007a\U000e0030\U000e0038\U000e007f" - FLAG_FOR_EMBERA_PA_EM = "\U0001f3f4\U000e0070\U000e0061\U000e0065\U000e006d\U000e007f" - FLAG_FOR_LUQA_MT_25 = "\U0001f3f4\U000e006d\U000e0074\U000e0032\U000e0035\U000e007f" - FLAG_FOR_IBB_YE_IB = "\U0001f3f4\U000e0079\U000e0065\U000e0069\U000e0062\U000e007f" - FLAG_FOR_EAST_BERBICE_CORENTYNE_GY_EB = "\U0001f3f4\U000e0067\U000e0079\U000e0065\U000e0062\U000e007f" - FLAG_FOR_U_S_VIRGIN_ISLANDS_US_VI = "\U0001f3f4\U000e0075\U000e0073\U000e0076\U000e0069\U000e007f" - FLAG_FOR_PHATTHALUNG_TH_93 = "\U0001f3f4\U000e0074\U000e0068\U000e0039\U000e0033\U000e007f" - FLAG_FOR_YUCATAN_MX_YUC = "\U0001f3f4\U000e006d\U000e0078\U000e0079\U000e0075\U000e0063\U000e007f" - FLAG_FOR_KALMYKIA_RU_KL = "\U0001f3f4\U000e0072\U000e0075\U000e006b\U000e006c\U000e007f" - FLAG_FOR_ANDALUSIA_ES_AN = "\U0001f3f4\U000e0065\U000e0073\U000e0061\U000e006e\U000e007f" - FLAG_FOR_CAPELLEN_LU_CA = "\U0001f3f4\U000e006c\U000e0075\U000e0063\U000e0061\U000e007f" - COUPLE_WITH_HEART_WOMAN_LIGHT_SKIN_TONE_WOMAN_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fd" - FLAG_FOR_POOL_CG_12 = "\U0001f3f4\U000e0063\U000e0067\U000e0031\U000e0032\U000e007f" - FLAG_FOR_CAUSENI_MD_CS = "\U0001f3f4\U000e006d\U000e0064\U000e0063\U000e0073\U000e007f" - FAMILY_MAN_LIGHT_SKIN_TONE_GIRL_LIGHT_SKIN_TONE_GIRL_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f467\U0001f3fb\u200d\U0001f467\U0001f3fb" - FLAG_FOR_SZABOLCS_SZATMAR_BEREG_HU_SZ = "\U0001f3f4\U000e0068\U000e0075\U000e0073\U000e007a\U000e007f" - FLAG_FOR_ZANZIBAR_NORTH_TZ_07 = "\U0001f3f4\U000e0074\U000e007a\U000e0030\U000e0037\U000e007f" - FLAG_FOR_AL_JAWF_YE_JA = "\U0001f3f4\U000e0079\U000e0065\U000e006a\U000e0061\U000e007f" - FLAG_FOR_KOKNESE_LV_046 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0034\U000e0036\U000e007f" - FLAG_FOR_DELTA_NG_DE = "\U0001f3f4\U000e006e\U000e0067\U000e0064\U000e0065\U000e007f" - FAMILY_MAN_DARK_SKIN_TONE_BABY_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f476\U0001f3ff" - SALTIRE = "\u2613" - FLAG_FOR_NORTH_SINAI_EG_SIN = "\U0001f3f4\U000e0065\U000e0067\U000e0073\U000e0069\U000e006e\U000e007f" - KISS_WOMAN_MEDIUM_DARK_SKIN_TONE_MAN_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd" - FAMILY_MAN_DARK_SKIN_TONE_BOY_DARK_SKIN_TONE_BOY_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f466\U0001f3ff\u200d\U0001f466\U0001f3ff" - FLAG_FOR_GORENJA_VAS_POLJANE_SI_027 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0032\U000e0037\U000e007f" - WHITE_FLAG_WITH_HORIZONTAL_MIDDLE_BLACK_STRIPE = "\u26ff" - FLAG_FOR_XEWKIJA_MT_62 = "\U0001f3f4\U000e006d\U000e0074\U000e0036\U000e0032\U000e007f" - FLAG_FOR_BAC_NINH_VN_56 = "\U0001f3f4\U000e0076\U000e006e\U000e0035\U000e0036\U000e007f" - FLAG_FOR_HADJER_LAMIS_TD_HL = "\U0001f3f4\U000e0074\U000e0064\U000e0068\U000e006c\U000e007f" - FLAG_FOR_SAINT_MICHAEL_BB_08 = "\U0001f3f4\U000e0062\U000e0062\U000e0030\U000e0038\U000e007f" - FLAG_FOR_MAPUTO_PROVINCE_MZ_L = "\U0001f3f4\U000e006d\U000e007a\U000e006c\U000e007f" - FLAG_FOR_GUIDIMAKA_MR_10 = "\U0001f3f4\U000e006d\U000e0072\U000e0031\U000e0030\U000e007f" - FLAG_FOR_ARACINOVO_MK_02 = "\U0001f3f4\U000e006d\U000e006b\U000e0030\U000e0032\U000e007f" - FLAG_FOR_PAHANG_MY_06 = "\U0001f3f4\U000e006d\U000e0079\U000e0030\U000e0036\U000e007f" - FLAG_FOR_SVAY_RIENG_KH_20 = "\U0001f3f4\U000e006b\U000e0068\U000e0032\U000e0030\U000e007f" - FLAG_FOR_JELGAVA_MUNICIPALITY_LV_041 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0034\U000e0031\U000e007f" - FLAG_FOR_NORTHERN_OSTROBOTHNIA_FI_14 = "\U0001f3f4\U000e0066\U000e0069\U000e0031\U000e0034\U000e007f" - FLAG_FOR_SOUSSE_TN_51 = "\U0001f3f4\U000e0074\U000e006e\U000e0035\U000e0031\U000e007f" - FLAG_FOR_ODRANCI_SI_086 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0038\U000e0036\U000e007f" - FLAG_FOR_ALTO_PARANA_PY_10 = "\U0001f3f4\U000e0070\U000e0079\U000e0031\U000e0030\U000e007f" - FLAG_FOR_WASHINGTON_US_WA = "\U0001f3f4\U000e0075\U000e0073\U000e0077\U000e0061\U000e007f" - FLAG_FOR_TAOYUAN_TW_TAO = "\U0001f3f4\U000e0074\U000e0077\U000e0074\U000e0061\U000e006f\U000e007f" - FLAG_FOR_OMSK_RU_OMS = "\U0001f3f4\U000e0072\U000e0075\U000e006f\U000e006d\U000e0073\U000e007f" - FLAG_FOR_BRATISLAVA_SK_BL = "\U0001f3f4\U000e0073\U000e006b\U000e0062\U000e006c\U000e007f" - FLAG_FOR_GIFU_JP_21 = "\U0001f3f4\U000e006a\U000e0070\U000e0032\U000e0031\U000e007f" - FLAG_FOR_CHIRIQUI_PA_4 = "\U0001f3f4\U000e0070\U000e0061\U000e0034\U000e007f" - FLAG_FOR_HESSE_DE_HE = "\U0001f3f4\U000e0064\U000e0065\U000e0068\U000e0065\U000e007f" - FLAG_FOR_AIWO_NR_01 = "\U0001f3f4\U000e006e\U000e0072\U000e0030\U000e0031\U000e007f" - FLAG_FOR_MA_RIB_YE_MA = "\U0001f3f4\U000e0079\U000e0065\U000e006d\U000e0061\U000e007f" - FLAG_FOR_SINT_EUSTATIUS_BQ_SE = "\U0001f3f4\U000e0062\U000e0071\U000e0073\U000e0065\U000e007f" - FLAG_FOR_ASSAM_IN_AS = "\U0001f3f4\U000e0069\U000e006e\U000e0061\U000e0073\U000e007f" - FLAG_FOR_HUNEDOARA_RO_HD = "\U0001f3f4\U000e0072\U000e006f\U000e0068\U000e0064\U000e007f" - FLAG_FOR_VENTSPILS_MUNICIPALITY_LV_106 = "\U0001f3f4\U000e006c\U000e0076\U000e0031\U000e0030\U000e0036\U000e007f" - FLAG_FOR_SAITAMA_JP_11 = "\U0001f3f4\U000e006a\U000e0070\U000e0031\U000e0031\U000e007f" - FLAG_FOR_CAPITAL_DISTRICT_CO_DC = "\U0001f3f4\U000e0063\U000e006f\U000e0064\U000e0063\U000e007f" - FLAG_FOR_SOUTH_CENTRAL_PROVINCE_MV_SC = "\U0001f3f4\U000e006d\U000e0076\U000e0073\U000e0063\U000e007f" - FLAG_FOR_CHARI_BAGUIRMI_TD_CB = "\U0001f3f4\U000e0074\U000e0064\U000e0063\U000e0062\U000e007f" - FAMILY_MAN_MEDIUM_DARK_SKIN_TONE_MAN_MEDIUM_DARK_SKIN_TONE_BOY_MEDIUM_DARK_SKIN_TONE_GIRL_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f468\U0001f3fe\u200d\U0001f466\U0001f3fe\u200d\U0001f467\U0001f3fe" - FLAG_FOR_ALYTUS_MUNICIPALITY_LT_02 = "\U0001f3f4\U000e006c\U000e0074\U000e0030\U000e0032\U000e007f" - FLAG_FOR_HANOI_VN_HN = "\U0001f3f4\U000e0076\U000e006e\U000e0068\U000e006e\U000e007f" - FLAG_FOR_CRIMEA_UA_43 = "\U0001f3f4\U000e0075\U000e0061\U000e0034\U000e0033\U000e007f" - FLAG_FOR_SAINT_ANDREW_JM_02 = "\U0001f3f4\U000e006a\U000e006d\U000e0030\U000e0032\U000e007f" - FLAG_FOR_SANMA_VU_SAM = "\U0001f3f4\U000e0076\U000e0075\U000e0073\U000e0061\U000e006d\U000e007f" - FLAG_FOR_KHOJAVEND_AZ_XVD = "\U0001f3f4\U000e0061\U000e007a\U000e0078\U000e0076\U000e0064\U000e007f" - FLAG_FOR_LOWER_SAXONY_DE_NI = "\U0001f3f4\U000e0064\U000e0065\U000e006e\U000e0069\U000e007f" - FLAG_FOR_SAN_MARCOS_GT_SM = "\U0001f3f4\U000e0067\U000e0074\U000e0073\U000e006d\U000e007f" - FLAG_FOR_HUNG_YEN_VN_66 = "\U0001f3f4\U000e0076\U000e006e\U000e0036\U000e0036\U000e007f" - FAMILY_WOMAN_DARK_SKIN_TONE_MAN_DARK_SKIN_TONE_GIRL_DARK_SKIN_TONE_BABY_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f468\U0001f3ff\u200d\U0001f467\U0001f3ff\u200d\U0001f476\U0001f3ff" - FLAG_FOR_SINALOA_MX_SIN = "\U0001f3f4\U000e006d\U000e0078\U000e0073\U000e0069\U000e006e\U000e007f" - FLAG_FOR_MICHIGAN_US_MI = "\U0001f3f4\U000e0075\U000e0073\U000e006d\U000e0069\U000e007f" - FAMILY_MAN_LIGHT_SKIN_TONE_MAN_LIGHT_SKIN_TONE_GIRL_LIGHT_SKIN_TONE_BABY_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f468\U0001f3fb\u200d\U0001f467\U0001f3fb\u200d\U0001f476\U0001f3fb" - FLAG_FOR_BURYAT_RU_BU = "\U0001f3f4\U000e0072\U000e0075\U000e0062\U000e0075\U000e007f" - FLAG_FOR_PODUNAVLJE_RS_10 = "\U0001f3f4\U000e0072\U000e0073\U000e0031\U000e0030\U000e007f" - FLAG_FOR_CURACAO_NL_CW = "\U0001f3f4\U000e006e\U000e006c\U000e0063\U000e0077\U000e007f" - FLAG_FOR_GUANGDONG_CN_44 = "\U0001f3f4\U000e0063\U000e006e\U000e0034\U000e0034\U000e007f" - FLAG_FOR_VASTRA_GOTALAND_SE_O = "\U0001f3f4\U000e0073\U000e0065\U000e006f\U000e007f" - FLAG_FOR_XORAZM_UZ_XO = "\U0001f3f4\U000e0075\U000e007a\U000e0078\U000e006f\U000e007f" - FLAG_FOR_CENTRALE_TG_C = "\U0001f3f4\U000e0074\U000e0067\U000e0063\U000e007f" - FLAG_FOR_BUCHAREST_RO_B = "\U0001f3f4\U000e0072\U000e006f\U000e0062\U000e007f" - FLAG_FOR_SREDISCE_OB_DRAVI_SI_202 = "\U0001f3f4\U000e0073\U000e0069\U000e0032\U000e0030\U000e0032\U000e007f" - FLAG_FOR_HODMEZOVASARHELY_HU_HV = "\U0001f3f4\U000e0068\U000e0075\U000e0068\U000e0076\U000e007f" - FLAG_FOR_BUENOS_AIRES_AR_C = "\U0001f3f4\U000e0061\U000e0072\U000e0063\U000e007f" - FLAG_FOR_KERALA_IN_KL = "\U0001f3f4\U000e0069\U000e006e\U000e006b\U000e006c\U000e007f" - FAMILY_MAN_MEDIUM_DARK_SKIN_TONE_MAN_MEDIUM_DARK_SKIN_TONE_GIRL_MEDIUM_DARK_SKIN_TONE_BABY_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f468\U0001f3fe\u200d\U0001f467\U0001f3fe\u200d\U0001f476\U0001f3fe" - FLAG_FOR_PHUKET_TH_83 = "\U0001f3f4\U000e0074\U000e0068\U000e0038\U000e0033\U000e007f" - FLAG_FOR_SOUTH_KAZAKHSTAN_KZ_YUZ = "\U0001f3f4\U000e006b\U000e007a\U000e0079\U000e0075\U000e007a\U000e007f" - FAMILY_MAN_MEDIUM_SKIN_TONE_MAN_MEDIUM_SKIN_TONE_GIRL_MEDIUM_SKIN_TONE_BABY_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f468\U0001f3fd\u200d\U0001f467\U0001f3fd\u200d\U0001f476\U0001f3fd" - FAMILY_MAN_LIGHT_SKIN_TONE_MAN_LIGHT_SKIN_TONE_BABY_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f468\U0001f3fb\u200d\U0001f476\U0001f3fb" - KISS_MAN_MEDIUM_SKIN_TONE_MAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc" - FLAG_FOR_BETHLEHEM_PS_BTH = "\U0001f3f4\U000e0070\U000e0073\U000e0062\U000e0074\U000e0068\U000e007f" - FLAG_FOR_OZOLNIEKI_LV_069 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0036\U000e0039\U000e007f" - FLAG_FOR_BUZAU_RO_BZ = "\U0001f3f4\U000e0072\U000e006f\U000e0062\U000e007a\U000e007f" - FLAG_FOR_OUAKA_CF_UK = "\U0001f3f4\U000e0063\U000e0066\U000e0075\U000e006b\U000e007f" - FAMILY_WOMAN_MEDIUM_SKIN_TONE_MAN_MEDIUM_SKIN_TONE_GIRL_MEDIUM_SKIN_TONE_BOY_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f468\U0001f3fd\u200d\U0001f467\U0001f3fd\u200d\U0001f466\U0001f3fd" - FLAG_FOR_BONG_LR_BG = "\U0001f3f4\U000e006c\U000e0072\U000e0062\U000e0067\U000e007f" - FAMILY_MAN_DARK_SKIN_TONE_MAN_DARK_SKIN_TONE_GIRL_DARK_SKIN_TONE_BABY_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f468\U0001f3ff\u200d\U0001f467\U0001f3ff\u200d\U0001f476\U0001f3ff" - FLAG_FOR_LEBAP_TM_L = "\U0001f3f4\U000e0074\U000e006d\U000e006c\U000e007f" - FLAG_FOR_VORONEZH_RU_VOR = "\U0001f3f4\U000e0072\U000e0075\U000e0076\U000e006f\U000e0072\U000e007f" - KISS_WOMAN_MEDIUM_DARK_SKIN_TONE_MAN_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe" - FLAG_FOR_SAN_JOSE_CR_SJ = "\U0001f3f4\U000e0063\U000e0072\U000e0073\U000e006a\U000e007f" - FLAG_FOR_KISUMU_KE_17 = "\U0001f3f4\U000e006b\U000e0065\U000e0031\U000e0037\U000e007f" - FAMILY_WOMAN_DARK_SKIN_TONE_GIRL_DARK_SKIN_TONE_BOY_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f467\U0001f3ff\u200d\U0001f466\U0001f3ff" - FLAG_FOR_LAHIJ_YE_LA = "\U0001f3f4\U000e0079\U000e0065\U000e006c\U000e0061\U000e007f" - FLAG_FOR_RACHA_LECHKHUMI_AND_KVEMO_SVANETI_GE_RL = "\U0001f3f4\U000e0067\U000e0065\U000e0072\U000e006c\U000e007f" - FLAG_FOR_ULCINJ_ME_20 = "\U0001f3f4\U000e006d\U000e0065\U000e0032\U000e0030\U000e007f" - FAMILY_MAN_MEDIUM_LIGHT_SKIN_TONE_MAN_MEDIUM_LIGHT_SKIN_TONE_BABY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f468\U0001f3fc\u200d\U0001f476\U0001f3fc" - FLAG_FOR_OSH_KG_GO = "\U0001f3f4\U000e006b\U000e0067\U000e0067\U000e006f\U000e007f" - FLAG_FOR_AMAZONAS_VE_Z = "\U0001f3f4\U000e0076\U000e0065\U000e007a\U000e007f" - FLAG_FOR_NORTH_RHINE_WESTPHALIA_DE_NW = "\U0001f3f4\U000e0064\U000e0065\U000e006e\U000e0077\U000e007f" - FLAG_FOR_MONAGAS_VE_N = "\U0001f3f4\U000e0076\U000e0065\U000e006e\U000e007f" - FAMILY_MAN_MEDIUM_LIGHT_SKIN_TONE_BABY_MEDIUM_LIGHT_SKIN_TONE_BOY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f476\U0001f3fc\u200d\U0001f466\U0001f3fc" - FLAG_FOR_CANTABRIA_ES_CB = "\U0001f3f4\U000e0065\U000e0073\U000e0063\U000e0062\U000e007f" - FAMILY_MAN_MEDIUM_DARK_SKIN_TONE_MAN_MEDIUM_DARK_SKIN_TONE_BABY_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f468\U0001f3fe\u200d\U0001f476\U0001f3fe" - FLAG_FOR_M_SILA_DZ_28 = "\U0001f3f4\U000e0064\U000e007a\U000e0032\U000e0038\U000e007f" - FAMILY_WOMAN_MAN_BOY = "\U0001f469\u200d\U0001f468\u200d\U0001f466" - FAMILY_MAN_MEDIUM_LIGHT_SKIN_TONE_GIRL_MEDIUM_LIGHT_SKIN_TONE_BOY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f467\U0001f3fc\u200d\U0001f466\U0001f3fc" - FAMILY_WOMAN_DARK_SKIN_TONE_MAN_DARK_SKIN_TONE_GIRL_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f468\U0001f3ff\u200d\U0001f467\U0001f3ff" - FLAG_FOR_EASTERN_HIGHLANDS_PG_EHG = "\U0001f3f4\U000e0070\U000e0067\U000e0065\U000e0068\U000e0067\U000e007f" - FLAG_FOR_OHRID_MK_58 = "\U0001f3f4\U000e006d\U000e006b\U000e0035\U000e0038\U000e007f" - FLAG_FOR_SATU_MARE_RO_SM = "\U0001f3f4\U000e0072\U000e006f\U000e0073\U000e006d\U000e007f" - TAG_LATIN_CAPITAL_LETTER_Y = "\U000e0059" - FLAG_FOR_CHERNIHIVSHCHYNA_UA_74 = "\U0001f3f4\U000e0075\U000e0061\U000e0037\U000e0034\U000e007f" - TAG_DIGIT_TWO = "\U000e0032" - FLAG_FOR_RODRIGUES_MU_RO = "\U0001f3f4\U000e006d\U000e0075\U000e0072\U000e006f\U000e007f" - COUPLE_WITH_HEART_WOMAN_MAN_DARK_SKIN_TONE = "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff" - FAMILY_MAN_DARK_SKIN_TONE_MAN_DARK_SKIN_TONE_BABY_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f468\U0001f3ff\u200d\U0001f476\U0001f3ff" - FLAG_FOR_HAWKE_S_BAY_NZ_HKB = "\U0001f3f4\U000e006e\U000e007a\U000e0068\U000e006b\U000e0062\U000e007f" - FAMILY_WOMAN_DARK_SKIN_TONE_MAN_DARK_SKIN_TONE_BOY_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f468\U0001f3ff\u200d\U0001f466\U0001f3ff" - FLAG_FOR_SANT_JULIA_DE_LORIA_AD_06 = "\U0001f3f4\U000e0061\U000e0064\U000e0030\U000e0036\U000e007f" - FAMILY_WOMAN_MEDIUM_DARK_SKIN_TONE_BABY_MEDIUM_DARK_SKIN_TONE_GIRL_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f476\U0001f3fe\u200d\U0001f467\U0001f3fe" - FAMILY_WOMAN_MEDIUM_DARK_SKIN_TONE_MAN_MEDIUM_DARK_SKIN_TONE_BOY_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f468\U0001f3fe\u200d\U0001f466\U0001f3fe" - FLAG_FOR_AN_GIANG_VN_44 = "\U0001f3f4\U000e0076\U000e006e\U000e0034\U000e0034\U000e007f" - FLAG_FOR_UPPSALA_SE_C = "\U0001f3f4\U000e0073\U000e0065\U000e0063\U000e007f" - FAMILY_WOMAN_MEDIUM_SKIN_TONE_BABY_MEDIUM_SKIN_TONE_GIRL_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f476\U0001f3fd\u200d\U0001f467\U0001f3fd" - FLAG_FOR_UDMURT_RU_UD = "\U0001f3f4\U000e0072\U000e0075\U000e0075\U000e0064\U000e007f" - FAMILY_WOMAN_MEDIUM_DARK_SKIN_TONE_GIRL_MEDIUM_DARK_SKIN_TONE_GIRL_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f467\U0001f3fe\u200d\U0001f467\U0001f3fe" - FAMILY_WOMAN_MEDIUM_DARK_SKIN_TONE_BABY_MEDIUM_DARK_SKIN_TONE_BOY_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f476\U0001f3fe\u200d\U0001f466\U0001f3fe" - FLAG_FOR_ERMERA_TL_ER = "\U0001f3f4\U000e0074\U000e006c\U000e0065\U000e0072\U000e007f" - FAMILY_WOMAN_MEDIUM_DARK_SKIN_TONE_BABY_MEDIUM_DARK_SKIN_TONE_BABY_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f476\U0001f3fe\u200d\U0001f476\U0001f3fe" - FAMILY_WOMAN_MEDIUM_SKIN_TONE_MAN_MEDIUM_SKIN_TONE_BOY_MEDIUM_SKIN_TONE_GIRL_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f468\U0001f3fd\u200d\U0001f466\U0001f3fd\u200d\U0001f467\U0001f3fd" - FLAG_FOR_PRIMORJE_GORSKI_KOTAR_HR_08 = "\U0001f3f4\U000e0068\U000e0072\U000e0030\U000e0038\U000e007f" - FAMILY_MAN_MEDIUM_LIGHT_SKIN_TONE_WOMAN_MEDIUM_LIGHT_SKIN_TONE_BOY_MEDIUM_LIGHT_SKIN_TONE_GIRL_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f469\U0001f3fc\u200d\U0001f466\U0001f3fc\u200d\U0001f467\U0001f3fc" - FLAG_FOR_MUS_TR_49 = "\U0001f3f4\U000e0074\U000e0072\U000e0034\U000e0039\U000e007f" - FLAG_FOR_AD_DAKHILIYAH_OM_DA = "\U0001f3f4\U000e006f\U000e006d\U000e0064\U000e0061\U000e007f" - FAMILY_WOMAN_MEDIUM_LIGHT_SKIN_TONE_MAN_MEDIUM_LIGHT_SKIN_TONE_GIRL_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f468\U0001f3fc\u200d\U0001f467\U0001f3fc" - FLAG_FOR_ORIENTAL_MA_04 = "\U0001f3f4\U000e006d\U000e0061\U000e0030\U000e0034\U000e007f" - FAMILY_WOMAN_MEDIUM_DARK_SKIN_TONE_MAN_MEDIUM_DARK_SKIN_TONE_BOY_MEDIUM_DARK_SKIN_TONE_BABY_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f468\U0001f3fe\u200d\U0001f466\U0001f3fe\u200d\U0001f476\U0001f3fe" - FLAG_FOR_MAYO_KEBBI_OUEST_TD_MO = "\U0001f3f4\U000e0074\U000e0064\U000e006d\U000e006f\U000e007f" - FLAG_FOR_TBILISI_GE_TB = "\U0001f3f4\U000e0067\U000e0065\U000e0074\U000e0062\U000e007f" - FAMILY_WOMAN_MEDIUM_DARK_SKIN_TONE_MAN_MEDIUM_DARK_SKIN_TONE_BOY_MEDIUM_DARK_SKIN_TONE_BOY_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f468\U0001f3fe\u200d\U0001f466\U0001f3fe\u200d\U0001f466\U0001f3fe" - FAMILY_WOMAN_LIGHT_SKIN_TONE_MAN_LIGHT_SKIN_TONE_BOY_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f468\U0001f3fb\u200d\U0001f466\U0001f3fb" - FAMILY_WOMAN_MEDIUM_SKIN_TONE_MAN_MEDIUM_SKIN_TONE_BOY_MEDIUM_SKIN_TONE_BABY_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f468\U0001f3fd\u200d\U0001f466\U0001f3fd\u200d\U0001f476\U0001f3fd" - FAMILY_WOMAN_MEDIUM_DARK_SKIN_TONE_GIRL_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f467\U0001f3fe" - FLAG_FOR_PHU_THO_VN_68 = "\U0001f3f4\U000e0076\U000e006e\U000e0036\U000e0038\U000e007f" - FAMILY_WOMAN_MEDIUM_SKIN_TONE_MAN_MEDIUM_SKIN_TONE_BOY_MEDIUM_SKIN_TONE_BOY_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f468\U0001f3fd\u200d\U0001f466\U0001f3fd\u200d\U0001f466\U0001f3fd" - FLAG_FOR_FREE_ZA_FS = "\U0001f3f4\U000e007a\U000e0061\U000e0066\U000e0073\U000e007f" - FAMILY_WOMAN_MEDIUM_DARK_SKIN_TONE_MAN_MEDIUM_DARK_SKIN_TONE_GIRL_MEDIUM_DARK_SKIN_TONE_BABY_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f468\U0001f3fe\u200d\U0001f467\U0001f3fe\u200d\U0001f476\U0001f3fe" - FAMILY_WOMAN_MEDIUM_LIGHT_SKIN_TONE_MAN_MEDIUM_LIGHT_SKIN_TONE_GIRL_MEDIUM_LIGHT_SKIN_TONE_BABY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f468\U0001f3fc\u200d\U0001f467\U0001f3fc\u200d\U0001f476\U0001f3fc" - FLAG_FOR_SUCRE_VE_R = "\U0001f3f4\U000e0076\U000e0065\U000e0072\U000e007f" - FAMILY_MAN_DARK_SKIN_TONE_WOMAN_DARK_SKIN_TONE_BOY_DARK_SKIN_TONE_BOY_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f469\U0001f3ff\u200d\U0001f466\U0001f3ff\u200d\U0001f466\U0001f3ff" - FLAG_FOR_SALGOTARJAN_HU_ST = "\U0001f3f4\U000e0068\U000e0075\U000e0073\U000e0074\U000e007f" - FAMILY_WOMAN_MEDIUM_LIGHT_SKIN_TONE_MAN_MEDIUM_LIGHT_SKIN_TONE_BABY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f468\U0001f3fc\u200d\U0001f476\U0001f3fc" - FLAG_FOR_RAYONG_TH_21 = "\U0001f3f4\U000e0074\U000e0068\U000e0032\U000e0031\U000e007f" - COUPLE_WITH_HEART_WOMAN_MEDIUM_LIGHT_SKIN_TONE_WOMAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fc" - FAMILY_MAN_MAN_BABY_GIRL = "\U0001f468\u200d\U0001f468\u200d\U0001f476\u200d\U0001f467" - FAMILY_WOMAN_DARK_SKIN_TONE_MAN_DARK_SKIN_TONE_BOY_DARK_SKIN_TONE_GIRL_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f468\U0001f3ff\u200d\U0001f466\U0001f3ff\u200d\U0001f467\U0001f3ff" - FAMILY_WOMAN_LIGHT_SKIN_TONE_MAN_LIGHT_SKIN_TONE_BABY_LIGHT_SKIN_TONE_BABY_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f468\U0001f3fb\u200d\U0001f476\U0001f3fb\u200d\U0001f476\U0001f3fb" - FAMILY_WOMAN_MEDIUM_SKIN_TONE_MAN_MEDIUM_SKIN_TONE_GIRL_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f468\U0001f3fd\u200d\U0001f467\U0001f3fd" - FAMILY_WOMAN_LIGHT_SKIN_TONE_MAN_LIGHT_SKIN_TONE_BABY_LIGHT_SKIN_TONE_BOY_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f468\U0001f3fb\u200d\U0001f476\U0001f3fb\u200d\U0001f466\U0001f3fb" - FAMILY_WOMAN_DARK_SKIN_TONE_MAN_DARK_SKIN_TONE_BABY_DARK_SKIN_TONE_BABY_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f468\U0001f3ff\u200d\U0001f476\U0001f3ff\u200d\U0001f476\U0001f3ff" - FAMILY_WOMAN_MEDIUM_DARK_SKIN_TONE_MAN_MEDIUM_DARK_SKIN_TONE_BABY_MEDIUM_DARK_SKIN_TONE_BABY_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f468\U0001f3fe\u200d\U0001f476\U0001f3fe\u200d\U0001f476\U0001f3fe" - FLAG_FOR_SUD_EST_HT_SE = "\U0001f3f4\U000e0068\U000e0074\U000e0073\U000e0065\U000e007f" - FLAG_FOR_CETINJE_ME_06 = "\U0001f3f4\U000e006d\U000e0065\U000e0030\U000e0036\U000e007f" - KISS_WOMAN_LIGHT_SKIN_TONE_MAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc" - FLAG_FOR_OVERIJSSEL_NL_OV = "\U0001f3f4\U000e006e\U000e006c\U000e006f\U000e0076\U000e007f" - FAMILY_WOMAN_MEDIUM_SKIN_TONE_MAN_MEDIUM_SKIN_TONE_GIRL_MEDIUM_SKIN_TONE_BABY_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f468\U0001f3fd\u200d\U0001f467\U0001f3fd\u200d\U0001f476\U0001f3fd" - FLAG_FOR_SMARTNO_OB_PAKI_SI_125 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0032\U000e0035\U000e007f" - FAMILY_WOMAN_MEDIUM_SKIN_TONE_MAN_MEDIUM_SKIN_TONE_BABY_MEDIUM_SKIN_TONE_GIRL_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f468\U0001f3fd\u200d\U0001f476\U0001f3fd\u200d\U0001f467\U0001f3fd" - FLAG_FOR_CENTRAL_ANDROS_BS_CS = "\U0001f3f4\U000e0062\U000e0073\U000e0063\U000e0073\U000e007f" - FLAG_FOR_GAZA_PS_GZA = "\U0001f3f4\U000e0070\U000e0073\U000e0067\U000e007a\U000e0061\U000e007f" - FAMILY_WOMAN_MEDIUM_DARK_SKIN_TONE_WOMAN_MEDIUM_DARK_SKIN_TONE_BOY_MEDIUM_DARK_SKIN_TONE_GIRL_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f469\U0001f3fe\u200d\U0001f466\U0001f3fe\u200d\U0001f467\U0001f3fe" - FLAG_FOR_TASMANIA_AU_TAS = "\U0001f3f4\U000e0061\U000e0075\U000e0074\U000e0061\U000e0073\U000e007f" - FAMILY_WOMAN_MEDIUM_SKIN_TONE_WOMAN_MEDIUM_SKIN_TONE_BOY_MEDIUM_SKIN_TONE_BABY_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f469\U0001f3fd\u200d\U0001f466\U0001f3fd\u200d\U0001f476\U0001f3fd" - FLAG_FOR_GAMBELA_ET_GA = "\U0001f3f4\U000e0065\U000e0074\U000e0067\U000e0061\U000e007f" - FLAG_FOR_SAINT_PETERSBURG_RU_SPE = "\U0001f3f4\U000e0072\U000e0075\U000e0073\U000e0070\U000e0065\U000e007f" - FAMILY_WOMAN_LIGHT_SKIN_TONE_WOMAN_LIGHT_SKIN_TONE_BOY_LIGHT_SKIN_TONE_GIRL_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f469\U0001f3fb\u200d\U0001f466\U0001f3fb\u200d\U0001f467\U0001f3fb" - FLAG_FOR_NORTH_OSSETIA_ALANIA_RU_SE = "\U0001f3f4\U000e0072\U000e0075\U000e0073\U000e0065\U000e007f" - FAMILY_WOMAN_MEDIUM_DARK_SKIN_TONE_WOMAN_MEDIUM_DARK_SKIN_TONE_BOY_MEDIUM_DARK_SKIN_TONE_BABY_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f469\U0001f3fe\u200d\U0001f466\U0001f3fe\u200d\U0001f476\U0001f3fe" - FAMILY_WOMAN_DARK_SKIN_TONE_WOMAN_DARK_SKIN_TONE_BOY_DARK_SKIN_TONE_BABY_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f469\U0001f3ff\u200d\U0001f466\U0001f3ff\u200d\U0001f476\U0001f3ff" - FLAG_FOR_GALICIA_ES_GA = "\U0001f3f4\U000e0065\U000e0073\U000e0067\U000e0061\U000e007f" - TAG_REVERSE_SOLIDUS = "\U000e005c" - KISS_MAN_LIGHT_SKIN_TONE_WOMAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fc" - FLAG_FOR_KILIMANJARO_TZ_09 = "\U0001f3f4\U000e0074\U000e007a\U000e0030\U000e0039\U000e007f" - FLAG_FOR_GIRESUN_TR_28 = "\U0001f3f4\U000e0074\U000e0072\U000e0032\U000e0038\U000e007f" - FLAG_FOR_KEBBI_NG_KE = "\U0001f3f4\U000e006e\U000e0067\U000e006b\U000e0065\U000e007f" - FLAG_FOR_MAROWIJNE_SR_MA = "\U0001f3f4\U000e0073\U000e0072\U000e006d\U000e0061\U000e007f" - FAMILY_WOMAN_MEDIUM_DARK_SKIN_TONE_BOY_MEDIUM_DARK_SKIN_TONE_BOY_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f466\U0001f3fe\u200d\U0001f466\U0001f3fe" - FAMILY_WOMAN_LIGHT_SKIN_TONE_WOMAN_LIGHT_SKIN_TONE_BABY_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f469\U0001f3fb\u200d\U0001f476\U0001f3fb" - FAMILY_WOMAN_MEDIUM_DARK_SKIN_TONE_WOMAN_MEDIUM_DARK_SKIN_TONE_GIRL_MEDIUM_DARK_SKIN_TONE_BABY_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f469\U0001f3fe\u200d\U0001f467\U0001f3fe\u200d\U0001f476\U0001f3fe" - FAMILY_WOMAN_MEDIUM_SKIN_TONE_BOY_MEDIUM_SKIN_TONE_BABY_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f466\U0001f3fd\u200d\U0001f476\U0001f3fd" - FAMILY_WOMAN_DARK_SKIN_TONE_GIRL_DARK_SKIN_TONE_BABY_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f467\U0001f3ff\u200d\U0001f476\U0001f3ff" - FLAG_FOR_SOUTHWEST_FINLAND_FI_19 = "\U0001f3f4\U000e0066\U000e0069\U000e0031\U000e0039\U000e007f" - FAMILY_MAN_MEDIUM_DARK_SKIN_TONE_WOMAN_MEDIUM_DARK_SKIN_TONE_GIRL_MEDIUM_DARK_SKIN_TONE_GIRL_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f469\U0001f3fe\u200d\U0001f467\U0001f3fe\u200d\U0001f467\U0001f3fe" - FAMILY_WOMAN_MEDIUM_LIGHT_SKIN_TONE_MAN_MEDIUM_LIGHT_SKIN_TONE_BOY_MEDIUM_LIGHT_SKIN_TONE_GIRL_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f468\U0001f3fc\u200d\U0001f466\U0001f3fc\u200d\U0001f467\U0001f3fc" - FAMILY_WOMAN_DARK_SKIN_TONE_WOMAN_DARK_SKIN_TONE_GIRL_DARK_SKIN_TONE_BABY_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f469\U0001f3ff\u200d\U0001f467\U0001f3ff\u200d\U0001f476\U0001f3ff" - FAMILY_WOMAN_MEDIUM_DARK_SKIN_TONE_WOMAN_MEDIUM_DARK_SKIN_TONE_BABY_MEDIUM_DARK_SKIN_TONE_GIRL_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f469\U0001f3fe\u200d\U0001f476\U0001f3fe\u200d\U0001f467\U0001f3fe" - FLAG_FOR_BOKEO_LA_BK = "\U0001f3f4\U000e006c\U000e0061\U000e0062\U000e006b\U000e007f" - FAMILY_WOMAN_MEDIUM_LIGHT_SKIN_TONE_WOMAN_MEDIUM_LIGHT_SKIN_TONE_BABY_MEDIUM_LIGHT_SKIN_TONE_BOY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f469\U0001f3fc\u200d\U0001f476\U0001f3fc\u200d\U0001f466\U0001f3fc" - FAMILY_WOMAN_LIGHT_SKIN_TONE_WOMAN_LIGHT_SKIN_TONE_BABY_LIGHT_SKIN_TONE_BOY_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f469\U0001f3fb\u200d\U0001f476\U0001f3fb\u200d\U0001f466\U0001f3fb" - FAMILY_WOMAN_MEDIUM_SKIN_TONE_WOMAN_MEDIUM_SKIN_TONE_BABY_MEDIUM_SKIN_TONE_BOY_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f469\U0001f3fd\u200d\U0001f476\U0001f3fd\u200d\U0001f466\U0001f3fd" - FAMILY_WOMAN_MEDIUM_SKIN_TONE_WOMAN_MEDIUM_SKIN_TONE_BABY_MEDIUM_SKIN_TONE_BABY_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f469\U0001f3fd\u200d\U0001f476\U0001f3fd\u200d\U0001f476\U0001f3fd" - FAMILY_WOMAN_DARK_SKIN_TONE_WOMAN_DARK_SKIN_TONE_BABY_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f469\U0001f3ff\u200d\U0001f476\U0001f3ff" - FAMILY_WOMAN_DARK_SKIN_TONE_WOMAN_DARK_SKIN_TONE_BABY_DARK_SKIN_TONE_GIRL_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f469\U0001f3ff\u200d\U0001f476\U0001f3ff\u200d\U0001f467\U0001f3ff" - FLAG_FOR_GASH_BARKA_ER_GB = "\U0001f3f4\U000e0065\U000e0072\U000e0067\U000e0062\U000e007f" - FAMILY_WOMAN_LIGHT_SKIN_TONE_WOMAN_LIGHT_SKIN_TONE_BABY_LIGHT_SKIN_TONE_GIRL_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f469\U0001f3fb\u200d\U0001f476\U0001f3fb\u200d\U0001f467\U0001f3fb" - FAMILY_WOMAN_MEDIUM_LIGHT_SKIN_TONE_WOMAN_MEDIUM_LIGHT_SKIN_TONE_BABY_MEDIUM_LIGHT_SKIN_TONE_BABY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f469\U0001f3fc\u200d\U0001f476\U0001f3fc\u200d\U0001f476\U0001f3fc" - FLAG_FOR_ARUNACHAL_PRADESH_IN_AR = "\U0001f3f4\U000e0069\U000e006e\U000e0061\U000e0072\U000e007f" - COUPLE_WITH_HEART_MAN_MAN_DARK_SKIN_TONE = "\U0001f468\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff" - FAMILY_MAN_LIGHT_SKIN_TONE_MAN_LIGHT_SKIN_TONE_GIRL_LIGHT_SKIN_TONE_GIRL_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f468\U0001f3fb\u200d\U0001f467\U0001f3fb\u200d\U0001f467\U0001f3fb" - FAMILY_WOMAN_MEDIUM_DARK_SKIN_TONE_WOMAN_MEDIUM_DARK_SKIN_TONE_BABY_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f469\U0001f3fe\u200d\U0001f476\U0001f3fe" - FAMILY_WOMAN_DARK_SKIN_TONE_MAN_DARK_SKIN_TONE_GIRL_DARK_SKIN_TONE_BOY_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f468\U0001f3ff\u200d\U0001f467\U0001f3ff\u200d\U0001f466\U0001f3ff" - FLAG_FOR_MINNESOTA_US_MN = "\U0001f3f4\U000e0075\U000e0073\U000e006d\U000e006e\U000e007f" - FLAG_FOR_ROZAJE_ME_17 = "\U0001f3f4\U000e006d\U000e0065\U000e0031\U000e0037\U000e007f" - FAMILY_MAN_DARK_SKIN_TONE_GIRL_DARK_SKIN_TONE_GIRL_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f467\U0001f3ff\u200d\U0001f467\U0001f3ff" - FAMILY_WOMAN_DARK_SKIN_TONE_WOMAN_DARK_SKIN_TONE_BABY_DARK_SKIN_TONE_BABY_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f469\U0001f3ff\u200d\U0001f476\U0001f3ff\u200d\U0001f476\U0001f3ff" - FLAG_FOR_EL_OUED_DZ_39 = "\U0001f3f4\U000e0064\U000e007a\U000e0033\U000e0039\U000e007f" - FAMILY_MAN_MEDIUM_SKIN_TONE_GIRL_MEDIUM_SKIN_TONE_GIRL_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f467\U0001f3fd\u200d\U0001f467\U0001f3fd" - FLAG_FOR_OMNOGOVI_MN_053 = "\U0001f3f4\U000e006d\U000e006e\U000e0030\U000e0035\U000e0033\U000e007f" - FAMILY_WOMAN_MEDIUM_LIGHT_SKIN_TONE_GIRL_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f467\U0001f3fc" - FLAG_FOR_WEST_COAST_DIVISION_GM_W = "\U0001f3f4\U000e0067\U000e006d\U000e0077\U000e007f" - FAMILY_WOMAN_MEDIUM_LIGHT_SKIN_TONE_BOY_MEDIUM_LIGHT_SKIN_TONE_GIRL_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f466\U0001f3fc\u200d\U0001f467\U0001f3fc" - FLAG_FOR_MALUKU_ISLANDS_ID_ML = "\U0001f3f4\U000e0069\U000e0064\U000e006d\U000e006c\U000e007f" - FLAG_FOR_SVETI_JURIJ_V_SLOVENSKIH_GORICAH_SI_210 = "\U0001f3f4\U000e0073\U000e0069\U000e0032\U000e0031\U000e0030\U000e007f" - FAMILY_MAN_MEDIUM_SKIN_TONE_WOMAN_MEDIUM_SKIN_TONE_GIRL_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f469\U0001f3fd\u200d\U0001f467\U0001f3fd" - FLAG_FOR_TANGA_TZ_25 = "\U0001f3f4\U000e0074\U000e007a\U000e0032\U000e0035\U000e007f" - FLAG_FOR_FAR_NORTH_CM_EN = "\U0001f3f4\U000e0063\U000e006d\U000e0065\U000e006e\U000e007f" - FLAG_FOR_SANNAT_MT_52 = "\U0001f3f4\U000e006d\U000e0074\U000e0035\U000e0032\U000e007f" - FLAG_FOR_INNER_MONGOLIA_CN_15 = "\U0001f3f4\U000e0063\U000e006e\U000e0031\U000e0035\U000e007f" - FLAG_FOR_ST_PIERRE_ANDAMP_MIQUELON_FR_PM = "\U0001f3f4\U000e0066\U000e0072\U000e0070\U000e006d\U000e007f" - NKO_SYMBOL_GBAKURUNEN = "\u07f7" - FLAG_FOR_RIVER_GEE_LR_RG = "\U0001f3f4\U000e006c\U000e0072\U000e0072\U000e0067\U000e007f" - FAMILY_MAN_MEDIUM_LIGHT_SKIN_TONE_GIRL_MEDIUM_LIGHT_SKIN_TONE_GIRL_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f467\U0001f3fc\u200d\U0001f467\U0001f3fc" - FLAG_FOR_NAIROBI_COUNTY_KE_30 = "\U0001f3f4\U000e006b\U000e0065\U000e0033\U000e0030\U000e007f" - FLAG_FOR_ORYOL_RU_ORL = "\U0001f3f4\U000e0072\U000e0075\U000e006f\U000e0072\U000e006c\U000e007f" - FLAG_FOR_BEJA_PT_02 = "\U0001f3f4\U000e0070\U000e0074\U000e0030\U000e0032\U000e007f" - FLAG_FOR_SKOPJE_MK_85 = "\U0001f3f4\U000e006d\U000e006b\U000e0038\U000e0035\U000e007f" - FAMILY_MAN_DARK_SKIN_TONE_MAN_DARK_SKIN_TONE_BOY_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f468\U0001f3ff\u200d\U0001f466\U0001f3ff" - FLAG_FOR_MOUNT_LEBANON_LB_JL = "\U0001f3f4\U000e006c\U000e0062\U000e006a\U000e006c\U000e007f" - FLAG_FOR_PERM_KRAI_RU_PER = "\U0001f3f4\U000e0072\U000e0075\U000e0070\U000e0065\U000e0072\U000e007f" - FAMILY_WOMAN_WOMAN_BABY_BABY = "\U0001f469\u200d\U0001f469\u200d\U0001f476\u200d\U0001f476" - KISS_MAN_WOMAN_LIGHT_SKIN_TONE = "\U0001f468\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fb" - FAMILY_WOMAN_MEDIUM_LIGHT_SKIN_TONE_BABY_MEDIUM_LIGHT_SKIN_TONE_BABY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f476\U0001f3fc\u200d\U0001f476\U0001f3fc" - FLAG_FOR_ISLA_DE_LA_JUVENTUD_CU_99 = "\U0001f3f4\U000e0063\U000e0075\U000e0039\U000e0039\U000e007f" - UNMARRIED_PARTNERSHIP_SYMBOL = "\u26af" - FLAG_FOR_LAGUNES_CI_LG = "\U0001f3f4\U000e0063\U000e0069\U000e006c\U000e0067\U000e007f" - FLAG_FOR_WESTERN_AUSTRALIA_AU_WA = "\U0001f3f4\U000e0061\U000e0075\U000e0077\U000e0061\U000e007f" - FAMILY_MAN_MEDIUM_SKIN_TONE_WOMAN_MEDIUM_SKIN_TONE_BOY_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f469\U0001f3fd\u200d\U0001f466\U0001f3fd" - FAMILY_WOMAN_DARK_SKIN_TONE_GIRL_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f467\U0001f3ff" - FAMILY_WOMAN_MEDIUM_SKIN_TONE_GIRL_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f467\U0001f3fd" - FLAG_FOR_HEREDIA_CR_H = "\U0001f3f4\U000e0063\U000e0072\U000e0068\U000e007f" - FAMILY_MAN_MEDIUM_LIGHT_SKIN_TONE_MAN_MEDIUM_LIGHT_SKIN_TONE_GIRL_MEDIUM_LIGHT_SKIN_TONE_BABY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f468\U0001f3fc\u200d\U0001f467\U0001f3fc\u200d\U0001f476\U0001f3fc" - FLAG_FOR_AIGA_I_LE_TAI_WS_AL = "\U0001f3f4\U000e0077\U000e0073\U000e0061\U000e006c\U000e007f" - KISS_WOMAN_LIGHT_SKIN_TONE_WOMAN_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fe" - FLAG_FOR_BAVARIA_DE_BY = "\U0001f3f4\U000e0064\U000e0065\U000e0062\U000e0079\U000e007f" - FLAG_FOR_VALPARAISO_CL_VS = "\U0001f3f4\U000e0063\U000e006c\U000e0076\U000e0073\U000e007f" - WOMAN_IN_BUSINESS_SUIT_LEVITATING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f574\U0001f3fc\u200d\u2640\ufe0f" - FLAG_FOR_BAGHDAD_IQ_BG = "\U0001f3f4\U000e0069\U000e0071\U000e0062\U000e0067\U000e007f" - FLAG_FOR_TENNESSEE_US_TN = "\U0001f3f4\U000e0075\U000e0073\U000e0074\U000e006e\U000e007f" - FAMILY_MAN_WOMAN_BABY = "\U0001f468\u200d\U0001f469\u200d\U0001f476" - FAMILY_WOMAN_MEDIUM_SKIN_TONE_MAN_MEDIUM_SKIN_TONE_BOY_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f468\U0001f3fd\u200d\U0001f466\U0001f3fd" - FLAG_FOR_LA_PAZ_SV_PA = "\U0001f3f4\U000e0073\U000e0076\U000e0070\U000e0061\U000e007f" - FAMILY_WOMAN_MEDIUM_SKIN_TONE_MAN_MEDIUM_SKIN_TONE_BABY_MEDIUM_SKIN_TONE_BABY_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f468\U0001f3fd\u200d\U0001f476\U0001f3fd\u200d\U0001f476\U0001f3fd" - FLAG_FOR_RABAT_MT_46 = "\U0001f3f4\U000e006d\U000e0074\U000e0034\U000e0036\U000e007f" - FLAG_FOR_IMO_NG_IM = "\U0001f3f4\U000e006e\U000e0067\U000e0069\U000e006d\U000e007f" - FLAG_FOR_WELLINGTON_NZ_WGN = "\U0001f3f4\U000e006e\U000e007a\U000e0077\U000e0067\U000e006e\U000e007f" - FAMILY_WOMAN_MEDIUM_SKIN_TONE_BABY_MEDIUM_SKIN_TONE_BABY_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f476\U0001f3fd\u200d\U0001f476\U0001f3fd" - FLAG_FOR_ZABLJAK_ME_21 = "\U0001f3f4\U000e006d\U000e0065\U000e0032\U000e0031\U000e007f" - FAMILY_MAN_DARK_SKIN_TONE_WOMAN_DARK_SKIN_TONE_BABY_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f469\U0001f3ff\u200d\U0001f476\U0001f3ff" - FLAG_FOR_BORNO_NG_BO = "\U0001f3f4\U000e006e\U000e0067\U000e0062\U000e006f\U000e007f" - FLAG_FOR_NAKURU_KE_31 = "\U0001f3f4\U000e006b\U000e0065\U000e0033\U000e0031\U000e007f" - FLAG_FOR_CARABOBO_VE_G = "\U0001f3f4\U000e0076\U000e0065\U000e0067\U000e007f" - FLAG_FOR_TEHRAN_IR_07 = "\U0001f3f4\U000e0069\U000e0072\U000e0030\U000e0037\U000e007f" - FLAG_FOR_BADEN_WURTTEMBERG_DE_BW = "\U0001f3f4\U000e0064\U000e0065\U000e0062\U000e0077\U000e007f" - FLAG_FOR_YANGON_MM_06 = "\U0001f3f4\U000e006d\U000e006d\U000e0030\U000e0036\U000e007f" - TAG_LEFT_PARENTHESIS = "\U000e0028" - FAMILY_MAN_MEDIUM_DARK_SKIN_TONE_MAN_MEDIUM_DARK_SKIN_TONE_GIRL_MEDIUM_DARK_SKIN_TONE_BOY_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f468\U0001f3fe\u200d\U0001f467\U0001f3fe\u200d\U0001f466\U0001f3fe" - FLAG_FOR_USTECKY_KRAJ_CZ_42 = "\U0001f3f4\U000e0063\U000e007a\U000e0034\U000e0032\U000e007f" - FLAG_FOR_KUALA_LUMPUR_MY_14 = "\U0001f3f4\U000e006d\U000e0079\U000e0031\U000e0034\U000e007f" - FLAG_FOR_AL_WUSTA_OM_WU = "\U0001f3f4\U000e006f\U000e006d\U000e0077\U000e0075\U000e007f" - FAMILY_WOMAN_LIGHT_SKIN_TONE_MAN_LIGHT_SKIN_TONE_GIRL_LIGHT_SKIN_TONE_BOY_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f468\U0001f3fb\u200d\U0001f467\U0001f3fb\u200d\U0001f466\U0001f3fb" - FLAG_FOR_SOUTH_JEOLLA_KR_46 = "\U0001f3f4\U000e006b\U000e0072\U000e0034\U000e0036\U000e007f" - FLAG_FOR_KHUZESTAN_IR_10 = "\U0001f3f4\U000e0069\U000e0072\U000e0031\U000e0030\U000e007f" - FLAG_FOR_NANTOU_TW_NAN = "\U0001f3f4\U000e0074\U000e0077\U000e006e\U000e0061\U000e006e\U000e007f" - FLAG_FOR_DUSHANBE_TJ_DU = "\U0001f3f4\U000e0074\U000e006a\U000e0064\U000e0075\U000e007f" - FAMILY_MAN_DARK_SKIN_TONE_BABY_DARK_SKIN_TONE_GIRL_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f476\U0001f3ff\u200d\U0001f467\U0001f3ff" - FAMILY_WOMAN_MEDIUM_LIGHT_SKIN_TONE_BABY_MEDIUM_LIGHT_SKIN_TONE_GIRL_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f476\U0001f3fc\u200d\U0001f467\U0001f3fc" - FLAG_FOR_LIMBURG_NL_LI = "\U0001f3f4\U000e006e\U000e006c\U000e006c\U000e0069\U000e007f" - FLAG_FOR_AYACUCHO_PE_AYA = "\U0001f3f4\U000e0070\U000e0065\U000e0061\U000e0079\U000e0061\U000e007f" - FAMILY_WOMAN_MEDIUM_SKIN_TONE_BABY_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f476\U0001f3fd" - FLAG_FOR_SAINT_PHILIP_AG_08 = "\U0001f3f4\U000e0061\U000e0067\U000e0030\U000e0038\U000e007f" - FLAG_FOR_VALKA_LV_101 = "\U0001f3f4\U000e006c\U000e0076\U000e0031\U000e0030\U000e0031\U000e007f" - FAMILY_WOMAN_MEDIUM_LIGHT_SKIN_TONE_MAN_MEDIUM_LIGHT_SKIN_TONE_GIRL_MEDIUM_LIGHT_SKIN_TONE_BOY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f468\U0001f3fc\u200d\U0001f467\U0001f3fc\u200d\U0001f466\U0001f3fc" - FLAG_FOR_MDINA_MT_29 = "\U0001f3f4\U000e006d\U000e0074\U000e0032\U000e0039\U000e007f" - FLAG_FOR_NORTHERN_DENMARK_DK_81 = "\U0001f3f4\U000e0064\U000e006b\U000e0038\U000e0031\U000e007f" - FAMILY_WOMAN_MEDIUM_SKIN_TONE_WOMAN_MEDIUM_SKIN_TONE_BOY_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f469\U0001f3fd\u200d\U0001f466\U0001f3fd" - FLAG_FOR_GUAM_US_GU = "\U0001f3f4\U000e0075\U000e0073\U000e0067\U000e0075\U000e007f" - FLAG_FOR_SELANGOR_MY_10 = "\U0001f3f4\U000e006d\U000e0079\U000e0031\U000e0030\U000e007f" - FLAG_FOR_PRINCE_EDWARD_ISLAND_CA_PE = "\U0001f3f4\U000e0063\U000e0061\U000e0070\U000e0065\U000e007f" - FAMILY_MAN_MEDIUM_DARK_SKIN_TONE_MAN_MEDIUM_DARK_SKIN_TONE_GIRL_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f468\U0001f3fe\u200d\U0001f467\U0001f3fe" - FLAG_FOR_GORNO_BADAKHSHAN_TJ_GB = "\U0001f3f4\U000e0074\U000e006a\U000e0067\U000e0062\U000e007f" - FLAG_FOR_OGUN_NG_OG = "\U0001f3f4\U000e006e\U000e0067\U000e006f\U000e0067\U000e007f" - FLAG_FOR_EASTERN_LK_5 = "\U0001f3f4\U000e006c\U000e006b\U000e0035\U000e007f" - FAMILY_WOMAN_MEDIUM_DARK_SKIN_TONE_MAN_MEDIUM_DARK_SKIN_TONE_BABY_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f468\U0001f3fe\u200d\U0001f476\U0001f3fe" - FLAG_FOR_SANTA_ROSA_GT_SR = "\U0001f3f4\U000e0067\U000e0074\U000e0073\U000e0072\U000e007f" - FAMILY_MAN_LIGHT_SKIN_TONE_BOY_LIGHT_SKIN_TONE_GIRL_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f466\U0001f3fb\u200d\U0001f467\U0001f3fb" - FLAG_FOR_JAFARA_LY_JI = "\U0001f3f4\U000e006c\U000e0079\U000e006a\U000e0069\U000e007f" - FLAG_FOR_BASILICATA_IT_77 = "\U0001f3f4\U000e0069\U000e0074\U000e0037\U000e0037\U000e007f" - FLAG_FOR_GRONINGEN_NL_GR = "\U0001f3f4\U000e006e\U000e006c\U000e0067\U000e0072\U000e007f" - FLAG_FOR_MATO_GROSSO_DO_SUL_BR_MS = "\U0001f3f4\U000e0062\U000e0072\U000e006d\U000e0073\U000e007f" - FLAG_FOR_KANDAL_KH_8 = "\U0001f3f4\U000e006b\U000e0068\U000e0038\U000e007f" - FLAG_FOR_NORTH_WEST_ZA_NW = "\U0001f3f4\U000e007a\U000e0061\U000e006e\U000e0077\U000e007f" - FLAG_FOR_CONNECTICUT_US_CT = "\U0001f3f4\U000e0075\U000e0073\U000e0063\U000e0074\U000e007f" - FAMILY_MAN_DARK_SKIN_TONE_MAN_DARK_SKIN_TONE_GIRL_DARK_SKIN_TONE_GIRL_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f468\U0001f3ff\u200d\U0001f467\U0001f3ff\u200d\U0001f467\U0001f3ff" - FLAG_FOR_HEBEI_CN_13 = "\U0001f3f4\U000e0063\U000e006e\U000e0031\U000e0033\U000e007f" - FLAG_FOR_FA_ASALELEAGA_WS_FA = "\U0001f3f4\U000e0077\U000e0073\U000e0066\U000e0061\U000e007f" - FLAG_FOR_ALBORZ_IR_32 = "\U0001f3f4\U000e0069\U000e0072\U000e0033\U000e0032\U000e007f" - FLAG_FOR_BRASLOVCE_SI_151 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0035\U000e0031\U000e007f" - FLAG_FOR_HARYANA_IN_HR = "\U0001f3f4\U000e0069\U000e006e\U000e0068\U000e0072\U000e007f" - COUPLE_WITH_HEART_WOMAN_MEDIUM_SKIN_TONE_WOMAN_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fe" - FLAG_FOR_SAN_VICENTE_SV_SV = "\U0001f3f4\U000e0073\U000e0076\U000e0073\U000e0076\U000e007f" - FLAG_FOR_MATO_GROSSO_BR_MT = "\U0001f3f4\U000e0062\U000e0072\U000e006d\U000e0074\U000e007f" - FAMILY_WOMAN_LIGHT_SKIN_TONE_MAN_LIGHT_SKIN_TONE_BABY_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f468\U0001f3fb\u200d\U0001f476\U0001f3fb" - FLAG_FOR_PELOPONNESE_GR_J = "\U0001f3f4\U000e0067\U000e0072\U000e006a\U000e007f" - FLAG_FOR_ADAMAWA_NG_AD = "\U0001f3f4\U000e006e\U000e0067\U000e0061\U000e0064\U000e007f" - FAMILY_WOMAN_MEDIUM_SKIN_TONE_WOMAN_MEDIUM_SKIN_TONE_GIRL_MEDIUM_SKIN_TONE_BOY_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f469\U0001f3fd\u200d\U0001f467\U0001f3fd\u200d\U0001f466\U0001f3fd" - FLAG_FOR_KWAZULU_NATAL_ZA_NL = "\U0001f3f4\U000e007a\U000e0061\U000e006e\U000e006c\U000e007f" - FLAG_FOR_SINT_MAARTEN_NL_SX = "\U0001f3f4\U000e006e\U000e006c\U000e0073\U000e0078\U000e007f" - FAMILY_WOMAN_MEDIUM_DARK_SKIN_TONE_GIRL_MEDIUM_DARK_SKIN_TONE_BOY_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f467\U0001f3fe\u200d\U0001f466\U0001f3fe" - TAG_SPACE = "\U000e0020" - FAMILY_MAN_MEDIUM_DARK_SKIN_TONE_WOMAN_MEDIUM_DARK_SKIN_TONE_BABY_MEDIUM_DARK_SKIN_TONE_GIRL_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f469\U0001f3fe\u200d\U0001f476\U0001f3fe\u200d\U0001f467\U0001f3fe" - FLAG_FOR_CORDILLERA_ADMINISTRATIVE_PH_15 = "\U0001f3f4\U000e0070\U000e0068\U000e0031\U000e0035\U000e007f" - FLAG_FOR_AUCE_LV_010 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0031\U000e0030\U000e007f" - FLAG_FOR_ROCHA_UY_RO = "\U0001f3f4\U000e0075\U000e0079\U000e0072\U000e006f\U000e007f" - FLAG_FOR_FRIESLAND_NL_FR = "\U0001f3f4\U000e006e\U000e006c\U000e0066\U000e0072\U000e007f" - FLAG_FOR_AUVERGNE_RHONE_ALPES_FR_ARA = "\U0001f3f4\U000e0066\U000e0072\U000e0061\U000e0072\U000e0061\U000e007f" - FAMILY_MAN_MEDIUM_LIGHT_SKIN_TONE_MAN_MEDIUM_LIGHT_SKIN_TONE_BOY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f468\U0001f3fc\u200d\U0001f466\U0001f3fc" - FLAG_FOR_NOHIYAHOI_TOBEI_JUMHURI_TJ_RA = "\U0001f3f4\U000e0074\U000e006a\U000e0072\U000e0061\U000e007f" - APPLE_LOGO = "\uf8ff" - FLAG_FOR_SAO_TOME_ST_S = "\U0001f3f4\U000e0073\U000e0074\U000e0073\U000e007f" - FAMILY_WOMAN_LIGHT_SKIN_TONE_WOMAN_LIGHT_SKIN_TONE_GIRL_LIGHT_SKIN_TONE_BOY_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f469\U0001f3fb\u200d\U0001f467\U0001f3fb\u200d\U0001f466\U0001f3fb" - FAMILY_WOMAN_LIGHT_SKIN_TONE_MAN_LIGHT_SKIN_TONE_GIRL_LIGHT_SKIN_TONE_GIRL_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f468\U0001f3fb\u200d\U0001f467\U0001f3fb\u200d\U0001f467\U0001f3fb" - FLAG_FOR_ANDHRA_PRADESH_IN_AP = "\U0001f3f4\U000e0069\U000e006e\U000e0061\U000e0070\U000e007f" - FLAG_FOR_INCUKALNS_LV_037 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0033\U000e0037\U000e007f" - FLAG_FOR_KAKHETI_GE_KA = "\U0001f3f4\U000e0067\U000e0065\U000e006b\U000e0061\U000e007f" - FLAG_FOR_BOURGOGNE_FRANCHE_COMTE_FR_BFC = "\U0001f3f4\U000e0066\U000e0072\U000e0062\U000e0066\U000e0063\U000e007f" - KISS_WOMAN_MEDIUM_SKIN_TONE_MAN = "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468" - FLAG_FOR_ENNEDI_OUEST_TD_EO = "\U0001f3f4\U000e0074\U000e0064\U000e0065\U000e006f\U000e007f" - FLAG_FOR_SOUSS_MASSA_DRAA_MA_13 = "\U0001f3f4\U000e006d\U000e0061\U000e0031\U000e0033\U000e007f" - FLAG_FOR_USAK_TR_64 = "\U0001f3f4\U000e0074\U000e0072\U000e0036\U000e0034\U000e007f" - FLAG_FOR_JEJU_KR_49 = "\U0001f3f4\U000e006b\U000e0072\U000e0034\U000e0039\U000e007f" - FLAG_FOR_CHHATTISGARH_IN_CT = "\U0001f3f4\U000e0069\U000e006e\U000e0063\U000e0074\U000e007f" - FAMILY_MAN_MEDIUM_DARK_SKIN_TONE_MAN_MEDIUM_DARK_SKIN_TONE_BABY_MEDIUM_DARK_SKIN_TONE_BABY_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f468\U0001f3fe\u200d\U0001f476\U0001f3fe\u200d\U0001f476\U0001f3fe" - FAMILY_WOMAN_LIGHT_SKIN_TONE_MAN_LIGHT_SKIN_TONE_GIRL_LIGHT_SKIN_TONE_BABY_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f468\U0001f3fb\u200d\U0001f467\U0001f3fb\u200d\U0001f476\U0001f3fb" - KISS_WOMAN_MEDIUM_LIGHT_SKIN_TONE_WOMAN_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fe" - FAMILY_MAN_MEDIUM_DARK_SKIN_TONE_WOMAN_MEDIUM_DARK_SKIN_TONE_BABY_MEDIUM_DARK_SKIN_TONE_BABY_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f469\U0001f3fe\u200d\U0001f476\U0001f3fe\u200d\U0001f476\U0001f3fe" - FAMILY_WOMAN_MEDIUM_SKIN_TONE_BOY_MEDIUM_SKIN_TONE_BOY_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f466\U0001f3fd\u200d\U0001f466\U0001f3fd" - FAMILY_MAN_MEDIUM_SKIN_TONE_BOY_MEDIUM_SKIN_TONE_BOY_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f466\U0001f3fd\u200d\U0001f466\U0001f3fd" - FAMILY_WOMAN_MEDIUM_DARK_SKIN_TONE_GIRL_MEDIUM_DARK_SKIN_TONE_BABY_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f467\U0001f3fe\u200d\U0001f476\U0001f3fe" - FAMILY_MAN_MEDIUM_LIGHT_SKIN_TONE_WOMAN_MEDIUM_LIGHT_SKIN_TONE_BOY_MEDIUM_LIGHT_SKIN_TONE_BOY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f469\U0001f3fc\u200d\U0001f466\U0001f3fc\u200d\U0001f466\U0001f3fc" - FLAG_FOR_TLEMCEN_DZ_13 = "\U0001f3f4\U000e0064\U000e007a\U000e0031\U000e0033\U000e007f" - FAMILY_WOMAN_LIGHT_SKIN_TONE_BABY_LIGHT_SKIN_TONE_BABY_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f476\U0001f3fb\u200d\U0001f476\U0001f3fb" - FLAG_FOR_QUINTANA_ROO_MX_ROO = "\U0001f3f4\U000e006d\U000e0078\U000e0072\U000e006f\U000e006f\U000e007f" - FLAG_FOR_WESTERN_IS_3 = "\U0001f3f4\U000e0069\U000e0073\U000e0033\U000e007f" - FLAG_FOR_BANAADIR_SO_BN = "\U0001f3f4\U000e0073\U000e006f\U000e0062\U000e006e\U000e007f" - FLAG_FOR_RHINELAND_PALATINATE_DE_RP = "\U0001f3f4\U000e0064\U000e0065\U000e0072\U000e0070\U000e007f" - FLAG_FOR_MARITIME_TG_M = "\U0001f3f4\U000e0074\U000e0067\U000e006d\U000e007f" - FLAG_FOR_GYOR_MOSON_SOPRON_HU_GS = "\U0001f3f4\U000e0068\U000e0075\U000e0067\U000e0073\U000e007f" - FLAG_FOR_BEN_AROUS_TN_13 = "\U0001f3f4\U000e0074\U000e006e\U000e0031\U000e0033\U000e007f" - WOMAN_IN_BUSINESS_SUIT_LEVITATING_LIGHT_SKIN_TONE = "\U0001f574\U0001f3fb\u200d\u2640\ufe0f" - FAMILY_MAN_MEDIUM_LIGHT_SKIN_TONE_MAN_MEDIUM_LIGHT_SKIN_TONE_BABY_MEDIUM_LIGHT_SKIN_TONE_BABY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f468\U0001f3fc\u200d\U0001f476\U0001f3fc\u200d\U0001f476\U0001f3fc" - FAMILY_WOMAN_DARK_SKIN_TONE_WOMAN_DARK_SKIN_TONE_GIRL_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f469\U0001f3ff\u200d\U0001f467\U0001f3ff" - FLAG_FOR_FUKUI_JP_18 = "\U0001f3f4\U000e006a\U000e0070\U000e0031\U000e0038\U000e007f" - FLAG_FOR_EAST_NEW_BRITAIN_PG_EBR = "\U0001f3f4\U000e0070\U000e0067\U000e0065\U000e0062\U000e0072\U000e007f" - VARIATION_SELECTOR_16 = "\ufe0f" - FLAG_FOR_CENTRAL_EQUATORIA_SS_EC = "\U0001f3f4\U000e0073\U000e0073\U000e0065\U000e0063\U000e007f" - FAMILY_MAN_MEDIUM_DARK_SKIN_TONE_WOMAN_MEDIUM_DARK_SKIN_TONE_GIRL_MEDIUM_DARK_SKIN_TONE_BABY_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f469\U0001f3fe\u200d\U0001f467\U0001f3fe\u200d\U0001f476\U0001f3fe" - FLAG_FOR_KHAMMOUANE_LA_KH = "\U0001f3f4\U000e006c\U000e0061\U000e006b\U000e0068\U000e007f" - FLAG_FOR_DADRA_AND_NAGAR_HAVELI_IN_DN = "\U0001f3f4\U000e0069\U000e006e\U000e0064\U000e006e\U000e007f" - FAMILY_WOMAN_MEDIUM_SKIN_TONE_WOMAN_MEDIUM_SKIN_TONE_BOY_MEDIUM_SKIN_TONE_BOY_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f469\U0001f3fd\u200d\U0001f466\U0001f3fd\u200d\U0001f466\U0001f3fd" - FAMILY_WOMAN_MEDIUM_LIGHT_SKIN_TONE_MAN_MEDIUM_LIGHT_SKIN_TONE_BOY_MEDIUM_LIGHT_SKIN_TONE_BABY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f468\U0001f3fc\u200d\U0001f466\U0001f3fc\u200d\U0001f476\U0001f3fc" - FLAG_FOR_SOUTHERN_RED_SEA_ER_DK = "\U0001f3f4\U000e0065\U000e0072\U000e0064\U000e006b\U000e007f" - COUPLE_WITH_HEART_WOMAN_MEDIUM_LIGHT_SKIN_TONE_WOMAN_DARK_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3ff" - FAMILY_WOMAN_MEDIUM_SKIN_TONE_GIRL_MEDIUM_SKIN_TONE_BOY_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f467\U0001f3fd\u200d\U0001f466\U0001f3fd" - FAMILY_MAN_LIGHT_SKIN_TONE_WOMAN_LIGHT_SKIN_TONE_GIRL_LIGHT_SKIN_TONE_GIRL_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f469\U0001f3fb\u200d\U0001f467\U0001f3fb\u200d\U0001f467\U0001f3fb" - FAMILY_WOMAN_MEDIUM_SKIN_TONE_WOMAN_MEDIUM_SKIN_TONE_GIRL_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f469\U0001f3fd\u200d\U0001f467\U0001f3fd" - FAMILY_WOMAN_MEDIUM_LIGHT_SKIN_TONE_MAN_MEDIUM_LIGHT_SKIN_TONE_BOY_MEDIUM_LIGHT_SKIN_TONE_BOY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f468\U0001f3fc\u200d\U0001f466\U0001f3fc\u200d\U0001f466\U0001f3fc" - FLAG_FOR_VOJVODINA_RS_VO = "\U0001f3f4\U000e0072\U000e0073\U000e0076\U000e006f\U000e007f" - COUPLE_WITH_HEART_MAN_MEDIUM_SKIN_TONE_WOMAN_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fe" - FAMILY_MAN_MEDIUM_DARK_SKIN_TONE_MAN_MEDIUM_DARK_SKIN_TONE_BOY_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f468\U0001f3fe\u200d\U0001f466\U0001f3fe" - FLAG_FOR_ATLANTICO_SUR_NI_AS = "\U0001f3f4\U000e006e\U000e0069\U000e0061\U000e0073\U000e007f" - COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_SKIN_TONE = "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd" - FAMILY_WOMAN_MEDIUM_LIGHT_SKIN_TONE_WOMAN_MEDIUM_LIGHT_SKIN_TONE_GIRL_MEDIUM_LIGHT_SKIN_TONE_BOY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f469\U0001f3fc\u200d\U0001f467\U0001f3fc\u200d\U0001f466\U0001f3fc" - FAMILY_MAN_LIGHT_SKIN_TONE_WOMAN_LIGHT_SKIN_TONE_BABY_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f469\U0001f3fb\u200d\U0001f476\U0001f3fb" - FLAG_FOR_KERMAN_IR_15 = "\U0001f3f4\U000e0069\U000e0072\U000e0031\U000e0035\U000e007f" - KISS_WOMAN_MEDIUM_DARK_SKIN_TONE_MAN = "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468" - FAMILY_MAN_DARK_SKIN_TONE_WOMAN_DARK_SKIN_TONE_BOY_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f469\U0001f3ff\u200d\U0001f466\U0001f3ff" - FLAG_FOR_KALMAR_SE_H = "\U0001f3f4\U000e0073\U000e0065\U000e0068\U000e007f" - COUPLE_WITH_HEART_MAN_LIGHT_SKIN_TONE_MAN_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe" - FLAG_FOR_ALMATY_REGION_KZ_ALM = "\U0001f3f4\U000e006b\U000e007a\U000e0061\U000e006c\U000e006d\U000e007f" - FLAG_FOR_ZLINSKY_KRAJ_CZ_72 = "\U0001f3f4\U000e0063\U000e007a\U000e0037\U000e0032\U000e007f" - FLAG_FOR_SANGRE_GRANDE_TT_SGE = "\U0001f3f4\U000e0074\U000e0074\U000e0073\U000e0067\U000e0065\U000e007f" - FAMILY_MAN_MEDIUM_LIGHT_SKIN_TONE_WOMAN_MEDIUM_LIGHT_SKIN_TONE_GIRL_MEDIUM_LIGHT_SKIN_TONE_BOY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f469\U0001f3fc\u200d\U0001f467\U0001f3fc\u200d\U0001f466\U0001f3fc" - FLAG_FOR_PUERTO_RICO_US_PR = "\U0001f3f4\U000e0075\U000e0073\U000e0070\U000e0072\U000e007f" - FLAG_FOR_ALO_WF_AL = "\U0001f3f4\U000e0077\U000e0066\U000e0061\U000e006c\U000e007f" - FLAG_FOR_WASHINGTON_DC_US_DC = "\U0001f3f4\U000e0075\U000e0073\U000e0064\U000e0063\U000e007f" - FLAG_FOR_LA_REUNION_FR_LRE = "\U0001f3f4\U000e0066\U000e0072\U000e006c\U000e0072\U000e0065\U000e007f" - FAMILY_MAN_DARK_SKIN_TONE_WOMAN_DARK_SKIN_TONE_GIRL_DARK_SKIN_TONE_BOY_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f469\U0001f3ff\u200d\U0001f467\U0001f3ff\u200d\U0001f466\U0001f3ff" - FLAG_FOR_GRAD_SI_158 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0035\U000e0038\U000e007f" - FLAG_FOR_TEXAS_US_TX = "\U0001f3f4\U000e0075\U000e0073\U000e0074\U000e0078\U000e007f" - MAN_ZOMBIE_MEDIUM_DARK_SKIN_TONE = "\U0001f9df\U0001f3fe\u200d\u2642\ufe0f" - FLAG_FOR_PARAIBA_BR_PB = "\U0001f3f4\U000e0062\U000e0072\U000e0070\U000e0062\U000e007f" - FLAG_FOR_VARGAS_VE_X = "\U0001f3f4\U000e0076\U000e0065\U000e0078\U000e007f" - TAG_LATIN_CAPITAL_LETTER_H = "\U000e0048" - FLAG_FOR_BERN_CH_BE = "\U0001f3f4\U000e0063\U000e0068\U000e0062\U000e0065\U000e007f" - FLAG_FOR_MARA_TZ_13 = "\U0001f3f4\U000e0074\U000e007a\U000e0031\U000e0033\U000e007f" - FLAG_FOR_THURINGIA_DE_TH = "\U0001f3f4\U000e0064\U000e0065\U000e0074\U000e0068\U000e007f" - COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE = "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe" - FAMILY_WOMAN_MEDIUM_DARK_SKIN_TONE_WOMAN_MEDIUM_DARK_SKIN_TONE_GIRL_MEDIUM_DARK_SKIN_TONE_GIRL_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f469\U0001f3fe\u200d\U0001f467\U0001f3fe\u200d\U0001f467\U0001f3fe" - FAMILY_WOMAN_LIGHT_SKIN_TONE_WOMAN_LIGHT_SKIN_TONE_BOY_LIGHT_SKIN_TONE_BOY_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f469\U0001f3fb\u200d\U0001f466\U0001f3fb\u200d\U0001f466\U0001f3fb" - TAG_LATIN_CAPITAL_LETTER_R = "\U000e0052" - FAMILY_WOMAN_MEDIUM_DARK_SKIN_TONE_BOY_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f466\U0001f3fe" - FLAG_FOR_GOTLAND_SE_I = "\U0001f3f4\U000e0073\U000e0065\U000e0069\U000e007f" - FLAG_FOR_ANDAMAN_AND_NICOBAR_ISLANDS_IN_AN = "\U0001f3f4\U000e0069\U000e006e\U000e0061\U000e006e\U000e007f" - FLAG_FOR_SAN_ANDRES_ANDAMP_PROVIDENCIA_CO_SAP = "\U0001f3f4\U000e0063\U000e006f\U000e0073\U000e0061\U000e0070\U000e007f" - FAMILY_WOMAN_MEDIUM_DARK_SKIN_TONE_WOMAN_MEDIUM_DARK_SKIN_TONE_BABY_MEDIUM_DARK_SKIN_TONE_BABY_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f469\U0001f3fe\u200d\U0001f476\U0001f3fe\u200d\U0001f476\U0001f3fe" - FLAG_FOR_SOUTHERN_DENMARK_DK_83 = "\U0001f3f4\U000e0064\U000e006b\U000e0038\U000e0033\U000e007f" - FLAG_FOR_AMUR_RU_AMU = "\U0001f3f4\U000e0072\U000e0075\U000e0061\U000e006d\U000e0075\U000e007f" - FAMILY_MAN_MEDIUM_SKIN_TONE_WOMAN_MEDIUM_SKIN_TONE_BABY_MEDIUM_SKIN_TONE_BOY_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f469\U0001f3fd\u200d\U0001f476\U0001f3fd\u200d\U0001f466\U0001f3fd" - FLAG_FOR_NAGASAKI_JP_42 = "\U0001f3f4\U000e006a\U000e0070\U000e0034\U000e0032\U000e007f" - FLAG_FOR_NEWFOUNDLAND_AND_LABRADOR_CA_NL = "\U0001f3f4\U000e0063\U000e0061\U000e006e\U000e006c\U000e007f" - FAMILY_MAN_DARK_SKIN_TONE_MAN_DARK_SKIN_TONE_BOY_DARK_SKIN_TONE_GIRL_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f468\U0001f3ff\u200d\U0001f466\U0001f3ff\u200d\U0001f467\U0001f3ff" - TAG_GREATER_THAN_SIGN = "\U000e003e" - FLAG_FOR_RAMALLAH_AND_AL_BIREH_PS_RBH = "\U0001f3f4\U000e0070\U000e0073\U000e0072\U000e0062\U000e0068\U000e007f" - FAMILY_WOMAN_MEDIUM_SKIN_TONE_WOMAN_MEDIUM_SKIN_TONE_BABY_MEDIUM_SKIN_TONE_GIRL_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f469\U0001f3fd\u200d\U0001f476\U0001f3fd\u200d\U0001f467\U0001f3fd" - KISS_MAN_MAN_MEDIUM_SKIN_TONE = "\U0001f468\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd" - FLAG_FOR_ANTOFAGASTA_CL_AN = "\U0001f3f4\U000e0063\U000e006c\U000e0061\U000e006e\U000e007f" - FAMILY_MAN_LIGHT_SKIN_TONE_BABY_LIGHT_SKIN_TONE_BOY_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f476\U0001f3fb\u200d\U0001f466\U0001f3fb" - FAMILY_MAN_LIGHT_SKIN_TONE_WOMAN_LIGHT_SKIN_TONE_GIRL_LIGHT_SKIN_TONE_BOY_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f469\U0001f3fb\u200d\U0001f467\U0001f3fb\u200d\U0001f466\U0001f3fb" - COUPLE_WITH_HEART_WOMAN_MEDIUM_DARK_SKIN_TONE_MAN_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb" - KISS_MAN_MEDIUM_SKIN_TONE_MAN_DARK_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff" - FLAG_FOR_CHUVASH_RU_CU = "\U0001f3f4\U000e0072\U000e0075\U000e0063\U000e0075\U000e007f" - FAMILY_MAN_MEDIUM_LIGHT_SKIN_TONE_WOMAN_MEDIUM_LIGHT_SKIN_TONE_BABY_MEDIUM_LIGHT_SKIN_TONE_BOY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f469\U0001f3fc\u200d\U0001f476\U0001f3fc\u200d\U0001f466\U0001f3fc" - FAMILY_MAN_MEDIUM_SKIN_TONE_WOMAN_MEDIUM_SKIN_TONE_GIRL_MEDIUM_SKIN_TONE_GIRL_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f469\U0001f3fd\u200d\U0001f467\U0001f3fd\u200d\U0001f467\U0001f3fd" - FAMILY_WOMAN_WOMAN_BOY_GIRL = "\U0001f469\u200d\U0001f469\u200d\U0001f466\u200d\U0001f467" - FAMILY_MAN_MEDIUM_DARK_SKIN_TONE_BOY_MEDIUM_DARK_SKIN_TONE_BOY_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f466\U0001f3fe\u200d\U0001f466\U0001f3fe" - FAMILY_MAN_MEDIUM_DARK_SKIN_TONE_MAN_MEDIUM_DARK_SKIN_TONE_GIRL_MEDIUM_DARK_SKIN_TONE_GIRL_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f468\U0001f3fe\u200d\U0001f467\U0001f3fe\u200d\U0001f467\U0001f3fe" - FLAG_FOR_ULSTER_IE_U = "\U0001f3f4\U000e0069\U000e0065\U000e0075\U000e007f" - FAMILY_WOMAN_DARK_SKIN_TONE_BABY_DARK_SKIN_TONE_GIRL_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f476\U0001f3ff\u200d\U0001f467\U0001f3ff" - FLAG_FOR_NABEUL_TN_21 = "\U0001f3f4\U000e0074\U000e006e\U000e0032\U000e0031\U000e007f" - FAMILY_MAN_BABY_BABY = "\U0001f468\u200d\U0001f476\u200d\U0001f476" - FLAG_FOR_TAIPEI_TW_TPE = "\U0001f3f4\U000e0074\U000e0077\U000e0074\U000e0070\U000e0065\U000e007f" - COUPLE_WITH_HEART_MAN_WOMAN_DARK_SKIN_TONE = "\U0001f468\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3ff" - FAMILY_MAN_MEDIUM_SKIN_TONE_MAN_MEDIUM_SKIN_TONE_BOY_MEDIUM_SKIN_TONE_BOY_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f468\U0001f3fd\u200d\U0001f466\U0001f3fd\u200d\U0001f466\U0001f3fd" - FLAG_FOR_ZEELAND_NL_ZE = "\U0001f3f4\U000e006e\U000e006c\U000e007a\U000e0065\U000e007f" - FLAG_FOR_JAVA_ID_JW = "\U0001f3f4\U000e0069\U000e0064\U000e006a\U000e0077\U000e007f" - FLAG_FOR_AZAD_KASHMIR_PK_JK = "\U0001f3f4\U000e0070\U000e006b\U000e006a\U000e006b\U000e007f" - FAMILY_WOMAN_MEDIUM_LIGHT_SKIN_TONE_BABY_MEDIUM_LIGHT_SKIN_TONE_BOY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f476\U0001f3fc\u200d\U0001f466\U0001f3fc" - FLAG_FOR_UNITY_SS_UY = "\U0001f3f4\U000e0073\U000e0073\U000e0075\U000e0079\U000e007f" - FLAG_FOR_CENTRAL_LUZON_PH_03 = "\U0001f3f4\U000e0070\U000e0068\U000e0030\U000e0033\U000e007f" - FAMILY_WOMAN_DARK_SKIN_TONE_MAN_DARK_SKIN_TONE_BOY_DARK_SKIN_TONE_BOY_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f468\U0001f3ff\u200d\U0001f466\U0001f3ff\u200d\U0001f466\U0001f3ff" - FAMILY_WOMAN_LIGHT_SKIN_TONE_WOMAN_LIGHT_SKIN_TONE_GIRL_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f469\U0001f3fb\u200d\U0001f467\U0001f3fb" - FLAG_FOR_SOUTHERN_LK_3 = "\U0001f3f4\U000e006c\U000e006b\U000e0033\U000e007f" - FLAG_FOR_SAO_PAULO_BR_SP = "\U0001f3f4\U000e0062\U000e0072\U000e0073\U000e0070\U000e007f" - FAMILY_MAN_LIGHT_SKIN_TONE_WOMAN_LIGHT_SKIN_TONE_BOY_LIGHT_SKIN_TONE_GIRL_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f469\U0001f3fb\u200d\U0001f466\U0001f3fb\u200d\U0001f467\U0001f3fb" - FLAG_FOR_JAMMU_AND_KASHMIR_IN_JK = "\U0001f3f4\U000e0069\U000e006e\U000e006a\U000e006b\U000e007f" - FAMILY_MAN_MEDIUM_SKIN_TONE_MAN_MEDIUM_SKIN_TONE_BABY_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f468\U0001f3fd\u200d\U0001f476\U0001f3fd" - FLAG_FOR_CAT_ISLAND_BS_CI = "\U0001f3f4\U000e0062\U000e0073\U000e0063\U000e0069\U000e007f" - FLAG_FOR_LAGHOUAT_DZ_03 = "\U0001f3f4\U000e0064\U000e007a\U000e0030\U000e0033\U000e007f" - FAMILY_WOMAN_MEDIUM_LIGHT_SKIN_TONE_WOMAN_MEDIUM_LIGHT_SKIN_TONE_BOY_MEDIUM_LIGHT_SKIN_TONE_GIRL_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f469\U0001f3fc\u200d\U0001f466\U0001f3fc\u200d\U0001f467\U0001f3fc" - FLAG_FOR_MARJ_LY_MJ = "\U0001f3f4\U000e006c\U000e0079\U000e006d\U000e006a\U000e007f" - FAMILY_WOMAN_LIGHT_SKIN_TONE_WOMAN_LIGHT_SKIN_TONE_BOY_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f469\U0001f3fb\u200d\U0001f466\U0001f3fb" - FAMILY_MAN_MEDIUM_LIGHT_SKIN_TONE_MAN_MEDIUM_LIGHT_SKIN_TONE_BOY_MEDIUM_LIGHT_SKIN_TONE_BABY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f468\U0001f3fc\u200d\U0001f466\U0001f3fc\u200d\U0001f476\U0001f3fc" - COUPLE_WITH_HEART_WOMAN_MEDIUM_SKIN_TONE_WOMAN_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fb" - FLAG_FOR_TRUJILLO_VE_T = "\U0001f3f4\U000e0076\U000e0065\U000e0074\U000e007f" - WOMAN_WITH_HEADSCARF_MEDIUM_DARK_SKIN_TONE = "\U0001f9d5\U0001f3fe\u200d\u2640\ufe0f" - FLAG_FOR_SAARLAND_DE_SL = "\U0001f3f4\U000e0064\U000e0065\U000e0073\U000e006c\U000e007f" - FAMILY_WOMAN_MEDIUM_DARK_SKIN_TONE_WOMAN_MEDIUM_DARK_SKIN_TONE_GIRL_MEDIUM_DARK_SKIN_TONE_BOY_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f469\U0001f3fe\u200d\U0001f467\U0001f3fe\u200d\U0001f466\U0001f3fe" - FLAG_FOR_TRENTINO_SOUTH_TYROL_IT_32 = "\U0001f3f4\U000e0069\U000e0074\U000e0033\U000e0032\U000e007f" - FLAG_FOR_KHARTOUM_SD_KH = "\U0001f3f4\U000e0073\U000e0064\U000e006b\U000e0068\U000e007f" - TAG_EXCLAMATION_MARK = "\U000e0021" - FAMILY_WOMAN_LIGHT_SKIN_TONE_BOY_LIGHT_SKIN_TONE_BOY_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f466\U0001f3fb\u200d\U0001f466\U0001f3fb" - FAMILY_WOMAN_MEDIUM_DARK_SKIN_TONE_MAN_MEDIUM_DARK_SKIN_TONE_BOY_MEDIUM_DARK_SKIN_TONE_GIRL_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f468\U0001f3fe\u200d\U0001f466\U0001f3fe\u200d\U0001f467\U0001f3fe" - FLAG_FOR_TRANSNISTRIA_MD_SN = "\U0001f3f4\U000e006d\U000e0064\U000e0073\U000e006e\U000e007f" - FLAG_FOR_PINAR_DEL_RIO_CU_01 = "\U0001f3f4\U000e0063\U000e0075\U000e0030\U000e0031\U000e007f" - FAMILY_WOMAN_MEDIUM_LIGHT_SKIN_TONE_MAN_MEDIUM_LIGHT_SKIN_TONE_BABY_MEDIUM_LIGHT_SKIN_TONE_BOY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f468\U0001f3fc\u200d\U0001f476\U0001f3fc\u200d\U0001f466\U0001f3fc" - FAMILY_WOMAN_LIGHT_SKIN_TONE_GIRL_LIGHT_SKIN_TONE_GIRL_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f467\U0001f3fb\u200d\U0001f467\U0001f3fb" - FLAG_FOR_KEDAH_MY_02 = "\U0001f3f4\U000e006d\U000e0079\U000e0030\U000e0032\U000e007f" - FLAG_FOR_JILIN_CN_22 = "\U0001f3f4\U000e0063\U000e006e\U000e0032\U000e0032\U000e007f" - ZERO_WIDTH_JOINER = "\u200d" - FAMILY_WOMAN_DARK_SKIN_TONE_MAN_DARK_SKIN_TONE_GIRL_DARK_SKIN_TONE_GIRL_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f468\U0001f3ff\u200d\U0001f467\U0001f3ff\u200d\U0001f467\U0001f3ff" - FLAG_FOR_MANUFAHI_TL_MF = "\U0001f3f4\U000e0074\U000e006c\U000e006d\U000e0066\U000e007f" - FAMILY_WOMAN_MEDIUM_SKIN_TONE_WOMAN_MEDIUM_SKIN_TONE_GIRL_MEDIUM_SKIN_TONE_GIRL_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f469\U0001f3fd\u200d\U0001f467\U0001f3fd\u200d\U0001f467\U0001f3fd" - FLAG_FOR_STUDENICANI_MK_74 = "\U0001f3f4\U000e006d\U000e006b\U000e0037\U000e0034\U000e007f" - FLAG_FOR_ILUKSTE_LV_036 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0033\U000e0036\U000e007f" - FLAG_FOR_MAZANDARAN_IR_21 = "\U0001f3f4\U000e0069\U000e0072\U000e0032\U000e0031\U000e007f" - FLAG_FOR_VISEU_PT_18 = "\U0001f3f4\U000e0070\U000e0074\U000e0031\U000e0038\U000e007f" - FLAG_FOR_ESTUAIRE_GA_1 = "\U0001f3f4\U000e0067\U000e0061\U000e0031\U000e007f" - FLAG_FOR_GANSU_CN_62 = "\U0001f3f4\U000e0063\U000e006e\U000e0036\U000e0032\U000e007f" - FLAG_FOR_SOUTH_KHORASAN_IR_29 = "\U0001f3f4\U000e0069\U000e0072\U000e0032\U000e0039\U000e007f" - FLAG_FOR_DIOURBEL_SN_DB = "\U0001f3f4\U000e0073\U000e006e\U000e0064\U000e0062\U000e007f" - FLAG_FOR_MORELOS_MX_MOR = "\U0001f3f4\U000e006d\U000e0078\U000e006d\U000e006f\U000e0072\U000e007f" - FLAG_FOR_DELAWARE_US_DE = "\U0001f3f4\U000e0075\U000e0073\U000e0064\U000e0065\U000e007f" - FLAG_FOR_SIGAVE_WF_SG = "\U0001f3f4\U000e0077\U000e0066\U000e0073\U000e0067\U000e007f" - FLAG_FOR_RIVERA_UY_RV = "\U0001f3f4\U000e0075\U000e0079\U000e0072\U000e0076\U000e007f" - FLAG_FOR_TUNGURAHUA_EC_T = "\U0001f3f4\U000e0065\U000e0063\U000e0074\U000e007f" - BEAMED_DESCENDING_MUSICAL_NOTES = "\U0001f39d" - FLAG_FOR_KALININGRAD_RU_KGD = "\U0001f3f4\U000e0072\U000e0075\U000e006b\U000e0067\U000e0064\U000e007f" - TAG_LATIN_CAPITAL_LETTER_T = "\U000e0054" - FLAG_FOR_TOMBOUCTOU_ML_6 = "\U0001f3f4\U000e006d\U000e006c\U000e0036\U000e007f" - COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fc" - FLAG_FOR_ENCAMP_AD_03 = "\U0001f3f4\U000e0061\U000e0064\U000e0030\U000e0033\U000e007f" - FLAG_FOR_MIAOLI_TW_MIA = "\U0001f3f4\U000e0074\U000e0077\U000e006d\U000e0069\U000e0061\U000e007f" - FLAG_FOR_KENTUCKY_US_KY = "\U0001f3f4\U000e0075\U000e0073\U000e006b\U000e0079\U000e007f" - FLAG_FOR_GUSINJE_ME_22 = "\U0001f3f4\U000e006d\U000e0065\U000e0032\U000e0032\U000e007f" - FLAG_FOR_SAATLY_AZ_SAT = "\U0001f3f4\U000e0061\U000e007a\U000e0073\U000e0061\U000e0074\U000e007f" - FLAG_FOR_NUNAVUT_CA_NU = "\U0001f3f4\U000e0063\U000e0061\U000e006e\U000e0075\U000e007f" - FLAG_FOR_SPANISH_WELLS_BS_SW = "\U0001f3f4\U000e0062\U000e0073\U000e0073\U000e0077\U000e007f" - FLAG_FOR_BITLIS_TR_13 = "\U0001f3f4\U000e0074\U000e0072\U000e0031\U000e0033\U000e007f" - FLAG_FOR_BADGHIS_AF_BDG = "\U0001f3f4\U000e0061\U000e0066\U000e0062\U000e0064\U000e0067\U000e007f" - KISS_MAN_DARK_SKIN_TONE_MAN_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd" - FLAG_FOR_SAGA_JP_41 = "\U0001f3f4\U000e006a\U000e0070\U000e0034\U000e0031\U000e007f" - FLAG_FOR_ST_PAUL_S_BAY_MT_51 = "\U0001f3f4\U000e006d\U000e0074\U000e0035\U000e0031\U000e007f" - FLAG_FOR_TORBA_VU_TOB = "\U0001f3f4\U000e0076\U000e0075\U000e0074\U000e006f\U000e0062\U000e007f" - KISS_MAN_DARK_SKIN_TONE_WOMAN_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3ff" - FLAG_FOR_BERLIN_DE_BE = "\U0001f3f4\U000e0064\U000e0065\U000e0062\U000e0065\U000e007f" - FLAG_FOR_BABITE_LV_012 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0031\U000e0032\U000e007f" - FLAG_FOR_EL_VALLE_DO_37 = "\U0001f3f4\U000e0064\U000e006f\U000e0033\U000e0037\U000e007f" - FLAG_FOR_BENI_SUEF_EG_BNS = "\U0001f3f4\U000e0065\U000e0067\U000e0062\U000e006e\U000e0073\U000e007f" - FLAG_FOR_SFAX_TN_61 = "\U0001f3f4\U000e0074\U000e006e\U000e0036\U000e0031\U000e007f" - FLAG_FOR_MONTE_CARLO_MC_MC = "\U0001f3f4\U000e006d\U000e0063\U000e006d\U000e0063\U000e007f" - FLAG_FOR_YUNNAN_CN_53 = "\U0001f3f4\U000e0063\U000e006e\U000e0035\U000e0033\U000e007f" - FLAG_FOR_KLAIPEDOS_MUNICIPALITY_LT_20 = "\U0001f3f4\U000e006c\U000e0074\U000e0032\U000e0030\U000e007f" - FLAG_FOR_BARINGO_KE_01 = "\U0001f3f4\U000e006b\U000e0065\U000e0030\U000e0031\U000e007f" - FLAG_FOR_CENTRAL_RIVER_DIVISION_GM_M = "\U0001f3f4\U000e0067\U000e006d\U000e006d\U000e007f" - FLAG_FOR_AMANAT_AL_ASIMAH_YE_SA = "\U0001f3f4\U000e0079\U000e0065\U000e0073\U000e0061\U000e007f" - FLAG_FOR_HAUTS_BASSINS_BF_09 = "\U0001f3f4\U000e0062\U000e0066\U000e0030\U000e0039\U000e007f" - FAMILY_MAN_MEDIUM_DARK_SKIN_TONE_BOY_MEDIUM_DARK_SKIN_TONE_BABY_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f466\U0001f3fe\u200d\U0001f476\U0001f3fe" - TAG_EQUALS_SIGN = "\U000e003d" - FAMILY_WOMAN_MEDIUM_DARK_SKIN_TONE_MAN_MEDIUM_DARK_SKIN_TONE_GIRL_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f468\U0001f3fe\u200d\U0001f467\U0001f3fe" - FLAG_FOR_SENGLEA_MT_20 = "\U0001f3f4\U000e006d\U000e0074\U000e0032\U000e0030\U000e007f" - ALTERNATE_ONE_WAY_LEFT_WAY_TRAFFIC = "\u26d5" - FLAG_FOR_G_ASRI_MT_16 = "\U0001f3f4\U000e006d\U000e0074\U000e0031\U000e0036\U000e007f" - WHITE_CHESS_PAWN = "\u2659" - FLAG_FOR_HAU_GIANG_VN_73 = "\U0001f3f4\U000e0076\U000e006e\U000e0037\U000e0033\U000e007f" - MAN_ZOMBIE_MEDIUM_SKIN_TONE = "\U0001f9df\U0001f3fd\u200d\u2642\ufe0f" - FLAG_FOR_TEARCE_MK_75 = "\U0001f3f4\U000e006d\U000e006b\U000e0037\U000e0035\U000e007f" - FLAG_FOR_FAMAGUSTA_CY_04 = "\U0001f3f4\U000e0063\U000e0079\U000e0030\U000e0034\U000e007f" - FLAG_FOR_COTOPAXI_EC_X = "\U0001f3f4\U000e0065\U000e0063\U000e0078\U000e007f" - FAMILY_WOMAN_MEDIUM_LIGHT_SKIN_TONE_BOY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f466\U0001f3fc" - FLAG_FOR_ANG_THONG_TH_15 = "\U0001f3f4\U000e0074\U000e0068\U000e0031\U000e0035\U000e007f" - RECYCLING_SYMBOL_FOR_TYPE_2_PLASTICS = "\u2674" - FLAG_FOR_SETUBAL_PT_15 = "\U0001f3f4\U000e0070\U000e0074\U000e0031\U000e0035\U000e007f" - BLACK_CIRCLE_WITH_WHITE_DOT_RIGHT = "\u2688" - WHITE_SPADE_SUIT = "\u2664" - FLAG_FOR_DAGDA_LV_024 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0032\U000e0034\U000e007f" - FLAG_FOR_BAR_ME_02 = "\U0001f3f4\U000e006d\U000e0065\U000e0030\U000e0032\U000e007f" - FLAG_FOR_CATALONIA_ES_CT = "\U0001f3f4\U000e0065\U000e0073\U000e0063\U000e0074\U000e007f" - COUPLE_WITH_HEART_MAN_MAN_MEDIUM_DARK_SKIN_TONE = "\U0001f468\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe" - FLAG_FOR_WESTERN_SB_WE = "\U0001f3f4\U000e0073\U000e0062\U000e0077\U000e0065\U000e007f" - FLAG_FOR_BAYQONGYR_KZ_BAY = "\U0001f3f4\U000e006b\U000e007a\U000e0062\U000e0061\U000e0079\U000e007f" - NOTE_PAD = "\U0001f5ca" - BLACK_CHESS_KING = "\u265a" - FLAG_FOR_PORTO_PT_13 = "\U0001f3f4\U000e0070\U000e0074\U000e0031\U000e0033\U000e007f" - FLAG_FOR_KAMPONG_CHHNANG_KH_4 = "\U0001f3f4\U000e006b\U000e0068\U000e0034\U000e007f" - FLAG_FOR_DAKAR_SN_DK = "\U0001f3f4\U000e0073\U000e006e\U000e0064\U000e006b\U000e007f" - FLAG_FOR_FEDERAL_CAPITAL_TERRITORY_PL_PM = "\U0001f3f4\U000e0070\U000e006c\U000e0070\U000e006d\U000e007f" - WHITE_TOUCHTONE_TELEPHONE = "\U0001f57e" - FLAG_FOR_TAMIL_NADU_IN_TN = "\U0001f3f4\U000e0069\U000e006e\U000e0074\U000e006e\U000e007f" - FLAG_FOR_BRAGA_PT_03 = "\U0001f3f4\U000e0070\U000e0074\U000e0030\U000e0033\U000e007f" - FLAG_FOR_SPELUGUES_MC_SP = "\U0001f3f4\U000e006d\U000e0063\U000e0073\U000e0070\U000e007f" - FLAG_FOR_JHARKHAND_IN_JH = "\U0001f3f4\U000e0069\U000e006e\U000e006a\U000e0068\U000e007f" - FLAG_FOR_BOCAS_DEL_TORO_PA_1 = "\U0001f3f4\U000e0070\U000e0061\U000e0031\U000e007f" - FLAG_FOR_CANAR_EC_F = "\U0001f3f4\U000e0065\U000e0063\U000e0066\U000e007f" - FLAG_FOR_AICHI_JP_23 = "\U0001f3f4\U000e006a\U000e0070\U000e0032\U000e0033\U000e007f" - FLAG_FOR_OMAHEKE_NA_OH = "\U0001f3f4\U000e006e\U000e0061\U000e006f\U000e0068\U000e007f" - FLAG_FOR_MERSIN_TR_33 = "\U0001f3f4\U000e0074\U000e0072\U000e0033\U000e0033\U000e007f" - FLAG_FOR_OSH_REGION_KG_O = "\U0001f3f4\U000e006b\U000e0067\U000e006f\U000e007f" - FLAG_FOR_MIYAZAKI_JP_45 = "\U0001f3f4\U000e006a\U000e0070\U000e0034\U000e0035\U000e007f" - WHITE_RIGHT_POINTING_INDEX = "\u261e" - COUPLE_WITH_HEART_WOMAN_MEDIUM_SKIN_TONE_MAN_DARK_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff" - FLAG_FOR_PAIJANNE_TAVASTIA_FI_16 = "\U0001f3f4\U000e0066\U000e0069\U000e0031\U000e0036\U000e007f" - FLAG_FOR_TARANAKI_NZ_TKI = "\U0001f3f4\U000e006e\U000e007a\U000e0074\U000e006b\U000e0069\U000e007f" - FLAG_FOR_ILLINOIS_US_IL = "\U0001f3f4\U000e0075\U000e0073\U000e0069\U000e006c\U000e007f" - FLAG_FOR_BAMYAN_AF_BAM = "\U0001f3f4\U000e0061\U000e0066\U000e0062\U000e0061\U000e006d\U000e007f" - FLAG_FOR_FLORIDA_UY_FD = "\U0001f3f4\U000e0075\U000e0079\U000e0066\U000e0064\U000e007f" - UP_POINTING_AIRPLANE = "\U0001f6e7" - FLAG_FOR_THAI_NGUYEN_VN_69 = "\U0001f3f4\U000e0076\U000e006e\U000e0036\U000e0039\U000e007f" - FLAG_FOR_VIROVITICA_PODRAVINA_HR_10 = "\U0001f3f4\U000e0068\U000e0072\U000e0031\U000e0030\U000e007f" - KISS_MAN_LIGHT_SKIN_TONE_WOMAN_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fe" - WHITE_LEFT_POINTING_INDEX = "\u261c" - FLAG_FOR_KIGOMA_TZ_08 = "\U0001f3f4\U000e0074\U000e007a\U000e0030\U000e0038\U000e007f" - FLAG_FOR_SUL_GW_S = "\U0001f3f4\U000e0067\U000e0077\U000e0073\U000e007f" - FLAG_FOR_BELIZE_BZ_BZ = "\U0001f3f4\U000e0062\U000e007a\U000e0062\U000e007a\U000e007f" - FLAG_FOR_HHOHHO_SZ_HH = "\U0001f3f4\U000e0073\U000e007a\U000e0068\U000e0068\U000e007f" - FLAG_FOR_AKTOBE_KZ_AKT = "\U0001f3f4\U000e006b\U000e007a\U000e0061\U000e006b\U000e0074\U000e007f" - FLAG_FOR_ZHEJIANG_CN_33 = "\U0001f3f4\U000e0063\U000e006e\U000e0033\U000e0033\U000e007f" - FLAG_FOR_EL_PARAISO_HN_EP = "\U0001f3f4\U000e0068\U000e006e\U000e0065\U000e0070\U000e007f" - MONOGRAM_FOR_YIN = "\u268b" - FLAG_FOR_NUGAL_SO_NU = "\U0001f3f4\U000e0073\U000e006f\U000e006e\U000e0075\U000e007f" - MAHJONG_TILE_NINE_OF_CHARACTERS = "\U0001f00f" - FLAG_FOR_ATTICA_GR_I = "\U0001f3f4\U000e0067\U000e0072\U000e0069\U000e007f" - NOTE_PAGE = "\U0001f5c9" - FLAG_FOR_DURBE_LV_028 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0032\U000e0038\U000e007f" - FLAG_FOR_CAYO_BZ_CY = "\U0001f3f4\U000e0062\U000e007a\U000e0063\U000e0079\U000e007f" - SOFT_SHELL_FLOPPY_DISK = "\U0001f5ac" - FLAG_FOR_SAKHA_RU_SA = "\U0001f3f4\U000e0072\U000e0075\U000e0073\U000e0061\U000e007f" - FLAG_FOR_SEGOU_ML_4 = "\U0001f3f4\U000e006d\U000e006c\U000e0034\U000e007f" - FLAG_FOR_BALKH_AF_BAL = "\U0001f3f4\U000e0061\U000e0066\U000e0062\U000e0061\U000e006c\U000e007f" - FLAG_FOR_WESTFJORDS_IS_4 = "\U0001f3f4\U000e0069\U000e0073\U000e0034\U000e007f" - FLAG_FOR_CALIFORNIA_US_CA = "\U0001f3f4\U000e0075\U000e0073\U000e0063\U000e0061\U000e007f" - FLAG_FOR_SETIF_DZ_19 = "\U0001f3f4\U000e0064\U000e007a\U000e0031\U000e0039\U000e007f" - TAG_COMMA = "\U000e002c" - FLAG_FOR_BAJA_VERAPAZ_GT_BV = "\U0001f3f4\U000e0067\U000e0074\U000e0062\U000e0076\U000e007f" - TAG_LATIN_CAPITAL_LETTER_E = "\U000e0045" - FLAG_FOR_WEST_CM_OU = "\U0001f3f4\U000e0063\U000e006d\U000e006f\U000e0075\U000e007f" - FLAG_FOR_SHAANXI_CN_61 = "\U0001f3f4\U000e0063\U000e006e\U000e0036\U000e0031\U000e007f" - FLAG_FOR_SOUTH_CM_SU = "\U0001f3f4\U000e0063\U000e006d\U000e0073\U000e0075\U000e007f" - FLAG_FOR_RAKHINE_MM_16 = "\U0001f3f4\U000e006d\U000e006d\U000e0031\U000e0036\U000e007f" - FLAG_FOR_BARIMA_WAINI_GY_BA = "\U0001f3f4\U000e0067\U000e0079\U000e0062\U000e0061\U000e007f" - FAMILY_WOMAN_MEDIUM_LIGHT_SKIN_TONE_BABY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f476\U0001f3fc" - FLAG_FOR_SANA_A_YE_SN = "\U0001f3f4\U000e0079\U000e0065\U000e0073\U000e006e\U000e007f" - FLAG_FOR_VILNIAUS_MUNICIPALITY_LT_57 = "\U0001f3f4\U000e006c\U000e0074\U000e0035\U000e0037\U000e007f" - FLAG_FOR_RANKOVCE_MK_65 = "\U0001f3f4\U000e006d\U000e006b\U000e0036\U000e0035\U000e007f" - FLAG_FOR_WEST_BENGAL_IN_WB = "\U0001f3f4\U000e0069\U000e006e\U000e0077\U000e0062\U000e007f" - TAG_LEFT_CURLY_BRACKET = "\U000e007b" - FLAG_FOR_KIROVOHRADSCHYNA_UA_35 = "\U0001f3f4\U000e0075\U000e0061\U000e0033\U000e0035\U000e007f" - LEFT_THOUGHT_BUBBLE = "\U0001f5ec" - FLAG_FOR_KAPOSVAR_HU_KV = "\U0001f3f4\U000e0068\U000e0075\U000e006b\U000e0076\U000e007f" - RIGHT_SPEAKER_WITH_THREE_SOUND_WAVES = "\U0001f56a" - FLAG_FOR_ARIMA_TT_ARI = "\U0001f3f4\U000e0074\U000e0074\U000e0061\U000e0072\U000e0069\U000e007f" - WHITE_CLUB_SUIT = "\u2667" - REVERSED_THUMBS_DOWN_SIGN = "\U0001f593" - BEAMED_EIGHTH_NOTES = "\u266b" - FLAG_FOR_JUFRA_LY_JU = "\U0001f3f4\U000e006c\U000e0079\U000e006a\U000e0075\U000e007f" - FLAG_FOR_BAGHLAN_AF_BGL = "\U0001f3f4\U000e0061\U000e0066\U000e0062\U000e0067\U000e006c\U000e007f" - TAG_RIGHT_PARENTHESIS = "\U000e0029" - FAMILY_MAN_MEDIUM_DARK_SKIN_TONE_BABY_MEDIUM_DARK_SKIN_TONE_BABY_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f476\U0001f3fe\u200d\U0001f476\U0001f3fe" - FLAG_FOR_OHIO_US_OH = "\U0001f3f4\U000e0075\U000e0073\U000e006f\U000e0068\U000e007f" - CANCEL_TAG = "\U000e007f" - FLAG_FOR_ST_MARTIN_FR_MF = "\U0001f3f4\U000e0066\U000e0072\U000e006d\U000e0066\U000e007f" - RIGHT_HAND_TELEPHONE_RECEIVER = "\U0001f57d" - COUPLE_WITH_HEART_MAN_MEDIUM_DARK_SKIN_TONE_MAN = "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468" - FLAG_FOR_ASTANA_KZ_AST = "\U0001f3f4\U000e006b\U000e007a\U000e0061\U000e0073\U000e0074\U000e007f" - FLAG_FOR_EASTERN_SL_E = "\U0001f3f4\U000e0073\U000e006c\U000e0065\U000e007f" - FLAG_FOR_YALA_TH_95 = "\U0001f3f4\U000e0074\U000e0068\U000e0039\U000e0035\U000e007f" - FLAG_FOR_AHAL_TM_A = "\U0001f3f4\U000e0074\U000e006d\U000e0061\U000e007f" - KISS_MAN_MEDIUM_LIGHT_SKIN_TONE_MAN_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb" - MAHJONG_TILE_SIX_OF_CIRCLES = "\U0001f01e" - TURNED_BLACK_SHOGI_PIECE = "\u26ca" - FLAG_FOR_WOQOOYI_GALBEED_SO_WO = "\U0001f3f4\U000e0073\U000e006f\U000e0077\U000e006f\U000e007f" - COUPLE_WITH_HEART_MAN_LIGHT_SKIN_TONE_WOMAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fc" - FLAG_FOR_MAGWAY_MM_03 = "\U0001f3f4\U000e006d\U000e006d\U000e0030\U000e0033\U000e007f" - FLAG_FOR_SAVANNAKHET_LA_SV = "\U0001f3f4\U000e006c\U000e0061\U000e0073\U000e0076\U000e007f" - FLAG_FOR_CRETE_GR_M = "\U0001f3f4\U000e0067\U000e0072\U000e006d\U000e007f" - FLAG_FOR_BIRZAI_LT_06 = "\U0001f3f4\U000e006c\U000e0074\U000e0030\U000e0036\U000e007f" - FLAG_FOR_NGIWAL_PW_228 = "\U0001f3f4\U000e0070\U000e0077\U000e0032\U000e0032\U000e0038\U000e007f" - FLAG_FOR_RADOVIS_MK_64 = "\U0001f3f4\U000e006d\U000e006b\U000e0036\U000e0034\U000e007f" - FLOWER = "\u2698" - FLAG_FOR_PLANKEN_LI_05 = "\U0001f3f4\U000e006c\U000e0069\U000e0030\U000e0035\U000e007f" - FLAG_FOR_MANGROVE_CAY_BS_MC = "\U0001f3f4\U000e0062\U000e0073\U000e006d\U000e0063\U000e007f" - FLAG_FOR_EMBU_KE_06 = "\U0001f3f4\U000e006b\U000e0065\U000e0030\U000e0036\U000e007f" - FLAG_FOR_FARAH_AF_FRA = "\U0001f3f4\U000e0061\U000e0066\U000e0066\U000e0072\U000e0061\U000e007f" - THREE_RAYS_BELOW = "\U0001f5e5" - COUPLE_WITH_HEART_MAN_MEDIUM_SKIN_TONE_WOMAN_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fd" - FLAG_FOR_THURGAU_CH_TG = "\U0001f3f4\U000e0063\U000e0068\U000e0074\U000e0067\U000e007f" - FLAG_FOR_KAMPONG_CHAM_KH_3 = "\U0001f3f4\U000e006b\U000e0068\U000e0033\U000e007f" - FLAG_FOR_LESSER_SUNDA_ISLANDS_ID_NU = "\U0001f3f4\U000e0069\U000e0064\U000e006e\U000e0075\U000e007f" - FLAG_FOR_BERRY_ISLANDS_BS_BY = "\U0001f3f4\U000e0062\U000e0073\U000e0062\U000e0079\U000e007f" - TWO_SPEECH_BUBBLES = "\U0001f5ea" - FLAG_FOR_WEST_GREECE_GR_G = "\U0001f3f4\U000e0067\U000e0072\U000e0067\U000e007f" - WHITE_SUN = "\U0001f323" - TAG_ASTERISK = "\U000e002a" - PAGE_WITH_CIRCLED_TEXT = "\U0001f5df" - FAMILY_WOMAN_LIGHT_SKIN_TONE_BOY_LIGHT_SKIN_TONE_BABY_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f466\U0001f3fb\u200d\U0001f476\U0001f3fb" - FLAG_FOR_OGOOUE_MARITIME_GA_8 = "\U0001f3f4\U000e0067\U000e0061\U000e0038\U000e007f" - FLAG_FOR_MADEIRA_PT_30 = "\U0001f3f4\U000e0070\U000e0074\U000e0033\U000e0030\U000e007f" - FLAG_FOR_GURIA_GE_GU = "\U0001f3f4\U000e0067\U000e0065\U000e0067\U000e0075\U000e007f" - FLAG_FOR_PARA_BR_PA = "\U0001f3f4\U000e0062\U000e0072\U000e0070\U000e0061\U000e007f" - FAMILY_WOMAN_BABY_BABY = "\U0001f469\u200d\U0001f476\u200d\U0001f476" - FLAG_FOR_SCHAFFHAUSEN_CH_SH = "\U0001f3f4\U000e0063\U000e0068\U000e0073\U000e0068\U000e007f" - FLAG_FOR_GHOR_AF_GHO = "\U0001f3f4\U000e0061\U000e0066\U000e0067\U000e0068\U000e006f\U000e007f" - FLAG_FOR_ARKANSAS_US_AR = "\U0001f3f4\U000e0075\U000e0073\U000e0061\U000e0072\U000e007f" - FLAG_FOR_DRENTHE_NL_DR = "\U0001f3f4\U000e006e\U000e006c\U000e0064\U000e0072\U000e007f" - FAMILY_WOMAN_MEDIUM_LIGHT_SKIN_TONE_MAN_MEDIUM_LIGHT_SKIN_TONE_BOY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f468\U0001f3fc\u200d\U0001f466\U0001f3fc" - FLAG_FOR_GHAZNI_AF_GHA = "\U0001f3f4\U000e0061\U000e0066\U000e0067\U000e0068\U000e0061\U000e007f" - FLAG_FOR_OZAMA_DO_40 = "\U0001f3f4\U000e0064\U000e006f\U000e0034\U000e0030\U000e007f" - FLAG_FOR_HERAT_AF_HER = "\U0001f3f4\U000e0061\U000e0066\U000e0068\U000e0065\U000e0072\U000e007f" - TAG_DIGIT_FOUR = "\U000e0034" - FLAG_FOR_NEBRASKA_US_NE = "\U0001f3f4\U000e0075\U000e0073\U000e006e\U000e0065\U000e007f" - FLAG_FOR_ARICA_Y_PARINACOTA_CL_AP = "\U0001f3f4\U000e0063\U000e006c\U000e0061\U000e0070\U000e007f" - FLAG_FOR_ZANJAN_IR_11 = "\U0001f3f4\U000e0069\U000e0072\U000e0031\U000e0031\U000e007f" - FLAG_FOR_BRANDENBURG_DE_BB = "\U0001f3f4\U000e0064\U000e0065\U000e0062\U000e0062\U000e007f" - FLAG_FOR_BISTRICA_OB_SOTLI_SI_149 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0034\U000e0039\U000e007f" - MAHJONG_TILE_NORTH_WIND = "\U0001f003" - FLAG_FOR_ALABAMA_US_AL = "\U0001f3f4\U000e0075\U000e0073\U000e0061\U000e006c\U000e007f" - FLAG_FOR_AL_RAYYAN_QA_RA = "\U0001f3f4\U000e0071\U000e0061\U000e0072\U000e0061\U000e007f" - LOWER_RIGHT_PENCIL = "\u270e" - FLAG_FOR_SKRUNDA_LV_093 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0039\U000e0033\U000e007f" - NUMBER_SIGN = "#\ufe0f" - EMPTY_NOTE_PAGE = "\U0001f5c6" - DIGIT_FOUR = "4\ufe0f" - FLAG_FOR_KIDAL_ML_8 = "\U0001f3f4\U000e006d\U000e006c\U000e0038\U000e007f" - FLAG_FOR_BACAU_RO_BC = "\U0001f3f4\U000e0072\U000e006f\U000e0062\U000e0063\U000e007f" - BACK_OF_ENVELOPE = "\U0001f582" - MAN_ZOMBIE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9df\U0001f3fc\u200d\u2642\ufe0f" - FLAG_FOR_BURGENLAND_AT_1 = "\U0001f3f4\U000e0061\U000e0074\U000e0031\U000e007f" - FLAG_FOR_KAYANGEL_PW_100 = "\U0001f3f4\U000e0070\U000e0077\U000e0031\U000e0030\U000e0030\U000e007f" - FLAG_FOR_TOPLICA_RS_21 = "\U0001f3f4\U000e0072\U000e0073\U000e0032\U000e0031\U000e007f" - FLAG_FOR_KRASNODAR_KRAI_RU_KDA = "\U0001f3f4\U000e0072\U000e0075\U000e006b\U000e0064\U000e0061\U000e007f" - FLAG_FOR_LAUTEM_TL_LA = "\U0001f3f4\U000e0074\U000e006c\U000e006c\U000e0061\U000e007f" - UPPER_RIGHT_PENCIL = "\u2710" - FLAG_FOR_BANGKOK_TH_10 = "\U0001f3f4\U000e0074\U000e0068\U000e0031\U000e0030\U000e007f" - FLAG_FOR_BOUGAINVILLE_PG_NSB = "\U0001f3f4\U000e0070\U000e0067\U000e006e\U000e0073\U000e0062\U000e007f" - FLAG_FOR_GRENADINES_VC_06 = "\U0001f3f4\U000e0076\U000e0063\U000e0030\U000e0036\U000e007f" - WIRED_KEYBOARD = "\U0001f5ae" - FLAG_FOR_KABUL_AF_KAB = "\U0001f3f4\U000e0061\U000e0066\U000e006b\U000e0061\U000e0062\U000e007f" - TAG_LATIN_CAPITAL_LETTER_J = "\U000e004a" - FLAG_FOR_AMAZONAS_CO_AMA = "\U0001f3f4\U000e0063\U000e006f\U000e0061\U000e006d\U000e0061\U000e007f" - DIGIT_NINE = "9\ufe0f" - FLAG_FOR_TASMAN_NZ_TAS = "\U0001f3f4\U000e006e\U000e007a\U000e0074\U000e0061\U000e0073\U000e007f" - FLAG_FOR_STYRIA_AT_6 = "\U0001f3f4\U000e0061\U000e0074\U000e0036\U000e007f" - FLAG_FOR_GABORONE_BW_GA = "\U0001f3f4\U000e0062\U000e0077\U000e0067\U000e0061\U000e007f" - FLAG_FOR_SAINT_MARK_GD_05 = "\U0001f3f4\U000e0067\U000e0064\U000e0030\U000e0035\U000e007f" - FLAG_FOR_VALLE_DEL_CAUCA_CO_VAC = "\U0001f3f4\U000e0063\U000e006f\U000e0076\U000e0061\U000e0063\U000e007f" - FLAG_FOR_KHAKASSIA_RU_KK = "\U0001f3f4\U000e0072\U000e0075\U000e006b\U000e006b\U000e007f" - FLAG_FOR_MISRATA_LY_MI = "\U0001f3f4\U000e006c\U000e0079\U000e006d\U000e0069\U000e007f" - FLAG_FOR_QUEENSLAND_AU_QLD = "\U0001f3f4\U000e0061\U000e0075\U000e0071\U000e006c\U000e0064\U000e007f" - FAMILY_MAN_LIGHT_SKIN_TONE_WOMAN_LIGHT_SKIN_TONE_BOY_LIGHT_SKIN_TONE_BABY_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f469\U0001f3fb\u200d\U0001f466\U0001f3fb\u200d\U0001f476\U0001f3fb" - FLAG_FOR_OSAKA_JP_27 = "\U0001f3f4\U000e006a\U000e0070\U000e0032\U000e0037\U000e007f" - FLAG_FOR_MICHOACAN_MX_MIC = "\U0001f3f4\U000e006d\U000e0078\U000e006d\U000e0069\U000e0063\U000e007f" - FAMILY_MAN_MEDIUM_DARK_SKIN_TONE_GIRL_MEDIUM_DARK_SKIN_TONE_GIRL_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f467\U0001f3fe\u200d\U0001f467\U0001f3fe" - FAMILY_MAN_WOMAN_BOY_GIRL = "\U0001f468\u200d\U0001f469\u200d\U0001f466\u200d\U0001f467" - KISS_WOMAN_MEDIUM_SKIN_TONE_MAN_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe" - BLACK_UP_POINTING_BACKHAND_INDEX = "\U0001f5a2" - RIGHT_SPEAKER = "\U0001f568" - COMBINING_ENCLOSING_KEYCAP = "\u20e3" - FLAG_FOR_AZUAY_EC_A = "\U0001f3f4\U000e0065\U000e0063\U000e0061\U000e007f" - FLAG_FOR_LUHANSHCHYNA_UA_09 = "\U0001f3f4\U000e0075\U000e0061\U000e0030\U000e0039\U000e007f" - FLAG_FOR_MAHARASHTRA_IN_MH = "\U0001f3f4\U000e0069\U000e006e\U000e006d\U000e0068\U000e007f" - UPPER_RIGHT_SHADOWED_WHITE_CIRCLE = "\U0001f53f" - FLAG_FOR_INSULAR_GQ_I = "\U0001f3f4\U000e0067\U000e0071\U000e0069\U000e007f" - JUPITER = "\u2643" - FLAG_FOR_AKKAR_LB_AK = "\U0001f3f4\U000e006c\U000e0062\U000e0061\U000e006b\U000e007f" - FLAG_FOR_SOHAG_EG_SHG = "\U0001f3f4\U000e0065\U000e0067\U000e0073\U000e0068\U000e0067\U000e007f" - FLAG_FOR_MOHELI_KM_M = "\U0001f3f4\U000e006b\U000e006d\U000e006d\U000e007f" - FLAG_FOR_AL_AHMADI_KW_AH = "\U0001f3f4\U000e006b\U000e0077\U000e0061\U000e0068\U000e007f" - WOMAN_IN_TUXEDO_LIGHT_SKIN_TONE = "\U0001f935\U0001f3fb\u200d\u2640\ufe0f" - FLAG_FOR_GUADALCANAL_SB_GU = "\U0001f3f4\U000e0073\U000e0062\U000e0067\U000e0075\U000e007f" - FLAG_FOR_CIBITOKE_BI_CI = "\U0001f3f4\U000e0062\U000e0069\U000e0063\U000e0069\U000e007f" - WOMAN_ZOMBIE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9df\U0001f3fc\u200d\u2640\ufe0f" - FLAG_FOR_BAS_UELE_CD_BU = "\U0001f3f4\U000e0063\U000e0064\U000e0062\U000e0075\U000e007f" - FLAG_FOR_NEVIS_KN_N = "\U0001f3f4\U000e006b\U000e006e\U000e006e\U000e007f" - FLAG_FOR_BONAIRE_BQ_BO = "\U0001f3f4\U000e0062\U000e0071\U000e0062\U000e006f\U000e007f" - FLAG_FOR_ANCASH_PE_ANC = "\U0001f3f4\U000e0070\U000e0065\U000e0061\U000e006e\U000e0063\U000e007f" - CAR_SLIDING = "\u26d0" - DIGRAM_FOR_LESSER_YANG = "\u268e" - THREE_NETWORKED_COMPUTERS = "\U0001f5a7" - FLAG_FOR_ASUNCION_PY_ASU = "\U0001f3f4\U000e0070\U000e0079\U000e0061\U000e0073\U000e0075\U000e007f" - CROSS_POMMEE_WITH_HALF_CIRCLE_BELOW = "\U0001f541" - FLAG_FOR_TOLIMA_CO_TOL = "\U0001f3f4\U000e0063\U000e006f\U000e0074\U000e006f\U000e006c\U000e007f" - TAG_DIGIT_ONE = "\U000e0031" - FAMILY_WOMAN_MEDIUM_DARK_SKIN_TONE_MAN_MEDIUM_DARK_SKIN_TONE_BABY_MEDIUM_DARK_SKIN_TONE_BOY_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f468\U0001f3fe\u200d\U0001f476\U0001f3fe\u200d\U0001f466\U0001f3fe" - FLAG_FOR_JEWISH_RU_YEV = "\U0001f3f4\U000e0072\U000e0075\U000e0079\U000e0065\U000e0076\U000e007f" - FLAG_FOR_LA_PAZ_HN_LP = "\U0001f3f4\U000e0068\U000e006e\U000e006c\U000e0070\U000e007f" - FLAG_FOR_RIO_SAN_JUAN_NI_SJ = "\U0001f3f4\U000e006e\U000e0069\U000e0073\U000e006a\U000e007f" - FLAG_FOR_PAYS_DE_LA_LOIRE_FR_PDL = "\U0001f3f4\U000e0066\U000e0072\U000e0070\U000e0064\U000e006c\U000e007f" - FLAG_FOR_RIVERS_NG_RI = "\U0001f3f4\U000e006e\U000e0067\U000e0072\U000e0069\U000e007f" - OLD_PERSONAL_COMPUTER = "\U0001f5b3" - FLAG_FOR_DAEGU_KR_27 = "\U0001f3f4\U000e006b\U000e0072\U000e0032\U000e0037\U000e007f" - EMPTY_NOTE_PAD = "\U0001f5c7" - FLAG_FOR_SHAN_MM_17 = "\U0001f3f4\U000e006d\U000e006d\U000e0031\U000e0037\U000e007f" - BLACK_FLAG_2 = "\u2691" - MAHJONG_TILE_SEVEN_OF_CHARACTERS = "\U0001f00d" - FAMILY_MAN_BOY_GIRL = "\U0001f468\u200d\U0001f466\u200d\U0001f467" - FLAG_FOR_ADANA_TR_01 = "\U0001f3f4\U000e0074\U000e0072\U000e0030\U000e0031\U000e007f" - TAG_LATIN_CAPITAL_LETTER_C = "\U000e0043" - TWO_BUTTON_MOUSE = "\U0001f5b0" - ASTERISK = "*\ufe0f" - MAHJONG_TILE_FOUR_OF_CIRCLES = "\U0001f01c" - FLAG_FOR_KAPISA_AF_KAP = "\U0001f3f4\U000e0061\U000e0066\U000e006b\U000e0061\U000e0070\U000e007f" - FLAG_FOR_MON_MM_15 = "\U0001f3f4\U000e006d\U000e006d\U000e0031\U000e0035\U000e007f" - FLAG_FOR_AL_BURAIMI_OM_BU = "\U0001f3f4\U000e006f\U000e006d\U000e0062\U000e0075\U000e007f" - FLAG_FOR_AGALEGA_MU_AG = "\U0001f3f4\U000e006d\U000e0075\U000e0061\U000e0067\U000e007f" - ONE_BUTTON_MOUSE = "\U0001f5af" - FLAG_FOR_LOGAR_AF_LOG = "\U0001f3f4\U000e0061\U000e0066\U000e006c\U000e006f\U000e0067\U000e007f" - FLAG_FOR_BEL_OMBRE_SC_10 = "\U0001f3f4\U000e0073\U000e0063\U000e0031\U000e0030\U000e007f" - FLAG_FOR_ORANGE_WALK_BZ_OW = "\U0001f3f4\U000e0062\U000e007a\U000e006f\U000e0077\U000e007f" - MALE_WITH_STROKE_AND_MALE_AND_FEMALE_SIGN = "\u26a7" - FLAG_FOR_KANDAHAR_AF_KAN = "\U0001f3f4\U000e0061\U000e0066\U000e006b\U000e0061\U000e006e\U000e007f" - TAPE_CARTRIDGE = "\U0001f5ad" - FLAG_FOR_ULAANBAATAR_MN_1 = "\U0001f3f4\U000e006d\U000e006e\U000e0031\U000e007f" - FLAG_FOR_FARYAB_AF_FYB = "\U0001f3f4\U000e0061\U000e0066\U000e0066\U000e0079\U000e0062\U000e007f" - TAG_LATIN_SMALL_LETTER_F = "\U000e0066" - FLAG_FOR_PARWAN_AF_PAR = "\U0001f3f4\U000e0061\U000e0066\U000e0070\U000e0061\U000e0072\U000e007f" - FLAG_FOR_NIMRUZ_AF_NIM = "\U0001f3f4\U000e0061\U000e0066\U000e006e\U000e0069\U000e006d\U000e007f" - FLAG_FOR_HIIU_EE_39 = "\U0001f3f4\U000e0065\U000e0065\U000e0033\U000e0039\U000e007f" - FLAG_FOR_EXTREMADURA_ES_EX = "\U0001f3f4\U000e0065\U000e0073\U000e0065\U000e0078\U000e007f" - FLAG_FOR_SABA_BQ_SA = "\U0001f3f4\U000e0062\U000e0071\U000e0073\U000e0061\U000e007f" - FLAG_FOR_KARLOVAC_HR_04 = "\U0001f3f4\U000e0068\U000e0072\U000e0030\U000e0034\U000e007f" - FLAG_FOR_BROD_POSAVINA_HR_12 = "\U0001f3f4\U000e0068\U000e0072\U000e0031\U000e0032\U000e007f" - FLAG_FOR_NANGARHAR_AF_NAN = "\U0001f3f4\U000e0061\U000e0066\U000e006e\U000e0061\U000e006e\U000e007f" - MAHJONG_TILE_SEVEN_OF_CIRCLES = "\U0001f01f" - TRIGRAM_FOR_HEAVEN = "\u2630" - DIGIT_SEVEN = "7\ufe0f" - COUPLE_WITH_HEART_WOMAN_MEDIUM_SKIN_TONE_WOMAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fc" - RECYCLING_SYMBOL_FOR_TYPE_1_PLASTICS = "\u2673" - FLAG_FOR_CORSE_FR_COR = "\U0001f3f4\U000e0066\U000e0072\U000e0063\U000e006f\U000e0072\U000e007f" - MAN_WITH_HEADSCARF_MEDIUM_SKIN_TONE = "\U0001f9d5\U0001f3fd\u200d\u2642\ufe0f" - FLAG_FOR_HAMADAN_IR_24 = "\U0001f3f4\U000e0069\U000e0072\U000e0032\U000e0034\U000e007f" - FLAG_FOR_FARS_IR_14 = "\U0001f3f4\U000e0069\U000e0072\U000e0031\U000e0034\U000e007f" - FLAG_FOR_FRENCH_GUIANA_FR_GF = "\U0001f3f4\U000e0066\U000e0072\U000e0067\U000e0066\U000e007f" - FLAG_FOR_TICINO_CH_TI = "\U0001f3f4\U000e0063\U000e0068\U000e0074\U000e0069\U000e007f" - FLAG_FOR_BOR_RS_14 = "\U0001f3f4\U000e0072\U000e0073\U000e0031\U000e0034\U000e007f" - FLAG_FOR_UTTARADIT_TH_53 = "\U0001f3f4\U000e0074\U000e0068\U000e0035\U000e0033\U000e007f" - TAG_LATIN_SMALL_LETTER_M = "\U000e006d" - FLAG_FOR_BAMAKO_ML_BKO = "\U0001f3f4\U000e006d\U000e006c\U000e0062\U000e006b\U000e006f\U000e007f" - FLAG_FOR_ESPIRITO_SANTO_BR_ES = "\U0001f3f4\U000e0062\U000e0072\U000e0065\U000e0073\U000e007f" - TAG_LATIN_SMALL_LETTER_R = "\U000e0072" - FLAG_FOR_BOLIKHAMSAI_LA_BL = "\U0001f3f4\U000e006c\U000e0061\U000e0062\U000e006c\U000e007f" - BULLHORN_WITH_SOUND_WAVES = "\U0001f56c" - FLAG_FOR_HALLAND_SE_N = "\U0001f3f4\U000e0073\U000e0065\U000e006e\U000e007f" - FLAG_FOR_UPPER_DEMERARA_BERBICE_GY_UD = "\U0001f3f4\U000e0067\U000e0079\U000e0075\U000e0064\U000e007f" - WOMAN_ZOMBIE_MEDIUM_SKIN_TONE = "\U0001f9df\U0001f3fd\u200d\u2640\ufe0f" - FLAG_FOR_ZIROVNICA_SI_192 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0039\U000e0032\U000e007f" - FLAG_FOR_TAITUNG_TW_TTT = "\U0001f3f4\U000e0074\U000e0077\U000e0074\U000e0074\U000e0074\U000e007f" - FLAG_FOR_NANUMANGA_TV_NMG = "\U0001f3f4\U000e0074\U000e0076\U000e006e\U000e006d\U000e0067\U000e007f" - FLAG_FOR_TUNAPUNA_PIARCO_TT_TUP = "\U0001f3f4\U000e0074\U000e0074\U000e0074\U000e0075\U000e0070\U000e007f" - TAG_DIGIT_SEVEN = "\U000e0037" - COUPLE_WITH_HEART_WOMAN_MEDIUM_LIGHT_SKIN_TONE_WOMAN_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fe" - KEYBOARD_AND_MOUSE = "\U0001f5a6" - MAHJONG_TILE_GREEN_DRAGON = "\U0001f005" - FLAG_FOR_CEUTA_ES_CE = "\U0001f3f4\U000e0065\U000e0073\U000e0063\U000e0065\U000e007f" - KISS_MAN_MEDIUM_SKIN_TONE_WOMAN = "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469" - FLAG_FOR_SABARAGAMUWA_LK_9 = "\U0001f3f4\U000e006c\U000e006b\U000e0039\U000e007f" - FAMILY_MAN_MEDIUM_DARK_SKIN_TONE_MAN_MEDIUM_DARK_SKIN_TONE_BABY_MEDIUM_DARK_SKIN_TONE_BOY_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f468\U0001f3fe\u200d\U0001f476\U0001f3fe\u200d\U0001f466\U0001f3fe" - COUPLE_WITH_HEART_MAN_LIGHT_SKIN_TONE_WOMAN_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fd" - FLAG_FOR_BUTNAN_LY_BU = "\U0001f3f4\U000e006c\U000e0079\U000e0062\U000e0075\U000e007f" - FLAG_FOR_COLIMA_MX_COL = "\U0001f3f4\U000e006d\U000e0078\U000e0063\U000e006f\U000e006c\U000e007f" - FLAG_FOR_QUEBEC_CA_QC = "\U0001f3f4\U000e0063\U000e0061\U000e0071\U000e0063\U000e007f" - WHITE_DOWN_POINTING_INDEX = "\u261f" - FLAG_FOR_CUNDINAMARCA_CO_CUN = "\U0001f3f4\U000e0063\U000e006f\U000e0063\U000e0075\U000e006e\U000e007f" - TAG_LATIN_CAPITAL_LETTER_D = "\U000e0044" - FLAG_FOR_DAMASCUS_SY_DI = "\U0001f3f4\U000e0073\U000e0079\U000e0064\U000e0069\U000e007f" - FLAG_FOR_DUBAI_AE_DU = "\U0001f3f4\U000e0061\U000e0065\U000e0064\U000e0075\U000e007f" - SHIBUYA = "\ue50a" - FLAG_FOR_KAYES_ML_1 = "\U0001f3f4\U000e006d\U000e006c\U000e0031\U000e007f" - FLAG_FOR_CEARA_BR_CE = "\U0001f3f4\U000e0062\U000e0072\U000e0063\U000e0065\U000e007f" - FLAG_FOR_SHARJAH_AE_SH = "\U0001f3f4\U000e0061\U000e0065\U000e0073\U000e0068\U000e007f" - FLAG_FOR_EASTERN_IS_7 = "\U0001f3f4\U000e0069\U000e0073\U000e0037\U000e007f" - WHITE_CHESS_BISHOP = "\u2657" - FLAG_FOR_AFAR_ET_AF = "\U0001f3f4\U000e0065\U000e0074\U000e0061\U000e0066\U000e007f" - TAG_QUESTION_MARK = "\U000e003f" - FAMILY_WOMAN_GIRL_BABY = "\U0001f469\u200d\U0001f467\u200d\U0001f476" - FLAG_FOR_RADENCI_SI_100 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0030\U000e0030\U000e007f" - FLAG_FOR_LIMA_REGION_PE_LIM = "\U0001f3f4\U000e0070\U000e0065\U000e006c\U000e0069\U000e006d\U000e007f" - FLAG_FOR_NORTH_CENTRAL_PROVINCE_MV_NC = "\U0001f3f4\U000e006d\U000e0076\U000e006e\U000e0063\U000e007f" - DOCUMENT_WITH_PICTURE = "\U0001f5bb" - FLAG_FOR_VALAIS_CH_VS = "\U0001f3f4\U000e0063\U000e0068\U000e0076\U000e0073\U000e007f" - FLAG_FOR_BAY_OF_PLENTY_NZ_BOP = "\U0001f3f4\U000e006e\U000e007a\U000e0062\U000e006f\U000e0070\U000e007f" - TAG_LATIN_CAPITAL_LETTER_P = "\U000e0050" - FLAG_FOR_BASSE_KOTTO_CF_BK = "\U0001f3f4\U000e0063\U000e0066\U000e0062\U000e006b\U000e007f" - DOCUMENT_WITH_TEXT_AND_PICTURE = "\U0001f5ba" - TAG_DIGIT_NINE = "\U000e0039" - WOMAN_IN_TUXEDO_MEDIUM_DARK_SKIN_TONE = "\U0001f935\U0001f3fe\u200d\u2640\ufe0f" - FLAG_FOR_ZURICH_CH_ZH = "\U0001f3f4\U000e0063\U000e0068\U000e007a\U000e0068\U000e007f" - MAHJONG_TILE_THREE_OF_CIRCLES = "\U0001f01b" - FLAG_FOR_LOG_DRAGOMER_SI_208 = "\U0001f3f4\U000e0073\U000e0069\U000e0032\U000e0030\U000e0038\U000e007f" - MAN_ZOMBIE_DARK_SKIN_TONE = "\U0001f9df\U0001f3ff\u200d\u2642\ufe0f" - FLAG_FOR_ARIZONA_US_AZ = "\U0001f3f4\U000e0075\U000e0073\U000e0061\U000e007a\U000e007f" - FLAG_FOR_PAKTIKA_AF_PKA = "\U0001f3f4\U000e0061\U000e0066\U000e0070\U000e006b\U000e0061\U000e007f" - PROHIBITED_SIGN = "\U0001f6c7" - COUPLE_WITH_HEART_WOMAN_MEDIUM_LIGHT_SKIN_TONE_MAN_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb" - FLAG_FOR_ALASKA_US_AK = "\U0001f3f4\U000e0075\U000e0073\U000e0061\U000e006b\U000e007f" - FLAG_FOR_SOOL_SO_SO = "\U0001f3f4\U000e0073\U000e006f\U000e0073\U000e006f\U000e007f" - FLAG_FOR_LAGHMAN_AF_LAG = "\U0001f3f4\U000e0061\U000e0066\U000e006c\U000e0061\U000e0067\U000e007f" - BLACK_CIRCLE_WITH_TWO_WHITE_DOTS = "\u2689" - FLAG_FOR_NUKULAELAE_TV_NKL = "\U0001f3f4\U000e0074\U000e0076\U000e006e\U000e006b\U000e006c\U000e007f" - FLAG_FOR_ABKHAZIA_GE_AB = "\U0001f3f4\U000e0067\U000e0065\U000e0061\U000e0062\U000e007f" - FLAG_FOR_ESCH_SUR_ALZETTE_LU_ES = "\U0001f3f4\U000e006c\U000e0075\U000e0065\U000e0073\U000e007f" - FLAG_FOR_MANIPUR_IN_MN = "\U0001f3f4\U000e0069\U000e006e\U000e006d\U000e006e\U000e007f" - FLAG_FOR_HELMAND_AF_HEL = "\U0001f3f4\U000e0061\U000e0066\U000e0068\U000e0065\U000e006c\U000e007f" - FLAG_FOR_VICHADA_CO_VID = "\U0001f3f4\U000e0063\U000e006f\U000e0076\U000e0069\U000e0064\U000e007f" - DOCUMENT = "\U0001f5ce" - FLAG_FOR_ISLAMABAD_PK_IS = "\U0001f3f4\U000e0070\U000e006b\U000e0069\U000e0073\U000e007f" - FAMILY_MAN_LIGHT_SKIN_TONE_MAN_LIGHT_SKIN_TONE_BOY_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f468\U0001f3fb\u200d\U0001f466\U0001f3fb" - FLAG_FOR_RAS_AL_KHAIMAH_AE_RK = "\U0001f3f4\U000e0061\U000e0065\U000e0072\U000e006b\U000e007f" - FAMILY_MAN_BABY = "\U0001f468\u200d\U0001f476" - FLAG_FOR_KONYA_TR_42 = "\U0001f3f4\U000e0074\U000e0072\U000e0034\U000e0032\U000e007f" - FLAG_FOR_CANKIRI_TR_18 = "\U0001f3f4\U000e0074\U000e0072\U000e0031\U000e0038\U000e007f" - FLAG_FOR_GANJA_AZ_GA = "\U0001f3f4\U000e0061\U000e007a\U000e0067\U000e0061\U000e007f" - MAHJONG_TILE_SIX_OF_CHARACTERS = "\U0001f00c" - TAG_LATIN_CAPITAL_LETTER_M = "\U000e004d" - FLAG_FOR_OITA_JP_44 = "\U0001f3f4\U000e006a\U000e0070\U000e0034\U000e0034\U000e007f" - FLAG_FOR_GUATEMALA_GT_GU = "\U0001f3f4\U000e0067\U000e0074\U000e0067\U000e0075\U000e007f" - FLAG_FOR_NORTH_BRABANT_NL_NB = "\U0001f3f4\U000e006e\U000e006c\U000e006e\U000e0062\U000e007f" - FAMILY_MAN_LIGHT_SKIN_TONE_BABY_LIGHT_SKIN_TONE_BABY_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f476\U0001f3fb\u200d\U0001f476\U0001f3fb" - KISS_WOMAN_LIGHT_SKIN_TONE_WOMAN_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fb" - FAMILY_MAN_MEDIUM_DARK_SKIN_TONE_WOMAN_MEDIUM_DARK_SKIN_TONE_BOY_MEDIUM_DARK_SKIN_TONE_GIRL_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f469\U0001f3fe\u200d\U0001f466\U0001f3fe\u200d\U0001f467\U0001f3fe" - FLAG_FOR_GILBERT_ISLANDS_KI_G = "\U0001f3f4\U000e006b\U000e0069\U000e0067\U000e007f" - FLAG_FOR_PAKTIA_AF_PIA = "\U0001f3f4\U000e0061\U000e0066\U000e0070\U000e0069\U000e0061\U000e007f" - FLAG_FOR_PARDUBICKY_KRAJ_CZ_53 = "\U0001f3f4\U000e0063\U000e007a\U000e0035\U000e0033\U000e007f" - FLAG_FOR_SALCININKAI_LT_42 = "\U0001f3f4\U000e006c\U000e0074\U000e0034\U000e0032\U000e007f" - BALLOT_BOX_WITH_BOLD_CHECK = "\U0001f5f9" - FLAG_FOR_CORUM_TR_19 = "\U0001f3f4\U000e0074\U000e0072\U000e0031\U000e0039\U000e007f" - FLAG_FOR_SAMANGAN_AF_SAM = "\U0001f3f4\U000e0061\U000e0066\U000e0073\U000e0061\U000e006d\U000e007f" - FLAG_FOR_MASAYA_NI_MS = "\U0001f3f4\U000e006e\U000e0069\U000e006d\U000e0073\U000e007f" - FLAG_FOR_CSONGRAD_HU_CS = "\U0001f3f4\U000e0068\U000e0075\U000e0063\U000e0073\U000e007f" - FLAG_FOR_AKSARAY_TR_68 = "\U0001f3f4\U000e0074\U000e0072\U000e0036\U000e0038\U000e007f" - COUPLE_WITH_HEART_WOMAN_MEDIUM_SKIN_TONE_WOMAN_DARK_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3ff" - FLAG_FOR_SAINT_MARY_AG_05 = "\U0001f3f4\U000e0061\U000e0067\U000e0030\U000e0035\U000e007f" - WOMAN_IN_BUSINESS_SUIT_LEVITATING_DARK_SKIN_TONE = "\U0001f574\U0001f3ff\u200d\u2640\ufe0f" - TAG_COMMERCIAL_AT = "\U000e0040" - FLAG_FOR_BERANE_ME_03 = "\U0001f3f4\U000e006d\U000e0065\U000e0030\U000e0033\U000e007f" - FLAG_FOR_MARYLAND_LR_MY = "\U0001f3f4\U000e006c\U000e0072\U000e006d\U000e0079\U000e007f" - FLAG_FOR_KARA_TG_K = "\U0001f3f4\U000e0074\U000e0067\U000e006b\U000e007f" - FAMILY_MAN_MEDIUM_DARK_SKIN_TONE_GIRL_MEDIUM_DARK_SKIN_TONE_BOY_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f467\U0001f3fe\u200d\U0001f466\U0001f3fe" - OPTICAL_DISC_ICON = "\U0001f5b8" - FLAG_FOR_MASCARA_DZ_29 = "\U0001f3f4\U000e0064\U000e007a\U000e0032\U000e0039\U000e007f" - THREE_RAYS_LEFT = "\U0001f5e6" - RECYCLING_SYMBOL_FOR_TYPE_3_PLASTICS = "\u2675" - FLAG_FOR_REDONDA_AG_11 = "\U0001f3f4\U000e0061\U000e0067\U000e0031\U000e0031\U000e007f" - FAX_ICON = "\U0001f5b7" - FLAG_FOR_GAGAUZIA_MD_GA = "\U0001f3f4\U000e006d\U000e0064\U000e0067\U000e0061\U000e007f" - THREE_SPEECH_BUBBLES = "\U0001f5eb" - TAG_LATIN_SMALL_LETTER_S = "\U000e0073" - FLAG_FOR_WAKAYAMA_JP_30 = "\U0001f3f4\U000e006a\U000e0070\U000e0033\U000e0030\U000e007f" - FLAG_FOR_SZEKSZARD_HU_SS = "\U0001f3f4\U000e0068\U000e0075\U000e0073\U000e0073\U000e007f" - FAMILY_WOMAN_LIGHT_SKIN_TONE_MAN_LIGHT_SKIN_TONE_BOY_LIGHT_SKIN_TONE_BOY_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f468\U0001f3fb\u200d\U0001f466\U0001f3fb\u200d\U0001f466\U0001f3fb" - OVERLAP = "\U0001f5d7" - FLAG_FOR_HSINCHU_COUNTY_TW_HSQ = "\U0001f3f4\U000e0074\U000e0077\U000e0068\U000e0073\U000e0071\U000e007f" - MAHJONG_TILE_EAST_WIND = "\U0001f000" - FLAG_FOR_GOMBE_NG_GO = "\U0001f3f4\U000e006e\U000e0067\U000e0067\U000e006f\U000e007f" - FLAG_FOR_VILNIUS_LT_58 = "\U0001f3f4\U000e006c\U000e0074\U000e0035\U000e0038\U000e007f" - TAG_LATIN_SMALL_LETTER_O = "\U000e006f" - PAGE = "\U0001f5cf" - STOCK_CHART = "\U0001f5e0" - FLAG_FOR_SAINT_JOHN_AG_04 = "\U0001f3f4\U000e0061\U000e0067\U000e0030\U000e0034\U000e007f" - FLAG_FOR_VERMONT_US_VT = "\U0001f3f4\U000e0075\U000e0073\U000e0076\U000e0074\U000e007f" - FLAG_FOR_MURCIA_REGION_ES_MC = "\U0001f3f4\U000e0065\U000e0073\U000e006d\U000e0063\U000e007f" - FLAG_FOR_PWANI_TZ_19 = "\U0001f3f4\U000e0074\U000e007a\U000e0031\U000e0039\U000e007f" - FLAG_FOR_SOMALI_ET_SO = "\U0001f3f4\U000e0065\U000e0074\U000e0073\U000e006f\U000e007f" - MAN_WITH_HEADSCARF_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d5\U0001f3fc\u200d\u2642\ufe0f" - DESKTOP_WINDOW = "\U0001f5d4" - FLAG_FOR_MADONA_LV_059 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0035\U000e0039\U000e007f" - FLAG_FOR_SING_BURI_TH_17 = "\U0001f3f4\U000e0074\U000e0068\U000e0031\U000e0037\U000e007f" - FLAG_FOR_MOKA_MU_MO = "\U0001f3f4\U000e006d\U000e0075\U000e006d\U000e006f\U000e007f" - KISS_MAN_LIGHT_SKIN_TONE_MAN_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe" - FAMILY_WOMAN_MEDIUM_LIGHT_SKIN_TONE_WOMAN_MEDIUM_LIGHT_SKIN_TONE_BOY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f469\U0001f3fc\u200d\U0001f466\U0001f3fc" - BLACK_LEFT_POINTING_BACKHAND_INDEX = "\U0001f59c" - REVERSED_VICTORY_HAND = "\U0001f594" - FAMILY_WOMAN_WOMAN_BABY = "\U0001f469\u200d\U0001f469\u200d\U0001f476" - FLAG_FOR_MICOUD_LC_08 = "\U0001f3f4\U000e006c\U000e0063\U000e0030\U000e0038\U000e007f" - TRIGRAM_FOR_MOUNTAIN = "\u2636" - WOMAN_WITH_HEADSCARF_2 = "\U0001f9d5\u200d\u2640\ufe0f" - FLAG_FOR_MARCHE_IT_57 = "\U0001f3f4\U000e0069\U000e0074\U000e0035\U000e0037\U000e007f" - MAHJONG_TILE_FIVE_OF_CIRCLES = "\U0001f01d" - FLAG_FOR_COVA_LIMA_TL_CO = "\U0001f3f4\U000e0074\U000e006c\U000e0063\U000e006f\U000e007f" - KISS_MAN_MEDIUM_LIGHT_SKIN_TONE_MAN = "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468" - FAMILY_MAN_DARK_SKIN_TONE_MAN_DARK_SKIN_TONE_BOY_DARK_SKIN_TONE_BABY_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f468\U0001f3ff\u200d\U0001f466\U0001f3ff\u200d\U0001f476\U0001f3ff" - FLAG_FOR_BEL_AIR_SC_09 = "\U0001f3f4\U000e0073\U000e0063\U000e0030\U000e0039\U000e007f" - FLAG_FOR_VILA_REAL_PT_17 = "\U0001f3f4\U000e0070\U000e0074\U000e0031\U000e0037\U000e007f" - FLAG_FOR_MACAU_SAR_CHINA_CN_92 = "\U0001f3f4\U000e0063\U000e006e\U000e0039\U000e0032\U000e007f" - FLAG_FOR_SOUTH_DAKOTA_US_SD = "\U0001f3f4\U000e0075\U000e0073\U000e0073\U000e0064\U000e007f" - FLAG_FOR_WEST_PANAMA_PA_10 = "\U0001f3f4\U000e0070\U000e0061\U000e0031\U000e0030\U000e007f" - FLAG_FOR_CHIAPAS_MX_CHP = "\U0001f3f4\U000e006d\U000e0078\U000e0063\U000e0068\U000e0070\U000e007f" - FLAG_FOR_KUKES_COUNTY_AL_07 = "\U0001f3f4\U000e0061\U000e006c\U000e0030\U000e0037\U000e007f" - PAGES = "\U0001f5d0" - TAG_LATIN_CAPITAL_LETTER_L = "\U000e004c" - EMPTY_PAGES = "\U0001f5cd" - FLAG_FOR_SVETA_ANA_SI_181 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0038\U000e0031\U000e007f" - FLAG_FOR_SKRIVERI_LV_092 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0039\U000e0032\U000e007f" - FLAG_FOR_KITUI_KE_18 = "\U0001f3f4\U000e006b\U000e0065\U000e0031\U000e0038\U000e007f" - FIRST_QUARTER_MOON_2 = "\u263d" - FLAG_FOR_PELELIU_PW_350 = "\U0001f3f4\U000e0070\U000e0077\U000e0033\U000e0035\U000e0030\U000e007f" - FLAG_FOR_MARY_TM_M = "\U0001f3f4\U000e0074\U000e006d\U000e006d\U000e007f" - FLAG_FOR_UPPER_SOUTH_PROVINCE_MV_US = "\U0001f3f4\U000e006d\U000e0076\U000e0075\U000e0073\U000e007f" - FLAG_FOR_MAKKAH_SA_02 = "\U0001f3f4\U000e0073\U000e0061\U000e0030\U000e0032\U000e007f" - FLAG_FOR_ZEBBUG_GOZO_MT_65 = "\U0001f3f4\U000e006d\U000e0074\U000e0036\U000e0035\U000e007f" - FLAG_FOR_OSMANIYE_TR_80 = "\U0001f3f4\U000e0074\U000e0072\U000e0038\U000e0030\U000e007f" - BLACK_TWO_WAY_LEFT_WAY_TRAFFIC = "\u26d6" - FLAG_FOR_LAGOS_NG_LA = "\U0001f3f4\U000e006e\U000e0067\U000e006c\U000e0061\U000e007f" - MAHJONG_TILE_JOKER = "\U0001f02a" - FLAG_FOR_KARS_TR_36 = "\U0001f3f4\U000e0074\U000e0072\U000e0033\U000e0036\U000e007f" - FLAG_FOR_NAYARIT_MX_NAY = "\U0001f3f4\U000e006d\U000e0078\U000e006e\U000e0061\U000e0079\U000e007f" - CUP_ON_BLACK_SQUARE = "\u26fe" - FLAG_FOR_RIVERCESS_LR_RI = "\U0001f3f4\U000e006c\U000e0072\U000e0072\U000e0069\U000e007f" - THREE_RAYS_ABOVE = "\U0001f5e4" - FLAG_FOR_UABOE_NR_13 = "\U0001f3f4\U000e006e\U000e0072\U000e0031\U000e0033\U000e007f" - TAG_LATIN_SMALL_LETTER_P = "\U000e0070" - FLAG_FOR_MGARR_MT_31 = "\U0001f3f4\U000e006d\U000e0074\U000e0033\U000e0031\U000e007f" - FLAG_FOR_SALAVAN_LA_SL = "\U0001f3f4\U000e006c\U000e0061\U000e0073\U000e006c\U000e007f" - FLAG_FOR_AJMAN_AE_AJ = "\U0001f3f4\U000e0061\U000e0065\U000e0061\U000e006a\U000e007f" - FLAG_FOR_DEMIR_KAPIJA_MK_24 = "\U0001f3f4\U000e006d\U000e006b\U000e0032\U000e0034\U000e007f" - FLAG_FOR_KHABAROVSK_KRAI_RU_KHA = "\U0001f3f4\U000e0072\U000e0075\U000e006b\U000e0068\U000e0061\U000e007f" - COUPLE_WITH_HEART_MAN_LIGHT_SKIN_TONE_MAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc" - FLAG_FOR_WEST_KAZAKHSTAN_KZ_ZAP = "\U0001f3f4\U000e006b\U000e007a\U000e007a\U000e0061\U000e0070\U000e007f" - TAG_LATIN_SMALL_LETTER_W = "\U000e0077" - ADI_SHAKTI = "\u262c" - BOYS_SYMBOL = "\U0001f6c9" - FLAG_FOR_NORTHERN_BAHR_EL_GHAZAL_SS_BN = "\U0001f3f4\U000e0073\U000e0073\U000e0062\U000e006e\U000e007f" - FAMILY_MAN_DARK_SKIN_TONE_MAN_DARK_SKIN_TONE_BABY_DARK_SKIN_TONE_GIRL_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f468\U0001f3ff\u200d\U0001f476\U0001f3ff\u200d\U0001f467\U0001f3ff" - FLAG_FOR_LINDI_TZ_12 = "\U0001f3f4\U000e0074\U000e007a\U000e0031\U000e0032\U000e007f" - FLAG_FOR_KUNAR_AF_KNR = "\U0001f3f4\U000e0061\U000e0066\U000e006b\U000e006e\U000e0072\U000e007f" - FOLDER = "\U0001f5c0" - COUPLE_WITH_HEART_MAN_MAN_LIGHT_SKIN_TONE = "\U0001f468\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb" - TAG_NUMBER_SIGN = "\U000e0023" - FLAG_FOR_VALENCIAN_COMMUNITY_ES_VC = "\U0001f3f4\U000e0065\U000e0073\U000e0076\U000e0063\U000e007f" - TAG_RIGHT_CURLY_BRACKET = "\U000e007d" - FLAG_FOR_SAINT_PAUL_AG_06 = "\U0001f3f4\U000e0061\U000e0067\U000e0030\U000e0036\U000e007f" - WOMAN_IN_TUXEDO = "\U0001f935\u200d\u2640\ufe0f" - FLAG_FOR_SICILY_IT_82 = "\U0001f3f4\U000e0069\U000e0074\U000e0038\U000e0032\U000e007f" - FLAG_FOR_LEZHE_COUNTY_AL_08 = "\U0001f3f4\U000e0061\U000e006c\U000e0030\U000e0038\U000e007f" - FLAG_FOR_CHIHUAHUA_MX_CHH = "\U0001f3f4\U000e006d\U000e0078\U000e0063\U000e0068\U000e0068\U000e007f" - BLACK_RIGHT_POINTING_BACKHAND_INDEX = "\U0001f59d" - WHITE_LATIN_CROSS = "\U0001f546" - FLAG_FOR_NORD_OUEST_HT_NO = "\U0001f3f4\U000e0068\U000e0074\U000e006e\U000e006f\U000e007f" - DIGIT_ONE = "1\ufe0f" - PORTABLE_STEREO = "\U0001f4fe" - FLAG_FOR_MADINAT_ASH_SHAMAL_QA_MS = "\U0001f3f4\U000e0071\U000e0061\U000e006d\U000e0073\U000e007f" - BULLHORN = "\U0001f56b" - TAG_DIGIT_SIX = "\U000e0036" - FLAG_FOR_SIKKIM_IN_SK = "\U0001f3f4\U000e0069\U000e006e\U000e0073\U000e006b\U000e007f" - CANCELLATION_X = "\U0001f5d9" - LEFT_WRITING_HAND = "\U0001f58e" - MAHJONG_TILE_FOUR_OF_CHARACTERS = "\U0001f00a" - RINGING_BELL = "\U0001f56d" - FLAG_FOR_TIRANA_COUNTY_AL_11 = "\U0001f3f4\U000e0061\U000e006c\U000e0031\U000e0031\U000e007f" - FLAG_FOR_TRAT_TH_23 = "\U0001f3f4\U000e0074\U000e0068\U000e0032\U000e0033\U000e007f" - TAG_LATIN_SMALL_LETTER_D = "\U000e0064" - FLAG_FOR_SAMARA_RU_SAM = "\U0001f3f4\U000e0072\U000e0075\U000e0073\U000e0061\U000e006d\U000e007f" - FLAG_FOR_NGOUNIE_GA_4 = "\U0001f3f4\U000e0067\U000e0061\U000e0034\U000e007f" - BLACK_ROSETTE = "\U0001f3f6" - TAG_CIRCUMFLEX_ACCENT = "\U000e005e" - FLAG_FOR_IASI_RO_IS = "\U0001f3f4\U000e0072\U000e006f\U000e0069\U000e0073\U000e007f" - FLAG_FOR_GEORGIA_US_GA = "\U0001f3f4\U000e0075\U000e0073\U000e0067\U000e0061\U000e007f" - FLAG_FOR_DURRES_COUNTY_AL_02 = "\U0001f3f4\U000e0061\U000e006c\U000e0030\U000e0032\U000e007f" - KISS_MAN_LIGHT_SKIN_TONE_WOMAN_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fd" - FLAG_FOR_TAZA_AL_HOCEIMA_TAOUNATE_MA_03 = "\U0001f3f4\U000e006d\U000e0061\U000e0030\U000e0033\U000e007f" - FLAG_FOR_LOMBARDY_IT_25 = "\U0001f3f4\U000e0069\U000e0074\U000e0032\U000e0035\U000e007f" - FLAG_FOR_TRABZON_TR_61 = "\U0001f3f4\U000e0074\U000e0072\U000e0036\U000e0031\U000e007f" - FLAG_FOR_UROZGAN_AF_URU = "\U0001f3f4\U000e0061\U000e0066\U000e0075\U000e0072\U000e0075\U000e007f" - FLAG_FOR_CENTRAL_GH_CP = "\U0001f3f4\U000e0067\U000e0068\U000e0063\U000e0070\U000e007f" - WOMAN_ZOMBIE_LIGHT_SKIN_TONE = "\U0001f9df\U0001f3fb\u200d\u2640\ufe0f" - MAHJONG_TILE_SUMMER = "\U0001f027" - FLAG_FOR_KIDRICEVO_SI_045 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0034\U000e0035\U000e007f" - FLAG_FOR_RAZAVI_KHORASAN_IR_30 = "\U0001f3f4\U000e0069\U000e0072\U000e0033\U000e0030\U000e007f" - FLAG_FOR_KOTAYK_AM_KT = "\U0001f3f4\U000e0061\U000e006d\U000e006b\U000e0074\U000e007f" - TELEPHONE_RECEIVER_WITH_PAGE = "\U0001f57c" - FLAG_FOR_SHKODER_COUNTY_AL_10 = "\U0001f3f4\U000e0061\U000e006c\U000e0031\U000e0030\U000e007f" - FLAG_FOR_AL_WAKRAH_QA_WA = "\U0001f3f4\U000e0071\U000e0061\U000e0077\U000e0061\U000e007f" - TAG_LATIN_SMALL_LETTER_U = "\U000e0075" - FLAG_FOR_CHACO_AR_H = "\U0001f3f4\U000e0061\U000e0072\U000e0068\U000e007f" - BLACK_HARD_SHELL_FLOPPY_DISK = "\U0001f5aa" - FAMILY_MAN_LIGHT_SKIN_TONE_MAN_LIGHT_SKIN_TONE_BABY_LIGHT_SKIN_TONE_BOY_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f468\U0001f3fb\u200d\U0001f476\U0001f3fb\u200d\U0001f466\U0001f3fb" - FLAG_FOR_HOKKAIDO_JP_01 = "\U0001f3f4\U000e006a\U000e0070\U000e0030\U000e0031\U000e007f" - FLAG_FOR_GEGHARKUNIK_AM_GR = "\U0001f3f4\U000e0061\U000e006d\U000e0067\U000e0072\U000e007f" - FLAG_FOR_BLACK_POINT_BS_BP = "\U0001f3f4\U000e0062\U000e0073\U000e0062\U000e0070\U000e007f" - FLAG_FOR_LUXOR_EG_LX = "\U0001f3f4\U000e0065\U000e0067\U000e006c\U000e0078\U000e007f" - FLAG_FOR_WISCONSIN_US_WI = "\U0001f3f4\U000e0075\U000e0073\U000e0077\U000e0069\U000e007f" - FLAG_FOR_MONACO_VILLE_MC_MO = "\U0001f3f4\U000e006d\U000e0063\U000e006d\U000e006f\U000e007f" - FLAG_FOR_ARMAVIR_AM_AV = "\U0001f3f4\U000e0061\U000e006d\U000e0061\U000e0076\U000e007f" - MAHJONG_TILE_EIGHT_OF_BAMBOOS = "\U0001f017" - TRIGRAM_FOR_EARTH = "\u2637" - FLAG_FOR_THUA_THIEN_HUE_VN_26 = "\U0001f3f4\U000e0076\U000e006e\U000e0032\U000e0036\U000e007f" - FLAG_FOR_POINTE_NOIRE_CG_16 = "\U0001f3f4\U000e0063\U000e0067\U000e0031\U000e0036\U000e007f" - FLAG_FOR_SAKHALIN_RU_SAK = "\U0001f3f4\U000e0072\U000e0075\U000e0073\U000e0061\U000e006b\U000e007f" - TAG_LATIN_SMALL_LETTER_Z = "\U000e007a" - FLAG_FOR_PAKRUOJIS_LT_30 = "\U0001f3f4\U000e006c\U000e0074\U000e0033\U000e0030\U000e007f" - FLAG_FOR_APE_LV_009 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0030\U000e0039\U000e007f" - LOWER_RIGHT_SHADOWED_WHITE_CIRCLE = "\U0001f53e" - FLAG_FOR_ARUBA_NL_AW = "\U0001f3f4\U000e006e\U000e006c\U000e0061\U000e0077\U000e007f" - COUPLE_WITH_HEART_WOMAN_DARK_SKIN_TONE_WOMAN_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3ff" - FLAG_FOR_KIEV_UA_30 = "\U0001f3f4\U000e0075\U000e0061\U000e0033\U000e0030\U000e007f" - FLAG_FOR_PORT_SAID_EG_PTS = "\U0001f3f4\U000e0065\U000e0067\U000e0070\U000e0074\U000e0073\U000e007f" - MAXIMIZE = "\U0001f5d6" - FLAG_FOR_TRA_VINH_VN_51 = "\U0001f3f4\U000e0076\U000e006e\U000e0035\U000e0031\U000e007f" - FLAG_FOR_RIYADH_SA_01 = "\U0001f3f4\U000e0073\U000e0061\U000e0030\U000e0031\U000e007f" - GIRLS_SYMBOL = "\U0001f6ca" - FLAG_FOR_SKIKDA_DZ_21 = "\U0001f3f4\U000e0064\U000e007a\U000e0032\U000e0031\U000e007f" - FLAG_FOR_ABU_DHABI_AE_AZ = "\U0001f3f4\U000e0061\U000e0065\U000e0061\U000e007a\U000e007f" - MAHJONG_TILE_ONE_OF_CIRCLES = "\U0001f019" - BLACK_DOWN_POINTING_BACKHAND_INDEX = "\U0001f5a3" - MAHJONG_TILE_NINE_OF_CIRCLES = "\U0001f021" - FLAG_FOR_RIGA_LV_RIX = "\U0001f3f4\U000e006c\U000e0076\U000e0072\U000e0069\U000e0078\U000e007f" - FLAG_FOR_MAKIRA_ULAWA_SB_MK = "\U0001f3f4\U000e0073\U000e0062\U000e006d\U000e006b\U000e007f" - FLAG_FOR_JUTIAPA_GT_JU = "\U0001f3f4\U000e0067\U000e0074\U000e006a\U000e0075\U000e007f" - FLAG_FOR_LORESTAN_IR_20 = "\U0001f3f4\U000e0069\U000e0072\U000e0032\U000e0030\U000e007f" - FLAG_FOR_BASHKORTOSTAN_RU_BA = "\U0001f3f4\U000e0072\U000e0075\U000e0062\U000e0061\U000e007f" - FLAG_FOR_BUSAN_KR_26 = "\U0001f3f4\U000e006b\U000e0072\U000e0032\U000e0036\U000e007f" - FLAG_FOR_MWARO_BI_MW = "\U0001f3f4\U000e0062\U000e0069\U000e006d\U000e0077\U000e007f" - FLAG_FOR_ASTURIAS_ES_AS = "\U0001f3f4\U000e0065\U000e0073\U000e0061\U000e0073\U000e007f" - FLAG_FOR_ALTA_VERAPAZ_GT_AV = "\U0001f3f4\U000e0067\U000e0074\U000e0061\U000e0076\U000e007f" - FLAG_FOR_BRUNEI_MUARA_BN_BM = "\U0001f3f4\U000e0062\U000e006e\U000e0062\U000e006d\U000e007f" - FLAG_FOR_LOVRENC_NA_POHORJU_SI_167 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0036\U000e0037\U000e007f" - FLAG_FOR_WALLIS_ANDAMP_FUTUNA_FR_WF = "\U0001f3f4\U000e0066\U000e0072\U000e0077\U000e0066\U000e007f" - KISS_MAN_WOMAN_DARK_SKIN_TONE = "\U0001f468\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3ff" - FLAG_FOR_LORI_AM_LO = "\U0001f3f4\U000e0061\U000e006d\U000e006c\U000e006f\U000e007f" - KISS_MAN_DARK_SKIN_TONE_WOMAN_LIGHT_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fb" - FLAG_FOR_GIZA_EG_GZ = "\U0001f3f4\U000e0065\U000e0067\U000e0067\U000e007a\U000e007f" - FLAG_FOR_SAINT_MARY_JM_05 = "\U0001f3f4\U000e006a\U000e006d\U000e0030\U000e0035\U000e007f" - DIGIT_FIVE = "5\ufe0f" - FAMILY_WOMAN_MEDIUM_LIGHT_SKIN_TONE_WOMAN_MEDIUM_LIGHT_SKIN_TONE_GIRL_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f469\U0001f3fc\u200d\U0001f467\U0001f3fc" - FLAG_FOR_MAGADAN_RU_MAG = "\U0001f3f4\U000e0072\U000e0075\U000e006d\U000e0061\U000e0067\U000e007f" - LEFT_ANGER_BUBBLE = "\U0001f5ee" - DIGIT_EIGHT = "8\ufe0f" - SIDEWAYS_BLACK_UP_POINTING_INDEX = "\U0001f5a0" - FLAG_FOR_SOUTHERN_HIGHLANDS_PG_SHM = "\U0001f3f4\U000e0070\U000e0067\U000e0073\U000e0068\U000e006d\U000e007f" - FLAG_FOR_NURISTAN_AF_NUR = "\U0001f3f4\U000e0061\U000e0066\U000e006e\U000e0075\U000e0072\U000e007f" - FLAG_FOR_LOUISIANA_US_LA = "\U0001f3f4\U000e0075\U000e0073\U000e006c\U000e0061\U000e007f" - FLAG_FOR_MAULE_CL_ML = "\U0001f3f4\U000e0063\U000e006c\U000e006d\U000e006c\U000e007f" - FAMILY_MAN_MEDIUM_SKIN_TONE_WOMAN_MEDIUM_SKIN_TONE_BOY_MEDIUM_SKIN_TONE_BOY_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f469\U0001f3fd\u200d\U0001f466\U0001f3fd\u200d\U0001f466\U0001f3fd" - FAMILY_WOMAN_LIGHT_SKIN_TONE_WOMAN_LIGHT_SKIN_TONE_BOY_LIGHT_SKIN_TONE_BABY_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f469\U0001f3fb\u200d\U0001f466\U0001f3fb\u200d\U0001f476\U0001f3fb" - FLAG_FOR_PANAMA_PA_8 = "\U0001f3f4\U000e0070\U000e0061\U000e0038\U000e007f" - FLAG_FOR_IZMIR_TR_35 = "\U0001f3f4\U000e0074\U000e0072\U000e0033\U000e0035\U000e007f" - FLAG_FOR_CABINDA_AO_CAB = "\U0001f3f4\U000e0061\U000e006f\U000e0063\U000e0061\U000e0062\U000e007f" - OPEN_FOLDER = "\U0001f5c1" - FLAG_FOR_VAUD_CH_VD = "\U0001f3f4\U000e0063\U000e0068\U000e0076\U000e0064\U000e007f" - LIGHTNING_MOOD = "\U0001f5f2" - KISS_WOMAN_MEDIUM_SKIN_TONE_WOMAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fc" - FLAG_FOR_SUCRE_CO_SUC = "\U0001f3f4\U000e0063\U000e006f\U000e0073\U000e0075\U000e0063\U000e007f" - MAHJONG_TILE_WHITE_DRAGON = "\U0001f006" - FLAG_FOR_MADRE_DE_DIOS_PE_MDD = "\U0001f3f4\U000e0070\U000e0065\U000e006d\U000e0064\U000e0064\U000e007f" - FLAG_FOR_BENGUELA_AO_BGU = "\U0001f3f4\U000e0061\U000e006f\U000e0062\U000e0067\U000e0075\U000e007f" - FLAG_FOR_ST_JULIAN_S_MT_48 = "\U0001f3f4\U000e006d\U000e0074\U000e0034\U000e0038\U000e007f" - FLAG_FOR_TIMIS_RO_TM = "\U0001f3f4\U000e0072\U000e006f\U000e0074\U000e006d\U000e007f" - FAMILY_MAN_MEDIUM_SKIN_TONE_GIRL_MEDIUM_SKIN_TONE_BABY_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f467\U0001f3fd\u200d\U0001f476\U0001f3fd" - FLAG_FOR_BIE_AO_BIE = "\U0001f3f4\U000e0061\U000e006f\U000e0062\U000e0069\U000e0065\U000e007f" - MAHJONG_TILE_WINTER = "\U0001f029" - FAMILY_MAN_MEDIUM_DARK_SKIN_TONE_WOMAN_MEDIUM_DARK_SKIN_TONE_BABY_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f469\U0001f3fe\u200d\U0001f476\U0001f3fe" - FLAG_FOR_BLUE_NILE_SD_NB = "\U0001f3f4\U000e0073\U000e0064\U000e006e\U000e0062\U000e007f" - EMPTY_DOCUMENT = "\U0001f5cb" - MINIMIZE = "\U0001f5d5" - FLAG_FOR_KWALE_KE_19 = "\U0001f3f4\U000e006b\U000e0065\U000e0031\U000e0039\U000e007f" - FLAG_FOR_GRAND_PORT_MU_GP = "\U0001f3f4\U000e006d\U000e0075\U000e0067\U000e0070\U000e007f" - FLAG_FOR_CHIMBORAZO_EC_H = "\U0001f3f4\U000e0065\U000e0063\U000e0068\U000e007f" - FLAG_FOR_HUAMBO_AO_HUA = "\U0001f3f4\U000e0061\U000e006f\U000e0068\U000e0075\U000e0061\U000e007f" - FLAG_FOR_CUANZA_SUL_AO_CUS = "\U0001f3f4\U000e0061\U000e006f\U000e0063\U000e0075\U000e0073\U000e007f" - FLAG_FOR_BREMEN_DE_HB = "\U0001f3f4\U000e0064\U000e0065\U000e0068\U000e0062\U000e007f" - LIPS = "\U0001f5e2" - FLAG_FOR_ALTO_PARAGUAY_PY_16 = "\U0001f3f4\U000e0070\U000e0079\U000e0031\U000e0036\U000e007f" - FLAG_FOR_NEW_YORK_US_NY = "\U0001f3f4\U000e0075\U000e0073\U000e006e\U000e0079\U000e007f" - KISS_WOMAN_MEDIUM_LIGHT_SKIN_TONE_MAN_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb" - FLAG_FOR_NORTH_CAROLINA_US_NC = "\U0001f3f4\U000e0075\U000e0073\U000e006e\U000e0063\U000e007f" - COUPLE_WITH_HEART_WOMAN_DARK_SKIN_TONE_MAN_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff" - WHITE_CIRCLE_WITH_TWO_DOTS = "\u2687" - KISS_MAN_MEDIUM_SKIN_TONE_MAN = "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468" - MAN_ZOMBIE_LIGHT_SKIN_TONE = "\U0001f9df\U0001f3fb\u200d\u2642\ufe0f" - FLAG_FOR_PERAK_MY_08 = "\U0001f3f4\U000e006d\U000e0079\U000e0030\U000e0038\U000e007f" - FLAG_FOR_SISAK_MOSLAVINA_HR_03 = "\U0001f3f4\U000e0068\U000e0072\U000e0030\U000e0033\U000e007f" - FLAG_FOR_ARDABIL_IR_03 = "\U0001f3f4\U000e0069\U000e0072\U000e0030\U000e0033\U000e007f" - FLAG_FOR_KARNATAKA_IN_KA = "\U0001f3f4\U000e0069\U000e006e\U000e006b\U000e0061\U000e007f" - LEFT_HAND_TELEPHONE_RECEIVER = "\U0001f57b" - FLAG_FOR_NEVADA_US_NV = "\U0001f3f4\U000e0075\U000e0073\U000e006e\U000e0076\U000e007f" - FLAG_FOR_DIBER_COUNTY_AL_09 = "\U0001f3f4\U000e0061\U000e006c\U000e0030\U000e0039\U000e007f" - FLAG_FOR_LUNDA_SUL_AO_LSU = "\U0001f3f4\U000e0061\U000e006f\U000e006c\U000e0073\U000e0075\U000e007f" - KISS_WOMAN_WOMAN_LIGHT_SKIN_TONE = "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fb" - SIDEWAYS_WHITE_DOWN_POINTING_INDEX = "\U0001f59f" - FLAG_FOR_NAKHON_SI_THAMMARAT_TH_80 = "\U0001f3f4\U000e0074\U000e0068\U000e0038\U000e0030\U000e007f" - FLAG_FOR_KYRENIA_CY_06 = "\U0001f3f4\U000e0063\U000e0079\U000e0030\U000e0036\U000e007f" - FLAG_FOR_GUNA_YALA_PA_KY = "\U0001f3f4\U000e0070\U000e0061\U000e006b\U000e0079\U000e007f" - FLAG_FOR_HUILA_AO_HUI = "\U0001f3f4\U000e0061\U000e006f\U000e0068\U000e0075\U000e0069\U000e007f" - FLAG_FOR_NAAMA_DZ_45 = "\U0001f3f4\U000e0064\U000e007a\U000e0034\U000e0035\U000e007f" - FLAG_FOR_SAR_E_POL_AF_SAR = "\U0001f3f4\U000e0061\U000e0066\U000e0073\U000e0061\U000e0072\U000e007f" - COUPLE_WITH_HEART_WOMAN_LIGHT_SKIN_TONE_MAN_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd" - FLAG_FOR_META_CO_MET = "\U0001f3f4\U000e0063\U000e006f\U000e006d\U000e0065\U000e0074\U000e007f" - FLAG_FOR_NORTHWEST_TERRITORIES_CA_NT = "\U0001f3f4\U000e0063\U000e0061\U000e006e\U000e0074\U000e007f" - FLAG_FOR_PENGHU_TW_PEN = "\U0001f3f4\U000e0074\U000e0077\U000e0070\U000e0065\U000e006e\U000e007f" - FLAG_FOR_TOKYO_JP_13 = "\U0001f3f4\U000e006a\U000e0070\U000e0031\U000e0033\U000e007f" - COUPLE_WITH_HEART_WOMAN_DARK_SKIN_TONE_MAN_LIGHT_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb" - TAG_LATIN_SMALL_LETTER_X = "\U000e0078" - FLAG_FOR_SAINT_GEORGE_AG_03 = "\U0001f3f4\U000e0061\U000e0067\U000e0030\U000e0033\U000e007f" - REVERSED_RAISED_HAND_WITH_FINGERS_SPLAYED = "\U0001f591" - JAPANESE_BANK_SYMBOL = "\u26fb" - FLAG_FOR_CUANZA_NORTE_AO_CNO = "\U0001f3f4\U000e0061\U000e006f\U000e0063\U000e006e\U000e006f\U000e007f" - FLAG_FOR_NANA_GREBIZI_CF_KB = "\U0001f3f4\U000e0063\U000e0066\U000e006b\U000e0062\U000e007f" - FLAG_FOR_MALANJE_AO_MAL = "\U0001f3f4\U000e0061\U000e006f\U000e006d\U000e0061\U000e006c\U000e007f" - ROTATED_HEAVY_BLACK_HEART_BULLET = "\u2765" - REVERSED_THUMBS_UP_SIGN = "\U0001f592" - CELTIC_CROSS = "\U0001f548" - KISS_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fc" - SYMBOL_FOR_MARKS_CHAPTER = "\U0001f545" - TAG_LATIN_SMALL_LETTER_E = "\U000e0065" - FLAG_FOR_NIGER_NG_NI = "\U0001f3f4\U000e006e\U000e0067\U000e006e\U000e0069\U000e007f" - TAG_DIGIT_EIGHT = "\U000e0038" - MAHJONG_TILE_ORCHID = "\U0001f023" - TAG_LATIN_CAPITAL_LETTER_B = "\U000e0042" - COUPLE_WITH_HEART_MAN_MEDIUM_DARK_SKIN_TONE_MAN_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb" - FLAG_FOR_BEIRUT_LB_BA = "\U0001f3f4\U000e006c\U000e0062\U000e0062\U000e0061\U000e007f" - BLACK_DROPLET = "\U0001f322" - FLAG_FOR_KALBAJAR_AZ_KAL = "\U0001f3f4\U000e0061\U000e007a\U000e006b\U000e0061\U000e006c\U000e007f" - FLAG_FOR_BINH_DUONG_VN_57 = "\U0001f3f4\U000e0076\U000e006e\U000e0035\U000e0037\U000e007f" - FLAG_FOR_NAMIBE_AO_NAM = "\U0001f3f4\U000e0061\U000e006f\U000e006e\U000e0061\U000e006d\U000e007f" - FLAG_FOR_CALARASI_RO_CL = "\U0001f3f4\U000e0072\U000e006f\U000e0063\U000e006c\U000e007f" - CIRCLED_CROSS_POMMEE = "\U0001f540" - FLAG_FOR_A_ANA_WS_AA = "\U0001f3f4\U000e0077\U000e0073\U000e0061\U000e0061\U000e007f" - FAMILY_WOMAN_LIGHT_SKIN_TONE_MAN_LIGHT_SKIN_TONE_BABY_LIGHT_SKIN_TONE_GIRL_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f468\U0001f3fb\u200d\U0001f476\U0001f3fb\u200d\U0001f467\U0001f3fb" - BALLOT_SCRIPT_X = "\U0001f5f4" - MAHJONG_TILE_WEST_WIND = "\U0001f002" - MAHJONG_TILE_EIGHT_OF_CIRCLES = "\U0001f020" - FLAG_FOR_MOXICO_AO_MOX = "\U0001f3f4\U000e0061\U000e006f\U000e006d\U000e006f\U000e0078\U000e007f" - EMPTY_NOTE = "\U0001f5c5" - FLAG_FOR_MISSOURI_US_MO = "\U0001f3f4\U000e0075\U000e0073\U000e006d\U000e006f\U000e007f" - FLAG_FOR_PENANG_MY_07 = "\U0001f3f4\U000e006d\U000e0079\U000e0030\U000e0037\U000e007f" - FLAG_FOR_WAJIR_KE_46 = "\U0001f3f4\U000e006b\U000e0065\U000e0034\U000e0036\U000e007f" - FLAG_FOR_CANILLO_AD_02 = "\U0001f3f4\U000e0061\U000e0064\U000e0030\U000e0032\U000e007f" - FLAG_FOR_VIRGINIA_US_VA = "\U0001f3f4\U000e0075\U000e0073\U000e0076\U000e0061\U000e007f" - CROSS_POMMEE = "\U0001f542" - FLAG_FOR_FIER_COUNTY_AL_04 = "\U0001f3f4\U000e0061\U000e006c\U000e0030\U000e0034\U000e007f" - FLAG_FOR_SCHLESWIG_HOLSTEIN_DE_SH = "\U0001f3f4\U000e0064\U000e0065\U000e0073\U000e0068\U000e007f" - COUPLE_WITH_HEART_MAN_MEDIUM_DARK_SKIN_TONE_WOMAN_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fe" - FLAG_FOR_LUALABA_CD_LU = "\U0001f3f4\U000e0063\U000e0064\U000e006c\U000e0075\U000e007f" - NORTHEAST_POINTING_AIRPLANE = "\U0001f6ea" - FAMILY_WOMAN_WOMAN_GIRL_BABY = "\U0001f469\u200d\U0001f469\u200d\U0001f467\u200d\U0001f476" - MAHJONG_TILE_SEVEN_OF_BAMBOOS = "\U0001f016" - FLAG_FOR_AGSTAFA_AZ_AGA = "\U0001f3f4\U000e0061\U000e007a\U000e0061\U000e0067\U000e0061\U000e007f" - FLAG_FOR_AMNAT_CHAROEN_TH_37 = "\U0001f3f4\U000e0074\U000e0068\U000e0033\U000e0037\U000e007f" - FLAG_FOR_QUANG_TRI_VN_25 = "\U0001f3f4\U000e0076\U000e006e\U000e0032\U000e0035\U000e007f" - STAMPED_ENVELOPE = "\U0001f583" - BEATS_1_LOGO = "\uf79c" - FLAG_FOR_AFYONKARAHISAR_TR_03 = "\U0001f3f4\U000e0074\U000e0072\U000e0030\U000e0033\U000e007f" - FLAG_FOR_MASSACHUSETTS_US_MA = "\U0001f3f4\U000e0075\U000e0073\U000e006d\U000e0061\U000e007f" - FLAG_FOR_GULF_PG_GPK = "\U0001f3f4\U000e0070\U000e0067\U000e0067\U000e0070\U000e006b\U000e007f" - FLAG_FOR_UIGE_AO_UIG = "\U0001f3f4\U000e0061\U000e006f\U000e0075\U000e0069\U000e0067\U000e007f" - KISS_MAN_MEDIUM_DARK_SKIN_TONE_MAN_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd" - FLAG_FOR_SAINT_KITTS_KN_K = "\U0001f3f4\U000e006b\U000e006e\U000e006b\U000e007f" - FLAG_FOR_CUANDO_CUBANGO_AO_CCU = "\U0001f3f4\U000e0061\U000e006f\U000e0063\U000e0063\U000e0075\U000e007f" - KISS_WOMAN_MEDIUM_SKIN_TONE_WOMAN_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fb" - FLAG_FOR_SAN_LUIS_AR_D = "\U0001f3f4\U000e0061\U000e0072\U000e0064\U000e007f" - FLAG_FOR_ZHYTOMYRSHCHYNA_UA_18 = "\U0001f3f4\U000e0075\U000e0061\U000e0031\U000e0038\U000e007f" - MAHJONG_TILE_TWO_OF_BAMBOOS = "\U0001f011" - FLAG_FOR_SAINT_THOMAS_JM_03 = "\U0001f3f4\U000e006a\U000e006d\U000e0030\U000e0033\U000e007f" - TAG_LATIN_SMALL_LETTER_I = "\U000e0069" - VESTA = "\u26b6" - TAG_LATIN_SMALL_LETTER_Y = "\U000e0079" - TAG_LATIN_SMALL_LETTER_Q = "\U000e0071" - SIDEWAYS_WHITE_LEFT_POINTING_INDEX = "\U0001f598" - FLAG_FOR_IDAHO_US_ID = "\U0001f3f4\U000e0075\U000e0073\U000e0069\U000e0064\U000e007f" - TAG_LATIN_CAPITAL_LETTER_Z = "\U000e005a" - FLAG_FOR_SYUNIK_AM_SU = "\U0001f3f4\U000e0061\U000e006d\U000e0073\U000e0075\U000e007f" - FLAG_FOR_EAST_KAZAKHSTAN_KZ_VOS = "\U0001f3f4\U000e006b\U000e007a\U000e0076\U000e006f\U000e0073\U000e007f" - FLAG_FOR_JAMTLAND_SE_Z = "\U0001f3f4\U000e0073\U000e0065\U000e007a\U000e007f" - WOMAN_ZOMBIE_MEDIUM_DARK_SKIN_TONE = "\U0001f9df\U0001f3fe\u200d\u2640\ufe0f" - FLAG_FOR_NEVSEHIR_TR_50 = "\U0001f3f4\U000e0074\U000e0072\U000e0035\U000e0030\U000e007f" - FLAG_FOR_SKANE_SE_M = "\U0001f3f4\U000e0073\U000e0065\U000e006d\U000e007f" - KISS_WOMAN_MEDIUM_SKIN_TONE_MAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc" - FRAME_WITH_AN_X = "\U0001f5be" - FLAG_FOR_SANTIAGO_DEL_ESTERO_AR_G = "\U0001f3f4\U000e0061\U000e0072\U000e0067\U000e007f" - FLAG_FOR_LA_MASSANA_AD_04 = "\U0001f3f4\U000e0061\U000e0064\U000e0030\U000e0034\U000e007f" - FLAG_FOR_STAVROPOL_KRAI_RU_STA = "\U0001f3f4\U000e0072\U000e0075\U000e0073\U000e0074\U000e0061\U000e007f" - FAMILY_MAN_BABY_GIRL = "\U0001f468\u200d\U0001f476\u200d\U0001f467" - NEUTER = "\u26b2" - FLAG_FOR_TRIPURA_IN_TR = "\U0001f3f4\U000e0069\U000e006e\U000e0074\U000e0072\U000e007f" - FAMILY_WOMAN_DARK_SKIN_TONE_MAN_DARK_SKIN_TONE_BABY_DARK_SKIN_TONE_GIRL_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f468\U0001f3ff\u200d\U0001f476\U0001f3ff\u200d\U0001f467\U0001f3ff" - FLAG_FOR_ENUGU_NG_EN = "\U0001f3f4\U000e006e\U000e0067\U000e0065\U000e006e\U000e007f" - FLAG_FOR_EL_ORO_EC_O = "\U0001f3f4\U000e0065\U000e0063\U000e006f\U000e007f" - SQUARED_KEY = "\u26bf" - MAHJONG_TILE_NINE_OF_BAMBOOS = "\U0001f018" - PRINTER_ICON = "\U0001f5b6" - SESQUIQUADRATE = "\u26bc" - FAMILY_WOMAN_DARK_SKIN_TONE_WOMAN_DARK_SKIN_TONE_BABY_DARK_SKIN_TONE_BOY_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f469\U0001f3ff\u200d\U0001f476\U0001f3ff\u200d\U0001f466\U0001f3ff" - MAHJONG_TILE_SIX_OF_BAMBOOS = "\U0001f015" - WOMAN_IN_TUXEDO_DARK_SKIN_TONE = "\U0001f935\U0001f3ff\u200d\u2640\ufe0f" - FLAG_FOR_LA_RIOJA_AR_F = "\U0001f3f4\U000e0061\U000e0072\U000e0066\U000e007f" - FLAG_FOR_BUSHEHR_IR_06 = "\U0001f3f4\U000e0069\U000e0072\U000e0030\U000e0036\U000e007f" - DIGIT_THREE = "3\ufe0f" - FLAG_FOR_LUXEMBOURG_LU_LU = "\U0001f3f4\U000e006c\U000e0075\U000e006c\U000e0075\U000e007f" - FLAG_FOR_DAGESTAN_RU_DA = "\U0001f3f4\U000e0072\U000e0075\U000e0064\U000e0061\U000e007f" - BLACK_DRAUGHTS_KING = "\u26c3" - WHITE_DRAUGHTS_KING = "\u26c1" - FLAG_FOR_CATAMARCA_AR_K = "\U0001f3f4\U000e0061\U000e0072\U000e006b\U000e007f" - TAG_LATIN_SMALL_LETTER_B = "\U000e0062" - FLAG_FOR_BARBUDA_AG_10 = "\U0001f3f4\U000e0061\U000e0067\U000e0031\U000e0030\U000e007f" - FLAG_FOR_MARL_NZ_MBH = "\U0001f3f4\U000e006e\U000e007a\U000e006d\U000e0062\U000e0068\U000e007f" - FAMILY_MAN_LIGHT_SKIN_TONE_BABY_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f476\U0001f3fb" - BLACK_SNOWMAN = "\u26c7" - ASTRONOMICAL_SYMBOL_FOR_URANUS = "\u26e2" - FLAG_FOR_LAO_CAI_VN_02 = "\U0001f3f4\U000e0076\U000e006e\U000e0030\U000e0032\U000e007f" - FLAG_FOR_JURA_CH_JU = "\U0001f3f4\U000e0063\U000e0068\U000e006a\U000e0075\U000e007f" - MAHJONG_TILE_FOUR_OF_BAMBOOS = "\U0001f013" - TAG_LATIN_CAPITAL_LETTER_N = "\U000e004e" - FAMILY_MAN_BABY_BOY = "\U0001f468\u200d\U0001f476\u200d\U0001f466" - FLAG_FOR_MISIONES_AR_N = "\U0001f3f4\U000e0061\U000e0072\U000e006e\U000e007f" - WOMAN_ZOMBIE_DARK_SKIN_TONE = "\U0001f9df\U0001f3ff\u200d\u2640\ufe0f" - FAMILY_WOMAN_BABY = "\U0001f469\u200d\U0001f476" - PALLAS = "\u26b4" - DIGIT_SIX = "6\ufe0f" - DOUBLED_MALE_SIGN = "\u26a3" - FLAG_FOR_JOWZJAN_AF_JOW = "\U0001f3f4\U000e0061\U000e0066\U000e006a\U000e006f\U000e0077\U000e007f" - FLAG_FOR_ESCALDES_ENGORDANY_AD_08 = "\U0001f3f4\U000e0061\U000e0064\U000e0030\U000e0038\U000e007f" - FLAG_FOR_MYKOLAYIVSCHYNA_UA_48 = "\U0001f3f4\U000e0075\U000e0061\U000e0034\U000e0038\U000e007f" - FLAG_FOR_DEIR_EZ_ZOR_SY_DY = "\U0001f3f4\U000e0073\U000e0079\U000e0064\U000e0079\U000e007f" - RIGHT_SPEECH_BUBBLE = "\U0001f5e9" - TAG_DIGIT_ZERO = "\U000e0030" - FLAG_FOR_FUJAIRAH_AE_FU = "\U0001f3f4\U000e0061\U000e0065\U000e0066\U000e0075\U000e007f" - LEFT_CLOSED_ENTRY = "\u26dc" - MAHJONG_TILE_BAMBOO = "\U0001f024" - SQUARED_SALTIRE = "\u26dd" - BLACK_MOON_LILITH = "\u26b8" - FLAG_FOR_LUNDA_NORTE_AO_LNO = "\U0001f3f4\U000e0061\U000e006f\U000e006c\U000e006e\U000e006f\U000e007f" - DRIVE_SLOW_SIGN = "\u26da" - FLAG_FOR_GRAND_CAPE_MOUNT_LR_CM = "\U0001f3f4\U000e006c\U000e0072\U000e0063\U000e006d\U000e007f" - FLAG_FOR_FORMOSA_AR_P = "\U0001f3f4\U000e0061\U000e0072\U000e0070\U000e007f" - FLAG_FOR_WEST_NEW_BRITAIN_PG_WBK = "\U0001f3f4\U000e0070\U000e0067\U000e0077\U000e0062\U000e006b\U000e007f" - BLACK_LEFT_LANE_MERGE = "\u26d8" - FLAG_FOR_ARARAT_AM_AR = "\U0001f3f4\U000e0061\U000e006d\U000e0061\U000e0072\U000e007f" - SEXTILE = "\u26b9" - FLAG_FOR_SAN_JUAN_AR_J = "\U0001f3f4\U000e0061\U000e0072\U000e006a\U000e007f" - FLAG_FOR_BLOKE_SI_150 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0035\U000e0030\U000e007f" - INCREASE_FONT_SIZE_SYMBOL = "\U0001f5da" - FLAG_FOR_ANHUI_CN_34 = "\U0001f3f4\U000e0063\U000e006e\U000e0033\U000e0034\U000e007f" - FLAG_FOR_NORTH_AEGEAN_GR_K = "\U0001f3f4\U000e0067\U000e0072\U000e006b\U000e007f" - FAMILY_MAN_WOMAN_BABY_GIRL = "\U0001f468\u200d\U0001f469\u200d\U0001f476\u200d\U0001f467" - FLAG_FOR_HADRAMAUT_YE_HD = "\U0001f3f4\U000e0079\U000e0065\U000e0068\U000e0064\U000e007f" - TURNED_WHITE_SHOGI_PIECE = "\u26c9" - MALE_WITH_STROKE_SIGN = "\u26a6" - RESTRICTED_LEFT_ENTRY_1 = "\u26e0" - FLAG_FOR_HAVANA_CU_03 = "\U0001f3f4\U000e0063\U000e0075\U000e0030\U000e0033\U000e007f" - LOWER_LEFT_PENCIL = "\U0001f589" - FLAG_FOR_CHUBUT_AR_U = "\U0001f3f4\U000e0061\U000e0072\U000e0075\U000e007f" - CLOCKWISE_RIGHT_AND_LEFT_SEMICIRCLE_ARROWS = "\U0001f5d8" - FLAG_FOR_SANTIAGO_METROPOLITAN_CL_RM = "\U0001f3f4\U000e0063\U000e006c\U000e0072\U000e006d\U000e007f" - CASTLE_2 = "\u26eb" - THREE_RAYS_RIGHT = "\U0001f5e7" - JUNO = "\u26b5" - FLAG_FOR_HAI_DUONG_VN_61 = "\U0001f3f4\U000e0076\U000e006e\U000e0036\U000e0031\U000e007f" - FLAG_FOR_ABSHERON_AZ_ABS = "\U0001f3f4\U000e0061\U000e007a\U000e0061\U000e0062\U000e0073\U000e007f" - FLAG_FOR_CORRIENTES_AR_W = "\U0001f3f4\U000e0061\U000e0072\U000e0077\U000e007f" - MAHJONG_TILE_SOUTH_WIND = "\U0001f001" - FLAG_FOR_GRAND_CASABLANCA_MA_08 = "\U0001f3f4\U000e006d\U000e0061\U000e0030\U000e0038\U000e007f" - FLAG_FOR_NGARCHELONG_PW_218 = "\U0001f3f4\U000e0070\U000e0077\U000e0032\U000e0031\U000e0038\U000e007f" - FLAG_FOR_CORDOBA_AR_X = "\U0001f3f4\U000e0061\U000e0072\U000e0078\U000e007f" - FLAG_FOR_CENTRAL_SINGAPORE_SG_01 = "\U0001f3f4\U000e0073\U000e0067\U000e0030\U000e0031\U000e007f" - FLAG_FOR_CAMPANIA_IT_72 = "\U0001f3f4\U000e0069\U000e0074\U000e0037\U000e0032\U000e007f" - FLAG_FOR_CIBAO_NORTE_DO_35 = "\U0001f3f4\U000e0064\U000e006f\U000e0033\U000e0035\U000e007f" - MAHJONG_TILE_THREE_OF_BAMBOOS = "\U0001f012" - FLAG_FOR_ARAGATSOTN_AM_AG = "\U0001f3f4\U000e0061\U000e006d\U000e0061\U000e0067\U000e007f" - FLAG_FOR_GRAND_GEDEH_LR_GG = "\U0001f3f4\U000e006c\U000e0072\U000e0067\U000e0067\U000e007f" - FLAG_FOR_ABRUZZO_IT_65 = "\U0001f3f4\U000e0069\U000e0074\U000e0036\U000e0035\U000e007f" - CERES = "\u26b3" - FAMILY_MAN_LIGHT_SKIN_TONE_BOY_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f466\U0001f3fb" - FLAG_FOR_CARINTHIA_AT_2 = "\U0001f3f4\U000e0061\U000e0074\U000e0032\U000e007f" - MARRIAGE_SYMBOL = "\u26ad" - LIGHTNING = "\u2607" - SUN_2 = "\u2609" - FLAG_FOR_CENTRAL_GREECE_GR_H = "\U0001f3f4\U000e0067\U000e0072\U000e0068\U000e007f" - FAMILY_WOMAN_MAN_BOY_GIRL = "\U0001f469\u200d\U0001f468\u200d\U0001f466\u200d\U0001f467" - FLAG_FOR_BASEL_LANDSCHAFT_CH_BL = "\U0001f3f4\U000e0063\U000e0068\U000e0062\U000e006c\U000e007f" - FLAG_FOR_UTTARAKHAND_IN_UT = "\U0001f3f4\U000e0069\U000e006e\U000e0075\U000e0074\U000e007f" - UP_POINTING_SMALL_AIRPLANE = "\U0001f6e8" - FLAG_FOR_MENDOZA_AR_M = "\U0001f3f4\U000e0061\U000e0072\U000e006d\U000e007f" - FLAG_FOR_MAMOU_REGION_GN_M = "\U0001f3f4\U000e0067\U000e006e\U000e006d\U000e007f" - FLAG_FOR_ATTAPEU_LA_AT = "\U0001f3f4\U000e006c\U000e0061\U000e0061\U000e0074\U000e007f" - FLAG_FOR_SABA_NL_BQ2 = "\U0001f3f4\U000e006e\U000e006c\U000e0062\U000e0071\U000e0032\U000e007f" - FLYING_ENVELOPE = "\U0001f585" - FLAG_FOR_KIRKUK_IQ_KI = "\U0001f3f4\U000e0069\U000e0071\U000e006b\U000e0069\U000e007f" - BLACK_PUSHPIN = "\U0001f588" - CROSSING_LANES = "\u26cc" - FAMILY_WOMAN_BABY_BOY = "\U0001f469\u200d\U0001f476\u200d\U0001f466" - TAG_LATIN_SMALL_LETTER_T = "\U000e0074" - COUPLE_WITH_HEART_MAN_MEDIUM_LIGHT_SKIN_TONE_WOMAN = "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f469" - MOOD_BUBBLE = "\U0001f5f0" - MAHJONG_TILE_PLUM = "\U0001f022" - FLAG_FOR_NOUAKCHOTT_OUEST_MR_13 = "\U0001f3f4\U000e006d\U000e0072\U000e0031\U000e0033\U000e007f" - RIGHT_THOUGHT_BUBBLE = "\U0001f5ed" - DIGIT_ZERO = "0\ufe0f" - FLAG_FOR_SAXONY_DE_SN = "\U0001f3f4\U000e0064\U000e0065\U000e0073\U000e006e\U000e007f" - FLAG_FOR_SACATEPEQUEZ_GT_SA = "\U0001f3f4\U000e0067\U000e0074\U000e0073\U000e0061\U000e007f" - KISS_MAN_DARK_SKIN_TONE_MAN = "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468" - DESCENDING_NODE = "\u260b" - FLAG_FOR_VIEUX_FORT_LC_11 = "\U0001f3f4\U000e006c\U000e0063\U000e0031\U000e0031\U000e007f" - ONCOMING_FIRE_ENGINE = "\U0001f6f1" - FLAG_FOR_QUANG_BINH_VN_24 = "\U0001f3f4\U000e0076\U000e006e\U000e0032\U000e0034\U000e007f" - FLAG_FOR_TYROL_AT_7 = "\U0001f3f4\U000e0061\U000e0074\U000e0037\U000e007f" - FLAG_FOR_FRANCISTOWN_BW_FR = "\U0001f3f4\U000e0062\U000e0077\U000e0066\U000e0072\U000e007f" - DISABLED_CAR = "\u26cd" - FAMILY_WOMAN_MEDIUM_LIGHT_SKIN_TONE_WOMAN_MEDIUM_LIGHT_SKIN_TONE_GIRL_MEDIUM_LIGHT_SKIN_TONE_GIRL_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f469\U0001f3fc\u200d\U0001f467\U0001f3fc\u200d\U0001f467\U0001f3fc" - FLAG_FOR_MBOMOU_CF_MB = "\U0001f3f4\U000e0063\U000e0066\U000e006d\U000e0062\U000e007f" - ASCENDING_NODE = "\u260a" - FLAG_FOR_ILOCOS_PH_01 = "\U0001f3f4\U000e0070\U000e0068\U000e0030\U000e0031\U000e007f" - HEAVY_CIRCLE_WITH_STROKE_AND_TWO_DOTS_ABOVE = "\u26e3" - MAHJONG_TILE_FIVE_OF_BAMBOOS = "\U0001f014" - FLAG_FOR_SAINT_GEORGE_GD_03 = "\U0001f3f4\U000e0067\U000e0064\U000e0030\U000e0033\U000e007f" - COUPLE_WITH_HEART_WOMAN_LIGHT_SKIN_TONE_MAN_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe" - FLAG_FOR_TAIWAN_CN_71 = "\U0001f3f4\U000e0063\U000e006e\U000e0037\U000e0031\U000e007f" - FLAG_FOR_VENETO_IT_34 = "\U0001f3f4\U000e0069\U000e0074\U000e0033\U000e0034\U000e007f" - FLAG_FOR_LUANDA_AO_LUA = "\U0001f3f4\U000e0061\U000e006f\U000e006c\U000e0075\U000e0061\U000e007f" - FLAG_FOR_SAINT_LOUIS_SN_SL = "\U0001f3f4\U000e0073\U000e006e\U000e0073\U000e006c\U000e007f" - FAMILY_MAN_DARK_SKIN_TONE_MAN_DARK_SKIN_TONE_GIRL_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f468\U0001f3ff\u200d\U0001f467\U0001f3ff" - EMPTY_PAGE = "\U0001f5cc" - BOUQUET_OF_FLOWERS = "\U0001f395" - FLAG_FOR_XAISOMBOUN_LA_XS = "\U0001f3f4\U000e006c\U000e0061\U000e0078\U000e0073\U000e007f" - FLAG_FOR_TUCUMAN_AR_T = "\U0001f3f4\U000e0061\U000e0072\U000e0074\U000e007f" - RAIN = "\u26c6" - FAMILY_MAN_MAN_BOY_GIRL = "\U0001f468\u200d\U0001f468\u200d\U0001f466\u200d\U0001f467" - FLAG_FOR_ANKARA_TR_06 = "\U0001f3f4\U000e0074\U000e0072\U000e0030\U000e0036\U000e007f" - FLAG_FOR_GUIZHOU_CN_52 = "\U0001f3f4\U000e0063\U000e006e\U000e0035\U000e0032\U000e007f" - FLAG_FOR_SVALBARD_NO_21 = "\U0001f3f4\U000e006e\U000e006f\U000e0032\U000e0031\U000e007f" - FARSI_SYMBOL = "\u262b" - PENTAGRAM = "\u26e4" - HARD_DISK = "\U0001f5b4" - FLAG_FOR_VORARLBERG_AT_8 = "\U0001f3f4\U000e0061\U000e0074\U000e0038\U000e007f" - NO_PIRACY = "\U0001f572" - RECYCLING_SYMBOL_FOR_TYPE_6_PLASTICS = "\u2678" - OPPOSITION = "\u260d" - FLAG_FOR_ZAIRE_AO_ZAI = "\U0001f3f4\U000e0061\U000e006f\U000e007a\U000e0061\U000e0069\U000e007f" - FLAG_FOR_AMHARA_ET_AM = "\U0001f3f4\U000e0065\U000e0074\U000e0061\U000e006d\U000e007f" - MAHJONG_TILE_SPRING = "\U0001f026" - FLAG_FOR_RED_SEA_SD_RS = "\U0001f3f4\U000e0073\U000e0064\U000e0072\U000e0073\U000e007f" - FLAG_FOR_PIETA_MT_41 = "\U0001f3f4\U000e006d\U000e0074\U000e0034\U000e0031\U000e007f" - FLAG_FOR_ZABUL_AF_ZAB = "\U0001f3f4\U000e0061\U000e0066\U000e007a\U000e0061\U000e0062\U000e007f" - FAMILY_WOMAN_MEDIUM_SKIN_TONE_GIRL_MEDIUM_SKIN_TONE_GIRL_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f467\U0001f3fd\u200d\U0001f467\U0001f3fd" - BLACK_SMILING_FACE = "\u263b" - FLAG_FOR_SANTA_CRUZ_BO_S = "\U0001f3f4\U000e0062\U000e006f\U000e0073\U000e007f" - KISS_MAN_DARK_SKIN_TONE_WOMAN = "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469" - BLACK_FOLDER = "\U0001f5bf" - FLAG_FOR_SANTA_CATARINA_BR_SC = "\U0001f3f4\U000e0062\U000e0072\U000e0073\U000e0063\U000e007f" - KISS_MAN_MEDIUM_LIGHT_SKIN_TONE_WOMAN = "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469" - FAMILY_WOMAN_MAN_GIRL_BABY = "\U0001f469\u200d\U0001f468\u200d\U0001f467\u200d\U0001f476" - FLAG_FOR_BASQUE_COUNTRY_ES_PV = "\U0001f3f4\U000e0065\U000e0073\U000e0070\U000e0076\U000e007f" - TAG_LATIN_CAPITAL_LETTER_V = "\U000e0056" - FLAG_FOR_MANABI_EC_M = "\U0001f3f4\U000e0065\U000e0063\U000e006d\U000e007f" - LAST_QUARTER_MOON_2 = "\u263e" - FLAG_FOR_NAVARRA_CHARTERED_COMMUNITY_ES_NC = "\U0001f3f4\U000e0065\U000e0073\U000e006e\U000e0063\U000e007f" - INVERTED_PENTAGRAM = "\u26e7" - FAMILY_WOMAN_MAN_GIRL = "\U0001f469\u200d\U0001f468\u200d\U0001f467" - FLAG_FOR_DALARNA_SE_W = "\U0001f3f4\U000e0073\U000e0065\U000e0077\U000e007f" - MAHJONG_TILE_TWO_OF_CHARACTERS = "\U0001f008" - BITCOIN_SIGN = "\u20bf" - FLAG_FOR_CHUUK_FM_TRK = "\U0001f3f4\U000e0066\U000e006d\U000e0074\U000e0072\U000e006b\U000e007f" - FLAG_FOR_KERICHO_KE_12 = "\U0001f3f4\U000e006b\U000e0065\U000e0031\U000e0032\U000e007f" - BLACK_LEFT_POINTING_INDEX = "\u261a" - BLACK_RIGHT_POINTING_INDEX = "\u261b" - KISS_MAN_LIGHT_SKIN_TONE_WOMAN = "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469" - FLAG_FOR_SARAWAK_MY_13 = "\U0001f3f4\U000e006d\U000e0079\U000e0031\U000e0033\U000e007f" - BLACK_DRAUGHTS_MAN = "\u26c2" - FLAG_FOR_BEAU_BASSIN_ROSE_HILL_MU_BR = "\U0001f3f4\U000e006d\U000e0075\U000e0062\U000e0072\U000e007f" - FLAG_FOR_GOA_IN_GA = "\U0001f3f4\U000e0069\U000e006e\U000e0067\U000e0061\U000e007f" - FLAG_FOR_PERNAMBUCO_BR_PE = "\U0001f3f4\U000e0062\U000e0072\U000e0070\U000e0065\U000e007f" - WOMAN_WITH_HEADSCARF_DARK_SKIN_TONE = "\U0001f9d5\U0001f3ff\u200d\u2640\ufe0f" - FLAG_FOR_YUKON_CA_YT = "\U0001f3f4\U000e0063\U000e0061\U000e0079\U000e0074\U000e007f" - FLAG_FOR_SICHUAN_CN_51 = "\U0001f3f4\U000e0063\U000e006e\U000e0035\U000e0031\U000e007f" - FLAG_FOR_LAKES_SS_LK = "\U0001f3f4\U000e0073\U000e0073\U000e006c\U000e006b\U000e007f" - HAMMER_AND_SICKLE = "\u262d" - FLAG_FOR_BEIJING_CN_11 = "\U0001f3f4\U000e0063\U000e006e\U000e0031\U000e0031\U000e007f" - FLAG_FOR_UPPER_AUSTRIA_AT_4 = "\U0001f3f4\U000e0061\U000e0074\U000e0034\U000e007f" - FLAG_FOR_LAPLAND_FI_10 = "\U0001f3f4\U000e0066\U000e0069\U000e0031\U000e0030\U000e007f" - FAMILY_MAN_MEDIUM_SKIN_TONE_WOMAN_MEDIUM_SKIN_TONE_BOY_MEDIUM_SKIN_TONE_BABY_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f469\U0001f3fd\u200d\U0001f466\U0001f3fd\u200d\U0001f476\U0001f3fd" - FLAG_FOR_HIMACHAL_PRADESH_IN_HP = "\U0001f3f4\U000e0069\U000e006e\U000e0068\U000e0070\U000e007f" - FLAG_FOR_VELIKA_POLANA_SI_187 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0038\U000e0037\U000e007f" - FLAG_FOR_MUSLIM_MINDANAO_PH_14 = "\U0001f3f4\U000e0070\U000e0068\U000e0031\U000e0034\U000e007f" - HEADSTONE_GRAVEYARD_SYMBOL = "\u26fc" - CLAMSHELL_MOBILE_PHONE = "\U0001f581" - FLAG_FOR_KOSOVO_METOHIJA_RS_KM = "\U0001f3f4\U000e0072\U000e0073\U000e006b\U000e006d\U000e007f" - DIGIT_TWO = "2\ufe0f" - FLAG_FOR_PUNJAB_IN_PB = "\U0001f3f4\U000e0069\U000e006e\U000e0070\U000e0062\U000e007f" - SIDEWAYS_BLACK_DOWN_POINTING_INDEX = "\U0001f5a1" - FLAG_FOR_DOHA_QA_DA = "\U0001f3f4\U000e0071\U000e0061\U000e0064\U000e0061\U000e007f" - FAMILY_WOMAN_DARK_SKIN_TONE_BOY_DARK_SKIN_TONE_BABY_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f466\U0001f3ff\u200d\U0001f476\U0001f3ff" - DOUBLED_FEMALE_SIGN = "\u26a2" - BLACK_SKULL_AND_CROSSBONES = "\U0001f571" - FAMILY_MAN_MEDIUM_SKIN_TONE_MAN_MEDIUM_SKIN_TONE_BABY_MEDIUM_SKIN_TONE_BABY_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f468\U0001f3fd\u200d\U0001f476\U0001f3fd\u200d\U0001f476\U0001f3fd" - FLAG_FOR_ZEALAND_DK_85 = "\U0001f3f4\U000e0064\U000e006b\U000e0038\U000e0035\U000e007f" - STAFF_OF_HERMES = "\u269a" - QUINCUNX = "\u26bb" - FLAG_FOR_SANTA_FE_AR_S = "\U0001f3f4\U000e0061\U000e0072\U000e0073\U000e007f" - FLAG_FOR_ADIYAMAN_TR_02 = "\U0001f3f4\U000e0074\U000e0072\U000e0030\U000e0032\U000e007f" - FLAG_FOR_KAFR_EL_SHEIKH_EG_KFS = "\U0001f3f4\U000e0065\U000e0067\U000e006b\U000e0066\U000e0073\U000e007f" - FLAG_FOR_ASTARA_AZ_AST = "\U0001f3f4\U000e0061\U000e007a\U000e0061\U000e0073\U000e0074\U000e007f" - FLAG_FOR_SAINT_PATRICK_DM_09 = "\U0001f3f4\U000e0064\U000e006d\U000e0030\U000e0039\U000e007f" - FLAG_FOR_AGDAM_AZ_AGM = "\U0001f3f4\U000e0061\U000e007a\U000e0061\U000e0067\U000e006d\U000e007f" - FLAG_FOR_TIERRA_DEL_FUEGO_AR_V = "\U0001f3f4\U000e0061\U000e0072\U000e0076\U000e007f" - SIDEWAYS_BLACK_LEFT_POINTING_INDEX = "\U0001f59a" - TAG_DIGIT_FIVE = "\U000e0035" - FLAG_FOR_KANSAS_US_KS = "\U0001f3f4\U000e0075\U000e0073\U000e006b\U000e0073\U000e007f" - MUSIC_FLAT_SIGN = "\u266d" - FAMILY_WOMAN_MEDIUM_LIGHT_SKIN_TONE_GIRL_MEDIUM_LIGHT_SKIN_TONE_BABY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f467\U0001f3fc\u200d\U0001f476\U0001f3fc" - BALLOT_BOX_WITH_X = "\u2612" - TAG_LOW_LINE = "\U000e005f" - FLAG_FOR_ADYGEA_RU_AD = "\U0001f3f4\U000e0072\U000e0075\U000e0061\U000e0064\U000e007f" - TELEPHONE_ON_TOP_OF_MODEM = "\U0001f580" - FLAG_FOR_NINGXIA_CN_64 = "\U0001f3f4\U000e0063\U000e006e\U000e0036\U000e0034\U000e007f" - FLAG_FOR_GAVLEBORG_SE_X = "\U0001f3f4\U000e0073\U000e0065\U000e0078\U000e007f" - FLAG_FOR_AGSU_AZ_AGU = "\U0001f3f4\U000e0061\U000e007a\U000e0061\U000e0067\U000e0075\U000e007f" - HISTORIC_SITE = "\u26ec" - FLAG_FOR_KAGAWA_JP_37 = "\U0001f3f4\U000e006a\U000e0070\U000e0033\U000e0037\U000e007f" - FLAG_FOR_BOMI_LR_BM = "\U0001f3f4\U000e006c\U000e0072\U000e0062\U000e006d\U000e007f" - FLAG_FOR_MIDLANDS_ZW_MI = "\U0001f3f4\U000e007a\U000e0077\U000e006d\U000e0069\U000e007f" - FLAG_FOR_AGHJABADI_AZ_AGC = "\U0001f3f4\U000e0061\U000e007a\U000e0061\U000e0067\U000e0063\U000e007f" - SIDEWAYS_WHITE_UP_POINTING_INDEX = "\U0001f59e" - KISS_MAN_WOMAN = "\U0001f468\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469" - FLAG_FOR_NEUQUEN_AR_Q = "\U0001f3f4\U000e0061\U000e0072\U000e0071\U000e007f" - FLAG_FOR_AZORES_PT_20 = "\U0001f3f4\U000e0070\U000e0074\U000e0032\U000e0030\U000e007f" - FLAG_FOR_BAS_CONGO_CD_BC = "\U0001f3f4\U000e0063\U000e0064\U000e0062\U000e0063\U000e007f" - FLAG_FOR_BILASUVAR_AZ_BIL = "\U0001f3f4\U000e0061\U000e007a\U000e0062\U000e0069\U000e006c\U000e007f" - FLAG_FOR_KURSK_RU_KRS = "\U0001f3f4\U000e0072\U000e0075\U000e006b\U000e0072\U000e0073\U000e007f" - FLAG_FOR_BARDA_AZ_BAR = "\U0001f3f4\U000e0061\U000e007a\U000e0062\U000e0061\U000e0072\U000e007f" - TRIGRAM_FOR_LAKE = "\u2631" - FLAG_FOR_JABRAYIL_AZ_CAB = "\U0001f3f4\U000e0061\U000e007a\U000e0063\U000e0061\U000e0062\U000e007f" - NOTCHED_RIGHT_SEMICIRCLE_WITH_THREE_DOTS = "\U0001f544" - FLAG_FOR_FIZULI_AZ_FUZ = "\U0001f3f4\U000e0061\U000e007a\U000e0066\U000e0075\U000e007a\U000e007f" - FLAG_FOR_BEYLAGAN_AZ_BEY = "\U0001f3f4\U000e0061\U000e007a\U000e0062\U000e0065\U000e0079\U000e007f" - FLAG_FOR_NIARI_CG_9 = "\U0001f3f4\U000e0063\U000e0067\U000e0039\U000e007f" - FAMILY_WOMAN_MAN_BABY = "\U0001f469\u200d\U0001f468\u200d\U0001f476" - FAMILY_WOMAN_DARK_SKIN_TONE_BOY_DARK_SKIN_TONE_BOY_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f466\U0001f3ff\u200d\U0001f466\U0001f3ff" - WHITE_DRAUGHTS_MAN = "\u26c0" - FLAG_FOR_SAINT_JOSEPH_DM_06 = "\U0001f3f4\U000e0064\U000e006d\U000e0030\U000e0036\U000e007f" - DIGRAM_FOR_LESSER_YIN = "\u268d" - TAG_DIGIT_THREE = "\U000e0033" - FLAG_FOR_PAPUA_ISLANDS_ID_PP = "\U0001f3f4\U000e0069\U000e0064\U000e0070\U000e0070\U000e007f" - FLAG_FOR_HARBOUR_ISLAND_BS_HI = "\U0001f3f4\U000e0062\U000e0073\U000e0068\U000e0069\U000e007f" - FLAG_FOR_YEREVAN_AM_ER = "\U0001f3f4\U000e0061\U000e006d\U000e0065\U000e0072\U000e007f" - KISS_WOMAN_LIGHT_SKIN_TONE_WOMAN = "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469" - ANKH = "\u2625" - LIGHT_CHECK_MARK = "\U0001f5f8" - KISS_WOMAN_WOMAN_DARK_SKIN_TONE = "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3ff" - FLAG_FOR_GOYCHAY_AZ_GOY = "\U0001f3f4\U000e0061\U000e007a\U000e0067\U000e006f\U000e0079\U000e007f" - TAG_LATIN_CAPITAL_LETTER_A = "\U000e0041" - FLAG_FOR_PORT_HERCULES_MC_PH = "\U0001f3f4\U000e006d\U000e0063\U000e0070\U000e0068\U000e007f" - CIRCLED_CROSSING_LANES = "\u26d2" - MONOGRAM_FOR_YANG = "\u268a" - FLAG_FOR_CESAR_CO_CES = "\U0001f3f4\U000e0063\U000e006f\U000e0063\U000e0065\U000e0073\U000e007f" - DECREASE_FONT_SIZE_SYMBOL = "\U0001f5db" - BALLOT_BOLD_SCRIPT_X = "\U0001f5f6" - FLAG_FOR_JERUSALEM_PS_JEM = "\U0001f3f4\U000e0070\U000e0073\U000e006a\U000e0065\U000e006d\U000e007f" - FLAG_FOR_UMM_SALAL_QA_US = "\U0001f3f4\U000e0071\U000e0061\U000e0075\U000e0073\U000e007f" - BLACK_CHESS_BISHOP = "\u265d" - FLAG_FOR_KALIMANTAN_ID_KA = "\U0001f3f4\U000e0069\U000e0064\U000e006b\U000e0061\U000e007f" - BALLOT_BOX = "\u2610" - FLAG_FOR_BRYANSK_RU_BRY = "\U0001f3f4\U000e0072\U000e0075\U000e0062\U000e0072\U000e0079\U000e007f" - KISS_MAN_MEDIUM_SKIN_TONE_WOMAN_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fe" - FLAG_FOR_AGDASH_AZ_AGS = "\U0001f3f4\U000e0061\U000e007a\U000e0061\U000e0067\U000e0073\U000e007f" - FLAG_FOR_GOYGOL_AZ_GYG = "\U0001f3f4\U000e0061\U000e007a\U000e0067\U000e0079\U000e0067\U000e007f" - FLAG_FOR_HAJIGABUL_AZ_HAC = "\U0001f3f4\U000e0061\U000e007a\U000e0068\U000e0061\U000e0063\U000e007f" - FLAG_FOR_KASTAMONU_TR_37 = "\U0001f3f4\U000e0074\U000e0072\U000e0033\U000e0037\U000e007f" - FLAG_FOR_REZEKNE_MUNICIPALITY_LV_077 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0037\U000e0037\U000e007f" - FLAG_FOR_BONAIRE_NL_BQ1 = "\U0001f3f4\U000e006e\U000e006c\U000e0062\U000e0071\U000e0031\U000e007f" - FLAG_FOR_TAVUSH_AM_TV = "\U0001f3f4\U000e0061\U000e006d\U000e0074\U000e0076\U000e007f" - FAMILY_MAN_MEDIUM_LIGHT_SKIN_TONE_GIRL_MEDIUM_LIGHT_SKIN_TONE_BABY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f467\U0001f3fc\u200d\U0001f476\U0001f3fc" - WHITE_TELEPHONE = "\u260f" - FLAG_FOR_VEST_AGDER_NO_10 = "\U0001f3f4\U000e006e\U000e006f\U000e0031\U000e0030\U000e007f" - FLAG_FOR_GORANBOY_AZ_GOR = "\U0001f3f4\U000e0061\U000e007a\U000e0067\U000e006f\U000e0072\U000e007f" - FLAG_FOR_CROSS_RIVER_NG_CR = "\U0001f3f4\U000e006e\U000e0067\U000e0063\U000e0072\U000e007f" - FLAG_FOR_IMISHLI_AZ_IMI = "\U0001f3f4\U000e0061\U000e007a\U000e0069\U000e006d\U000e0069\U000e007f" - FAMILY_WOMAN_MEDIUM_LIGHT_SKIN_TONE_WOMAN_MEDIUM_LIGHT_SKIN_TONE_GIRL_MEDIUM_LIGHT_SKIN_TONE_BABY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f469\U0001f3fc\u200d\U0001f467\U0001f3fc\u200d\U0001f476\U0001f3fc" - FLAG_FOR_TANGANYIKA_CD_TA = "\U0001f3f4\U000e0063\U000e0064\U000e0074\U000e0061\U000e007f" - BLACK_STAR = "\u2605" - CAUTION_SIGN = "\u2621" - FLAG_FOR_SHANGHAI_CN_31 = "\U0001f3f4\U000e0063\U000e006e\U000e0033\U000e0031\U000e007f" - CHI_RHO = "\u2627" - BALLOT_BOX_WITH_BOLD_SCRIPT_X = "\U0001f5f7" - TRIGRAM_FOR_FIRE = "\u2632" - FLAG_FOR_WEST_VIRGINIA_US_WV = "\U0001f3f4\U000e0075\U000e0073\U000e0077\U000e0076\U000e007f" - FLAG_FOR_CLIPPERTON_ISLAND_FR_CP = "\U0001f3f4\U000e0066\U000e0072\U000e0063\U000e0070\U000e007f" - EIGHTH_NOTE = "\u266a" - CROSS_OF_LORRAINE = "\u2628" - CADUCEUS = "\u2624" - FLAG_FOR_GORJ_RO_GJ = "\U0001f3f4\U000e0072\U000e006f\U000e0067\U000e006a\U000e007f" - OUTLINED_WHITE_STAR = "\u269d" - FAMILY_MAN_MEDIUM_DARK_SKIN_TONE_WOMAN_MEDIUM_DARK_SKIN_TONE_BOY_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f469\U0001f3fe\u200d\U0001f466\U0001f3fe" - FLAG_FOR_ALTAI_KRAI_RU_ALT = "\U0001f3f4\U000e0072\U000e0075\U000e0061\U000e006c\U000e0074\U000e007f" - KISS_WOMAN_MEDIUM_SKIN_TONE_WOMAN_DARK_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3ff" - FLAG_FOR_LACHIN_AZ_LAC = "\U0001f3f4\U000e0061\U000e007a\U000e006c\U000e0061\U000e0063\U000e007f" - FLAG_FOR_TAKHAR_AF_TAK = "\U0001f3f4\U000e0061\U000e0066\U000e0074\U000e0061\U000e006b\U000e007f" - FLAG_FOR_LANKARAN_AZ_LA = "\U0001f3f4\U000e0061\U000e007a\U000e006c\U000e0061\U000e007f" - TAG_LATIN_CAPITAL_LETTER_S = "\U000e0053" - FLAG_FOR_CENTRAL_UG_C = "\U0001f3f4\U000e0075\U000e0067\U000e0063\U000e007f" - FLAG_FOR_ZUG_CH_ZG = "\U0001f3f4\U000e0063\U000e0068\U000e007a\U000e0067\U000e007f" - TAG_LATIN_CAPITAL_LETTER_I = "\U000e0049" - BEAMED_ASCENDING_MUSICAL_NOTES = "\U0001f39c" - FLAG_FOR_LERIK_AZ_LER = "\U0001f3f4\U000e0061\U000e007a\U000e006c\U000e0065\U000e0072\U000e007f" - PLUTO = "\u2647" - FLAG_FOR_AMERICAN_SAMOA_US_AS = "\U0001f3f4\U000e0075\U000e0073\U000e0061\U000e0073\U000e007f" - PARTIALLY_RECYCLED_PAPER_SYMBOL = "\u267d" - FLAG_FOR_OKINAWA_JP_47 = "\U0001f3f4\U000e006a\U000e0070\U000e0034\U000e0037\U000e007f" - FLAG_FOR_GADABAY_AZ_GAD = "\U0001f3f4\U000e0061\U000e007a\U000e0067\U000e0061\U000e0064\U000e007f" - FLAG_FOR_VASTERNORRLAND_SE_Y = "\U0001f3f4\U000e0073\U000e0065\U000e0079\U000e007f" - FLAG_FOR_ORDINO_AD_05 = "\U0001f3f4\U000e0061\U000e0064\U000e0030\U000e0035\U000e007f" - EARTH = "\u2641" - VERTICAL_MALE_WITH_STROKE_SIGN = "\u26a8" - TAG_VERTICAL_LINE = "\U000e007c" - COUPLE_WITH_HEART_MAN_LIGHT_SKIN_TONE_MAN = "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468" - FLAG_FOR_NAFTALAN_AZ_NA = "\U0001f3f4\U000e0061\U000e007a\U000e006e\U000e0061\U000e007f" - FLAG_FOR_MASALLY_AZ_MAS = "\U0001f3f4\U000e0061\U000e007a\U000e006d\U000e0061\U000e0073\U000e007f" - FLAG_FOR_MINGACHEVIR_AZ_MI = "\U0001f3f4\U000e0061\U000e007a\U000e006d\U000e0069\U000e007f" - FLAG_FOR_KVEMO_KARTLI_GE_KK = "\U0001f3f4\U000e0067\U000e0065\U000e006b\U000e006b\U000e007f" - TAG_LATIN_CAPITAL_LETTER_X = "\U000e0058" - HEART_WITH_TIP_ON_THE_LEFT = "\U0001f394" - TAG_PERCENT_SIGN = "\U000e0025" - SATURN = "\u2644" - NEPTUNE = "\u2646" - FLAG_FOR_LAS_TUNAS_CU_10 = "\U0001f3f4\U000e0063\U000e0075\U000e0031\U000e0030\U000e007f" - THREE_LINES_CONVERGING_RIGHT = "\u269e" - FAMILY_WOMAN_WOMAN_BOY_BABY = "\U0001f469\u200d\U0001f469\u200d\U0001f466\u200d\U0001f476" - REVERSED_ROTATED_FLORAL_HEART_BULLET = "\u2619" - FLAG_FOR_FAIYUM_EG_FYM = "\U0001f3f4\U000e0065\U000e0067\U000e0066\U000e0079\U000e006d\U000e007f" - FLAG_FOR_CELJE_SI_011 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0031\U000e0031\U000e007f" - FLAG_FOR_GILAN_IR_19 = "\U0001f3f4\U000e0069\U000e0072\U000e0031\U000e0039\U000e007f" - FLAG_FOR_PANEVEZIO_MUNICIPALITY_LT_32 = "\U0001f3f4\U000e006c\U000e0074\U000e0033\U000e0032\U000e007f" - FLAG_FOR_AYSEN_CL_AI = "\U0001f3f4\U000e0063\U000e006c\U000e0061\U000e0069\U000e007f" - SCREEN = "\U0001f5b5" - WHITE_CHESS_ROOK = "\u2656" - FLAG_FOR_WEST_COAST_NZ_WTC = "\U0001f3f4\U000e006e\U000e007a\U000e0077\U000e0074\U000e0063\U000e007f" - FLAG_FOR_TANGIER_TETOUAN_MA_01 = "\U0001f3f4\U000e006d\U000e0061\U000e0030\U000e0031\U000e007f" - FLAG_FOR_NEFTCHALA_AZ_NEF = "\U0001f3f4\U000e0061\U000e007a\U000e006e\U000e0065\U000e0066\U000e007f" - FLAG_FOR_MOSCOW_RU_MOW = "\U0001f3f4\U000e0072\U000e0075\U000e006d\U000e006f\U000e0077\U000e007f" - COUPLE_WITH_HEART_WOMAN_MEDIUM_DARK_SKIN_TONE_WOMAN_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3ff" - WHITE_CHESS_QUEEN = "\u2655" - FLAG_FOR_ISMAILLI_AZ_ISM = "\U0001f3f4\U000e0061\U000e007a\U000e0069\U000e0073\U000e006d\U000e007f" - BLACK_CROSS_ON_SHIELD = "\u26e8" - FLAG_FOR_TARACLIA_MD_TA = "\U0001f3f4\U000e006d\U000e0064\U000e0074\U000e0061\U000e007f" - FLAG_FOR_DROCHIA_MD_DR = "\U0001f3f4\U000e006d\U000e0064\U000e0064\U000e0072\U000e007f" - FLAG_FOR_ONTARIO_CA_ON = "\U0001f3f4\U000e0063\U000e0061\U000e006f\U000e006e\U000e007f" - EAST_SYRIAC_CROSS = "\u2671" - TAG_PLUS_SIGN = "\U000e002b" - DIGRAM_FOR_GREATER_YIN = "\u268f" - WHITE_PENNANT = "\U0001f3f1" - COUPLE_WITH_HEART_MAN_LIGHT_SKIN_TONE_WOMAN_DARK_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3ff" - FLAG_FOR_GOBUSTAN_AZ_QOB = "\U0001f3f4\U000e0061\U000e007a\U000e0071\U000e006f\U000e0062\U000e007f" - FLAG_FOR_NORTHERN_IRELAND_GB_NIR = "\U0001f3f4\U000e0067\U000e0062\U000e006e\U000e0069\U000e0072\U000e007f" - KISS_MAN_WOMAN_MEDIUM_SKIN_TONE = "\U0001f468\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fd" - COUPLE_WITH_HEART_WOMAN_MEDIUM_DARK_SKIN_TONE_WOMAN_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fb" - FLAG_FOR_NAPO_EC_N = "\U0001f3f4\U000e0065\U000e0063\U000e006e\U000e007f" - FLAG_FOR_KORCE_COUNTY_AL_06 = "\U0001f3f4\U000e0061\U000e006c\U000e0030\U000e0036\U000e007f" - FLAG_FOR_QABALA_AZ_QAB = "\U0001f3f4\U000e0061\U000e007a\U000e0071\U000e0061\U000e0062\U000e007f" - TRIGRAM_FOR_WIND = "\u2634" - FLAG_FOR_NANA_MAMBERE_CF_NM = "\U0001f3f4\U000e0063\U000e0066\U000e006e\U000e006d\U000e007f" - FLAG_FOR_OGHUZ_AZ_OGU = "\U0001f3f4\U000e0061\U000e007a\U000e006f\U000e0067\U000e0075\U000e007f" - KISS_MAN_MEDIUM_DARK_SKIN_TONE_WOMAN_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3ff" - FLAG_FOR_SMARJESKE_TOPLICE_SI_206 = "\U0001f3f4\U000e0073\U000e0069\U000e0032\U000e0030\U000e0036\U000e007f" - FLAG_FOR_HO_CHI_MINH_CITY_VN_SG = "\U0001f3f4\U000e0076\U000e006e\U000e0073\U000e0067\U000e007f" - FLAG_FOR_QUBADLI_AZ_QBI = "\U0001f3f4\U000e0061\U000e007a\U000e0071\U000e0062\U000e0069\U000e007f" - FLAG_FOR_SUD_UBANGI_CD_SU = "\U0001f3f4\U000e0063\U000e0064\U000e0073\U000e0075\U000e007f" - KISS_WOMAN_LIGHT_SKIN_TONE_WOMAN_DARK_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3ff" - RECYCLING_SYMBOL_FOR_TYPE_5_PLASTICS = "\u2677" - MAHJONG_TILE_CHRYSANTHEMUM = "\U0001f025" - FLAG_FOR_BRASOV_RO_BV = "\U0001f3f4\U000e0072\U000e006f\U000e0062\U000e0076\U000e007f" - FLAG_FOR_MOGILA_MK_53 = "\U0001f3f4\U000e006d\U000e006b\U000e0035\U000e0033\U000e007f" - THUNDERSTORM = "\u2608" - FLAG_FOR_JALILABAD_AZ_CAL = "\U0001f3f4\U000e0061\U000e007a\U000e0063\U000e0061\U000e006c\U000e007f" - FLAG_FOR_QUSAR_AZ_QUS = "\U0001f3f4\U000e0061\U000e007a\U000e0071\U000e0075\U000e0073\U000e007f" - TAG_QUOTATION_MARK = "\U000e0022" - DIE_FACE_3 = "\u2682" - FAMILY_WOMAN_DARK_SKIN_TONE_MAN_DARK_SKIN_TONE_BABY_DARK_SKIN_TONE_BOY_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f468\U0001f3ff\u200d\U0001f476\U0001f3ff\u200d\U0001f466\U0001f3ff" - FLAG_FOR_QAZAKH_AZ_QAZ = "\U0001f3f4\U000e0061\U000e007a\U000e0071\U000e0061\U000e007a\U000e007f" - KISS_WOMAN_MEDIUM_LIGHT_SKIN_TONE_WOMAN_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fb" - FLAG_FOR_KAVANGO_EAST_NA_KE = "\U0001f3f4\U000e006e\U000e0061\U000e006b\U000e0065\U000e007f" - FLAG_FOR_QAKH_AZ_QAX = "\U0001f3f4\U000e0061\U000e007a\U000e0071\U000e0061\U000e0078\U000e007f" - FLAG_FOR_SAINT_PETER_AG_07 = "\U0001f3f4\U000e0061\U000e0067\U000e0030\U000e0037\U000e007f" - FLAG_FOR_BAJA_CALIFORNIA_MX_BCN = "\U0001f3f4\U000e006d\U000e0078\U000e0062\U000e0063\U000e006e\U000e007f" - KISS_WOMAN_DARK_SKIN_TONE_WOMAN_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fd" - FLAG_FOR_ASGABAT_TM_S = "\U0001f3f4\U000e0074\U000e006d\U000e0073\U000e007f" - FLAG_FOR_SABIRABAD_AZ_SAB = "\U0001f3f4\U000e0061\U000e007a\U000e0073\U000e0061\U000e0062\U000e007f" - BLACK_SHOGI_PIECE = "\u2617" - THREE_LINES_CONVERGING_LEFT = "\u269f" - FAMILY_WOMAN_BOY_BABY = "\U0001f469\u200d\U0001f466\u200d\U0001f476" - MUSIC_NATURAL_SIGN = "\u266e" - FLAG_FOR_QUTHING_LS_G = "\U0001f3f4\U000e006c\U000e0073\U000e0067\U000e007f" - FLAG_FOR_JONGLEI_SS_JG = "\U0001f3f4\U000e0073\U000e0073\U000e006a\U000e0067\U000e007f" - FLAG_FOR_NELSON_NZ_NSN = "\U0001f3f4\U000e006e\U000e007a\U000e006e\U000e0073\U000e006e\U000e007f" - COUPLE_WITH_HEART_MAN_MEDIUM_LIGHT_SKIN_TONE_WOMAN_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fd" - FLAG_FOR_VIENNA_AT_9 = "\U0001f3f4\U000e0061\U000e0074\U000e0039\U000e007f" - FLAG_FOR_SHABRAN_AZ_SBN = "\U0001f3f4\U000e0061\U000e007a\U000e0073\U000e0062\U000e006e\U000e007f" - DIE_FACE_4 = "\u2683" - WHITE_SHOGI_PIECE = "\u2616" - RECYCLING_SYMBOL_FOR_TYPE_4_PLASTICS = "\u2676" - FLAG_FOR_MAKEDONSKI_BROD_MK_52 = "\U0001f3f4\U000e006d\U000e006b\U000e0035\U000e0032\U000e007f" - KISS_WOMAN_MAN_MEDIUM_SKIN_TONE = "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd" - FLAG_FOR_SHAKI_DISTRICT_AZ_SAK = "\U0001f3f4\U000e0061\U000e007a\U000e0073\U000e0061\U000e006b\U000e007f" - URANUS = "\u2645" - FLAG_FOR_QUBA_AZ_QBA = "\U0001f3f4\U000e0061\U000e007a\U000e0071\U000e0062\U000e0061\U000e007f" - FLAG_FOR_SAMUKH_AZ_SMX = "\U0001f3f4\U000e0061\U000e007a\U000e0073\U000e006d\U000e0078\U000e007f" - FLAG_FOR_FEDERAL_CAPITAL_TERRITORY_NG_FC = "\U0001f3f4\U000e006e\U000e0067\U000e0066\U000e0063\U000e007f" - FLAG_FOR_FRIULI_VENEZIA_GIULIA_IT_36 = "\U0001f3f4\U000e0069\U000e0074\U000e0033\U000e0036\U000e007f" - WHITE_FLAG_2 = "\u2690" - DIE_FACE_2 = "\u2681" - FLAG_FOR_SHAKI_AZ_SA = "\U0001f3f4\U000e0061\U000e007a\U000e0073\U000e0061\U000e007f" - FLAG_FOR_LA_PAMPA_AR_L = "\U0001f3f4\U000e0061\U000e0072\U000e006c\U000e007f" - FLAG_FOR_SIAZAN_AZ_SIY = "\U0001f3f4\U000e0061\U000e007a\U000e0073\U000e0069\U000e0079\U000e007f" - FLAG_FOR_NEW_IRELAND_PG_NIK = "\U0001f3f4\U000e0070\U000e0067\U000e006e\U000e0069\U000e006b\U000e007f" - FLAG_FOR_AKMENE_LT_01 = "\U0001f3f4\U000e006c\U000e0074\U000e0030\U000e0031\U000e007f" - FLAG_FOR_SAINT_LOUIS_SC_22 = "\U0001f3f4\U000e0073\U000e0063\U000e0032\U000e0032\U000e007f" - FLAG_FOR_UPPER_TAKUTU_UPPER_ESSEQUIBO_GY_UT = "\U0001f3f4\U000e0067\U000e0079\U000e0075\U000e0074\U000e007f" - FLAG_FOR_SHIRVAN_AZ_SR = "\U0001f3f4\U000e0061\U000e007a\U000e0073\U000e0072\U000e007f" - FLAG_FOR_HAUT_MBOMOU_CF_HM = "\U0001f3f4\U000e0063\U000e0066\U000e0068\U000e006d\U000e007f" - TRIANGLE_WITH_ROUNDED_CORNERS = "\U0001f6c6" - FLAG_FOR_SHAMAKHI_AZ_SMI = "\U0001f3f4\U000e0061\U000e007a\U000e0073\U000e006d\U000e0069\U000e007f" - KISS_WOMAN_DARK_SKIN_TONE_MAN = "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468" - FAMILY_MAN_DARK_SKIN_TONE_WOMAN_DARK_SKIN_TONE_GIRL_DARK_SKIN_TONE_GIRL_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f469\U0001f3ff\u200d\U0001f467\U0001f3ff\u200d\U0001f467\U0001f3ff" - KISS_WOMAN_DARK_SKIN_TONE_WOMAN = "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469" - DOCUMENT_WITH_TEXT = "\U0001f5b9" - FLAG_FOR_TOVUZ_AZ_TOV = "\U0001f3f4\U000e0061\U000e007a\U000e0074\U000e006f\U000e0076\U000e007f" - FLAG_FOR_PORT_MORESBY_PG_NCD = "\U0001f3f4\U000e0070\U000e0067\U000e006e\U000e0063\U000e0064\U000e007f" - WHITE_SUN_WITH_RAYS = "\u263c" - FAMILY_MAN_LIGHT_SKIN_TONE_WOMAN_LIGHT_SKIN_TONE_GIRL_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f469\U0001f3fb\u200d\U0001f467\U0001f3fb" - FAMILY_MAN_MEDIUM_SKIN_TONE_WOMAN_MEDIUM_SKIN_TONE_BOY_MEDIUM_SKIN_TONE_GIRL_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f469\U0001f3fd\u200d\U0001f466\U0001f3fd\u200d\U0001f467\U0001f3fd" - MAHJONG_TILE_AUTUMN = "\U0001f028" - FLAG_FOR_UJAR_AZ_UCA = "\U0001f3f4\U000e0061\U000e007a\U000e0075\U000e0063\U000e0061\U000e007f" - FLAG_FOR_STRUMICA_MK_73 = "\U0001f3f4\U000e006d\U000e006b\U000e0037\U000e0033\U000e007f" - FLAG_FOR_LVIVSHCHYNA_UA_46 = "\U0001f3f4\U000e0075\U000e0061\U000e0034\U000e0036\U000e007f" - TAG_LATIN_SMALL_LETTER_H = "\U000e0068" - KISS_MAN_DARK_SKIN_TONE_MAN_LIGHT_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb" - FAMILY_MAN_DARK_SKIN_TONE_GIRL_DARK_SKIN_TONE_BOY_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f467\U0001f3ff\u200d\U0001f466\U0001f3ff" - SEMISEXTILE = "\u26ba" - BLACK_TOUCHTONE_TELEPHONE = "\U0001f57f" - WHITE_CIRCLE_WITH_DOT_RIGHT = "\u2686" - FAMILY_MAN_MEDIUM_SKIN_TONE_BOY_MEDIUM_SKIN_TONE_GIRL_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f466\U0001f3fd\u200d\U0001f467\U0001f3fd" - FLAG_FOR_LIPETSK_RU_LIP = "\U0001f3f4\U000e0072\U000e0075\U000e006c\U000e0069\U000e0070\U000e007f" - FLAG_FOR_DOL_PRI_LJUBLJANI_SI_022 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0032\U000e0032\U000e007f" - KISS_MAN_MAN_DARK_SKIN_TONE = "\U0001f468\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff" - WHITE_DIAMOND_IN_SQUARE = "\u26cb" - COUPLE_WITH_HEART_MAN_MEDIUM_SKIN_TONE_MAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc" - FLAG_FOR_KHIZI_AZ_XIZ = "\U0001f3f4\U000e0061\U000e007a\U000e0078\U000e0069\U000e007a\U000e007f" - FAMILY_MAN_MEDIUM_SKIN_TONE_MAN_MEDIUM_SKIN_TONE_GIRL_MEDIUM_SKIN_TONE_GIRL_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f468\U0001f3fd\u200d\U0001f467\U0001f3fd\u200d\U0001f467\U0001f3fd" - KISS_WOMAN_DARK_SKIN_TONE_WOMAN_LIGHT_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fb" - UP_POINTING_MILITARY_AIRPLANE = "\U0001f6e6" - FLAG_FOR_KHOJALI_AZ_XCI = "\U0001f3f4\U000e0061\U000e007a\U000e0078\U000e0063\U000e0069\U000e007f" - FLAG_FOR_YEVLAKH_DISTRICT_AZ_YEV = "\U0001f3f4\U000e0061\U000e007a\U000e0079\U000e0065\U000e0076\U000e007f" - FLAG_FOR_ZANZIBAR_CENTRAL_SOUTH_TZ_11 = "\U0001f3f4\U000e0074\U000e007a\U000e0031\U000e0031\U000e007f" - TAG_LEFT_SQUARE_BRACKET = "\U000e005b" - FAMILY_MAN_MEDIUM_SKIN_TONE_WOMAN_MEDIUM_SKIN_TONE_GIRL_MEDIUM_SKIN_TONE_BABY_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f469\U0001f3fd\u200d\U0001f467\U0001f3fd\u200d\U0001f476\U0001f3fd" - FLAG_FOR_NORTHLAND_NZ_NTL = "\U0001f3f4\U000e006e\U000e007a\U000e006e\U000e0074\U000e006c\U000e007f" - DIVORCE_SYMBOL = "\u26ae" - FLAG_FOR_BALAKAN_AZ_BAL = "\U0001f3f4\U000e0061\U000e007a\U000e0062\U000e0061\U000e006c\U000e007f" - FLAG_FOR_PROVENCE_ALPES_COTE_D_AZUR_FR_PAC = "\U0001f3f4\U000e0066\U000e0072\U000e0070\U000e0061\U000e0063\U000e007f" - FLAG_FOR_SHUSHA_AZ_SUS = "\U0001f3f4\U000e0061\U000e007a\U000e0073\U000e0075\U000e0073\U000e007f" - COUPLE_WITH_HEART_WOMAN_DARK_SKIN_TONE_MAN_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe" - FLAG_FOR_MONTSERRADO_LR_MO = "\U0001f3f4\U000e006c\U000e0072\U000e006d\U000e006f\U000e007f" - FAMILY_MAN_MAN_GIRL_BABY = "\U0001f468\u200d\U0001f468\u200d\U0001f467\u200d\U0001f476" - FLAG_FOR_YARDYMLI_AZ_YAR = "\U0001f3f4\U000e0061\U000e007a\U000e0079\U000e0061\U000e0072\U000e007f" - FLAG_FOR_SALYAN_AZ_SAL = "\U0001f3f4\U000e0061\U000e007a\U000e0073\U000e0061\U000e006c\U000e007f" - MALE_AND_FEMALE_SIGN = "\u26a5" - FLAG_FOR_NEW_CALEDONIA_FR_NC = "\U0001f3f4\U000e0066\U000e0072\U000e006e\U000e0063\U000e007f" - FRAME_WITH_TILES = "\U0001f5bd" - FAMILY_WOMAN_MEDIUM_DARK_SKIN_TONE_WOMAN_MEDIUM_DARK_SKIN_TONE_BABY_MEDIUM_DARK_SKIN_TONE_BOY_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f469\U0001f3fe\u200d\U0001f476\U0001f3fe\u200d\U0001f466\U0001f3fe" - FAMILY_MAN_DARK_SKIN_TONE_WOMAN_DARK_SKIN_TONE_BOY_DARK_SKIN_TONE_BABY_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f469\U0001f3ff\u200d\U0001f466\U0001f3ff\u200d\U0001f476\U0001f3ff" - FLAG_FOR_SAINT_THOMAS_BB_11 = "\U0001f3f4\U000e0062\U000e0062\U000e0031\U000e0031\U000e007f" - FLAG_FOR_CONSTANTINE_DZ_25 = "\U0001f3f4\U000e0064\U000e007a\U000e0032\U000e0035\U000e007f" - FLAG_FOR_YEVLAKH_AZ_YE = "\U0001f3f4\U000e0061\U000e007a\U000e0079\U000e0065\U000e007f" - FAMILY_MAN_DARK_SKIN_TONE_WOMAN_DARK_SKIN_TONE_GIRL_DARK_SKIN_TONE_BABY_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f469\U0001f3ff\u200d\U0001f467\U0001f3ff\u200d\U0001f476\U0001f3ff" - COUPLE_WITH_HEART_MAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fc" - COUPLE_WITH_HEART_MAN_MEDIUM_DARK_SKIN_TONE_WOMAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fc" - FLAG_FOR_BOTOSANI_RO_BT = "\U0001f3f4\U000e0072\U000e006f\U000e0062\U000e0074\U000e007f" - FLAG_FOR_ZAQATALA_AZ_ZAQ = "\U0001f3f4\U000e0061\U000e007a\U000e007a\U000e0061\U000e0071\U000e007f" - FLAG_FOR_CHIN_MM_14 = "\U0001f3f4\U000e006d\U000e006d\U000e0031\U000e0034\U000e007f" - FLAG_FOR_KHOST_AF_KHO = "\U0001f3f4\U000e0061\U000e0066\U000e006b\U000e0068\U000e006f\U000e007f" - FAMILY_MAN_LIGHT_SKIN_TONE_BOY_LIGHT_SKIN_TONE_BABY_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f466\U0001f3fb\u200d\U0001f476\U0001f3fb" - QUARTER_NOTE = "\u2669" - HEAVY_LATIN_CROSS = "\U0001f547" - FLAG_FOR_QUINDIO_CO_QUI = "\U0001f3f4\U000e0063\U000e006f\U000e0071\U000e0075\U000e0069\U000e007f" - FLAG_FOR_CUSCO_PE_CUS = "\U0001f3f4\U000e0070\U000e0065\U000e0063\U000e0075\U000e0073\U000e007f" - COUPLE_WITH_HEART_WOMAN_WOMAN_DARK_SKIN_TONE = "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3ff" - MAHJONG_TILE_BACK = "\U0001f02b" - FLAG_FOR_SAINT_ANDREW_BB_02 = "\U0001f3f4\U000e0062\U000e0062\U000e0030\U000e0032\U000e007f" - FLAG_FOR_SOUTH_HOLLAND_NL_ZH = "\U0001f3f4\U000e006e\U000e006c\U000e007a\U000e0068\U000e007f" - FLAG_FOR_LAC_TD_LC = "\U0001f3f4\U000e0074\U000e0064\U000e006c\U000e0063\U000e007f" - FLAG_FOR_VELIKO_TARNOVO_BG_04 = "\U0001f3f4\U000e0062\U000e0067\U000e0030\U000e0034\U000e007f" - FLAG_FOR_EASTERN_FJ_E = "\U0001f3f4\U000e0066\U000e006a\U000e0065\U000e007f" - COUPLE_WITH_HEART_MAN_WOMAN_MEDIUM_SKIN_TONE = "\U0001f468\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fd" - FLAG_FOR_SHAMKIR_AZ_SKR = "\U0001f3f4\U000e0061\U000e007a\U000e0073\U000e006b\U000e0072\U000e007f" - PEN_OVER_STAMPED_ENVELOPE = "\U0001f586" - FLAG_FOR_BALEARIC_ISLANDS_ES_IB = "\U0001f3f4\U000e0065\U000e0073\U000e0069\U000e0062\U000e007f" - FLAG_FOR_BATKEN_KG_B = "\U0001f3f4\U000e006b\U000e0067\U000e0062\U000e007f" - FLAG_FOR_NAKHCHIVAN_AR_AZ_NX = "\U0001f3f4\U000e0061\U000e007a\U000e006e\U000e0078\U000e007f" - FLAG_FOR_SAINT_LUCY_BB_07 = "\U0001f3f4\U000e0062\U000e0062\U000e0030\U000e0037\U000e007f" - TAG_LATIN_SMALL_LETTER_N = "\U000e006e" - FLAG_FOR_INDIANA_US_IN = "\U0001f3f4\U000e0075\U000e0073\U000e0069\U000e006e\U000e007f" - FLAG_FOR_SAINT_JOSEPH_BB_06 = "\U0001f3f4\U000e0062\U000e0062\U000e0030\U000e0036\U000e007f" - FAMILY_MAN_WOMAN_BABY_BOY = "\U0001f468\u200d\U0001f469\u200d\U0001f476\u200d\U0001f466" - FLAG_FOR_TARTAR_AZ_TAR = "\U0001f3f4\U000e0061\U000e007a\U000e0074\U000e0061\U000e0072\U000e007f" - CONJUNCTION = "\u260c" - FLAG_FOR_SAINT_ROMAN_MC_SR = "\U0001f3f4\U000e006d\U000e0063\U000e0073\U000e0072\U000e007f" - FLAG_FOR_BREST_BY_BR = "\U0001f3f4\U000e0062\U000e0079\U000e0062\U000e0072\U000e007f" - WOMAN_IN_TUXEDO_MEDIUM_SKIN_TONE = "\U0001f935\U0001f3fd\u200d\u2640\ufe0f" - FLAG_FOR_SASKATCHEWAN_CA_SK = "\U0001f3f4\U000e0063\U000e0061\U000e0073\U000e006b\U000e007f" - FLAG_FOR_SAINT_GEORGE_VC_04 = "\U0001f3f4\U000e0076\U000e0063\U000e0030\U000e0034\U000e007f" - FAMILY_WOMAN_LIGHT_SKIN_TONE_BABY_LIGHT_SKIN_TONE_BOY_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f476\U0001f3fb\u200d\U0001f466\U0001f3fb" - MAHJONG_TILE_THREE_OF_CHARACTERS = "\U0001f009" - FLAG_FOR_SAINT_PHILIP_BB_10 = "\U0001f3f4\U000e0062\U000e0062\U000e0031\U000e0030\U000e007f" - FAMILY_MAN_LIGHT_SKIN_TONE_BABY_LIGHT_SKIN_TONE_GIRL_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f476\U0001f3fb\u200d\U0001f467\U0001f3fb" - FLAG_FOR_ZARDAB_AZ_ZAR = "\U0001f3f4\U000e0061\U000e007a\U000e007a\U000e0061\U000e0072\U000e007f" - FAMILY_MAN_MAN_BABY_BOY = "\U0001f468\u200d\U0001f468\u200d\U0001f476\u200d\U0001f466" - FLAG_FOR_BARISAL_BD_A = "\U0001f3f4\U000e0062\U000e0064\U000e0061\U000e007f" - FLAG_FOR_SANTO_DOMINGO_DE_LOS_TSACHILAS_EC_SD = "\U0001f3f4\U000e0065\U000e0063\U000e0073\U000e0064\U000e007f" - FLAG_FOR_ZANGILAN_AZ_ZAN = "\U0001f3f4\U000e0061\U000e007a\U000e007a\U000e0061\U000e006e\U000e007f" - INTERLOCKED_FEMALE_AND_MALE_SIGN = "\u26a4" - FLAG_FOR_WESTERN_LK_1 = "\U0001f3f4\U000e006c\U000e006b\U000e0031\U000e007f" - FLAG_FOR_KINGSTON_JM_01 = "\U0001f3f4\U000e006a\U000e006d\U000e0030\U000e0031\U000e007f" - DIESEL_LOCOMOTIVE = "\U0001f6f2" - NOTE = "\U0001f5c8" - MAHJONG_TILE_EIGHT_OF_CHARACTERS = "\U0001f00e" - SIDEWAYS_WHITE_RIGHT_POINTING_INDEX = "\U0001f599" - TAG_LESS_THAN_SIGN = "\U000e003c" - FLAG_FOR_RAJSHAHI_DIVISION_BD_E = "\U0001f3f4\U000e0062\U000e0064\U000e0065\U000e007f" - KISS_MAN_MEDIUM_SKIN_TONE_WOMAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fc" - FAMILY_WOMAN_DARK_SKIN_TONE_BABY_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f476\U0001f3ff" - FLAG_FOR_SARDINIA_IT_88 = "\U0001f3f4\U000e0069\U000e0074\U000e0038\U000e0038\U000e007f" - HEAVY_WHITE_DOWN_POINTING_TRIANGLE = "\u26db" - FLAG_FOR_KHULNA_DIVISION_BD_D = "\U0001f3f4\U000e0062\U000e0064\U000e0064\U000e007f" - FLAG_FOR_SAINT_PETER_BB_09 = "\U0001f3f4\U000e0062\U000e0062\U000e0030\U000e0039\U000e007f" - MUSIC_SHARP_SIGN = "\u266f" - RIGHT_HANDED_INTERLACED_PENTAGRAM = "\u26e5" - TAG_LATIN_SMALL_LETTER_C = "\U000e0063" - FLAG_FOR_ADDIS_ABABA_ET_AA = "\U0001f3f4\U000e0065\U000e0074\U000e0061\U000e0061\U000e007f" - FLAG_FOR_PORTLAND_JM_04 = "\U0001f3f4\U000e006a\U000e006d\U000e0030\U000e0034\U000e007f" - FLAG_FOR_MYMENSINGH_DIVISION_BD_H = "\U0001f3f4\U000e0062\U000e0064\U000e0068\U000e007f" - FLAG_FOR_PORT_OF_SPAIN_TT_POS = "\U0001f3f4\U000e0074\U000e0074\U000e0070\U000e006f\U000e0073\U000e007f" - FLAG_FOR_CASANARE_CO_CAS = "\U0001f3f4\U000e0063\U000e006f\U000e0063\U000e0061\U000e0073\U000e007f" - TAG_LATIN_CAPITAL_LETTER_Q = "\U000e0051" - TRIGRAM_FOR_THUNDER = "\u2633" - POCKET_CALCULATOR = "\U0001f5a9" - RIGHT_SPEAKER_WITH_ONE_SOUND_WAVE = "\U0001f569" - FLAG_FOR_LOS_SANTOS_PA_7 = "\U0001f3f4\U000e0070\U000e0061\U000e0037\U000e007f" - FLAG_FOR_LOWER_AUSTRIA_AT_3 = "\U0001f3f4\U000e0061\U000e0074\U000e0033\U000e007f" - FLAG_FOR_SUMQAYIT_AZ_SM = "\U0001f3f4\U000e0061\U000e007a\U000e0073\U000e006d\U000e007f" - FLAG_FOR_CASCADES_BF_02 = "\U0001f3f4\U000e0062\U000e0066\U000e0030\U000e0032\U000e007f" - FLAG_FOR_BOUCLE_DU_MOUHOUN_BF_01 = "\U0001f3f4\U000e0062\U000e0066\U000e0030\U000e0031\U000e007f" - FLAG_FOR_AL_HUDAYDAH_YE_HU = "\U0001f3f4\U000e0079\U000e0065\U000e0068\U000e0075\U000e007f" - FLAG_FOR_SAINT_PATRICK_GD_06 = "\U0001f3f4\U000e0067\U000e0064\U000e0030\U000e0036\U000e007f" - FLAG_FOR_NOVA_SCOTIA_CA_NS = "\U0001f3f4\U000e0063\U000e0061\U000e006e\U000e0073\U000e007f" - FLAG_FOR_MINAS_GERAIS_BR_MG = "\U0001f3f4\U000e0062\U000e0072\U000e006d\U000e0067\U000e007f" - FLAG_FOR_CENTRE_SUD_BF_07 = "\U0001f3f4\U000e0062\U000e0066\U000e0030\U000e0037\U000e007f" - MUSICAL_KEYBOARD_WITH_JACKS = "\U0001f398" - FLAG_FOR_BENGO_AO_BGO = "\U0001f3f4\U000e0061\U000e006f\U000e0062\U000e0067\U000e006f\U000e007f" - COUPLE_WITH_HEART_MAN_MEDIUM_SKIN_TONE_WOMAN_DARK_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3ff" - FLAG_FOR_CENTRE_OUEST_BF_06 = "\U0001f3f4\U000e0062\U000e0066\U000e0030\U000e0036\U000e007f" - GEAR_WITHOUT_HUB = "\u26ed" - FAMILY_MAN_LIGHT_SKIN_TONE_WOMAN_LIGHT_SKIN_TONE_BABY_LIGHT_SKIN_TONE_BABY_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f469\U0001f3fb\u200d\U0001f476\U0001f3fb\u200d\U0001f476\U0001f3fb" - FLAG_FOR_CENTRE_BF_03 = "\U0001f3f4\U000e0062\U000e0066\U000e0030\U000e0033\U000e007f" - FAMILY_WOMAN_MAN_GIRL_GIRL = "\U0001f469\u200d\U0001f468\u200d\U0001f467\u200d\U0001f467" - WHITE_TWO_WAY_LEFT_WAY_TRAFFIC = "\u26d7" - FLAG_FOR_HOWLAND_ISLAND_UM_84 = "\U0001f3f4\U000e0075\U000e006d\U000e0038\U000e0034\U000e007f" - FLAG_FOR_EST_BF_08 = "\U0001f3f4\U000e0062\U000e0066\U000e0030\U000e0038\U000e007f" - FLAG_FOR_ELBASAN_COUNTY_AL_03 = "\U0001f3f4\U000e0061\U000e006c\U000e0030\U000e0033\U000e007f" - FLAG_FOR_RATAK_CHAIN_MH_T = "\U0001f3f4\U000e006d\U000e0068\U000e0074\U000e007f" - FLAG_FOR_CANARY_ISLANDS_ES_CN = "\U0001f3f4\U000e0065\U000e0073\U000e0063\U000e006e\U000e007f" - FLAG_FOR_PLATEAU_CENTRAL_BF_11 = "\U0001f3f4\U000e0062\U000e0066\U000e0031\U000e0031\U000e007f" - FLAG_FOR_DOUKKALA_ABDA_MA_10 = "\U0001f3f4\U000e006d\U000e0061\U000e0031\U000e0030\U000e007f" - FLAG_FOR_TERENGGANU_MY_11 = "\U0001f3f4\U000e006d\U000e0079\U000e0031\U000e0031\U000e007f" - MAN_WITH_HEADSCARF_MEDIUM_DARK_SKIN_TONE = "\U0001f9d5\U0001f3fe\u200d\u2642\ufe0f" - COUPLE_WITH_HEART_WOMAN_MEDIUM_DARK_SKIN_TONE_WOMAN_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fd" - FLAG_FOR_SANTA_CRUZ_AR_Z = "\U0001f3f4\U000e0061\U000e0072\U000e007a\U000e007f" - FLAG_FOR_DHAKA_DIVISION_BD_C = "\U0001f3f4\U000e0062\U000e0064\U000e0063\U000e007f" - FLAG_FOR_JUJUY_AR_Y = "\U0001f3f4\U000e0061\U000e0072\U000e0079\U000e007f" - CIRCLED_INFORMATION_SOURCE = "\U0001f6c8" - FLAG_FOR_SYLHET_DIVISION_BD_G = "\U0001f3f4\U000e0062\U000e0064\U000e0067\U000e007f" - FLAG_FOR_VIDIN_BG_05 = "\U0001f3f4\U000e0062\U000e0067\U000e0030\U000e0035\U000e007f" - FLAG_FOR_ARKHANGELSK_RU_ARK = "\U0001f3f4\U000e0072\U000e0075\U000e0061\U000e0072\U000e006b\U000e007f" - MAHJONG_TILE_ONE_OF_CHARACTERS = "\U0001f007" - FLAG_FOR_BADAKHSHAN_AF_BDS = "\U0001f3f4\U000e0061\U000e0066\U000e0062\U000e0064\U000e0073\U000e007f" - FLAG_FOR_ALGIERS_DZ_16 = "\U0001f3f4\U000e0064\U000e007a\U000e0031\U000e0036\U000e007f" - FLAG_FOR_STEPANAKERT_AZ_XA = "\U0001f3f4\U000e0061\U000e007a\U000e0078\U000e0061\U000e007f" - FLAG_FOR_GABROVO_BG_07 = "\U0001f3f4\U000e0062\U000e0067\U000e0030\U000e0037\U000e007f" - FLAG_FOR_BUENOS_AIRES_PROVINCE_AR_B = "\U0001f3f4\U000e0061\U000e0072\U000e0062\U000e007f" - FLAG_FOR_DOBRICH_BG_08 = "\U0001f3f4\U000e0062\U000e0067\U000e0030\U000e0038\U000e007f" - BLACK_PENNANT = "\U0001f3f2" - FLAG_FOR_CENTRE_EST_BF_04 = "\U0001f3f4\U000e0062\U000e0066\U000e0030\U000e0034\U000e007f" - COUPLE_WITH_HEART_MAN_DARK_SKIN_TONE_WOMAN_LIGHT_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fb" - KISS_MAN_LIGHT_SKIN_TONE_WOMAN_DARK_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3ff" - FLAG_FOR_BAHIA_BR_BA = "\U0001f3f4\U000e0062\U000e0072\U000e0062\U000e0061\U000e007f" - COUPLE_WITH_HEART_WOMAN_MEDIUM_SKIN_TONE_MAN_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb" - MAHJONG_TILE_FIVE_OF_CHARACTERS = "\U0001f00b" - KISS_MAN_MEDIUM_DARK_SKIN_TONE_WOMAN_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fe" - FAMILY_MAN_MEDIUM_LIGHT_SKIN_TONE_WOMAN_MEDIUM_LIGHT_SKIN_TONE_BABY_MEDIUM_LIGHT_SKIN_TONE_GIRL_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f469\U0001f3fc\u200d\U0001f476\U0001f3fc\u200d\U0001f467\U0001f3fc" - FLAG_FOR_AKITA_JP_05 = "\U0001f3f4\U000e006a\U000e0070\U000e0030\U000e0035\U000e007f" - FLAG_FOR_KYUSTENDIL_BG_10 = "\U0001f3f4\U000e0062\U000e0067\U000e0031\U000e0030\U000e007f" - CHIRON = "\u26b7" - FLAG_FOR_RALIK_CHAIN_MH_L = "\U0001f3f4\U000e006d\U000e0068\U000e006c\U000e007f" - FLAG_FOR_LOVECH_BG_11 = "\U0001f3f4\U000e0062\U000e0067\U000e0031\U000e0031\U000e007f" - FLAG_FOR_VAYOTS_DZOR_AM_VD = "\U0001f3f4\U000e0061\U000e006d\U000e0076\U000e0064\U000e007f" - FLAG_FOR_BANTEAY_MEANCHEY_KH_1 = "\U0001f3f4\U000e006b\U000e0068\U000e0031\U000e007f" - FLAG_FOR_PAZARDZHIK_BG_13 = "\U0001f3f4\U000e0062\U000e0067\U000e0031\U000e0033\U000e007f" - FAMILY_WOMAN_MEDIUM_DARK_SKIN_TONE_MAN_MEDIUM_DARK_SKIN_TONE_BABY_MEDIUM_DARK_SKIN_TONE_GIRL_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f468\U0001f3fe\u200d\U0001f476\U0001f3fe\u200d\U0001f467\U0001f3fe" - DIE_FACE_5 = "\u2684" - FAMILY_WOMAN_WOMAN_BABY_GIRL = "\U0001f469\u200d\U0001f469\u200d\U0001f476\u200d\U0001f467" - FLAG_FOR_BLAGOEVGRAD_BG_01 = "\U0001f3f4\U000e0062\U000e0067\U000e0030\U000e0031\U000e007f" - FLAG_FOR_CARRIACOU_AND_PETITE_MARTINIQUE_GD_10 = "\U0001f3f4\U000e0067\U000e0064\U000e0031\U000e0030\U000e007f" - FAMILY_MAN_MEDIUM_LIGHT_SKIN_TONE_WOMAN_MEDIUM_LIGHT_SKIN_TONE_BOY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f469\U0001f3fc\u200d\U0001f466\U0001f3fc" - FLAG_FOR_PLOVDIV_BG_16 = "\U0001f3f4\U000e0062\U000e0067\U000e0031\U000e0036\U000e007f" - FLAG_FOR_PLEVEN_BG_15 = "\U0001f3f4\U000e0062\U000e0067\U000e0031\U000e0035\U000e007f" - FLAG_FOR_NORD_BF_10 = "\U0001f3f4\U000e0062\U000e0066\U000e0031\U000e0030\U000e007f" - FLAG_FOR_KURDISTAN_IR_16 = "\U0001f3f4\U000e0069\U000e0072\U000e0031\U000e0036\U000e007f" - FLAG_FOR_DASHKASAN_AZ_DAS = "\U0001f3f4\U000e0061\U000e007a\U000e0064\U000e0061\U000e0073\U000e007f" - COUPLE_WITH_HEART_MAN_DARK_SKIN_TONE_MAN = "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468" - FLAG_FOR_BORDJ_BOU_ARRERIDJ_DZ_34 = "\U0001f3f4\U000e0064\U000e007a\U000e0033\U000e0034\U000e007f" - TAG_AMPERSAND = "\U000e0026" - FLAG_FOR_VALLEE_DU_BANDAMA_CI_VB = "\U0001f3f4\U000e0063\U000e0069\U000e0076\U000e0062\U000e007f" - FLAG_FOR_RAZGRAD_BG_17 = "\U0001f3f4\U000e0062\U000e0067\U000e0031\U000e0037\U000e007f" - FAMILY_WOMAN_DARK_SKIN_TONE_BABY_DARK_SKIN_TONE_BOY_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f476\U0001f3ff\u200d\U0001f466\U0001f3ff" - FLAG_FOR_SOFIA_BG_22 = "\U0001f3f4\U000e0062\U000e0067\U000e0032\U000e0032\U000e007f" - FLAG_FOR_SOFIA_DISTRICT_BG_23 = "\U0001f3f4\U000e0062\U000e0067\U000e0032\U000e0033\U000e007f" - FLAG_FOR_TARGOVISHTE_BG_25 = "\U0001f3f4\U000e0062\U000e0067\U000e0032\U000e0035\U000e007f" - FLAG_FOR_LOPBURI_TH_16 = "\U0001f3f4\U000e0074\U000e0068\U000e0031\U000e0036\U000e007f" - FAMILY_WOMAN_MEDIUM_DARK_SKIN_TONE_MAN_MEDIUM_DARK_SKIN_TONE_GIRL_MEDIUM_DARK_SKIN_TONE_BOY_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f468\U0001f3fe\u200d\U0001f467\U0001f3fe\u200d\U0001f466\U0001f3fe" - FLAG_FOR_CUNENE_AO_CNN = "\U0001f3f4\U000e0061\U000e006f\U000e0063\U000e006e\U000e006e\U000e007f" - FLAG_FOR_ADJARA_GE_AJ = "\U0001f3f4\U000e0067\U000e0065\U000e0061\U000e006a\U000e007f" - COUPLE_WITH_HEART_MAN_DARK_SKIN_TONE_WOMAN_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3ff" - FLAG_FOR_VARNA_BG_03 = "\U0001f3f4\U000e0062\U000e0067\U000e0030\U000e0033\U000e007f" - FAMILY_MAN_MEDIUM_LIGHT_SKIN_TONE_MAN_MEDIUM_LIGHT_SKIN_TONE_BABY_MEDIUM_LIGHT_SKIN_TONE_GIRL_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f468\U0001f3fc\u200d\U0001f476\U0001f3fc\u200d\U0001f467\U0001f3fc" - FLAG_FOR_YOBE_NG_YO = "\U0001f3f4\U000e006e\U000e0067\U000e0079\U000e006f\U000e007f" - FLAG_FOR_PERNIK_BG_14 = "\U0001f3f4\U000e0062\U000e0067\U000e0031\U000e0034\U000e007f" - FLAG_FOR_BAY_SOMALIA_SO_BY = "\U0001f3f4\U000e0073\U000e006f\U000e0062\U000e0079\U000e007f" - FLAG_FOR_SAINT_GEORGE_BB_03 = "\U0001f3f4\U000e0062\U000e0062\U000e0030\U000e0033\U000e007f" - FAMILY_WOMAN_LIGHT_SKIN_TONE_MAN_LIGHT_SKIN_TONE_BOY_LIGHT_SKIN_TONE_BABY_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f468\U0001f3fb\u200d\U0001f466\U0001f3fb\u200d\U0001f476\U0001f3fb" - WOMAN_WITH_HEADSCARF_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d5\U0001f3fc\u200d\u2640\ufe0f" - FLAG_FOR_WESTERN_CAPE_ZA_WC = "\U0001f3f4\U000e007a\U000e0061\U000e0077\U000e0063\U000e007f" - FAMILY_MAN_LIGHT_SKIN_TONE_MAN_LIGHT_SKIN_TONE_GIRL_LIGHT_SKIN_TONE_BOY_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f468\U0001f3fb\u200d\U0001f467\U0001f3fb\u200d\U0001f466\U0001f3fb" - FLAG_FOR_HASKOVO_BG_26 = "\U0001f3f4\U000e0062\U000e0067\U000e0032\U000e0036\U000e007f" - FLAG_FOR_YAMBOL_BG_28 = "\U0001f3f4\U000e0062\U000e0067\U000e0032\U000e0038\U000e007f" - COUPLE_WITH_HEART_MAN_MEDIUM_DARK_SKIN_TONE_MAN_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd" - FLAG_FOR_SCHAAN_LI_07 = "\U0001f3f4\U000e006c\U000e0069\U000e0030\U000e0037\U000e007f" - FLAG_FOR_STREDOCESKY_KRAJ_CZ_20 = "\U0001f3f4\U000e0063\U000e007a\U000e0032\U000e0030\U000e007f" - FLAG_FOR_ZAVRC_SI_143 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0034\U000e0033\U000e007f" - COUPLE_WITH_HEART_WOMAN_LIGHT_SKIN_TONE_MAN_DARK_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff" - FLAG_FOR_TACHIRA_VE_S = "\U0001f3f4\U000e0076\U000e0065\U000e0073\U000e007f" - FAMILY_WOMAN_MEDIUM_SKIN_TONE_BOY_MEDIUM_SKIN_TONE_GIRL_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f466\U0001f3fd\u200d\U0001f467\U0001f3fd" - FLAG_FOR_SOUTHERN_BH_14 = "\U0001f3f4\U000e0062\U000e0068\U000e0031\U000e0034\U000e007f" - FLAG_FOR_SIBIU_RO_SB = "\U0001f3f4\U000e0072\U000e006f\U000e0073\U000e0062\U000e007f" - COUPLE_WITH_HEART_WOMAN_LIGHT_SKIN_TONE_WOMAN = "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f469" - DIE_FACE_1 = "\u2680" - FAMILY_WOMAN_LIGHT_SKIN_TONE_BABY_LIGHT_SKIN_TONE_GIRL_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f476\U0001f3fb\u200d\U0001f467\U0001f3fb" - FLAG_FOR_SOUTHERN_IS_8 = "\U0001f3f4\U000e0069\U000e0073\U000e0038\U000e007f" - FLAG_FOR_MUHARRAQ_BH_15 = "\U0001f3f4\U000e0062\U000e0068\U000e0031\U000e0035\U000e007f" - FLAG_FOR_BRUSSELS_BE_BRU = "\U0001f3f4\U000e0062\U000e0065\U000e0062\U000e0072\U000e0075\U000e007f" - KISS_MAN_MEDIUM_LIGHT_SKIN_TONE_MAN_DARK_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff" - WOMAN_WITH_HEADSCARF_MEDIUM_SKIN_TONE = "\U0001f9d5\U0001f3fd\u200d\u2640\ufe0f" - FLAG_FOR_CENTRE_NORD_BF_05 = "\U0001f3f4\U000e0062\U000e0066\U000e0030\U000e0035\U000e007f" - ENVELOPE_WITH_LIGHTNING = "\U0001f584" - FLAG_FOR_COLORADO_US_CO = "\U0001f3f4\U000e0075\U000e0073\U000e0063\U000e006f\U000e007f" - FLAG_FOR_CAPITAL_BH_13 = "\U0001f3f4\U000e0062\U000e0068\U000e0031\U000e0033\U000e007f" - FAMILY_WOMAN_LIGHT_SKIN_TONE_MAN_LIGHT_SKIN_TONE_GIRL_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f468\U0001f3fb\u200d\U0001f467\U0001f3fb" - FLAG_FOR_TATARSTAN_RU_TA = "\U0001f3f4\U000e0072\U000e0075\U000e0074\U000e0061\U000e007f" - FLAG_FOR_NORTHERN_BH_17 = "\U0001f3f4\U000e0062\U000e0068\U000e0031\U000e0037\U000e007f" - FAMILY_MAN_MEDIUM_LIGHT_SKIN_TONE_WOMAN_MEDIUM_LIGHT_SKIN_TONE_GIRL_MEDIUM_LIGHT_SKIN_TONE_GIRL_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f469\U0001f3fc\u200d\U0001f467\U0001f3fc\u200d\U0001f467\U0001f3fc" - FLAG_FOR_BUJUMBURA_BI_BM = "\U0001f3f4\U000e0062\U000e0069\U000e0062\U000e006d\U000e007f" - WEST_SYRIAC_CROSS = "\u2670" - FLAG_FOR_SA_DAH_YE_SD = "\U0001f3f4\U000e0079\U000e0065\U000e0073\U000e0064\U000e007f" - FLAG_FOR_AL_QASSIM_SA_05 = "\U0001f3f4\U000e0073\U000e0061\U000e0030\U000e0035\U000e007f" - FLAG_FOR_CANKUZO_BI_CA = "\U0001f3f4\U000e0062\U000e0069\U000e0063\U000e0061\U000e007f" - FAMILY_MAN_LIGHT_SKIN_TONE_MAN_LIGHT_SKIN_TONE_BOY_LIGHT_SKIN_TONE_BABY_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f468\U0001f3fb\u200d\U0001f466\U0001f3fb\u200d\U0001f476\U0001f3fb" - FLAG_FOR_NORTHERN_TERRITORY_AU_NT = "\U0001f3f4\U000e0061\U000e0075\U000e006e\U000e0074\U000e007f" - CROSS_OF_JERUSALEM = "\u2629" - FLAG_FOR_CHUKOTKA_OKRUG_RU_CHU = "\U0001f3f4\U000e0072\U000e0075\U000e0063\U000e0068\U000e0075\U000e007f" - FLAG_FOR_KARDZHALI_BG_09 = "\U0001f3f4\U000e0062\U000e0067\U000e0030\U000e0039\U000e007f" - FLAG_FOR_SALA_LV_085 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0038\U000e0035\U000e007f" - FLAG_FOR_CENTRAL_DENMARK_DK_82 = "\U0001f3f4\U000e0064\U000e006b\U000e0038\U000e0032\U000e007f" - FLAG_FOR_BURURI_BI_BR = "\U0001f3f4\U000e0062\U000e0069\U000e0062\U000e0072\U000e007f" - FLAG_FOR_WALLONIA_BE_WAL = "\U0001f3f4\U000e0062\U000e0065\U000e0077\U000e0061\U000e006c\U000e007f" - KISS_MAN_MEDIUM_DARK_SKIN_TONE_WOMAN_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fd" - TAG_LATIN_SMALL_LETTER_G = "\U000e0067" - FLAG_FOR_MARTINIQUE_FR_MQ = "\U0001f3f4\U000e0066\U000e0072\U000e006d\U000e0071\U000e007f" - FLAG_FOR_KARUZI_BI_KR = "\U0001f3f4\U000e0062\U000e0069\U000e006b\U000e0072\U000e007f" - FLAG_FOR_BABYLON_IQ_BB = "\U0001f3f4\U000e0069\U000e0071\U000e0062\U000e0062\U000e007f" - FLAG_FOR_KIRUNDO_BI_KI = "\U0001f3f4\U000e0062\U000e0069\U000e006b\U000e0069\U000e007f" - FLAG_FOR_LANKARAN_DISTRICT_AZ_LAN = "\U0001f3f4\U000e0061\U000e007a\U000e006c\U000e0061\U000e006e\U000e007f" - FLAG_FOR_MURAMVYA_BI_MU = "\U0001f3f4\U000e0062\U000e0069\U000e006d\U000e0075\U000e007f" - FLAG_FOR_MUYINGA_BI_MY = "\U0001f3f4\U000e0062\U000e0069\U000e006d\U000e0079\U000e007f" - FLAG_FOR_TIANJIN_CN_12 = "\U0001f3f4\U000e0063\U000e006e\U000e0031\U000e0032\U000e007f" - FLAG_FOR_LAAYOUNE_BOUJDOUR_SAKIA_EL_HAMRA_MA_15 = "\U0001f3f4\U000e006d\U000e0061\U000e0031\U000e0035\U000e007f" - DIGRAM_FOR_GREATER_YANG = "\u268c" - FLAG_FOR_SHUMEN_BG_27 = "\U0001f3f4\U000e0062\U000e0067\U000e0032\U000e0037\U000e007f" - RECYCLED_PAPER_SYMBOL = "\u267c" - FLAG_FOR_NGOZI_BI_NG = "\U0001f3f4\U000e0062\U000e0069\U000e006e\U000e0067\U000e007f" - FAMILY_WOMAN_MAN_BOY_BOY = "\U0001f469\u200d\U0001f468\u200d\U0001f466\u200d\U0001f466" - KISS_WOMAN_MEDIUM_LIGHT_SKIN_TONE_WOMAN_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fd" - FLAG_FOR_SISTAN_AND_BALUCHESTAN_IR_13 = "\U0001f3f4\U000e0069\U000e0072\U000e0031\U000e0033\U000e007f" - FLAG_FOR_RUMONGE_BI_RM = "\U0001f3f4\U000e0062\U000e0069\U000e0072\U000e006d\U000e007f" - FLAG_FOR_AOSTA_VALLEY_IT_23 = "\U0001f3f4\U000e0069\U000e0074\U000e0032\U000e0033\U000e007f" - FLAG_FOR_NOVO_MESTO_SI_085 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0038\U000e0035\U000e007f" - FAMILY_MAN_MEDIUM_LIGHT_SKIN_TONE_WOMAN_MEDIUM_LIGHT_SKIN_TONE_BABY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f469\U0001f3fc\u200d\U0001f476\U0001f3fc" - BEAMED_SIXTEENTH_NOTES = "\u266c" - FAMILY_WOMAN_LIGHT_SKIN_TONE_MAN_LIGHT_SKIN_TONE_BOY_LIGHT_SKIN_TONE_GIRL_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f468\U0001f3fb\u200d\U0001f466\U0001f3fb\u200d\U0001f467\U0001f3fb" - FLAG_FOR_HAUTS_DE_FRANCE_FR_HDF = "\U0001f3f4\U000e0066\U000e0072\U000e0068\U000e0064\U000e0066\U000e007f" - FLAG_FOR_RIO_NEGRO_AR_R = "\U0001f3f4\U000e0061\U000e0072\U000e0072\U000e007f" - WHITE_CHESS_KNIGHT = "\u2658" - FLAG_FOR_ALIBORI_BJ_AL = "\U0001f3f4\U000e0062\U000e006a\U000e0061\U000e006c\U000e007f" - FLAG_FOR_DONGA_BJ_DO = "\U0001f3f4\U000e0062\U000e006a\U000e0064\U000e006f\U000e007f" - FLAG_FOR_HAMBURG_DE_HH = "\U0001f3f4\U000e0064\U000e0065\U000e0068\U000e0068\U000e007f" - FLAG_FOR_SAHEL_BF_12 = "\U0001f3f4\U000e0062\U000e0066\U000e0031\U000e0032\U000e007f" - FLAG_FOR_ATAKORA_BJ_AK = "\U0001f3f4\U000e0062\U000e006a\U000e0061\U000e006b\U000e007f" - FLAG_FOR_RUYIGI_BI_RY = "\U0001f3f4\U000e0062\U000e0069\U000e0072\U000e0079\U000e007f" - COUPLE_WITH_HEART_WOMAN_MEDIUM_LIGHT_SKIN_TONE_WOMAN = "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f469" - FLAG_FOR_MA_AN_JO_MN = "\U0001f3f4\U000e006a\U000e006f\U000e006d\U000e006e\U000e007f" - FLAG_FOR_SMOLYAN_BG_21 = "\U0001f3f4\U000e0062\U000e0067\U000e0032\U000e0031\U000e007f" - FLAG_FOR_LITTORAL_BJ_LI = "\U0001f3f4\U000e0062\U000e006a\U000e006c\U000e0069\U000e007f" - FAMILY_MAN_DARK_SKIN_TONE_MAN_DARK_SKIN_TONE_BABY_DARK_SKIN_TONE_BABY_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f468\U0001f3ff\u200d\U0001f476\U0001f3ff\u200d\U0001f476\U0001f3ff" - FLAG_FOR_ZOU_BJ_ZO = "\U0001f3f4\U000e0062\U000e006a\U000e007a\U000e006f\U000e007f" - FLAG_FOR_RED_SEA_EG_BA = "\U0001f3f4\U000e0065\U000e0067\U000e0062\U000e0061\U000e007f" - FLAG_FOR_VLORE_COUNTY_AL_12 = "\U0001f3f4\U000e0061\U000e006c\U000e0031\U000e0032\U000e007f" - FLAG_FOR_KOPER_SI_050 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0035\U000e0030\U000e007f" - KISS_WOMAN_DARK_SKIN_TONE_MAN_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd" - FAMILY_WOMAN_DARK_SKIN_TONE_WOMAN_DARK_SKIN_TONE_BOY_DARK_SKIN_TONE_GIRL_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f469\U0001f3ff\u200d\U0001f466\U0001f3ff\u200d\U0001f467\U0001f3ff" - FLAG_FOR_MANAWATU_WANGANUI_NZ_MWT = "\U0001f3f4\U000e006e\U000e007a\U000e006d\U000e0077\U000e0074\U000e007f" - FLAG_FOR_ENTRE_RIOS_AR_E = "\U0001f3f4\U000e0061\U000e0072\U000e0065\U000e007f" - FLAG_FOR_NORTH_DAKOTA_US_ND = "\U0001f3f4\U000e0075\U000e0073\U000e006e\U000e0064\U000e007f" - MEDIUM_SMALL_WHITE_CIRCLE = "\u26ac" - FLAG_FOR_BEQAA_LB_BI = "\U0001f3f4\U000e006c\U000e0062\U000e0062\U000e0069\U000e007f" - FLAG_FOR_SEKONG_LA_XE = "\U0001f3f4\U000e006c\U000e0061\U000e0078\U000e0065\U000e007f" - FAMILY_MAN_MEDIUM_LIGHT_SKIN_TONE_MAN_MEDIUM_LIGHT_SKIN_TONE_BOY_MEDIUM_LIGHT_SKIN_TONE_GIRL_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f468\U0001f3fc\u200d\U0001f466\U0001f3fc\u200d\U0001f467\U0001f3fc" - FLAG_FOR_OUEME_BJ_OU = "\U0001f3f4\U000e0062\U000e006a\U000e006f\U000e0075\U000e007f" - COUPLE_WITH_HEART_MAN_MEDIUM_SKIN_TONE_WOMAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fc" - BALLOT_BOX_WITH_SCRIPT_X = "\U0001f5f5" - FLAG_FOR_TUTONG_BN_TU = "\U0001f3f4\U000e0062\U000e006e\U000e0074\U000e0075\U000e007f" - COUPLE_WITH_HEART_WOMAN_MEDIUM_SKIN_TONE_MAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc" - FLAG_FOR_VRATSA_BG_06 = "\U0001f3f4\U000e0062\U000e0067\U000e0030\U000e0036\U000e007f" - MAN_WITH_HEADSCARF = "\U0001f9d5\u200d\u2642\ufe0f" - COUPLE_WITH_HEART_WOMAN_MEDIUM_LIGHT_SKIN_TONE_MAN = "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468" - FAMILY_MAN_MEDIUM_SKIN_TONE_WOMAN_MEDIUM_SKIN_TONE_BABY_MEDIUM_SKIN_TONE_GIRL_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f469\U0001f3fd\u200d\U0001f476\U0001f3fd\u200d\U0001f467\U0001f3fd" - FLAG_FOR_KHENTII_MN_039 = "\U0001f3f4\U000e006d\U000e006e\U000e0030\U000e0033\U000e0039\U000e007f" - FLAG_FOR_DELTA_AMACURO_VE_Y = "\U0001f3f4\U000e0076\U000e0065\U000e0079\U000e007f" - RECYCLING_SYMBOL_FOR_GENERIC_MATERIALS = "\u267a" - FLAG_FOR_ASHANTI_GH_AH = "\U0001f3f4\U000e0067\U000e0068\U000e0061\U000e0068\U000e007f" - FLAG_FOR_DOLNENI_MK_27 = "\U0001f3f4\U000e006d\U000e006b\U000e0032\U000e0037\U000e007f" - BLACK_TRUCK = "\u26df" - KISS_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE = "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fe" - FLAG_FOR_BORGOU_BJ_BO = "\U0001f3f4\U000e0062\U000e006a\U000e0062\U000e006f\U000e007f" - FAMILY_MAN_DARK_SKIN_TONE_WOMAN_DARK_SKIN_TONE_BABY_DARK_SKIN_TONE_BABY_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f469\U0001f3ff\u200d\U0001f476\U0001f3ff\u200d\U0001f476\U0001f3ff" - FLAG_FOR_SAINT_JAMES_BB_04 = "\U0001f3f4\U000e0062\U000e0062\U000e0030\U000e0034\U000e007f" - FLAG_FOR_PANDO_BO_N = "\U0001f3f4\U000e0062\U000e006f\U000e006e\U000e007f" - FLAG_FOR_LA_PAZ_BO_L = "\U0001f3f4\U000e0062\U000e006f\U000e006c\U000e007f" - FAMILY_WOMAN_DARK_SKIN_TONE_WOMAN_DARK_SKIN_TONE_BOY_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f469\U0001f3ff\u200d\U0001f466\U0001f3ff" - FLAG_FOR_STARA_ZAGORA_BG_24 = "\U0001f3f4\U000e0062\U000e0067\U000e0032\U000e0034\U000e007f" - FLAG_FOR_CHUQUISACA_BO_H = "\U0001f3f4\U000e0062\U000e006f\U000e0068\U000e007f" - FLAG_FOR_GUMUSHANE_TR_29 = "\U0001f3f4\U000e0074\U000e0072\U000e0032\U000e0039\U000e007f" - FLAG_FOR_PANJSHIR_AF_PAN = "\U0001f3f4\U000e0061\U000e0066\U000e0070\U000e0061\U000e006e\U000e007f" - FLAG_FOR_COCHABAMBA_BO_C = "\U0001f3f4\U000e0062\U000e006f\U000e0063\U000e007f" - FLAG_FOR_ROCHE_CAIMAN_SC_25 = "\U0001f3f4\U000e0073\U000e0063\U000e0032\U000e0035\U000e007f" - FAMILY_WOMAN_DARK_SKIN_TONE_BOY_DARK_SKIN_TONE_GIRL_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f466\U0001f3ff\u200d\U0001f467\U0001f3ff" - COUPLE_WITH_HEART_WOMAN_MEDIUM_DARK_SKIN_TONE_MAN = "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468" - COUPLE_WITH_HEART_WOMAN_MEDIUM_DARK_SKIN_TONE_MAN_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd" - FLAG_FOR_BRCKO_DISTRICT_BA_BRC = "\U0001f3f4\U000e0062\U000e0061\U000e0062\U000e0072\U000e0063\U000e007f" - FLAG_FOR_BRETAGNE_FR_BRE = "\U0001f3f4\U000e0066\U000e0072\U000e0062\U000e0072\U000e0065\U000e007f" - COUPLE_WITH_HEART_WOMAN_MEDIUM_SKIN_TONE_MAN_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe" - FLAG_FOR_CHECHEN_RU_CE = "\U0001f3f4\U000e0072\U000e0075\U000e0063\U000e0065\U000e007f" - FLAG_FOR_KICEVO_MK_40 = "\U0001f3f4\U000e006d\U000e006b\U000e0034\U000e0030\U000e007f" - FLAG_FOR_SAINT_JOHN_BB_05 = "\U0001f3f4\U000e0062\U000e0062\U000e0030\U000e0035\U000e007f" - FLAG_FOR_NORTHERN_MARIANA_ISLANDS_US_MP = "\U0001f3f4\U000e0075\U000e0073\U000e006d\U000e0070\U000e007f" - KISS_WOMAN_MAN_LIGHT_SKIN_TONE = "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb" - FLAG_FOR_KAUNAS_COUNTY_LT_KU = "\U0001f3f4\U000e006c\U000e0074\U000e006b\U000e0075\U000e007f" - FLAG_FOR_GJIROKASTER_COUNTY_AL_05 = "\U0001f3f4\U000e0061\U000e006c\U000e0030\U000e0035\U000e007f" - KISS_MAN_DARK_SKIN_TONE_MAN_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe" - KISS_WOMAN_MEDIUM_DARK_SKIN_TONE_WOMAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fc" - KISS_MAN_DARK_SKIN_TONE_WOMAN_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fe" - COUPLE_WITH_HEART_WOMAN_DARK_SKIN_TONE_WOMAN_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fd" - KISS_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc" - FLAG_FOR_LENART_SI_058 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0035\U000e0038\U000e007f" - FLAG_FOR_TEMBURONG_BN_TE = "\U0001f3f4\U000e0062\U000e006e\U000e0074\U000e0065\U000e007f" - FLAG_FOR_BELAIT_BN_BE = "\U0001f3f4\U000e0062\U000e006e\U000e0062\U000e0065\U000e007f" - COUPLE_WITH_HEART_MAN_MEDIUM_LIGHT_SKIN_TONE_MAN_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb" - KISS_WOMAN_LIGHT_SKIN_TONE_WOMAN_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fd" - COUPLE_WITH_HEART_MAN_MEDIUM_SKIN_TONE_MAN_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb" - FAMILY_MAN_MEDIUM_SKIN_TONE_WOMAN_MEDIUM_SKIN_TONE_GIRL_MEDIUM_SKIN_TONE_BOY_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f469\U0001f3fd\u200d\U0001f467\U0001f3fd\u200d\U0001f466\U0001f3fd" - FLAG_FOR_POTOSI_BO_P = "\U0001f3f4\U000e0062\U000e006f\U000e0070\U000e007f" - KISS_MAN_MEDIUM_LIGHT_SKIN_TONE_MAN_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd" - KISS_MAN_MEDIUM_LIGHT_SKIN_TONE_MAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc" - MAN_WITH_HEADSCARF_DARK_SKIN_TONE = "\U0001f9d5\U0001f3ff\u200d\u2642\ufe0f" - KISS_WOMAN_MEDIUM_LIGHT_SKIN_TONE_MAN_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe" - FAMILY_MAN_MEDIUM_DARK_SKIN_TONE_BOY_MEDIUM_DARK_SKIN_TONE_GIRL_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f466\U0001f3fe\u200d\U0001f467\U0001f3fe" - COUPLE_WITH_HEART_WOMAN_DARK_SKIN_TONE_WOMAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fc" - SQUARE_FOUR_CORNERS = "\u26f6" - KISS_WOMAN_LIGHT_SKIN_TONE_WOMAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fc" - KISS_MAN_MEDIUM_SKIN_TONE_WOMAN_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fb" - MAHJONG_TILE_ONE_OF_BAMBOOS = "\U0001f010" - FLAG_FOR_TARIJA_BO_T = "\U0001f3f4\U000e0062\U000e006f\U000e0074\U000e007f" - BLACK_CHESS_ROOK = "\u265c" - FLAG_FOR_PREAH_VIHEAR_KH_13 = "\U0001f3f4\U000e006b\U000e0068\U000e0031\U000e0033\U000e007f" - TRIGRAM_FOR_WATER = "\u2635" - FLAG_FOR_VICTORIA_AU_VIC = "\U0001f3f4\U000e0061\U000e0075\U000e0076\U000e0069\U000e0063\U000e007f" - KISS_MAN_MEDIUM_DARK_SKIN_TONE_MAN_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff" - FLAG_FOR_ANDORRA_LA_VELLA_AD_07 = "\U0001f3f4\U000e0061\U000e0064\U000e0030\U000e0037\U000e007f" - KISS_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE = "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe" - FLAG_FOR_ACRE_BR_AC = "\U0001f3f4\U000e0062\U000e0072\U000e0061\U000e0063\U000e007f" - KISS_MAN_MEDIUM_DARK_SKIN_TONE_MAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc" - FLAG_FOR_URI_CH_UR = "\U0001f3f4\U000e0063\U000e0068\U000e0075\U000e0072\U000e007f" - KISS_MAN_MEDIUM_SKIN_TONE_MAN_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd" - KISS_WOMAN_DARK_SKIN_TONE_MAN_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff" - COUPLE_WITH_HEART_MAN_LIGHT_SKIN_TONE_MAN_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd" - FAMILY_MAN_MEDIUM_LIGHT_SKIN_TONE_BOY_MEDIUM_LIGHT_SKIN_TONE_GIRL_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f466\U0001f3fc\u200d\U0001f467\U0001f3fc" - KISS_WOMAN_DARK_SKIN_TONE_MAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc" - FLAG_FOR_SAN_SALVADOR_SV_SS = "\U0001f3f4\U000e0073\U000e0076\U000e0073\U000e0073\U000e007f" - KISS_MAN_MEDIUM_DARK_SKIN_TONE_MAN_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe" - KISS_WOMAN_MEDIUM_DARK_SKIN_TONE_WOMAN_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3ff" - FLAG_FOR_MADRID_AUTONOMOUS_COMMUNITY_ES_MD = "\U0001f3f4\U000e0065\U000e0073\U000e006d\U000e0064\U000e007f" - FAMILY_WOMAN_MEDIUM_DARK_SKIN_TONE_WOMAN_MEDIUM_DARK_SKIN_TONE_BOY_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f469\U0001f3fe\u200d\U0001f466\U0001f3fe" - FLAG_FOR_COLLINES_BJ_CO = "\U0001f3f4\U000e0062\U000e006a\U000e0063\U000e006f\U000e007f" - KISS_WOMAN_MEDIUM_DARK_SKIN_TONE_WOMAN_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fb" - FLAG_FOR_KAYANZA_BI_KY = "\U0001f3f4\U000e0062\U000e0069\U000e006b\U000e0079\U000e007f" - FLAG_FOR_AOMORI_JP_02 = "\U0001f3f4\U000e006a\U000e0070\U000e0030\U000e0032\U000e007f" - TURNED_OK_HAND_SIGN = "\U0001f58f" - BOOK = "\U0001f56e" - COUPLE_WITH_HEART_MAN_MEDIUM_DARK_SKIN_TONE_MAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc" - COUPLE_WITH_HEART_MAN_MEDIUM_SKIN_TONE_MAN_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd" - KISS_WOMAN_DARK_SKIN_TONE_WOMAN_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fe" - FLAG_FOR_FARO_PT_08 = "\U0001f3f4\U000e0070\U000e0074\U000e0030\U000e0038\U000e007f" - COUPLE_WITH_HEART_MAN_DARK_SKIN_TONE_WOMAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fc" - COUPLE_WITH_HEART_MAN_MEDIUM_SKIN_TONE_MAN_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe" - FALLING_DIAGONAL_IN_WHITE_CIRCLE_IN_BLACK_SQUARE = "\u26de" - FLAG_FOR_FRENCH_SOUTHERN_TERRITORIES_FR_TF = "\U0001f3f4\U000e0066\U000e0072\U000e0074\U000e0066\U000e007f" - COUPLE_WITH_HEART_MAN_MEDIUM_LIGHT_SKIN_TONE_MAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc" - FLAG_FOR_ISTANBUL_TR_34 = "\U0001f3f4\U000e0074\U000e0072\U000e0033\U000e0034\U000e007f" - FAMILY_MAN_MEDIUM_SKIN_TONE_MAN_MEDIUM_SKIN_TONE_BABY_MEDIUM_SKIN_TONE_GIRL_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f468\U0001f3fd\u200d\U0001f476\U0001f3fd\u200d\U0001f467\U0001f3fd" - FLAG_FOR_SUD_OUEST_BF_13 = "\U0001f3f4\U000e0062\U000e0066\U000e0031\U000e0033\U000e007f" - FAMILY_MAN_MEDIUM_LIGHT_SKIN_TONE_WOMAN_MEDIUM_LIGHT_SKIN_TONE_BOY_MEDIUM_LIGHT_SKIN_TONE_BABY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f469\U0001f3fc\u200d\U0001f466\U0001f3fc\u200d\U0001f476\U0001f3fc" - FLAG_FOR_FEDERAL_DEPENDENCIES_VE_W = "\U0001f3f4\U000e0076\U000e0065\U000e0077\U000e007f" - COUPLE_WITH_HEART_MAN_DARK_SKIN_TONE_MAN_LIGHT_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb" - KISS_MAN_MEDIUM_DARK_SKIN_TONE_WOMAN_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fb" - FAMILY_MAN_WOMAN_GIRL_BABY = "\U0001f468\u200d\U0001f469\u200d\U0001f467\u200d\U0001f476" - FLAG_FOR_LONG_ISLAND_BS_LI = "\U0001f3f4\U000e0062\U000e0073\U000e006c\U000e0069\U000e007f" - FLAG_FOR_RANGPUR_DIVISION_BD_F = "\U0001f3f4\U000e0062\U000e0064\U000e0066\U000e007f" - COUPLE_WITH_HEART_MAN_MEDIUM_DARK_SKIN_TONE_MAN_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff" - FLAG_FOR_ALAGOAS_BR_AL = "\U0001f3f4\U000e0062\U000e0072\U000e0061\U000e006c\U000e007f" - COUPLE_WITH_HEART_MAN_DARK_SKIN_TONE_WOMAN_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fe" - KISS_WOMAN_MAN_DARK_SKIN_TONE = "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff" - FLAG_FOR_MAIDAN_WARDAK_AF_WAR = "\U0001f3f4\U000e0061\U000e0066\U000e0077\U000e0061\U000e0072\U000e007f" - COUPLE_WITH_HEART_MAN_DARK_SKIN_TONE_WOMAN_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fd" - COUPLE_WITH_HEART_MAN_MEDIUM_DARK_SKIN_TONE_WOMAN_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3ff" - FAMILY_MAN_LIGHT_SKIN_TONE_WOMAN_LIGHT_SKIN_TONE_BABY_LIGHT_SKIN_TONE_GIRL_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f469\U0001f3fb\u200d\U0001f476\U0001f3fb\u200d\U0001f467\U0001f3fb" - COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc" - FLAG_FOR_JALISCO_MX_JAL = "\U0001f3f4\U000e006d\U000e0078\U000e006a\U000e0061\U000e006c\U000e007f" - FLAG_FOR_LAZIO_IT_62 = "\U0001f3f4\U000e0069\U000e0074\U000e0036\U000e0032\U000e007f" - FLAG_FOR_SERGIPE_BR_SE = "\U0001f3f4\U000e0062\U000e0072\U000e0073\U000e0065\U000e007f" - KISS_MAN_MEDIUM_SKIN_TONE_WOMAN_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fd" - FLAG_FOR_KOUFFO_BJ_KO = "\U0001f3f4\U000e0062\U000e006a\U000e006b\U000e006f\U000e007f" - TAG_LATIN_SMALL_LETTER_L = "\U000e006c" - TAG_LATIN_SMALL_LETTER_J = "\U000e006a" - FLAG_FOR_BAKU_AZ_BA = "\U0001f3f4\U000e0061\U000e007a\U000e0062\U000e0061\U000e007f" - FLAG_FOR_DAUGAVPILS_MUNICIPALITY_LV_025 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0032\U000e0035\U000e007f" - KISS_WOMAN_MEDIUM_SKIN_TONE_WOMAN_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fd" - FLAG_FOR_VINH_LONG_VN_49 = "\U0001f3f4\U000e0076\U000e006e\U000e0034\U000e0039\U000e007f" - COUPLE_WITH_HEART_MAN_MEDIUM_SKIN_TONE_WOMAN_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fb" - COUPLE_WITH_HEART_WOMAN_MEDIUM_DARK_SKIN_TONE_MAN_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff" - FLAG_FOR_STRASENI_MD_ST = "\U0001f3f4\U000e006d\U000e0064\U000e0073\U000e0074\U000e007f" - FLAG_FOR_RORAIMA_BR_RR = "\U0001f3f4\U000e0062\U000e0072\U000e0072\U000e0072\U000e007f" - FAMILY_MAN_LIGHT_SKIN_TONE_GIRL_LIGHT_SKIN_TONE_BOY_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f467\U0001f3fb\u200d\U0001f466\U0001f3fb" - FLAG_FOR_BIMINI_BS_BI = "\U0001f3f4\U000e0062\U000e0073\U000e0062\U000e0069\U000e007f" - COUPLE_WITH_HEART_MAN_DARK_SKIN_TONE_MAN_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd" - FLAG_FOR_NORTHERN_RED_SEA_ER_SK = "\U0001f3f4\U000e0065\U000e0072\U000e0073\U000e006b\U000e007f" - COUPLE_WITH_HEART_WOMAN_MEDIUM_LIGHT_SKIN_TONE_MAN_DARK_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff" - FLAG_FOR_TIARET_DZ_14 = "\U0001f3f4\U000e0064\U000e007a\U000e0031\U000e0034\U000e007f" - FLAG_FOR_SOGN_OG_FJORDANE_NO_14 = "\U0001f3f4\U000e006e\U000e006f\U000e0031\U000e0034\U000e007f" - WHITE_LEFT_LANE_MERGE = "\u26d9" - FAMILY_MAN_MEDIUM_LIGHT_SKIN_TONE_MAN_MEDIUM_LIGHT_SKIN_TONE_GIRL_MEDIUM_LIGHT_SKIN_TONE_GIRL_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f468\U0001f3fc\u200d\U0001f467\U0001f3fc\u200d\U0001f467\U0001f3fc" - FLAG_FOR_KHACHMAZ_AZ_XAC = "\U0001f3f4\U000e0061\U000e007a\U000e0078\U000e0061\U000e0063\U000e007f" - COUPLE_WITH_HEART_MAN_MEDIUM_LIGHT_SKIN_TONE_WOMAN_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fb" - COUPLE_WITH_HEART_WOMAN_MEDIUM_SKIN_TONE_WOMAN_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fd" - FLAG_FOR_SVETI_ANDRAZ_V_SLOVENSKIH_GORICAH_SI_182 = "\U0001f3f4\U000e0073\U000e0069\U000e0031\U000e0038\U000e0032\U000e007f" - FLAG_FOR_BALOCHISTAN_PK_BA = "\U0001f3f4\U000e0070\U000e006b\U000e0062\U000e0061\U000e007f" - FAMILY_WOMAN_LIGHT_SKIN_TONE_BABY_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f476\U0001f3fb" - FLAG_FOR_TOCANTINS_BR_TO = "\U0001f3f4\U000e0062\U000e0072\U000e0074\U000e006f\U000e007f" - FAMILY_WOMAN_MAN_BABY_GIRL = "\U0001f469\u200d\U0001f468\u200d\U0001f476\u200d\U0001f467" - TAG_SEMICOLON = "\U000e003b" - FLAG_FOR_EAST_GRAND_BAHAMA_BS_EG = "\U0001f3f4\U000e0062\U000e0073\U000e0065\U000e0067\U000e007f" - FLAG_FOR_SOUTH_AEGEAN_GR_L = "\U0001f3f4\U000e0067\U000e0072\U000e006c\U000e007f" - WOMAN_IN_BUSINESS_SUIT_LEVITATING = "\U0001f574\ufe0f\u200d\u2640\ufe0f" - FLAG_FOR_MAKAMBA_BI_MA = "\U0001f3f4\U000e0062\U000e0069\U000e006d\U000e0061\U000e007f" - COUPLE_WITH_HEART_WOMAN_MEDIUM_DARK_SKIN_TONE_WOMAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fc" - FLAG_FOR_CROOKED_ISLAND_BS_CK = "\U0001f3f4\U000e0062\U000e0073\U000e0063\U000e006b\U000e007f" - KISS_MAN_MEDIUM_LIGHT_SKIN_TONE_WOMAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fc" - TAG_LATIN_SMALL_LETTER_K = "\U000e006b" - FLAG_FOR_EXUMA_BS_EX = "\U0001f3f4\U000e0062\U000e0073\U000e0065\U000e0078\U000e007f" - COUPLE_WITH_HEART_MAN_DARK_SKIN_TONE_MAN_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe" - FAMILY_MAN_LIGHT_SKIN_TONE_GIRL_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f467\U0001f3fb" - FAMILY_MAN_MEDIUM_LIGHT_SKIN_TONE_GIRL_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f467\U0001f3fc" - FLAG_FOR_PLATEAU_BJ_PL = "\U0001f3f4\U000e0062\U000e006a\U000e0070\U000e006c\U000e007f" - KISS_WOMAN_MEDIUM_DARK_SKIN_TONE_MAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc" - FLAG_FOR_FEDERAL_DISTRICT_BR_DF = "\U0001f3f4\U000e0062\U000e0072\U000e0064\U000e0066\U000e007f" - FLAG_FOR_NEW_SOUTH_WALES_AU_NSW = "\U0001f3f4\U000e0061\U000e0075\U000e006e\U000e0073\U000e0077\U000e007f" - FLAG_FOR_NORTH_ABACO_BS_NO = "\U0001f3f4\U000e0062\U000e0073\U000e006e\U000e006f\U000e007f" - FAMILY_WOMAN_DARK_SKIN_TONE_WOMAN_DARK_SKIN_TONE_GIRL_DARK_SKIN_TONE_GIRL_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f469\U0001f3ff\u200d\U0001f467\U0001f3ff\u200d\U0001f467\U0001f3ff" - FLAG_FOR_JERUSALEM_IL_JM = "\U0001f3f4\U000e0069\U000e006c\U000e006a\U000e006d\U000e007f" - FLAG_FOR_INAGUA_BS_IN = "\U0001f3f4\U000e0062\U000e0073\U000e0069\U000e006e\U000e007f" - FLAG_FOR_BOHINJ_SI_004 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0030\U000e0034\U000e007f" - FLAG_FOR_FREEPORT_BS_FP = "\U0001f3f4\U000e0062\U000e0073\U000e0066\U000e0070\U000e007f" - FAMILY_MAN_MEDIUM_SKIN_TONE_MAN_MEDIUM_SKIN_TONE_BOY_MEDIUM_SKIN_TONE_BABY_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f468\U0001f3fd\u200d\U0001f466\U0001f3fd\u200d\U0001f476\U0001f3fd" - FLAG_FOR_CENTRAL_ELEUTHERA_BS_CE = "\U0001f3f4\U000e0062\U000e0073\U000e0063\U000e0065\U000e007f" - FLAG_FOR_GRAND_CAY_BS_GC = "\U0001f3f4\U000e0062\U000e0073\U000e0067\U000e0063\U000e007f" - TAG_LATIN_SMALL_LETTER_A = "\U000e0061" - FLAG_FOR_MAYAGUANA_BS_MG = "\U0001f3f4\U000e0062\U000e0073\U000e006d\U000e0067\U000e007f" - FLAG_FOR_SOUTH_AUSTRALIA_AU_SA = "\U0001f3f4\U000e0061\U000e0075\U000e0073\U000e0061\U000e007f" - FAMILY_WOMAN_LIGHT_SKIN_TONE_GIRL_LIGHT_SKIN_TONE_BOY_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f467\U0001f3fb\u200d\U0001f466\U0001f3fb" - COUPLE_WITH_HEART_WOMAN_DARK_SKIN_TONE_MAN = "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468" - COUPLE_WITH_HEART_WOMAN_MEDIUM_LIGHT_SKIN_TONE_MAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc" - FLAG_FOR_KAOHSIUNG_TW_KHH = "\U0001f3f4\U000e0074\U000e0077\U000e006b\U000e0068\U000e0068\U000e007f" - WOMAN_IN_BUSINESS_SUIT_LEVITATING_MEDIUM_DARK_SKIN_TONE = "\U0001f574\U0001f3fe\u200d\u2640\ufe0f" - FLAG_FOR_TRANS_NZOIA_KE_42 = "\U0001f3f4\U000e006b\U000e0065\U000e0034\U000e0032\U000e007f" - COUPLE_WITH_HEART_WOMAN_DARK_SKIN_TONE_WOMAN_LIGHT_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fb" - FLAG_FOR_NORTH_ANDROS_BS_NS = "\U0001f3f4\U000e0062\U000e0073\U000e006e\U000e0073\U000e007f" - COUPLE_WITH_HEART_MAN_MEDIUM_DARK_SKIN_TONE_WOMAN_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fd" - FAMILY_WOMAN_MAN_BOY_BABY = "\U0001f469\u200d\U0001f468\u200d\U0001f466\u200d\U0001f476" - KISS_MAN_MEDIUM_LIGHT_SKIN_TONE_WOMAN_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fd" - FAMILY_WOMAN_MEDIUM_SKIN_TONE_GIRL_MEDIUM_SKIN_TONE_BABY_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f467\U0001f3fd\u200d\U0001f476\U0001f3fd" - FAMILY_MAN_MEDIUM_SKIN_TONE_BOY_MEDIUM_SKIN_TONE_BABY_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f466\U0001f3fd\u200d\U0001f476\U0001f3fd" - FLAG_FOR_OREGON_US_OR = "\U0001f3f4\U000e0075\U000e0073\U000e006f\U000e0072\U000e007f" - KISS_MAN_LIGHT_SKIN_TONE_MAN = "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468" - FAMILY_MAN_BOY_BABY = "\U0001f468\u200d\U0001f466\u200d\U0001f476" - FAMILY_WOMAN_LIGHT_SKIN_TONE_BOY_LIGHT_SKIN_TONE_GIRL_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f466\U0001f3fb\u200d\U0001f467\U0001f3fb" - KISS_MAN_MEDIUM_LIGHT_SKIN_TONE_WOMAN_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fe" - FLAG_FOR_OCCITANIE_FR_OCC = "\U0001f3f4\U000e0066\U000e0072\U000e006f\U000e0063\U000e0063\U000e007f" - FLAG_FOR_EAST_AZERBAIJAN_IR_01 = "\U0001f3f4\U000e0069\U000e0072\U000e0030\U000e0031\U000e007f" - FLAG_FOR_SOUTH_ANDROS_BS_SA = "\U0001f3f4\U000e0062\U000e0073\U000e0073\U000e0061\U000e007f" - FLAG_FOR_AMAZONAS_BR_AM = "\U0001f3f4\U000e0062\U000e0072\U000e0061\U000e006d\U000e007f" - FLAG_FOR_TANA_RIVER_KE_40 = "\U0001f3f4\U000e006b\U000e0065\U000e0034\U000e0030\U000e007f" - KISS_MAN_WOMAN_MEDIUM_DARK_SKIN_TONE = "\U0001f468\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fe" - GEAR_WITH_HANDLES = "\u26ee" - FLAG_FOR_SOUTH_ELEUTHERA_BS_SE = "\U0001f3f4\U000e0062\U000e0073\U000e0073\U000e0065\U000e007f" - COUPLE_WITH_HEART_WOMAN_MEDIUM_SKIN_TONE_WOMAN = "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f469" - KISS_WOMAN_MEDIUM_LIGHT_SKIN_TONE_MAN_DARK_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff" - KISS_MAN_MEDIUM_DARK_SKIN_TONE_MAN_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb" - KISS_MAN_LIGHT_SKIN_TONE_MAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc" - KISS_MAN_LIGHT_SKIN_TONE_WOMAN_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fb" - KISS_WOMAN_LIGHT_SKIN_TONE_MAN = "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468" - COUPLE_WITH_HEART_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc" - FAMILY_WOMAN_MEDIUM_DARK_SKIN_TONE_MAN_MEDIUM_DARK_SKIN_TONE_GIRL_MEDIUM_DARK_SKIN_TONE_GIRL_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f468\U0001f3fe\u200d\U0001f467\U0001f3fe\u200d\U0001f467\U0001f3fe" - KISS_WOMAN_MEDIUM_DARK_SKIN_TONE_MAN_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb" - COUPLE_WITH_HEART_WOMAN_MEDIUM_SKIN_TONE_MAN = "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468" - KISS_WOMAN_MEDIUM_SKIN_TONE_MAN_DARK_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff" - FLAG_FOR_BUDAPEST_HU_BU = "\U0001f3f4\U000e0068\U000e0075\U000e0062\U000e0075\U000e007f" - KISS_MAN_DARK_SKIN_TONE_MAN_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff" - KISS_MAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fc" - SIDEWAYS_BLACK_RIGHT_POINTING_INDEX = "\U0001f59b" - KISS_WOMAN_MEDIUM_DARK_SKIN_TONE_MAN_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff" - KISS_WOMAN_LIGHT_SKIN_TONE_MAN_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe" - KISS_WOMAN_DARK_SKIN_TONE_WOMAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fc" - FAMILY_MAN_MEDIUM_LIGHT_SKIN_TONE_BOY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f466\U0001f3fc" - FLAG_FOR_SOUTH_ABACO_BS_SO = "\U0001f3f4\U000e0062\U000e0073\U000e0073\U000e006f\U000e007f" - KISS_WOMAN_LIGHT_SKIN_TONE_MAN_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb" - COUPLE_WITH_HEART_MAN_DARK_SKIN_TONE_MAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc" - FLAG_FOR_RUM_CAY_BS_RC = "\U0001f3f4\U000e0062\U000e0073\U000e0072\U000e0063\U000e007f" - TAG_SOLIDUS = "\U000e002f" - COUPLE_WITH_HEART_MAN_MEDIUM_LIGHT_SKIN_TONE_WOMAN_DARK_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3ff" - FAMILY_MAN_MEDIUM_SKIN_TONE_BOY_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f466\U0001f3fd" - COUPLE_WITH_HEART_MAN_MEDIUM_SKIN_TONE_MAN_DARK_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff" - FAMILY_MAN_DARK_SKIN_TONE_WOMAN_DARK_SKIN_TONE_BABY_DARK_SKIN_TONE_GIRL_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f469\U0001f3ff\u200d\U0001f476\U0001f3ff\u200d\U0001f467\U0001f3ff" - FAMILY_MAN_DARK_SKIN_TONE_BOY_DARK_SKIN_TONE_BABY_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f466\U0001f3ff\u200d\U0001f476\U0001f3ff" - COUPLE_WITH_HEART_MAN_MEDIUM_LIGHT_SKIN_TONE_MAN_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe" - FAMILY_WOMAN_BOY_GIRL = "\U0001f469\u200d\U0001f466\u200d\U0001f467" - COUPLE_WITH_HEART_MAN_LIGHT_SKIN_TONE_WOMAN_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fe" - FLAG_FOR_ANSE_ROYALE_SC_05 = "\U0001f3f4\U000e0073\U000e0063\U000e0030\U000e0035\U000e007f" - FLAG_FOR_DELHI_IN_DL = "\U0001f3f4\U000e0069\U000e006e\U000e0064\U000e006c\U000e007f" - FAMILY_WOMAN_MEDIUM_LIGHT_SKIN_TONE_MAN_MEDIUM_LIGHT_SKIN_TONE_BABY_MEDIUM_LIGHT_SKIN_TONE_BABY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f468\U0001f3fc\u200d\U0001f476\U0001f3fc\u200d\U0001f476\U0001f3fc" - FAMILY_MAN_LIGHT_SKIN_TONE_GIRL_LIGHT_SKIN_TONE_BABY_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f467\U0001f3fb\u200d\U0001f476\U0001f3fb" - KISS_WOMAN_MEDIUM_DARK_SKIN_TONE_WOMAN_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fd" - FLAG_FOR_THIMPHU_BT_15 = "\U0001f3f4\U000e0062\U000e0074\U000e0031\U000e0035\U000e007f" - FLAG_FOR_CIEGO_DE_AVILA_CU_08 = "\U0001f3f4\U000e0063\U000e0075\U000e0030\U000e0038\U000e007f" - FLAG_FOR_PARO_BT_11 = "\U0001f3f4\U000e0062\U000e0074\U000e0031\U000e0031\U000e007f" - KISS_WOMAN_MEDIUM_SKIN_TONE_WOMAN_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fe" - FLAG_FOR_HAA_BT_13 = "\U0001f3f4\U000e0062\U000e0074\U000e0031\U000e0033\U000e007f" - FLAG_FOR_MADHYA_PRADESH_IN_MP = "\U0001f3f4\U000e0069\U000e006e\U000e006d\U000e0070\U000e007f" - KISS_MAN_LIGHT_SKIN_TONE_MAN_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd" - FAMILY_WOMAN_LIGHT_SKIN_TONE_BOY_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f466\U0001f3fb" - FAMILY_MAN_MEDIUM_DARK_SKIN_TONE_WOMAN_MEDIUM_DARK_SKIN_TONE_BABY_MEDIUM_DARK_SKIN_TONE_BOY_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f469\U0001f3fe\u200d\U0001f476\U0001f3fe\u200d\U0001f466\U0001f3fe" - FLAG_FOR_WEST_GRAND_BAHAMA_BS_WG = "\U0001f3f4\U000e0062\U000e0073\U000e0077\U000e0067\U000e007f" - FLAG_FOR_PYONGYANG_KP_01 = "\U0001f3f4\U000e006b\U000e0070\U000e0030\U000e0031\U000e007f" - FAMILY_WOMAN_MEDIUM_SKIN_TONE_WOMAN_MEDIUM_SKIN_TONE_BOY_MEDIUM_SKIN_TONE_GIRL_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f469\U0001f3fd\u200d\U0001f466\U0001f3fd\u200d\U0001f467\U0001f3fd" - FLAG_FOR_DAGANA_BT_22 = "\U0001f3f4\U000e0062\U000e0074\U000e0032\U000e0032\U000e007f" - FLAG_FOR_ZHEMGANG_BT_34 = "\U0001f3f4\U000e0062\U000e0074\U000e0033\U000e0034\U000e007f" - FLAG_FOR_SARPANG_BT_31 = "\U0001f3f4\U000e0062\U000e0074\U000e0033\U000e0031\U000e007f" - KISS_WOMAN_MEDIUM_SKIN_TONE_MAN_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd" - FLAG_FOR_KUNDUZ_AF_KDZ = "\U0001f3f4\U000e0061\U000e0066\U000e006b\U000e0064\U000e007a\U000e007f" - FLAG_FOR_TRASHIGANG_BT_41 = "\U0001f3f4\U000e0062\U000e0074\U000e0034\U000e0031\U000e007f" - COUPLE_WITH_HEART_WOMAN_DARK_SKIN_TONE_MAN_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd" - FLAG_FOR_TRONGSA_BT_32 = "\U0001f3f4\U000e0062\U000e0074\U000e0033\U000e0032\U000e007f" - FLAG_FOR_BUMTHANG_BT_33 = "\U0001f3f4\U000e0062\U000e0074\U000e0033\U000e0033\U000e007f" - FLAG_FOR_MONGAR_BT_42 = "\U0001f3f4\U000e0062\U000e0074\U000e0034\U000e0032\U000e007f" - FAMILY_MAN_DARK_SKIN_TONE_MAN_DARK_SKIN_TONE_BOY_DARK_SKIN_TONE_BOY_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f468\U0001f3ff\u200d\U0001f466\U0001f3ff\u200d\U0001f466\U0001f3ff" - KISS_WOMAN_LIGHT_SKIN_TONE_MAN_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd" - FLAG_FOR_PEMAGATSHEL_BT_43 = "\U0001f3f4\U000e0062\U000e0074\U000e0034\U000e0033\U000e007f" - FLAG_FOR_ACKLINS_BS_AK = "\U0001f3f4\U000e0062\U000e0073\U000e0061\U000e006b\U000e007f" - FLAG_FOR_CHOBE_BW_CH = "\U0001f3f4\U000e0062\U000e0077\U000e0063\U000e0068\U000e007f" - FLAG_FOR_SALTA_AR_A = "\U0001f3f4\U000e0061\U000e0072\U000e0061\U000e007f" - FLAG_FOR_LHUNTSE_BT_44 = "\U0001f3f4\U000e0062\U000e0074\U000e0034\U000e0034\U000e007f" - FLAG_FOR_TRASHIYANGTSE_BT_TY = "\U0001f3f4\U000e0062\U000e0074\U000e0074\U000e0079\U000e007f" - COUPLE_WITH_HEART_MAN_WOMAN_MEDIUM_DARK_SKIN_TONE = "\U0001f468\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fe" - FLAG_FOR_NEW_MEXICO_US_NM = "\U0001f3f4\U000e0075\U000e0073\U000e006e\U000e006d\U000e007f" - FAMILY_MAN_MAN_BABY = "\U0001f468\u200d\U0001f468\u200d\U0001f476" - LIGHTNING_MOOD_BUBBLE = "\U0001f5f1" - FAMILY_WOMAN_MEDIUM_LIGHT_SKIN_TONE_WOMAN_MEDIUM_LIGHT_SKIN_TONE_BABY_MEDIUM_LIGHT_SKIN_TONE_GIRL_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f469\U0001f3fc\u200d\U0001f476\U0001f3fc\u200d\U0001f467\U0001f3fc" - FLAG_FOR_JWANENG_BW_JW = "\U0001f3f4\U000e0062\U000e0077\U000e006a\U000e0077\U000e007f" - FAMILY_MAN_MEDIUM_SKIN_TONE_GIRL_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f467\U0001f3fd" - KISS_MAN_MEDIUM_LIGHT_SKIN_TONE_WOMAN_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fb" - COUPLE_WITH_HEART_MAN_MEDIUM_SKIN_TONE_MAN = "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468" - FLAG_FOR_SOUTH_EAST_BW_SE = "\U0001f3f4\U000e0062\U000e0077\U000e0073\U000e0065\U000e007f" - LEFT_HANDED_INTERLACED_PENTAGRAM = "\u26e6" - FLAG_FOR_ATLANTIQUE_BJ_AQ = "\U0001f3f4\U000e0062\U000e006a\U000e0061\U000e0071\U000e007f" - FLAG_FOR_CENTRAL_BW_CE = "\U0001f3f4\U000e0062\U000e0077\U000e0063\U000e0065\U000e007f" - FLAG_FOR_WANGDUE_PHODRANG_BT_24 = "\U0001f3f4\U000e0062\U000e0074\U000e0032\U000e0034\U000e007f" - FLAG_FOR_KGATLENG_BW_KL = "\U0001f3f4\U000e0062\U000e0077\U000e006b\U000e006c\U000e007f" - FLAG_FOR_RONDONIA_BR_RO = "\U0001f3f4\U000e0062\U000e0072\U000e0072\U000e006f\U000e007f" - FLAG_FOR_CENTRAL_FINLAND_FI_08 = "\U0001f3f4\U000e0066\U000e0069\U000e0030\U000e0038\U000e007f" - KISS_MAN_MEDIUM_DARK_SKIN_TONE_WOMAN = "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469" - FLAG_FOR_RUTANA_BI_RT = "\U0001f3f4\U000e0062\U000e0069\U000e0072\U000e0074\U000e007f" - FLAG_FOR_GHANZI_BW_GH = "\U0001f3f4\U000e0062\U000e0077\U000e0067\U000e0068\U000e007f" - FLAG_FOR_KGALAGADI_BW_KG = "\U0001f3f4\U000e0062\U000e0077\U000e006b\U000e0067\U000e007f" - FLAG_FOR_NORTH_EAST_BW_NE = "\U0001f3f4\U000e0062\U000e0077\U000e006e\U000e0065\U000e007f" - FLAG_FOR_TISINA_SI_010 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0031\U000e0030\U000e007f" - FLAG_FOR_KWENENG_BW_KW = "\U0001f3f4\U000e0062\U000e0077\U000e006b\U000e0077\U000e007f" - COUPLE_WITH_HEART_WOMAN_MAN_LIGHT_SKIN_TONE = "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb" - FLAG_FOR_SAMDRUP_JONGKHAR_BT_45 = "\U0001f3f4\U000e0062\U000e0074\U000e0034\U000e0035\U000e007f" - FLAG_FOR_SAMTSE_BT_14 = "\U0001f3f4\U000e0062\U000e0074\U000e0031\U000e0034\U000e007f" - FLAG_FOR_JIHOCESKY_KRAJ_CZ_31 = "\U0001f3f4\U000e0063\U000e007a\U000e0033\U000e0031\U000e007f" - FLAG_FOR_CHRIST_CHURCH_BB_01 = "\U0001f3f4\U000e0062\U000e0062\U000e0030\U000e0031\U000e007f" - FLAG_FOR_SELIBE_PHIKWE_BW_SP = "\U0001f3f4\U000e0062\U000e0077\U000e0073\U000e0070\U000e007f" - KISS_MAN_MEDIUM_SKIN_TONE_MAN_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe" - COUPLE_WITH_HEART_MAN_LIGHT_SKIN_TONE_MAN_DARK_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff" - FAMILY_MAN_DARK_SKIN_TONE_MAN_DARK_SKIN_TONE_BABY_DARK_SKIN_TONE_BOY_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f468\U0001f3ff\u200d\U0001f476\U0001f3ff\u200d\U0001f466\U0001f3ff" - FAMILY_MAN_LIGHT_SKIN_TONE_WOMAN_LIGHT_SKIN_TONE_GIRL_LIGHT_SKIN_TONE_BABY_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f469\U0001f3fb\u200d\U0001f467\U0001f3fb\u200d\U0001f476\U0001f3fb" - FLAG_FOR_NORTH_WEST_BW_NW = "\U0001f3f4\U000e0062\U000e0077\U000e006e\U000e0077\U000e007f" - COUPLE_WITH_HEART_MAN_MEDIUM_LIGHT_SKIN_TONE_MAN = "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468" - FLAG_FOR_SOWA_TOWN_BW_ST = "\U0001f3f4\U000e0062\U000e0077\U000e0073\U000e0074\U000e007f" - FAMILY_WOMAN_MAN_BABY_BABY = "\U0001f469\u200d\U0001f468\u200d\U0001f476\u200d\U0001f476" - WHITE_CHESS_KING = "\u2654" - FLAG_FOR_ORURO_BO_O = "\U0001f3f4\U000e0062\U000e006f\U000e006f\U000e007f" - FLAG_FOR_PHOENIX_ISLANDS_KI_P = "\U0001f3f4\U000e006b\U000e0069\U000e0070\U000e007f" - FLAG_FOR_PIAUI_BR_PI = "\U0001f3f4\U000e0062\U000e0072\U000e0070\U000e0069\U000e007f" - FLAG_FOR_HRODNA_BY_HR = "\U0001f3f4\U000e0062\U000e0079\U000e0068\U000e0072\U000e007f" - FLAG_FOR_HOMEL_BY_HO = "\U0001f3f4\U000e0062\U000e0079\U000e0068\U000e006f\U000e007f" - FAMILY_WOMAN_DARK_SKIN_TONE_WOMAN_DARK_SKIN_TONE_BOY_DARK_SKIN_TONE_BOY_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f469\U0001f3ff\u200d\U0001f466\U0001f3ff\u200d\U0001f466\U0001f3ff" - FLAG_FOR_MINSK_BY_HM = "\U0001f3f4\U000e0062\U000e0079\U000e0068\U000e006d\U000e007f" - KISS_MAN_MEDIUM_LIGHT_SKIN_TONE_MAN_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe" - FLAG_FOR_CHITTAGONG_DIVISION_BD_B = "\U0001f3f4\U000e0062\U000e0064\U000e0062\U000e007f" - MAP_SYMBOL_FOR_LIGHTHOUSE = "\u26ef" - FAMILY_WOMAN_DARK_SKIN_TONE_MAN_DARK_SKIN_TONE_BOY_DARK_SKIN_TONE_BABY_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f468\U0001f3ff\u200d\U0001f466\U0001f3ff\u200d\U0001f476\U0001f3ff" - FLAG_FOR_MOORE_S_ISLAND_BS_MI = "\U0001f3f4\U000e0062\U000e0073\U000e006d\U000e0069\U000e007f" - FLAG_FOR_MINSK_REGION_BY_MI = "\U0001f3f4\U000e0062\U000e0079\U000e006d\U000e0069\U000e007f" - COUPLE_WITH_HEART_MAN_LIGHT_SKIN_TONE_WOMAN = "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f469" - FAMILY_MAN_MEDIUM_DARK_SKIN_TONE_WOMAN_MEDIUM_DARK_SKIN_TONE_GIRL_MEDIUM_DARK_SKIN_TONE_BOY_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f469\U0001f3fe\u200d\U0001f467\U0001f3fe\u200d\U0001f466\U0001f3fe" - FLAG_FOR_GUARDA_PT_09 = "\U0001f3f4\U000e0070\U000e0074\U000e0030\U000e0039\U000e007f" - KISS_WOMAN_MEDIUM_LIGHT_SKIN_TONE_WOMAN = "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469" - FAMILY_MAN_LIGHT_SKIN_TONE_WOMAN_LIGHT_SKIN_TONE_BOY_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f469\U0001f3fb\u200d\U0001f466\U0001f3fb" - FAMILY_WOMAN_BABY_GIRL = "\U0001f469\u200d\U0001f476\u200d\U0001f467" - FAMILY_MAN_LIGHT_SKIN_TONE_WOMAN_LIGHT_SKIN_TONE_BOY_LIGHT_SKIN_TONE_BOY_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f469\U0001f3fb\u200d\U0001f466\U0001f3fb\u200d\U0001f466\U0001f3fb" - FLAG_FOR_VITEBSK_BY_VI = "\U0001f3f4\U000e0062\U000e0079\U000e0076\U000e0069\U000e007f" - FLAG_FOR_CASTILE_LA_MANCHA_ES_CM = "\U0001f3f4\U000e0065\U000e0073\U000e0063\U000e006d\U000e007f" - FAMILY_MAN_MEDIUM_DARK_SKIN_TONE_BABY_MEDIUM_DARK_SKIN_TONE_BOY_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f476\U0001f3fe\u200d\U0001f466\U0001f3fe" - FLAG_FOR_ALMATY_KZ_ALA = "\U0001f3f4\U000e006b\U000e007a\U000e0061\U000e006c\U000e0061\U000e007f" - FLAG_FOR_METRO_MANILA_PH_00 = "\U0001f3f4\U000e0070\U000e0068\U000e0030\U000e0030\U000e007f" - FLAG_FOR_MAGILEU_BY_MA = "\U0001f3f4\U000e0062\U000e0079\U000e006d\U000e0061\U000e007f" - FAMILY_WOMAN_LIGHT_SKIN_TONE_WOMAN_LIGHT_SKIN_TONE_BABY_LIGHT_SKIN_TONE_BABY_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f469\U0001f3fb\u200d\U0001f476\U0001f3fb\u200d\U0001f476\U0001f3fb" - COUPLE_WITH_HEART_MAN_DARK_SKIN_TONE_WOMAN = "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f469" - FLAG_FOR_SILISTRA_BG_19 = "\U0001f3f4\U000e0062\U000e0067\U000e0031\U000e0039\U000e007f" - FAMILY_MAN_DARK_SKIN_TONE_BOY_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f466\U0001f3ff" - FLAG_FOR_STANN_CREEK_BZ_SC = "\U0001f3f4\U000e0062\U000e007a\U000e0073\U000e0063\U000e007f" - FLAG_FOR_NORTH_ELEUTHERA_BS_NE = "\U0001f3f4\U000e0062\U000e0073\U000e006e\U000e0065\U000e007f" - FLAG_FOR_NEW_BRUNSWICK_CA_NB = "\U0001f3f4\U000e0063\U000e0061\U000e006e\U000e0062\U000e007f" - FAMILY_MAN_GIRL_BABY = "\U0001f468\u200d\U0001f467\u200d\U0001f476" - COUPLE_WITH_HEART_WOMAN_LIGHT_SKIN_TONE_MAN = "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468" - COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_SKIN_TONE = "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fd" - COUPLE_WITH_HEART_WOMAN_LIGHT_SKIN_TONE_WOMAN_DARK_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3ff" - FLAG_FOR_TOLEDO_BZ_TOL = "\U0001f3f4\U000e0062\U000e007a\U000e0074\U000e006f\U000e006c\U000e007f" - KISS_WOMAN_MEDIUM_SKIN_TONE_WOMAN = "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469" - KISS_MAN_MEDIUM_LIGHT_SKIN_TONE_WOMAN_DARK_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3ff" - FAMILY_MAN_MAN_BABY_BABY = "\U0001f468\u200d\U0001f468\u200d\U0001f476\u200d\U0001f476" - FLAG_FOR_TAMAULIPAS_MX_TAM = "\U0001f3f4\U000e006d\U000e0078\U000e0074\U000e0061\U000e006d\U000e007f" - FLAG_FOR_TRISTAN_DA_CUNHA_SH_TA = "\U0001f3f4\U000e0073\U000e0068\U000e0074\U000e0061\U000e007f" - FAMILY_MAN_WOMAN_BABY_BABY = "\U0001f468\u200d\U0001f469\u200d\U0001f476\u200d\U0001f476" - FLAG_FOR_SAN_LUIS_POTOSI_MX_SLP = "\U0001f3f4\U000e006d\U000e0078\U000e0073\U000e006c\U000e0070\U000e007f" - FLAG_FOR_VIENTIANE_PROVINCE_LA_VI = "\U0001f3f4\U000e006c\U000e0061\U000e0076\U000e0069\U000e007f" - FAMILY_WOMAN_MEDIUM_DARK_SKIN_TONE_BOY_MEDIUM_DARK_SKIN_TONE_BABY_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f466\U0001f3fe\u200d\U0001f476\U0001f3fe" - FLAG_FOR_SOUTHERN_BW_SO = "\U0001f3f4\U000e0062\U000e0077\U000e0073\U000e006f\U000e007f" - FLAG_FOR_MAMBERE_KADEI_CF_HS = "\U0001f3f4\U000e0063\U000e0066\U000e0068\U000e0073\U000e007f" - FLAG_FOR_ITURI_CD_IT = "\U0001f3f4\U000e0063\U000e0064\U000e0069\U000e0074\U000e007f" - NOTCHED_LEFT_SEMICIRCLE_WITH_THREE_DOTS = "\U0001f543" - FLAG_FOR_MAI_NDOMBE_CD_MN = "\U0001f3f4\U000e0063\U000e0064\U000e006d\U000e006e\U000e007f" - FLAG_FOR_SOUTH_KIVU_CD_SK = "\U0001f3f4\U000e0063\U000e0064\U000e0073\U000e006b\U000e007f" - FLAG_FOR_KASAI_CENTRAL_CD_KC = "\U0001f3f4\U000e0063\U000e0064\U000e006b\U000e0063\U000e007f" - FLAG_FOR_HAUT_UELE_CD_HU = "\U0001f3f4\U000e0063\U000e0064\U000e0068\U000e0075\U000e007f" - FLAG_FOR_KWANGO_CD_KG = "\U0001f3f4\U000e0063\U000e0064\U000e006b\U000e0067\U000e007f" - FLAG_FOR_SWIETOKRZYSKIE_PL_SK = "\U0001f3f4\U000e0070\U000e006c\U000e0073\U000e006b\U000e007f" - FLAG_FOR_KASAI_CD_KS = "\U0001f3f4\U000e0063\U000e0064\U000e006b\U000e0073\U000e007f" - FLAG_FOR_RAGGED_ISLAND_BS_RI = "\U0001f3f4\U000e0062\U000e0073\U000e0072\U000e0069\U000e007f" - FAMILY_MAN_MEDIUM_SKIN_TONE_MAN_MEDIUM_SKIN_TONE_BOY_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f468\U0001f3fd\u200d\U0001f466\U0001f3fd" - FLAG_FOR_MANIEMA_CD_MA = "\U0001f3f4\U000e0063\U000e0064\U000e006d\U000e0061\U000e007f" - FLAG_FOR_SANKURU_CD_SA = "\U0001f3f4\U000e0063\U000e0064\U000e0073\U000e0061\U000e007f" - FLAG_FOR_MONTANA_BG_12 = "\U0001f3f4\U000e0062\U000e0067\U000e0031\U000e0032\U000e007f" - FLAG_FOR_OUHAM_CF_AC = "\U0001f3f4\U000e0063\U000e0066\U000e0061\U000e0063\U000e007f" - FLAG_FOR_HAUT_LOMAMI_CD_HL = "\U0001f3f4\U000e0063\U000e0064\U000e0068\U000e006c\U000e007f" - FLAG_FOR_BAMINGUI_BANGORAN_CF_BB = "\U0001f3f4\U000e0063\U000e0066\U000e0062\U000e0062\U000e007f" - FLAG_FOR_BUBANZA_BI_BB = "\U0001f3f4\U000e0062\U000e0069\U000e0062\U000e0062\U000e007f" - FLAG_FOR_KINSHASA_CD_KN = "\U0001f3f4\U000e0063\U000e0064\U000e006b\U000e006e\U000e007f" - FLAG_FOR_KASAI_ORIENTAL_CD_KE = "\U0001f3f4\U000e0063\U000e0064\U000e006b\U000e0065\U000e007f" - FAMILY_WOMAN_MEDIUM_LIGHT_SKIN_TONE_WOMAN_MEDIUM_LIGHT_SKIN_TONE_BABY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f469\U0001f3fc\u200d\U0001f476\U0001f3fc" - FLAG_FOR_MONGALA_CD_MO = "\U0001f3f4\U000e0063\U000e0064\U000e006d\U000e006f\U000e007f" - FLAG_FOR_TSHUAPA_CD_TU = "\U0001f3f4\U000e0063\U000e0064\U000e0074\U000e0075\U000e007f" - FLAG_FOR_TSHOPO_CD_TO = "\U0001f3f4\U000e0063\U000e0064\U000e0074\U000e006f\U000e007f" - FLAG_FOR_LUCERNE_CH_LU = "\U0001f3f4\U000e0063\U000e0068\U000e006c\U000e0075\U000e007f" - FLAG_FOR_RIO_GRANDE_DO_NORTE_BR_RN = "\U0001f3f4\U000e0062\U000e0072\U000e0072\U000e006e\U000e007f" - WOMAN_WITH_HEADSCARF_LIGHT_SKIN_TONE = "\U0001f9d5\U0001f3fb\u200d\u2640\ufe0f" - FAMILY_MAN_DARK_SKIN_TONE_GIRL_DARK_SKIN_TONE_BABY_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f467\U0001f3ff\u200d\U0001f476\U0001f3ff" - FLAG_FOR_LOBAYE_CF_LB = "\U0001f3f4\U000e0063\U000e0066\U000e006c\U000e0062\U000e007f" - FAMILY_MAN_MEDIUM_DARK_SKIN_TONE_MAN_MEDIUM_DARK_SKIN_TONE_BOY_MEDIUM_DARK_SKIN_TONE_BOY_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f468\U0001f3fe\u200d\U0001f466\U0001f3fe\u200d\U0001f466\U0001f3fe" - FLAG_FOR_LOMAMI_CD_LO = "\U0001f3f4\U000e0063\U000e0064\U000e006c\U000e006f\U000e007f" - FLAG_FOR_KWILU_CD_KL = "\U0001f3f4\U000e0063\U000e0064\U000e006b\U000e006c\U000e007f" - FLAG_FOR_LOBATSE_BW_LO = "\U0001f3f4\U000e0062\U000e0077\U000e006c\U000e006f\U000e007f" - FLAG_FOR_SANGHA_CG_13 = "\U0001f3f4\U000e0063\U000e0067\U000e0031\U000e0033\U000e007f" - FLAG_FOR_KOUILOU_CG_5 = "\U0001f3f4\U000e0063\U000e0067\U000e0035\U000e007f" - FLAG_FOR_LIKOUALA_CG_7 = "\U0001f3f4\U000e0063\U000e0067\U000e0037\U000e007f" - FLAG_FOR_BANGUI_CF_BGF = "\U0001f3f4\U000e0063\U000e0066\U000e0062\U000e0067\U000e0066\U000e007f" - FLAG_FOR_SANGHA_MBAERE_CF_SE = "\U0001f3f4\U000e0063\U000e0066\U000e0073\U000e0065\U000e007f" - FLAG_FOR_NIDWALDEN_CH_NW = "\U0001f3f4\U000e0063\U000e0068\U000e006e\U000e0077\U000e007f" - FLAG_FOR_ST_GALLEN_CH_SG = "\U0001f3f4\U000e0063\U000e0068\U000e0073\U000e0067\U000e007f" - FLAG_FOR_LEKOUMOU_CG_2 = "\U0001f3f4\U000e0063\U000e0067\U000e0032\U000e007f" - FLAG_FOR_MONO_BJ_MO = "\U0001f3f4\U000e0062\U000e006a\U000e006d\U000e006f\U000e007f" - FLAG_FOR_APPENZELL_INNERRHODEN_CH_AI = "\U0001f3f4\U000e0063\U000e0068\U000e0061\U000e0069\U000e007f" - FLAG_FOR_AARGAU_CH_AG = "\U0001f3f4\U000e0063\U000e0068\U000e0061\U000e0067\U000e007f" - FLAG_FOR_CUVETTE_OUEST_CG_15 = "\U0001f3f4\U000e0063\U000e0067\U000e0031\U000e0035\U000e007f" - FLAG_FOR_BRAZZAVILLE_CG_BZV = "\U0001f3f4\U000e0063\U000e0067\U000e0062\U000e007a\U000e0076\U000e007f" - FLAG_FOR_CUVETTE_CG_8 = "\U0001f3f4\U000e0063\U000e0067\U000e0038\U000e007f" - FLAG_FOR_LOS_RIOS_CL_LR = "\U0001f3f4\U000e0063\U000e006c\U000e006c\U000e0072\U000e007f" - FLAG_FOR_MONTAGNES_CI_MG = "\U0001f3f4\U000e0063\U000e0069\U000e006d\U000e0067\U000e007f" - FLAG_FOR_BAS_SASSANDRA_CI_BS = "\U0001f3f4\U000e0063\U000e0069\U000e0062\U000e0073\U000e007f" - FLAG_FOR_BASEL_STADT_CH_BS = "\U0001f3f4\U000e0063\U000e0068\U000e0062\U000e0073\U000e007f" - FAMILY_MAN_DARK_SKIN_TONE_GIRL_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f467\U0001f3ff" - FLAG_FOR_LITTORAL_CM_LT = "\U0001f3f4\U000e0063\U000e006d\U000e006c\U000e0074\U000e007f" - FLAG_FOR_NORTHWEST_CM_NW = "\U0001f3f4\U000e0063\U000e006d\U000e006e\U000e0077\U000e007f" - FAMILY_WOMAN_MEDIUM_DARK_SKIN_TONE_BOY_MEDIUM_DARK_SKIN_TONE_GIRL_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f466\U0001f3fe\u200d\U0001f467\U0001f3fe" - FLAG_FOR_FRIBOURG_CH_FR = "\U0001f3f4\U000e0063\U000e0068\U000e0066\U000e0072\U000e007f" - FLAG_FOR_ATACAMA_CL_AT = "\U0001f3f4\U000e0063\U000e006c\U000e0061\U000e0074\U000e007f" - FLAG_FOR_SAVANES_CI_SV = "\U0001f3f4\U000e0063\U000e0069\U000e0073\U000e0076\U000e007f" - FLAG_FOR_COQUIMBO_CL_CO = "\U0001f3f4\U000e0063\U000e006c\U000e0063\U000e006f\U000e007f" - FLAG_FOR_ADAMAWA_CM_AD = "\U0001f3f4\U000e0063\U000e006d\U000e0061\U000e0064\U000e007f" - FLAG_FOR_AL_MADINAH_SA_03 = "\U0001f3f4\U000e0073\U000e0061\U000e0030\U000e0033\U000e007f" - FLAG_FOR_SOUTHWEST_CM_SW = "\U0001f3f4\U000e0063\U000e006d\U000e0073\U000e0077\U000e007f" - FLAG_FOR_NORTH_CM_NO = "\U0001f3f4\U000e0063\U000e006d\U000e006e\U000e006f\U000e007f" - COUPLE_WITH_HEART_MAN_WOMAN = "\U0001f468\u200d\u2764\ufe0f\u200d\U0001f469" - FLAG_FOR_DENGUELE_CI_DN = "\U0001f3f4\U000e0063\U000e0069\U000e0064\U000e006e\U000e007f" - FLAG_FOR_MAGALLANES_REGION_CL_MA = "\U0001f3f4\U000e0063\U000e006c\U000e006d\U000e0061\U000e007f" - FLAG_FOR_HAUT_KATANGA_CD_HK = "\U0001f3f4\U000e0063\U000e0064\U000e0068\U000e006b\U000e007f" - FLAG_FOR_GOH_DJIBOUA_CI_GD = "\U0001f3f4\U000e0063\U000e0069\U000e0067\U000e0064\U000e007f" - FAMILY_MAN_MEDIUM_DARK_SKIN_TONE_GIRL_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f467\U0001f3fe" - FLAG_FOR_WOROBA_CI_WR = "\U0001f3f4\U000e0063\U000e0069\U000e0077\U000e0072\U000e007f" - FLAG_FOR_COMOE_CI_CM = "\U0001f3f4\U000e0063\U000e0069\U000e0063\U000e006d\U000e007f" - FAMILY_MAN_MEDIUM_LIGHT_SKIN_TONE_BABY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f476\U0001f3fc" - FLAG_FOR_YAMOUSSOUKRO_CI_YM = "\U0001f3f4\U000e0063\U000e0069\U000e0079\U000e006d\U000e007f" - FLAG_FOR_LACS_CI_LC = "\U0001f3f4\U000e0063\U000e0069\U000e006c\U000e0063\U000e007f" - FLAG_FOR_TARAPACA_CL_TA = "\U0001f3f4\U000e0063\U000e006c\U000e0074\U000e0061\U000e007f" - FLAG_FOR_PANEVEZYS_COUNTY_LT_PN = "\U0001f3f4\U000e006c\U000e0074\U000e0070\U000e006e\U000e007f" - FLAG_FOR_CENTRE_CM_CE = "\U0001f3f4\U000e0063\U000e006d\U000e0063\U000e0065\U000e007f" - FLAG_FOR_GUANGXI_CN_45 = "\U0001f3f4\U000e0063\U000e006e\U000e0034\U000e0035\U000e007f" - FLAG_FOR_HENAN_CN_41 = "\U0001f3f4\U000e0063\U000e006e\U000e0034\U000e0031\U000e007f" - FLAG_FOR_JIANGSU_CN_32 = "\U0001f3f4\U000e0063\U000e006e\U000e0033\U000e0032\U000e007f" - BLACK_CHESS_KNIGHT = "\u265e" - WHITE_HARD_SHELL_FLOPPY_DISK = "\U0001f5ab" - FLAG_FOR_BOYACA_CO_BOY = "\U0001f3f4\U000e0063\U000e006f\U000e0062\U000e006f\U000e0079\U000e007f" - FLAG_FOR_FUJIAN_CN_35 = "\U0001f3f4\U000e0063\U000e006e\U000e0033\U000e0035\U000e007f" - FAMILY_MAN_MEDIUM_LIGHT_SKIN_TONE_BOY_MEDIUM_LIGHT_SKIN_TONE_BOY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f466\U0001f3fc\u200d\U0001f466\U0001f3fc" - FLAG_FOR_CAQUETA_CO_CAQ = "\U0001f3f4\U000e0063\U000e006f\U000e0063\U000e0061\U000e0071\U000e007f" - FLAG_FOR_JIANGXI_CN_36 = "\U0001f3f4\U000e0063\U000e006e\U000e0033\U000e0036\U000e007f" - FLAG_FOR_OMBELLA_M_POKO_CF_MP = "\U0001f3f4\U000e0063\U000e0066\U000e006d\U000e0070\U000e007f" - FLAG_FOR_RUSE_BG_18 = "\U0001f3f4\U000e0062\U000e0067\U000e0031\U000e0038\U000e007f" - FAMILY_MAN_MEDIUM_DARK_SKIN_TONE_MAN_MEDIUM_DARK_SKIN_TONE_BABY_MEDIUM_DARK_SKIN_TONE_GIRL_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f468\U0001f3fe\u200d\U0001f476\U0001f3fe\u200d\U0001f467\U0001f3fe" - FLAG_FOR_LIBERTADOR_GENERAL_BERNARDO_O_HIGGINS_CL_LI = "\U0001f3f4\U000e0063\U000e006c\U000e006c\U000e0069\U000e007f" - FLAG_FOR_HAINAN_CN_46 = "\U0001f3f4\U000e0063\U000e006e\U000e0034\U000e0036\U000e007f" - FLAG_FOR_BATMAN_TR_72 = "\U0001f3f4\U000e0074\U000e0072\U000e0037\U000e0032\U000e007f" - FLAG_FOR_CALDAS_CO_CAL = "\U0001f3f4\U000e0063\U000e006f\U000e0063\U000e0061\U000e006c\U000e007f" - FLAG_FOR_QINGHAI_CN_63 = "\U0001f3f4\U000e0063\U000e006e\U000e0036\U000e0033\U000e007f" - FLAG_FOR_ARAUCA_CO_ARA = "\U0001f3f4\U000e0063\U000e006f\U000e0061\U000e0072\U000e0061\U000e007f" - COUPLE_WITH_HEART_WOMAN_DARK_SKIN_TONE_WOMAN_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fe" - FLAG_FOR_GUAVIARE_CO_GUV = "\U0001f3f4\U000e0063\U000e006f\U000e0067\U000e0075\U000e0076\U000e007f" - FLAG_FOR_VAUPES_CO_VAU = "\U0001f3f4\U000e0063\U000e006f\U000e0076\U000e0061\U000e0075\U000e007f" - COUPLE_WITH_HEART_MAN_MEDIUM_LIGHT_SKIN_TONE_WOMAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fc" - FLAG_FOR_CARTAGO_CR_C = "\U0001f3f4\U000e0063\U000e0072\U000e0063\U000e007f" - FLAG_FOR_LA_GUAJIRA_CO_LAG = "\U0001f3f4\U000e0063\U000e006f\U000e006c\U000e0061\U000e0067\U000e007f" - FLAG_FOR_MATANZAS_CU_04 = "\U0001f3f4\U000e0063\U000e0075\U000e0030\U000e0034\U000e007f" - FLAG_FOR_ARIANA_TN_12 = "\U0001f3f4\U000e0074\U000e006e\U000e0031\U000e0032\U000e007f" - FLAG_FOR_LIMON_CR_L = "\U0001f3f4\U000e0063\U000e0072\U000e006c\U000e007f" - FLAG_FOR_FEDERALLY_ADMINISTERED_TRIBAL_AREAS_PK_TA = "\U0001f3f4\U000e0070\U000e006b\U000e0074\U000e0061\U000e007f" - FLAG_FOR_CIENFUEGOS_CU_06 = "\U0001f3f4\U000e0063\U000e0075\U000e0030\U000e0036\U000e007f" - FLAG_FOR_CORDOBA_CO_COR = "\U0001f3f4\U000e0063\U000e006f\U000e0063\U000e006f\U000e0072\U000e007f" - FLAG_FOR_NORTE_DE_SANTANDER_CO_NSA = "\U0001f3f4\U000e0063\U000e006f\U000e006e\U000e0073\U000e0061\U000e007f" - FLAG_FOR_HUBEI_CN_42 = "\U0001f3f4\U000e0063\U000e006e\U000e0034\U000e0032\U000e007f" - FLAG_FOR_PUNTARENAS_CR_P = "\U0001f3f4\U000e0063\U000e0072\U000e0070\U000e007f" - FLAG_FOR_CAMAGUEY_CU_09 = "\U0001f3f4\U000e0063\U000e0075\U000e0030\U000e0039\U000e007f" - KISS_WOMAN_MEDIUM_LIGHT_SKIN_TONE_WOMAN_DARK_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3ff" - FLAG_FOR_SAN_SALVADOR_BS_SS = "\U0001f3f4\U000e0062\U000e0073\U000e0073\U000e0073\U000e007f" - FLAG_FOR_LIMASSOL_CY_02 = "\U0001f3f4\U000e0063\U000e0079\U000e0030\U000e0032\U000e007f" - FLAG_FOR_TSIRANG_BT_21 = "\U0001f3f4\U000e0062\U000e0074\U000e0032\U000e0031\U000e007f" - FLAG_FOR_MORAVSKOSLEZSKY_KRAJ_CZ_80 = "\U0001f3f4\U000e0063\U000e007a\U000e0038\U000e0030\U000e007f" - FLAG_FOR_SLIVEN_BG_20 = "\U0001f3f4\U000e0062\U000e0067\U000e0032\U000e0030\U000e007f" - FLAG_FOR_BURGAS_BG_02 = "\U0001f3f4\U000e0062\U000e0067\U000e0030\U000e0032\U000e007f" - FLAG_FOR_SOTAVENTO_ISLANDS_CV_S = "\U0001f3f4\U000e0063\U000e0076\U000e0073\U000e007f" - FLAG_FOR_SALZBURG_AT_5 = "\U0001f3f4\U000e0061\U000e0074\U000e0035\U000e007f" - FLAG_FOR_KRAJ_VYSOCINA_CZ_63 = "\U0001f3f4\U000e0063\U000e007a\U000e0036\U000e0033\U000e007f" - FLAG_FOR_HOLGUIN_CU_11 = "\U0001f3f4\U000e0063\U000e0075\U000e0031\U000e0031\U000e007f" - KISS_WOMAN_MEDIUM_DARK_SKIN_TONE_WOMAN = "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469" - FLAG_FOR_PARANA_BR_PR = "\U0001f3f4\U000e0062\U000e0072\U000e0070\U000e0072\U000e007f" - FLAG_FOR_GUANTANAMO_CU_14 = "\U0001f3f4\U000e0063\U000e0075\U000e0031\U000e0034\U000e007f" - FLAG_FOR_XINJIANG_CN_65 = "\U0001f3f4\U000e0063\U000e006e\U000e0036\U000e0035\U000e007f" - FLAG_FOR_KRALOVEHRADECKY_KRAJ_CZ_52 = "\U0001f3f4\U000e0063\U000e007a\U000e0035\U000e0032\U000e007f" - FAMILY_WOMAN_LIGHT_SKIN_TONE_WOMAN_LIGHT_SKIN_TONE_GIRL_LIGHT_SKIN_TONE_BABY_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f469\U0001f3fb\u200d\U0001f467\U0001f3fb\u200d\U0001f476\U0001f3fb" - FLAG_FOR_KARLOVARSKY_KRAJ_CZ_41 = "\U0001f3f4\U000e0063\U000e007a\U000e0034\U000e0031\U000e007f" - FLAG_FOR_AMAPA_BR_AP = "\U0001f3f4\U000e0062\U000e0072\U000e0061\U000e0070\U000e007f" - FLAG_FOR_GASA_BT_GA = "\U0001f3f4\U000e0062\U000e0074\U000e0067\U000e0061\U000e007f" - FLAG_FOR_BARLAVENTO_ISLANDS_CV_B = "\U0001f3f4\U000e0063\U000e0076\U000e0062\U000e007f" - FLAG_FOR_MAYABEQUE_CU_16 = "\U0001f3f4\U000e0063\U000e0075\U000e0031\U000e0036\U000e007f" - KISS_MAN_DARK_SKIN_TONE_MAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc" - FLAG_FOR_LIBERECKY_KRAJ_CZ_51 = "\U0001f3f4\U000e0063\U000e007a\U000e0035\U000e0031\U000e007f" - FLAG_FOR_CIBAO_NOROESTE_DO_34 = "\U0001f3f4\U000e0064\U000e006f\U000e0033\U000e0034\U000e007f" - FLAG_FOR_RISARALDA_CO_RIS = "\U0001f3f4\U000e0063\U000e006f\U000e0072\U000e0069\U000e0073\U000e007f" - FLAG_FOR_HOPE_TOWN_BS_HT = "\U0001f3f4\U000e0062\U000e0073\U000e0068\U000e0074\U000e007f" - FLAG_FOR_BRITISH_COLUMBIA_CA_BC = "\U0001f3f4\U000e0063\U000e0061\U000e0062\U000e0063\U000e007f" - FLAG_FOR_NICOSIA_CY_01 = "\U0001f3f4\U000e0063\U000e0079\U000e0030\U000e0031\U000e007f" - FLAG_FOR_SHANXI_CN_14 = "\U0001f3f4\U000e0063\U000e006e\U000e0031\U000e0034\U000e007f" - FLAG_FOR_SAXONY_ANHALT_DE_ST = "\U0001f3f4\U000e0064\U000e0065\U000e0073\U000e0074\U000e007f" - FAMILY_MAN_MEDIUM_LIGHT_SKIN_TONE_MAN_MEDIUM_LIGHT_SKIN_TONE_BABY_MEDIUM_LIGHT_SKIN_TONE_BOY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f468\U0001f3fc\u200d\U0001f476\U0001f3fc\u200d\U0001f466\U0001f3fc" - FLAG_FOR_TADJOURAH_DJ_TA = "\U0001f3f4\U000e0064\U000e006a\U000e0074\U000e0061\U000e007f" - FLAG_FOR_CAPITAL_REGION_DK_84 = "\U0001f3f4\U000e0064\U000e006b\U000e0038\U000e0034\U000e007f" - FLAG_FOR_HIGUAMO_DO_39 = "\U0001f3f4\U000e0064\U000e006f\U000e0033\U000e0039\U000e007f" - FLAG_FOR_CIBAO_NORDESTE_DO_33 = "\U0001f3f4\U000e0064\U000e006f\U000e0033\U000e0033\U000e007f" - FLAG_FOR_CIBAO_SUR_DO_36 = "\U0001f3f4\U000e0064\U000e006f\U000e0033\U000e0036\U000e007f" - COUPLE_WITH_HEART_MAN_LIGHT_SKIN_TONE_WOMAN_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fb" - FLAG_FOR_POHNPEI_FM_PNI = "\U0001f3f4\U000e0066\U000e006d\U000e0070\U000e006e\U000e0069\U000e007f" - FLAG_FOR_VALDESIA_DO_41 = "\U0001f3f4\U000e0064\U000e006f\U000e0034\U000e0031\U000e007f" - FLAG_FOR_YOZGAT_TR_66 = "\U0001f3f4\U000e0074\U000e0072\U000e0036\U000e0036\U000e007f" - FLAG_FOR_CAUCA_CO_CAU = "\U0001f3f4\U000e0063\U000e006f\U000e0063\U000e0061\U000e0075\U000e007f" - FLAG_FOR_OLOMOUCKY_KRAJ_CZ_71 = "\U0001f3f4\U000e0063\U000e007a\U000e0037\U000e0031\U000e007f" - FLAG_FOR_SAINT_MARK_DM_08 = "\U0001f3f4\U000e0064\U000e006d\U000e0030\U000e0038\U000e007f" - FAMILY_WOMAN_MEDIUM_DARK_SKIN_TONE_WOMAN_MEDIUM_DARK_SKIN_TONE_BOY_MEDIUM_DARK_SKIN_TONE_BOY_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f469\U0001f3fe\u200d\U0001f466\U0001f3fe\u200d\U0001f466\U0001f3fe" - FAMILY_WOMAN_LIGHT_SKIN_TONE_WOMAN_LIGHT_SKIN_TONE_GIRL_LIGHT_SKIN_TONE_GIRL_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f469\U0001f3fb\u200d\U0001f467\U0001f3fb\u200d\U0001f467\U0001f3fb" - FLAG_FOR_KHENCHELA_DZ_40 = "\U0001f3f4\U000e0064\U000e007a\U000e0034\U000e0030\U000e007f" - FAMILY_WOMAN_MEDIUM_DARK_SKIN_TONE_WOMAN_MEDIUM_DARK_SKIN_TONE_GIRL_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f469\U0001f3fe\u200d\U0001f467\U0001f3fe" - FLAG_FOR_TIZI_OUZOU_DZ_15 = "\U0001f3f4\U000e0064\U000e007a\U000e0031\U000e0035\U000e007f" - FAMILY_MAN_MEDIUM_DARK_SKIN_TONE_GIRL_MEDIUM_DARK_SKIN_TONE_BABY_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f467\U0001f3fe\u200d\U0001f476\U0001f3fe" - FLAG_FOR_MOSTAGANEM_DZ_27 = "\U0001f3f4\U000e0064\U000e007a\U000e0032\U000e0037\U000e007f" - FLAG_FOR_OUARGLA_DZ_30 = "\U0001f3f4\U000e0064\U000e007a\U000e0033\U000e0030\U000e007f" - FLAG_FOR_BOUMERDES_DZ_35 = "\U0001f3f4\U000e0064\U000e007a\U000e0033\U000e0035\U000e007f" - FLAG_FOR_TIPASA_DZ_42 = "\U0001f3f4\U000e0064\U000e007a\U000e0034\U000e0032\U000e007f" - FLAG_FOR_BISKRA_DZ_07 = "\U0001f3f4\U000e0064\U000e007a\U000e0030\U000e0037\U000e007f" - FLAG_FOR_MEDEA_DZ_26 = "\U0001f3f4\U000e0064\U000e007a\U000e0032\U000e0036\U000e007f" - FLAG_FOR_SOUK_AHRAS_DZ_41 = "\U0001f3f4\U000e0064\U000e007a\U000e0034\U000e0031\U000e007f" - FLAG_FOR_ANNABA_DZ_23 = "\U0001f3f4\U000e0064\U000e007a\U000e0032\U000e0033\U000e007f" - FLAG_FOR_WESTERN_HIGHLANDS_PG_WHM = "\U0001f3f4\U000e0070\U000e0067\U000e0077\U000e0068\U000e006d\U000e007f" - FLAG_FOR_TINDOUF_DZ_37 = "\U0001f3f4\U000e0064\U000e007a\U000e0033\U000e0037\U000e007f" - FLAG_FOR_DJELFA_DZ_17 = "\U0001f3f4\U000e0064\U000e007a\U000e0031\U000e0037\U000e007f" - FLAG_FOR_MILA_DZ_43 = "\U0001f3f4\U000e0064\U000e007a\U000e0034\U000e0033\U000e007f" - FLAG_FOR_BATNA_DZ_05 = "\U0001f3f4\U000e0064\U000e007a\U000e0030\U000e0035\U000e007f" - FLAG_FOR_TISSEMSILT_DZ_38 = "\U0001f3f4\U000e0064\U000e007a\U000e0033\U000e0038\U000e007f" - FLAG_FOR_EL_BAYADH_DZ_32 = "\U0001f3f4\U000e0064\U000e007a\U000e0033\U000e0032\U000e007f" - FLAG_FOR_HUILA_CO_HUI = "\U0001f3f4\U000e0063\U000e006f\U000e0068\U000e0075\U000e0069\U000e007f" - FLAG_FOR_ENRIQUILLO_DO_38 = "\U0001f3f4\U000e0064\U000e006f\U000e0033\U000e0038\U000e007f" - FLAG_FOR_SUDUR_PASHCHIMANCHAL_NP_5 = "\U0001f3f4\U000e006e\U000e0070\U000e0035\U000e007f" - FLAG_FOR_BEJAIA_DZ_06 = "\U0001f3f4\U000e0064\U000e007a\U000e0030\U000e0036\U000e007f" - FLAG_FOR_BOUIRA_DZ_10 = "\U0001f3f4\U000e0064\U000e007a\U000e0031\U000e0030\U000e007f" - FLAG_FOR_ILLIZI_DZ_33 = "\U0001f3f4\U000e0064\U000e007a\U000e0033\U000e0033\U000e007f" - FLAG_FOR_PLATEAUX_CG_14 = "\U0001f3f4\U000e0063\U000e0067\U000e0031\U000e0034\U000e007f" - FLAG_FOR_SAINT_DAVID_DM_03 = "\U0001f3f4\U000e0064\U000e006d\U000e0030\U000e0033\U000e007f" - FLAG_FOR_TAMANGHASSET_DZ_11 = "\U0001f3f4\U000e0064\U000e007a\U000e0031\U000e0031\U000e007f" - FLAG_FOR_MORONA_SANTIAGO_EC_S = "\U0001f3f4\U000e0065\U000e0063\U000e0073\U000e007f" - FLAG_FOR_VALGA_EE_82 = "\U0001f3f4\U000e0065\U000e0065\U000e0038\U000e0032\U000e007f" - FLAG_FOR_RELIZANE_DZ_48 = "\U0001f3f4\U000e0064\U000e007a\U000e0034\U000e0038\U000e007f" - FAMILY_MAN_MEDIUM_SKIN_TONE_MAN_MEDIUM_SKIN_TONE_GIRL_MEDIUM_SKIN_TONE_BOY_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f468\U0001f3fd\u200d\U0001f467\U0001f3fd\u200d\U0001f466\U0001f3fd" - FLAG_FOR_LOJA_EC_L = "\U0001f3f4\U000e0065\U000e0063\U000e006c\U000e007f" - FLAG_FOR_GHARDAIA_DZ_47 = "\U0001f3f4\U000e0064\U000e007a\U000e0034\U000e0037\U000e007f" - FLAG_FOR_PASTAZA_EC_Y = "\U0001f3f4\U000e0065\U000e0063\U000e0079\U000e007f" - FLAG_FOR_AIN_TEMOUCHENT_DZ_46 = "\U0001f3f4\U000e0064\U000e007a\U000e0034\U000e0036\U000e007f" - FLAG_FOR_LOS_RIOS_EC_R = "\U0001f3f4\U000e0065\U000e0063\U000e0072\U000e007f" - FLAG_FOR_LAANE_EE_57 = "\U0001f3f4\U000e0065\U000e0065\U000e0035\U000e0037\U000e007f" - FAMILY_MAN_MEDIUM_SKIN_TONE_BABY_MEDIUM_SKIN_TONE_BOY_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f476\U0001f3fd\u200d\U0001f466\U0001f3fd" - FLAG_FOR_SAARE_EE_74 = "\U0001f3f4\U000e0065\U000e0065\U000e0037\U000e0034\U000e007f" - FLAG_FOR_JOGEVA_EE_49 = "\U0001f3f4\U000e0065\U000e0065\U000e0034\U000e0039\U000e007f" - FLAG_FOR_POLVA_EE_65 = "\U0001f3f4\U000e0065\U000e0065\U000e0036\U000e0035\U000e007f" - FLAG_FOR_JARVA_EE_51 = "\U0001f3f4\U000e0065\U000e0065\U000e0035\U000e0031\U000e007f" - FLAG_FOR_PAPHOS_CY_05 = "\U0001f3f4\U000e0063\U000e0079\U000e0030\U000e0035\U000e007f" - FLAG_FOR_PARNU_EE_67 = "\U0001f3f4\U000e0065\U000e0065\U000e0036\U000e0037\U000e007f" - FLAG_FOR_CARCHI_EC_C = "\U0001f3f4\U000e0065\U000e0063\U000e0063\U000e007f" - FLAG_FOR_SUCUMBIOS_EC_U = "\U0001f3f4\U000e0065\U000e0063\U000e0075\U000e007f" - FLAG_FOR_OUM_EL_BOUAGHI_DZ_04 = "\U0001f3f4\U000e0064\U000e007a\U000e0030\U000e0034\U000e007f" - FLAG_FOR_GALAPAGOS_EC_W = "\U0001f3f4\U000e0065\U000e0063\U000e0077\U000e007f" - FLAG_FOR_GHARBIA_EG_GH = "\U0001f3f4\U000e0065\U000e0067\U000e0067\U000e0068\U000e007f" - FLAG_FOR_DAKAHLIA_EG_DK = "\U0001f3f4\U000e0065\U000e0067\U000e0064\U000e006b\U000e007f" - FLAG_FOR_AL_SHARQIA_EG_SHR = "\U0001f3f4\U000e0065\U000e0067\U000e0073\U000e0068\U000e0072\U000e007f" - FLAG_FOR_QALYUBIA_EG_KB = "\U0001f3f4\U000e0065\U000e0067\U000e006b\U000e0062\U000e007f" - FLAG_FOR_FEDERATION_OF_BOSNIA_AND_HERZEGOVINA_BA_BIH = "\U0001f3f4\U000e0062\U000e0061\U000e0062\U000e0069\U000e0068\U000e007f" - FLAG_FOR_CENTRAL_ABACO_BS_CO = "\U0001f3f4\U000e0062\U000e0073\U000e0063\U000e006f\U000e007f" - FLAG_FOR_MONUFIA_EG_MNF = "\U0001f3f4\U000e0065\U000e0067\U000e006d\U000e006e\U000e0066\U000e007f" - FAMILY_MAN_MEDIUM_DARK_SKIN_TONE_BABY_MEDIUM_DARK_SKIN_TONE_GIRL_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f476\U0001f3fe\u200d\U0001f467\U0001f3fe" - FLAG_FOR_BUJUMBURA_RURAL_BI_BL = "\U0001f3f4\U000e0062\U000e0069\U000e0062\U000e006c\U000e007f" - FLAG_FOR_MAEKEL_ER_MA = "\U0001f3f4\U000e0065\U000e0072\U000e006d\U000e0061\U000e007f" - FLAG_FOR_ISMAILIA_EG_IS = "\U0001f3f4\U000e0065\U000e0067\U000e0069\U000e0073\U000e007f" - FLAG_FOR_SAIDA_DZ_20 = "\U0001f3f4\U000e0064\U000e007a\U000e0032\U000e0030\U000e007f" - FLAG_FOR_QENA_EG_KN = "\U0001f3f4\U000e0065\U000e0067\U000e006b\U000e006e\U000e007f" - FLAG_FOR_TARTU_EE_78 = "\U0001f3f4\U000e0065\U000e0065\U000e0037\U000e0038\U000e007f" - FLAG_FOR_ANSEBA_ER_AN = "\U0001f3f4\U000e0065\U000e0072\U000e0061\U000e006e\U000e007f" - FLAG_FOR_ASWAN_EG_ASN = "\U0001f3f4\U000e0065\U000e0067\U000e0061\U000e0073\U000e006e\U000e007f" - FLAG_FOR_DEBUB_ER_DU = "\U0001f3f4\U000e0065\U000e0072\U000e0064\U000e0075\U000e007f" - FLAG_FOR_ASYUT_EG_AST = "\U0001f3f4\U000e0065\U000e0067\U000e0061\U000e0073\U000e0074\U000e007f" - FLAG_FOR_BEHEIRA_EG_BH = "\U0001f3f4\U000e0065\U000e0067\U000e0062\U000e0068\U000e007f" - FAMILY_MAN_MEDIUM_DARK_SKIN_TONE_BABY_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f476\U0001f3fe" - FLAG_FOR_IMBABURA_EC_I = "\U0001f3f4\U000e0065\U000e0063\U000e0069\U000e007f" - FLAG_FOR_VILJANDI_EE_84 = "\U0001f3f4\U000e0065\U000e0065\U000e0038\U000e0034\U000e007f" - FLAG_FOR_VORU_EE_86 = "\U0001f3f4\U000e0065\U000e0065\U000e0038\U000e0036\U000e007f" - COUPLE_WITH_HEART_MAN_MEDIUM_DARK_SKIN_TONE_MAN_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe" - FLAG_FOR_HAUTE_KOTTO_CF_HK = "\U0001f3f4\U000e0063\U000e0066\U000e0068\U000e006b\U000e007f" - FLAG_FOR_GUAINIA_CO_GUA = "\U0001f3f4\U000e0063\U000e006f\U000e0067\U000e0075\U000e0061\U000e007f" - FLAG_FOR_DAMIETTA_EG_DT = "\U0001f3f4\U000e0065\U000e0067\U000e0064\U000e0074\U000e007f" - FLAG_FOR_SAINT_PAUL_DM_10 = "\U0001f3f4\U000e0064\U000e006d\U000e0031\U000e0030\U000e007f" - FLAG_FOR_KAINUU_FI_05 = "\U0001f3f4\U000e0066\U000e0069\U000e0030\U000e0035\U000e007f" - FAMILY_MAN_MEDIUM_SKIN_TONE_BABY_MEDIUM_SKIN_TONE_GIRL_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f476\U0001f3fd\u200d\U0001f467\U0001f3fd" - FLAG_FOR_KYMENLAAKSO_FI_09 = "\U0001f3f4\U000e0066\U000e0069\U000e0030\U000e0039\U000e007f" - COUPLE_WITH_HEART_WOMAN_MEDIUM_LIGHT_SKIN_TONE_WOMAN_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fd" - FLAG_FOR_BENISHANGUL_GUMUZ_ET_BE = "\U0001f3f4\U000e0065\U000e0074\U000e0062\U000e0065\U000e007f" - FLAG_FOR_SOUTH_KARELIA_FI_02 = "\U0001f3f4\U000e0066\U000e0069\U000e0030\U000e0032\U000e007f" - FLAG_FOR_PUNAKHA_BT_23 = "\U0001f3f4\U000e0062\U000e0074\U000e0032\U000e0033\U000e007f" - FLAG_FOR_YUMA_DO_42 = "\U0001f3f4\U000e0064\U000e006f\U000e0034\U000e0032\U000e007f" - FLAG_FOR_SANTA_ELENA_EC_SE = "\U0001f3f4\U000e0065\U000e0063\U000e0073\U000e0065\U000e007f" - FAMILY_MAN_MEDIUM_LIGHT_SKIN_TONE_BABY_MEDIUM_LIGHT_SKIN_TONE_GIRL_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f476\U0001f3fc\u200d\U0001f467\U0001f3fc" - KISS_MAN_MEDIUM_SKIN_TONE_WOMAN_DARK_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3ff" - FLAG_FOR_BISTRITA_NASAUD_RO_BN = "\U0001f3f4\U000e0072\U000e006f\U000e0062\U000e006e\U000e007f" - FLAG_FOR_KEMO_CF_KG = "\U0001f3f4\U000e0063\U000e0066\U000e006b\U000e0067\U000e007f" - FLAG_FOR_BLIDA_DZ_09 = "\U0001f3f4\U000e0064\U000e007a\U000e0030\U000e0039\U000e007f" - FLAG_FOR_HARARI_ET_HA = "\U0001f3f4\U000e0065\U000e0074\U000e0068\U000e0061\U000e007f" - FLAG_FOR_ARTEMISA_CU_15 = "\U0001f3f4\U000e0063\U000e0075\U000e0031\U000e0035\U000e007f" - FLAG_FOR_SOUTHERN_OSTROBOTHNIA_FI_03 = "\U0001f3f4\U000e0066\U000e0069\U000e0030\U000e0033\U000e007f" - FLAG_FOR_OBOCK_DJ_OB = "\U0001f3f4\U000e0064\U000e006a\U000e006f\U000e0062\U000e007f" - FLAG_FOR_MATROUH_EG_MT = "\U0001f3f4\U000e0065\U000e0067\U000e006d\U000e0074\U000e007f" - TAG_LATIN_CAPITAL_LETTER_W = "\U000e0057" - FLAG_FOR_VILLA_CLARA_CU_05 = "\U0001f3f4\U000e0063\U000e0075\U000e0030\U000e0035\U000e007f" - FLAG_FOR_CENTRAL_FJ_C = "\U0001f3f4\U000e0066\U000e006a\U000e0063\U000e007f" - FLAG_FOR_NORTH_KARELIA_FI_13 = "\U0001f3f4\U000e0066\U000e0069\U000e0031\U000e0033\U000e007f" - FLAG_FOR_CHUKHA_BT_12 = "\U0001f3f4\U000e0062\U000e0074\U000e0031\U000e0032\U000e007f" - FLAG_FOR_BENI_BO_B = "\U0001f3f4\U000e0062\U000e006f\U000e0062\U000e007f" - FLAG_FOR_ORAN_DZ_31 = "\U0001f3f4\U000e0064\U000e007a\U000e0033\U000e0031\U000e007f" - FLAG_FOR_NORTHERN_SAVONIA_FI_15 = "\U0001f3f4\U000e0066\U000e0069\U000e0031\U000e0035\U000e007f" - FLAG_FOR_APPENZELL_AUSSERRHODEN_CH_AR = "\U0001f3f4\U000e0063\U000e0068\U000e0061\U000e0072\U000e007f" - FAMILY_MAN_MEDIUM_LIGHT_SKIN_TONE_MAN_MEDIUM_LIGHT_SKIN_TONE_GIRL_MEDIUM_LIGHT_SKIN_TONE_BOY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f468\U0001f3fc\u200d\U0001f467\U0001f3fc\u200d\U0001f466\U0001f3fc" - FLAG_FOR_PIRKANMAA_FI_11 = "\U0001f3f4\U000e0066\U000e0069\U000e0031\U000e0031\U000e007f" - FLAG_FOR_KOSRAE_FM_KSA = "\U0001f3f4\U000e0066\U000e006d\U000e006b\U000e0073\U000e0061\U000e007f" - FAMILY_MAN_MEDIUM_LIGHT_SKIN_TONE_WOMAN_MEDIUM_LIGHT_SKIN_TONE_BABY_MEDIUM_LIGHT_SKIN_TONE_BABY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f469\U0001f3fc\u200d\U0001f476\U0001f3fc\u200d\U0001f476\U0001f3fc" - FLAG_FOR_OUHAM_PENDE_CF_OP = "\U0001f3f4\U000e0063\U000e0066\U000e006f\U000e0070\U000e007f" - FLAG_FOR_BIO_BIO_CL_BI = "\U0001f3f4\U000e0063\U000e006c\U000e0062\U000e0069\U000e007f" - HORIZONTAL_MALE_WITH_STROKE_SIGN = "\u26a9" - FLAG_FOR_NOUVELLE_AQUITAINE_FR_NAQ = "\U0001f3f4\U000e0066\U000e0072\U000e006e\U000e0061\U000e0071\U000e007f" - FLAG_FOR_ANSE_AUX_PINS_SC_01 = "\U0001f3f4\U000e0073\U000e0063\U000e0030\U000e0031\U000e007f" - FLAG_FOR_SAINT_DAVID_GD_02 = "\U0001f3f4\U000e0067\U000e0064\U000e0030\U000e0032\U000e007f" - FLAG_FOR_OGOOUE_IVINDO_GA_6 = "\U0001f3f4\U000e0067\U000e0061\U000e0036\U000e007f" - FLAG_FOR_ZAMORA_CHINCHIPE_EC_Z = "\U0001f3f4\U000e0065\U000e0063\U000e007a\U000e007f" - COUPLE_WITH_HEART_WOMAN_MEDIUM_DARK_SKIN_TONE_MAN_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe" - FLAG_FOR_MOYEN_OGOOUE_GA_3 = "\U0001f3f4\U000e0067\U000e0061\U000e0033\U000e007f" - FLAG_FOR_SATAKUNTA_FI_17 = "\U0001f3f4\U000e0066\U000e0069\U000e0031\U000e0037\U000e007f" - FLAG_FOR_BRONG_AHAFO_GH_BA = "\U0001f3f4\U000e0067\U000e0068\U000e0062\U000e0061\U000e007f" - FAMILY_MAN_MEDIUM_LIGHT_SKIN_TONE_BABY_MEDIUM_LIGHT_SKIN_TONE_BABY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f476\U0001f3fc\u200d\U0001f476\U0001f3fc" - FLAG_FOR_VAN_TR_65 = "\U0001f3f4\U000e0074\U000e0072\U000e0036\U000e0035\U000e007f" - FLAG_FOR_MTSKHETA_MTIANETI_GE_MM = "\U0001f3f4\U000e0067\U000e0065\U000e006d\U000e006d\U000e007f" - FLAG_FOR_TEBESSA_DZ_12 = "\U0001f3f4\U000e0064\U000e007a\U000e0031\U000e0032\U000e007f" - FLAG_FOR_WOLEU_NTEM_GA_9 = "\U0001f3f4\U000e0067\U000e0061\U000e0039\U000e007f" - FLAG_FOR_SAMEGRELO_ZEMO_SVANETI_GE_SZ = "\U0001f3f4\U000e0067\U000e0065\U000e0073\U000e007a\U000e007f" - FLAG_FOR_IMERETI_GE_IM = "\U0001f3f4\U000e0067\U000e0065\U000e0069\U000e006d\U000e007f" - FLAG_FOR_SAMTSKHE_JAVAKHETI_GE_SJ = "\U0001f3f4\U000e0067\U000e0065\U000e0073\U000e006a\U000e007f" - FLAG_FOR_ABIDJAN_CI_AB = "\U0001f3f4\U000e0063\U000e0069\U000e0061\U000e0062\U000e007f" - FLAG_FOR_SHIRAK_AM_SH = "\U0001f3f4\U000e0061\U000e006d\U000e0073\U000e0068\U000e007f" - FAMILY_MAN_MEDIUM_SKIN_TONE_BABY_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f476\U0001f3fd" - FLAG_FOR_GUELMA_DZ_24 = "\U0001f3f4\U000e0064\U000e007a\U000e0032\U000e0034\U000e007f" - FLAG_FOR_HAUT_OGOOUE_GA_2 = "\U0001f3f4\U000e0067\U000e0061\U000e0032\U000e007f" - FLAG_FOR_JIHOMORAVSKY_KRAJ_CZ_64 = "\U0001f3f4\U000e0063\U000e007a\U000e0036\U000e0034\U000e007f" - COUPLE_WITH_HEART_MAN_MEDIUM_LIGHT_SKIN_TONE_WOMAN_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fe" - FAMILY_WOMAN_MEDIUM_LIGHT_SKIN_TONE_GIRL_MEDIUM_LIGHT_SKIN_TONE_BOY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f467\U0001f3fc\u200d\U0001f466\U0001f3fc" - FLAG_FOR_KUJALLEQ_GL_KU = "\U0001f3f4\U000e0067\U000e006c\U000e006b\U000e0075\U000e007f" - FLAG_FOR_THESSALY_GR_E = "\U0001f3f4\U000e0067\U000e0072\U000e0065\U000e007f" - FLAG_FOR_EPIRUS_GR_D = "\U0001f3f4\U000e0067\U000e0072\U000e0064\U000e007f" - FLAG_FOR_PICHINCHA_EC_P = "\U0001f3f4\U000e0065\U000e0063\U000e0070\U000e007f" - FLAG_FOR_ALI_SABIEH_DJ_AS = "\U0001f3f4\U000e0064\U000e006a\U000e0061\U000e0073\U000e007f" - FLAG_FOR_NORTHERN_FJ_N = "\U0001f3f4\U000e0066\U000e006a\U000e006e\U000e007f" - FLAG_FOR_NORTHERN_GH_NP = "\U0001f3f4\U000e0067\U000e0068\U000e006e\U000e0070\U000e007f" - FLAG_FOR_UPPER_WEST_GH_UW = "\U0001f3f4\U000e0067\U000e0068\U000e0075\U000e0077\U000e007f" - FLAG_FOR_UPPER_EAST_GH_UE = "\U0001f3f4\U000e0067\U000e0068\U000e0075\U000e0065\U000e007f" - FLAG_FOR_KINDIA_REGION_GN_D = "\U0001f3f4\U000e0067\U000e006e\U000e0064\U000e007f" - FLAG_FOR_CONAKRY_GN_C = "\U0001f3f4\U000e0067\U000e006e\U000e0063\U000e007f" - FLAG_FOR_IONIAN_ISLANDS_GR_F = "\U0001f3f4\U000e0067\U000e0072\U000e0066\U000e007f" - FAMILY_WOMAN_DARK_SKIN_TONE_MAN_DARK_SKIN_TONE_BABY_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f468\U0001f3ff\u200d\U0001f476\U0001f3ff" - COUPLE_WITH_HEART_WOMAN_LIGHT_SKIN_TONE_MAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc" - FLAG_FOR_SINOE_LR_SI = "\U0001f3f4\U000e006c\U000e0072\U000e0073\U000e0069\U000e007f" - FLAG_FOR_FARANAH_REGION_GN_F = "\U0001f3f4\U000e0067\U000e006e\U000e0066\U000e007f" - COUPLE_WITH_HEART_WOMAN_LIGHT_SKIN_TONE_WOMAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fc" - FLAG_FOR_HEILONGJIANG_CN_23 = "\U0001f3f4\U000e0063\U000e006e\U000e0032\U000e0033\U000e007f" - FLAG_FOR_RIO_MUNI_GQ_C = "\U0001f3f4\U000e0067\U000e0071\U000e0063\U000e007f" - WHITE_DOWN_POINTING_LEFT_HAND_INDEX = "\U0001f597" - FLAG_FOR_VOLTA_GH_TV = "\U0001f3f4\U000e0067\U000e0068\U000e0074\U000e0076\U000e007f" - FLAG_FOR_QAASUITSUP_GL_QA = "\U0001f3f4\U000e0067\U000e006c\U000e0071\U000e0061\U000e007f" - KISS_MAN_LIGHT_SKIN_TONE_MAN_DARK_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff" - FLAG_FOR_QUICHE_GT_QC = "\U0001f3f4\U000e0067\U000e0074\U000e0071\U000e0063\U000e007f" - FLAG_FOR_HUNAN_CN_43 = "\U0001f3f4\U000e0063\U000e006e\U000e0034\U000e0033\U000e007f" - COUPLE_WITH_HEART_WOMAN_MEDIUM_LIGHT_SKIN_TONE_MAN_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe" - FLAG_FOR_QUETZALTENANGO_GT_QZ = "\U0001f3f4\U000e0067\U000e0074\U000e0071\U000e007a\U000e007f" - FLAG_FOR_LESTE_GW_L = "\U0001f3f4\U000e0067\U000e0077\U000e006c\U000e007f" - FLAG_FOR_CUYUNI_MAZARUNI_GY_CU = "\U0001f3f4\U000e0067\U000e0079\U000e0063\U000e0075\U000e007f" - FAMILY_MAN_DARK_SKIN_TONE_BABY_DARK_SKIN_TONE_BABY_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f476\U0001f3ff\u200d\U0001f476\U0001f3ff" - FLAG_FOR_PETEN_GT_PE = "\U0001f3f4\U000e0067\U000e0074\U000e0070\U000e0065\U000e007f" - FLAG_FOR_ZACAPA_GT_ZA = "\U0001f3f4\U000e0067\U000e0074\U000e007a\U000e0061\U000e007f" - FLAG_FOR_LIAONING_CN_21 = "\U0001f3f4\U000e0063\U000e006e\U000e0032\U000e0031\U000e007f" - FLAG_FOR_CHOLUTECA_HN_CH = "\U0001f3f4\U000e0068\U000e006e\U000e0063\U000e0068\U000e007f" - FLAG_FOR_CHIMALTENANGO_GT_CM = "\U0001f3f4\U000e0067\U000e0074\U000e0063\U000e006d\U000e007f" - FLAG_FOR_SOLOLA_GT_SO = "\U0001f3f4\U000e0067\U000e0074\U000e0073\U000e006f\U000e007f" - FLAG_FOR_HUEHUETENANGO_GT_HU = "\U0001f3f4\U000e0067\U000e0074\U000e0068\U000e0075\U000e007f" - KISS_MAN_MAN_LIGHT_SKIN_TONE = "\U0001f468\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb" - FLAG_FOR_ESCUINTLA_GT_ES = "\U0001f3f4\U000e0067\U000e0074\U000e0065\U000e0073\U000e007f" - FLAG_FOR_CHIQUIMULA_GT_CQ = "\U0001f3f4\U000e0067\U000e0074\U000e0063\U000e0071\U000e007f" - FLAG_FOR_IZABAL_GT_IZ = "\U0001f3f4\U000e0067\U000e0074\U000e0069\U000e007a\U000e007f" - FLAG_FOR_ATLANTIDA_HN_AT = "\U0001f3f4\U000e0068\U000e006e\U000e0061\U000e0074\U000e007f" - FLAG_FOR_NEW_JERSEY_US_NJ = "\U0001f3f4\U000e0075\U000e0073\U000e006e\U000e006a\U000e007f" - FLAG_FOR_ESSEQUIBO_ISLANDS_WEST_DEMERARA_GY_ES = "\U0001f3f4\U000e0067\U000e0079\U000e0065\U000e0073\U000e007f" - FLAG_FOR_POTARO_SIPARUNI_GY_PT = "\U0001f3f4\U000e0067\U000e0079\U000e0070\U000e0074\U000e007f" - FLAG_FOR_BISSAU_GW_BS = "\U0001f3f4\U000e0067\U000e0077\U000e0062\U000e0073\U000e007f" - FLAG_FOR_DEMERARA_MAHAICA_GY_DE = "\U0001f3f4\U000e0067\U000e0079\U000e0064\U000e0065\U000e007f" - FLAG_FOR_IDA_VIRU_EE_44 = "\U0001f3f4\U000e0065\U000e0065\U000e0034\U000e0034\U000e007f" - FLAG_FOR_LIKA_SENJ_HR_09 = "\U0001f3f4\U000e0068\U000e0072\U000e0030\U000e0039\U000e007f" - FLAG_FOR_KRAPINA_ZAGORJE_HR_02 = "\U0001f3f4\U000e0068\U000e0072\U000e0030\U000e0032\U000e007f" - FLAG_FOR_ARTIBONITE_HT_AR = "\U0001f3f4\U000e0068\U000e0074\U000e0061\U000e0072\U000e007f" - FLAG_FOR_OCOTEPEQUE_HN_OC = "\U0001f3f4\U000e0068\U000e006e\U000e006f\U000e0063\U000e007f" - FLAG_FOR_CENTRE_HT_CE = "\U0001f3f4\U000e0068\U000e0074\U000e0063\U000e0065\U000e007f" - FLAG_FOR_SOLOTHURN_CH_SO = "\U0001f3f4\U000e0063\U000e0068\U000e0073\U000e006f\U000e007f" - FLAG_FOR_JALAPA_GT_JA = "\U0001f3f4\U000e0067\U000e0074\U000e006a\U000e0061\U000e007f" - FLAG_FOR_DUBROVNIK_NERETVA_HR_19 = "\U0001f3f4\U000e0068\U000e0072\U000e0031\U000e0039\U000e007f" - FLAG_FOR_VUKOVAR_SYRMIA_HR_16 = "\U0001f3f4\U000e0068\U000e0072\U000e0031\U000e0036\U000e007f" - FLAG_FOR_OSIJEK_BARANJA_HR_14 = "\U0001f3f4\U000e0068\U000e0072\U000e0031\U000e0034\U000e007f" - FLAG_FOR_KOPRIVNICA_KRIZEVCI_HR_06 = "\U0001f3f4\U000e0068\U000e0072\U000e0030\U000e0036\U000e007f" - FLAG_FOR_INTIBUCA_HN_IN = "\U0001f3f4\U000e0068\U000e006e\U000e0069\U000e006e\U000e007f" - FLAG_FOR_POZEGA_SLAVONIA_HR_11 = "\U0001f3f4\U000e0068\U000e0072\U000e0031\U000e0031\U000e007f" - FLAG_FOR_BERAT_COUNTY_AL_01 = "\U0001f3f4\U000e0061\U000e006c\U000e0030\U000e0031\U000e007f" - FLAG_FOR_SAINT_LUKE_DM_07 = "\U0001f3f4\U000e0064\U000e006d\U000e0030\U000e0037\U000e007f" - FLAG_FOR_COMAYAGUA_HN_CM = "\U0001f3f4\U000e0068\U000e006e\U000e0063\U000e006d\U000e007f" - FLAG_FOR_YORO_HN_YO = "\U0001f3f4\U000e0068\U000e006e\U000e0079\U000e006f\U000e007f" - FLAG_FOR_COPAN_HN_CP = "\U0001f3f4\U000e0068\U000e006e\U000e0063\U000e0070\U000e007f" - FLAG_FOR_VALLE_HN_VA = "\U0001f3f4\U000e0068\U000e006e\U000e0076\U000e0061\U000e007f" - FLAG_FOR_LEMPIRA_HN_LE = "\U0001f3f4\U000e0068\U000e006e\U000e006c\U000e0065\U000e007f" - FLAG_FOR_ME_IMURJE_HR_20 = "\U0001f3f4\U000e0068\U000e0072\U000e0032\U000e0030\U000e007f" - FLAG_FOR_SIBENIK_KNIN_HR_15 = "\U0001f3f4\U000e0068\U000e0072\U000e0031\U000e0035\U000e007f" - MERCURY = "\u263f\ufe0f" - FLAG_FOR_FRANCISCO_MORAZAN_HN_FM = "\U0001f3f4\U000e0068\U000e006e\U000e0066\U000e006d\U000e007f" - FLAG_FOR_OLANCHO_HN_OL = "\U0001f3f4\U000e0068\U000e006e\U000e006f\U000e006c\U000e007f" - FLAG_FOR_CORTES_HN_CR = "\U0001f3f4\U000e0068\U000e006e\U000e0063\U000e0072\U000e007f" - FLAG_FOR_ZAGREB_COUNTY_HR_01 = "\U0001f3f4\U000e0068\U000e0072\U000e0030\U000e0031\U000e007f" - FLAG_FOR_ISTRIA_HR_18 = "\U0001f3f4\U000e0068\U000e0072\U000e0031\U000e0038\U000e007f" - FLAG_FOR_ZADAR_HR_13 = "\U0001f3f4\U000e0068\U000e0072\U000e0031\U000e0033\U000e007f" - FLAG_FOR_PEST_HU_PE = "\U0001f3f4\U000e0068\U000e0075\U000e0070\U000e0065\U000e007f" - FAMILY_MAN_MEDIUM_SKIN_TONE_GIRL_MEDIUM_SKIN_TONE_BOY_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f467\U0001f3fd\u200d\U0001f466\U0001f3fd" - FLAG_FOR_HAJDU_BIHAR_HU_HB = "\U0001f3f4\U000e0068\U000e0075\U000e0068\U000e0062\U000e007f" - COUPLE_WITH_HEART_MAN_DARK_SKIN_TONE_MAN_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff" - FLAG_FOR_OUEST_HT_OU = "\U0001f3f4\U000e0068\U000e0074\U000e006f\U000e0075\U000e007f" - FLAG_FOR_SZOLNOK_HU_SK = "\U0001f3f4\U000e0068\U000e0075\U000e0073\U000e006b\U000e007f" - FLAG_FOR_NOGRAD_HU_NO = "\U0001f3f4\U000e0068\U000e0075\U000e006e\U000e006f\U000e007f" - FLAG_FOR_BORSOD_ABAUJ_ZEMPLEN_HU_BZ = "\U0001f3f4\U000e0068\U000e0075\U000e0062\U000e007a\U000e007f" - FLAG_FOR_SOPRON_HU_SN = "\U0001f3f4\U000e0068\U000e0075\U000e0073\U000e006e\U000e007f" - FLAG_FOR_JIJEL_DZ_18 = "\U0001f3f4\U000e0064\U000e007a\U000e0031\U000e0038\U000e007f" - FLAG_FOR_BEKESCSABA_HU_BC = "\U0001f3f4\U000e0068\U000e0075\U000e0062\U000e0063\U000e007f" - FLAG_FOR_NYIREGYHAZA_HU_NY = "\U0001f3f4\U000e0068\U000e0075\U000e006e\U000e0079\U000e007f" - FLAG_FOR_NORD_HT_ND = "\U0001f3f4\U000e0068\U000e0074\U000e006e\U000e0064\U000e007f" - FLAG_FOR_FEJER_HU_FE = "\U0001f3f4\U000e0068\U000e0075\U000e0066\U000e0065\U000e007f" - FLAG_FOR_NIPPES_HT_NI = "\U0001f3f4\U000e0068\U000e0074\U000e006e\U000e0069\U000e007f" - FLAG_FOR_ERD_HU_ER = "\U0001f3f4\U000e0068\U000e0075\U000e0065\U000e0072\U000e007f" - FLAG_FOR_SZOMBATHELY_HU_SH = "\U0001f3f4\U000e0068\U000e0075\U000e0073\U000e0068\U000e007f" - FLAG_FOR_EASTERN_GH_EP = "\U0001f3f4\U000e0067\U000e0068\U000e0065\U000e0070\U000e007f" - FLAG_FOR_DIRE_DAWA_ET_DD = "\U0001f3f4\U000e0065\U000e0074\U000e0064\U000e0064\U000e007f" - COUPLE_WITH_HEART_WOMAN_MEDIUM_DARK_SKIN_TONE_WOMAN = "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f469" - FLAG_FOR_SZEKESFEHERVAR_HU_SF = "\U0001f3f4\U000e0068\U000e0075\U000e0073\U000e0066\U000e007f" - COUPLE_WITH_HEART_MAN_MEDIUM_DARK_SKIN_TONE_WOMAN_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fb" - FLAG_FOR_KOMAROM_ESZTERGOM_HU_KE = "\U0001f3f4\U000e0068\U000e0075\U000e006b\U000e0065\U000e007f" - FLAG_FOR_MISKOLC_HU_MI = "\U0001f3f4\U000e0068\U000e0075\U000e006d\U000e0069\U000e007f" - FLAG_FOR_DEBRECEN_HU_DE = "\U0001f3f4\U000e0068\U000e0075\U000e0064\U000e0065\U000e007f" - FLAG_FOR_BACS_KISKUN_HU_BK = "\U0001f3f4\U000e0068\U000e0075\U000e0062\U000e006b\U000e007f" - FLAG_FOR_GYOR_HU_GY = "\U0001f3f4\U000e0068\U000e0075\U000e0067\U000e0079\U000e007f" - FLAG_FOR_DUNAUJVAROS_HU_DU = "\U0001f3f4\U000e0068\U000e0075\U000e0064\U000e0075\U000e007f" - FLAG_FOR_HEVES_HU_HE = "\U0001f3f4\U000e0068\U000e0075\U000e0068\U000e0065\U000e007f" - FLAG_FOR_BEKES_HU_BE = "\U0001f3f4\U000e0068\U000e0075\U000e0062\U000e0065\U000e007f" - FLAG_FOR_JASZ_NAGYKUN_SZOLNOK_HU_JN = "\U0001f3f4\U000e0068\U000e0075\U000e006a\U000e006e\U000e007f" - FLAG_FOR_RAPLA_EE_70 = "\U0001f3f4\U000e0065\U000e0065\U000e0037\U000e0030\U000e007f" - FLAG_FOR_ZALAEGERSZEG_HU_ZE = "\U0001f3f4\U000e0068\U000e0075\U000e007a\U000e0065\U000e007f" - FLAG_FOR_CONNACHT_IE_C = "\U0001f3f4\U000e0069\U000e0065\U000e0063\U000e007f" - COUPLE_WITH_HEART_WOMAN_MEDIUM_SKIN_TONE_MAN_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd" - FLAG_FOR_CENTRAL_DISTRICT_IL_M = "\U0001f3f4\U000e0069\U000e006c\U000e006d\U000e007f" - FAMILY_MAN_MEDIUM_SKIN_TONE_MAN_MEDIUM_SKIN_TONE_BABY_MEDIUM_SKIN_TONE_BOY_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f468\U0001f3fd\u200d\U0001f476\U0001f3fd\u200d\U0001f466\U0001f3fd" - FLAG_FOR_GREATER_ACCRA_GH_AA = "\U0001f3f4\U000e0067\U000e0068\U000e0061\U000e0061\U000e007f" - FLAG_FOR_HAIFA_DISTRICT_IL_HA = "\U0001f3f4\U000e0069\U000e006c\U000e0068\U000e0061\U000e007f" - FLAG_FOR_BARANYA_HU_BA = "\U0001f3f4\U000e0068\U000e0075\U000e0062\U000e0061\U000e007f" - FLAG_FOR_VESZPREM_HU_VM = "\U0001f3f4\U000e0068\U000e0075\U000e0076\U000e006d\U000e007f" - FLAG_FOR_VESZPREM_COUNTY_HU_VE = "\U0001f3f4\U000e0068\U000e0075\U000e0076\U000e0065\U000e007f" - FLAG_FOR_MAGDALENA_CO_MAG = "\U0001f3f4\U000e0063\U000e006f\U000e006d\U000e0061\U000e0067\U000e007f" - FLAG_FOR_NAGYKANIZSA_HU_NK = "\U0001f3f4\U000e0068\U000e0075\U000e006e\U000e006b\U000e007f" - FLAG_FOR_SOMOGY_HU_SO = "\U0001f3f4\U000e0068\U000e0075\U000e0073\U000e006f\U000e007f" - FLAG_FOR_SOUTHERN_DISTRICT_IL_D = "\U0001f3f4\U000e0069\U000e006c\U000e0064\U000e007f" - COUPLE_WITH_HEART_WOMAN_MEDIUM_DARK_SKIN_TONE_MAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc" - FLAG_FOR_SAINT_PETER_DM_11 = "\U0001f3f4\U000e0064\U000e006d\U000e0031\U000e0031\U000e007f" - KISS_WOMAN_MEDIUM_SKIN_TONE_MAN_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb" - FLAG_FOR_TOLNA_HU_TO = "\U0001f3f4\U000e0068\U000e0075\U000e0074\U000e006f\U000e007f" - FLAG_FOR_ODISHA_IN_OR = "\U0001f3f4\U000e0069\U000e006e\U000e006f\U000e0072\U000e007f" - FLAG_FOR_PUTUMAYO_CO_PUT = "\U0001f3f4\U000e0063\U000e006f\U000e0070\U000e0075\U000e0074\U000e007f" - FLAG_FOR_DOHUK_IQ_DA = "\U0001f3f4\U000e0069\U000e0071\U000e0064\U000e0061\U000e007f" - FLAG_FOR_AL_MUTHANNA_IQ_MU = "\U0001f3f4\U000e0069\U000e0071\U000e006d\U000e0075\U000e007f" - WHITE_DIAMOND_SUIT = "\u2662" - BLACK_CHESS_QUEEN = "\u265b" - FLAG_FOR_SALADIN_IQ_SD = "\U0001f3f4\U000e0069\U000e0071\U000e0073\U000e0064\U000e007f" - TAG_LATIN_SMALL_LETTER_V = "\U000e0076" - FAMILY_WOMAN_DARK_SKIN_TONE_GIRL_DARK_SKIN_TONE_GIRL_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f467\U0001f3ff\u200d\U0001f467\U0001f3ff" - FLAG_FOR_SHANDONG_CN_37 = "\U0001f3f4\U000e0063\U000e006e\U000e0033\U000e0037\U000e007f" - FLAG_FOR_AL_ANBAR_IQ_AN = "\U0001f3f4\U000e0069\U000e0071\U000e0061\U000e006e\U000e007f" - FLAG_FOR_NAJAF_IQ_NA = "\U0001f3f4\U000e0069\U000e0071\U000e006e\U000e0061\U000e007f" - FLAG_FOR_KARBALA_IQ_KA = "\U0001f3f4\U000e0069\U000e0071\U000e006b\U000e0061\U000e007f" - FLAG_FOR_SULAWESI_ID_SL = "\U0001f3f4\U000e0069\U000e0064\U000e0073\U000e006c\U000e007f" - FLAG_FOR_ATLANTICO_CO_ATL = "\U0001f3f4\U000e0063\U000e006f\U000e0061\U000e0074\U000e006c\U000e007f" - FAMILY_MAN_MEDIUM_SKIN_TONE_BABY_MEDIUM_SKIN_TONE_BABY_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f476\U0001f3fd\u200d\U0001f476\U0001f3fd" - FLAG_FOR_DIYALA_IQ_DI = "\U0001f3f4\U000e0069\U000e0071\U000e0064\U000e0069\U000e007f" - FLAG_FOR_AL_QADISIYYAH_IQ_QA = "\U0001f3f4\U000e0069\U000e0071\U000e0071\U000e0061\U000e007f" - FLAG_FOR_NAGALAND_IN_NL = "\U0001f3f4\U000e0069\U000e006e\U000e006e\U000e006c\U000e007f" - FLAG_FOR_SULAYMANIYAH_IQ_SU = "\U0001f3f4\U000e0069\U000e0071\U000e0073\U000e0075\U000e007f" - FLAG_FOR_SUEZ_EG_SUZ = "\U0001f3f4\U000e0065\U000e0067\U000e0073\U000e0075\U000e007a\U000e007f" - FLAG_FOR_MEGHALAYA_IN_ML = "\U0001f3f4\U000e0069\U000e006e\U000e006d\U000e006c\U000e007f" - FLAG_FOR_NINEVEH_IQ_NI = "\U0001f3f4\U000e0069\U000e0071\U000e006e\U000e0069\U000e007f" - FLAG_FOR_NEUCHATEL_CH_NE = "\U0001f3f4\U000e0063\U000e0068\U000e006e\U000e0065\U000e007f" - FLAG_FOR_MIZORAM_IN_MZ = "\U0001f3f4\U000e0069\U000e006e\U000e006d\U000e007a\U000e007f" - FLAG_FOR_OBWALDEN_CH_OW = "\U0001f3f4\U000e0063\U000e0068\U000e006f\U000e0077\U000e007f" - FLAG_FOR_LAKSHADWEEP_IN_LD = "\U0001f3f4\U000e0069\U000e006e\U000e006c\U000e0064\U000e007f" - FLAG_FOR_HORMOZGAN_IR_23 = "\U0001f3f4\U000e0069\U000e0072\U000e0032\U000e0033\U000e007f" - FLAG_FOR_QAZVIN_IR_28 = "\U0001f3f4\U000e0069\U000e0072\U000e0032\U000e0038\U000e007f" - FLAG_FOR_MAYSAN_IQ_MA = "\U0001f3f4\U000e0069\U000e0071\U000e006d\U000e0061\U000e007f" - FLAG_FOR_SEMNAN_IR_12 = "\U0001f3f4\U000e0069\U000e0072\U000e0031\U000e0032\U000e007f" - FLAG_FOR_ALAJUELA_CR_A = "\U0001f3f4\U000e0063\U000e0072\U000e0061\U000e007f" - FLAG_FOR_MARKAZI_IR_22 = "\U0001f3f4\U000e0069\U000e0072\U000e0032\U000e0032\U000e007f" - COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE = "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fe" - FLAG_FOR_RHODE_ISLAND_US_RI = "\U0001f3f4\U000e0075\U000e0073\U000e0072\U000e0069\U000e007f" - FLAG_FOR_PIRAN_SI_090 = "\U0001f3f4\U000e0073\U000e0069\U000e0030\U000e0039\U000e0030\U000e007f" - FLAG_FOR_LEINSTER_IE_L = "\U0001f3f4\U000e0069\U000e0065\U000e006c\U000e007f" - FLAG_FOR_BANJUL_GM_B = "\U0001f3f4\U000e0067\U000e006d\U000e0062\U000e007f" - FLAG_FOR_NORTHWESTERN_IS_5 = "\U0001f3f4\U000e0069\U000e0073\U000e0035\U000e007f" - FAMILY_WOMAN_DARK_SKIN_TONE_WOMAN_DARK_SKIN_TONE_GIRL_DARK_SKIN_TONE_BOY_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f469\U0001f3ff\u200d\U0001f467\U0001f3ff\u200d\U0001f466\U0001f3ff" - FLAG_FOR_MAHAICA_BERBICE_GY_MA = "\U0001f3f4\U000e0067\U000e0079\U000e006d\U000e0061\U000e007f" - FLAG_FOR_YAZD_IR_25 = "\U0001f3f4\U000e0069\U000e0072\U000e0032\U000e0035\U000e007f" - FLAG_FOR_WASIT_IQ_WA = "\U0001f3f4\U000e0069\U000e0071\U000e0077\U000e0061\U000e007f" - FLAG_FOR_SAINT_ANDREW_DM_02 = "\U0001f3f4\U000e0064\U000e006d\U000e0030\U000e0032\U000e007f" - FLAG_FOR_SUMATRA_ID_SM = "\U0001f3f4\U000e0069\U000e0064\U000e0073\U000e006d\U000e007f" - FLAG_FOR_ZALA_HU_ZA = "\U0001f3f4\U000e0068\U000e0075\U000e007a\U000e0061\U000e007f" - COUPLE_WITH_HEART_WOMAN_MEDIUM_DARK_SKIN_TONE_WOMAN_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fe" - FLAG_FOR_BOLIVAR_CO_BOL = "\U0001f3f4\U000e0063\U000e006f\U000e0062\U000e006f\U000e006c\U000e007f" - FLAG_FOR_SAINT_ELIZABETH_JM_11 = "\U0001f3f4\U000e006a\U000e006d\U000e0031\U000e0031\U000e007f" - FLAG_FOR_WESTERN_GH_WP = "\U0001f3f4\U000e0067\U000e0068\U000e0077\U000e0070\U000e007f" - FLAG_FOR_JERASH_JO_JA = "\U0001f3f4\U000e006a\U000e006f\U000e006a\U000e0061\U000e007f" - FLAG_FOR_TRELAWNY_JM_07 = "\U0001f3f4\U000e006a\U000e006d\U000e0030\U000e0037\U000e007f" - FLAG_FOR_MOLISE_IT_67 = "\U0001f3f4\U000e0069\U000e0074\U000e0036\U000e0037\U000e007f" - FLAG_FOR_NORTHEASTERN_IS_6 = "\U0001f3f4\U000e0069\U000e0073\U000e0036\U000e007f" - FLAG_FOR_AMMAN_JO_AM = "\U0001f3f4\U000e006a\U000e006f\U000e0061\U000e006d\U000e007f" - FLAG_FOR_UMM_AL_QUWAIN_AE_UQ = "\U0001f3f4\U000e0061\U000e0065\U000e0075\U000e0071\U000e007f" - FLAG_FOR_GOLESTAN_IR_27 = "\U0001f3f4\U000e0069\U000e0072\U000e0032\U000e0037\U000e007f" - FLAG_FOR_TAFILAH_JO_AT = "\U0001f3f4\U000e006a\U000e006f\U000e0061\U000e0074\U000e007f" - FLAG_FOR_KECSKEMET_HU_KM = "\U0001f3f4\U000e0068\U000e0075\U000e006b\U000e006d\U000e007f" - FLAG_FOR_UMBRIA_IT_55 = "\U0001f3f4\U000e0069\U000e0074\U000e0035\U000e0035\U000e007f" - FLAG_FOR_SAINT_ANN_JM_06 = "\U0001f3f4\U000e006a\U000e006d\U000e0030\U000e0036\U000e007f" - FLAG_FOR_SUD_HT_SD = "\U0001f3f4\U000e0068\U000e0074\U000e0073\U000e0064\U000e007f" - COUPLE_WITH_HEART_MAN_MEDIUM_DARK_SKIN_TONE_WOMAN = "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f469" - FLAG_FOR_CHAHARMAHAL_AND_BAKHTIARI_IR_08 = "\U0001f3f4\U000e0069\U000e0072\U000e0030\U000e0038\U000e007f" - FLAG_FOR_WESTMORELAND_JM_10 = "\U0001f3f4\U000e006a\U000e006d\U000e0031\U000e0030\U000e007f" - FLAG_FOR_LIGURIA_IT_42 = "\U0001f3f4\U000e0069\U000e0074\U000e0034\U000e0032\U000e007f" - FLAG_FOR_GRACIAS_A_DIOS_HN_GD = "\U0001f3f4\U000e0068\U000e006e\U000e0067\U000e0064\U000e007f" - FLAG_FOR_CLARENDON_JM_13 = "\U0001f3f4\U000e006a\U000e006d\U000e0031\U000e0033\U000e007f" - FLAG_FOR_AQABA_JO_AQ = "\U0001f3f4\U000e006a\U000e006f\U000e0061\U000e0071\U000e007f" - FLAG_FOR_IRBID_JO_IR = "\U0001f3f4\U000e006a\U000e006f\U000e0069\U000e0072\U000e007f" - FLAG_FOR_BALQA_JO_BA = "\U0001f3f4\U000e006a\U000e006f\U000e0062\U000e0061\U000e007f" - FLAG_FOR_ZARQA_JO_AZ = "\U0001f3f4\U000e006a\U000e006f\U000e0061\U000e007a\U000e007f" - FLAG_FOR_OSTROBOTHNIA_FI_12 = "\U0001f3f4\U000e0066\U000e0069\U000e0031\U000e0032\U000e007f" - FAMILY_MAN_DARK_SKIN_TONE_BABY_DARK_SKIN_TONE_BOY_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f476\U0001f3ff\u200d\U0001f466\U0001f3ff" - FLAG_FOR_MAFRAQ_JO_MA = "\U0001f3f4\U000e006a\U000e006f\U000e006d\U000e0061\U000e007f" - FLAG_FOR_ARAUCANIA_CL_AR = "\U0001f3f4\U000e0063\U000e006c\U000e0061\U000e0072\U000e007f" - FLAG_FOR_EAST_CM_ES = "\U0001f3f4\U000e0063\U000e006d\U000e0065\U000e0073\U000e007f" - FLAG_FOR_YAMAGUCHI_JP_35 = "\U0001f3f4\U000e006a\U000e0070\U000e0033\U000e0035\U000e007f" - FLAG_FOR_NAGANO_JP_20 = "\U0001f3f4\U000e006a\U000e0070\U000e0032\U000e0030\U000e007f" - FLAG_FOR_TOCHIGI_JP_09 = "\U0001f3f4\U000e006a\U000e0070\U000e0030\U000e0039\U000e007f" - FLAG_FOR_SHIMANE_JP_32 = "\U0001f3f4\U000e006a\U000e0070\U000e0033\U000e0032\U000e007f" - FLAG_FOR_ILAM_IR_05 = "\U0001f3f4\U000e0069\U000e0072\U000e0030\U000e0035\U000e007f" - FLAG_FOR_NARA_JP_29 = "\U0001f3f4\U000e006a\U000e0070\U000e0032\U000e0039\U000e007f" - FLAG_FOR_SPLIT_DALMATIA_HR_17 = "\U0001f3f4\U000e0068\U000e0072\U000e0031\U000e0037\U000e007f" - FLAG_FOR_EHIME_JP_38 = "\U0001f3f4\U000e006a\U000e0070\U000e0033\U000e0038\U000e007f" - FLAG_FOR_ORELLANA_EC_D = "\U0001f3f4\U000e0065\U000e0063\U000e0064\U000e007f" - FLAG_FOR_LA_RIOJA_ES_RI = "\U0001f3f4\U000e0065\U000e0073\U000e0072\U000e0069\U000e007f" - FLAG_FOR_IBARAKI_JP_08 = "\U0001f3f4\U000e006a\U000e0070\U000e0030\U000e0038\U000e007f" - FLAG_FOR_GRANMA_CU_12 = "\U0001f3f4\U000e0063\U000e0075\U000e0031\U000e0032\U000e007f" - FLAG_FOR_SHIZUOKA_JP_22 = "\U0001f3f4\U000e006a\U000e0070\U000e0032\U000e0032\U000e007f" - FLAG_FOR_IWATE_JP_03 = "\U0001f3f4\U000e006a\U000e0070\U000e0030\U000e0033\U000e007f" - FLAG_FOR_TOYAMA_JP_16 = "\U0001f3f4\U000e006a\U000e0070\U000e0031\U000e0036\U000e007f" - FLAG_FOR_MADABA_JO_MD = "\U0001f3f4\U000e006a\U000e006f\U000e006d\U000e0064\U000e007f" - FLAG_FOR_YAMANASHI_JP_19 = "\U0001f3f4\U000e006a\U000e0070\U000e0031\U000e0039\U000e007f" - FLAG_FOR_TOKUSHIMA_JP_36 = "\U0001f3f4\U000e006a\U000e0070\U000e0033\U000e0036\U000e007f" - FLAG_FOR_ISHIKAWA_JP_17 = "\U0001f3f4\U000e006a\U000e0070\U000e0031\U000e0037\U000e007f" - FLAG_FOR_KANAGAWA_JP_14 = "\U0001f3f4\U000e006a\U000e0070\U000e0031\U000e0034\U000e007f" - FLAG_FOR_MIE_JP_24 = "\U0001f3f4\U000e006a\U000e0070\U000e0032\U000e0034\U000e007f" - FLAG_FOR_NIIGATA_JP_15 = "\U0001f3f4\U000e006a\U000e0070\U000e0031\U000e0035\U000e007f" - FLAG_FOR_HYOGO_JP_28 = "\U0001f3f4\U000e006a\U000e0070\U000e0032\U000e0038\U000e007f" - FLAG_FOR_DHI_QAR_IQ_DQ = "\U0001f3f4\U000e0069\U000e0071\U000e0064\U000e0071\U000e007f" - FLAG_FOR_SHIGA_JP_25 = "\U0001f3f4\U000e006a\U000e0070\U000e0032\U000e0035\U000e007f" - FLAG_FOR_TOTTORI_JP_31 = "\U0001f3f4\U000e006a\U000e0070\U000e0033\U000e0031\U000e007f" - FLAG_FOR_OKAYAMA_JP_33 = "\U0001f3f4\U000e006a\U000e0070\U000e0033\U000e0033\U000e007f" - FLAG_FOR_CHIBA_JP_12 = "\U0001f3f4\U000e006a\U000e0070\U000e0031\U000e0032\U000e007f" - FLAG_FOR_MURANG_A_KE_29 = "\U0001f3f4\U000e006b\U000e0065\U000e0032\U000e0039\U000e007f" - FLAG_FOR_SOUTH_SINAI_EG_JS = "\U0001f3f4\U000e0065\U000e0067\U000e006a\U000e0073\U000e007f" - FLAG_FOR_KAJIADO_KE_10 = "\U0001f3f4\U000e006b\U000e0065\U000e0031\U000e0030\U000e007f" - FLAG_FOR_KOCHI_JP_39 = "\U0001f3f4\U000e006a\U000e0070\U000e0033\U000e0039\U000e007f" - FLAG_FOR_BASRA_IQ_BA = "\U0001f3f4\U000e0069\U000e0071\U000e0062\U000e0061\U000e007f" - FLAG_FOR_NAROK_KE_33 = "\U0001f3f4\U000e006b\U000e0065\U000e0033\U000e0033\U000e007f" - FLAG_FOR_FUKUOKA_JP_40 = "\U0001f3f4\U000e006a\U000e0070\U000e0034\U000e0030\U000e007f" - FLAG_FOR_BUNGOMA_KE_03 = "\U0001f3f4\U000e006b\U000e0065\U000e0030\U000e0033\U000e007f" - FLAG_FOR_GUNMA_JP_10 = "\U0001f3f4\U000e006a\U000e0070\U000e0031\U000e0030\U000e007f" - FLAG_FOR_MERU_KE_26 = "\U0001f3f4\U000e006b\U000e0065\U000e0032\U000e0036\U000e007f" - FLAG_FOR_ELGEYO_MARAKWET_KE_05 = "\U0001f3f4\U000e006b\U000e0065\U000e0030\U000e0035\U000e007f" - FLAG_FOR_LAIKIPIA_KE_20 = "\U0001f3f4\U000e006b\U000e0065\U000e0032\U000e0030\U000e007f" - FLAG_FOR_ISIOLO_KE_09 = "\U0001f3f4\U000e006b\U000e0065\U000e0030\U000e0039\U000e007f" - FLAG_FOR_KISII_KE_16 = "\U0001f3f4\U000e006b\U000e0065\U000e0031\U000e0036\U000e007f" - FLAG_FOR_SOUTHERN_PENINSULA_IS_2 = "\U0001f3f4\U000e0069\U000e0073\U000e0032\U000e007f" - FLAG_FOR_KAKAMEGA_KE_11 = "\U0001f3f4\U000e006b\U000e0065\U000e0031\U000e0031\U000e007f" - FLAG_FOR_MAKUENI_KE_23 = "\U0001f3f4\U000e006b\U000e0065\U000e0032\U000e0033\U000e007f" - FLAG_FOR_BITOLA_MK_04 = "\U0001f3f4\U000e006d\U000e006b\U000e0030\U000e0034\U000e007f" - FLAG_FOR_MIGORI_KE_27 = "\U0001f3f4\U000e006b\U000e0065\U000e0032\U000e0037\U000e007f" - FLAG_FOR_KILIFI_KE_14 = "\U0001f3f4\U000e006b\U000e0065\U000e0031\U000e0034\U000e007f" - FLAG_FOR_LAMU_KE_21 = "\U0001f3f4\U000e006b\U000e0065\U000e0032\U000e0031\U000e007f" - FLAG_FOR_MOMBASA_KE_28 = "\U0001f3f4\U000e006b\U000e0065\U000e0032\U000e0038\U000e007f" - FLAG_FOR_TATABANYA_HU_TB = "\U0001f3f4\U000e0068\U000e0075\U000e0074\U000e0062\U000e007f" - FLAG_FOR_BOMET_KE_02 = "\U0001f3f4\U000e006b\U000e0065\U000e0030\U000e0032\U000e007f" - FLAG_FOR_KUMAMOTO_JP_43 = "\U0001f3f4\U000e006a\U000e0070\U000e0034\U000e0033\U000e007f" - FLAG_FOR_KIAMBU_KE_13 = "\U0001f3f4\U000e006b\U000e0065\U000e0031\U000e0033\U000e007f" - FLAG_FOR_NANDI_KE_32 = "\U0001f3f4\U000e006b\U000e0065\U000e0033\U000e0032\U000e007f" - FLAG_FOR_YAMAGATA_JP_06 = "\U0001f3f4\U000e006a\U000e0070\U000e0030\U000e0036\U000e007f" - FLAG_FOR_BUSIA_KE_04 = "\U0001f3f4\U000e006b\U000e0065\U000e0030\U000e0034\U000e007f" - FLAG_FOR_MANDERA_KE_24 = "\U0001f3f4\U000e006b\U000e0065\U000e0032\U000e0034\U000e007f" - FLAG_FOR_TURKANA_KE_43 = "\U0001f3f4\U000e006b\U000e0065\U000e0034\U000e0033\U000e007f" - FLAG_FOR_NARYN_KG_N = "\U0001f3f4\U000e006b\U000e0067\U000e006e\U000e007f" - FLAG_FOR_SIAYA_KE_38 = "\U0001f3f4\U000e006b\U000e0065\U000e0033\U000e0038\U000e007f" - FLAG_FOR_UASIN_GISHU_KE_44 = "\U0001f3f4\U000e006b\U000e0065\U000e0034\U000e0034\U000e007f" - FLAG_FOR_JALAL_ABAD_KG_J = "\U0001f3f4\U000e006b\U000e0067\U000e006a\U000e007f" - FLAG_FOR_MONDULKIRI_KH_11 = "\U0001f3f4\U000e006b\U000e0068\U000e0031\U000e0031\U000e007f" - FAMILY_MAN_MEDIUM_DARK_SKIN_TONE_BOY_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f466\U0001f3fe" - FLAG_FOR_VIHIGA_KE_45 = "\U0001f3f4\U000e006b\U000e0065\U000e0034\U000e0035\U000e007f" - FLAG_FOR_WEST_POKOT_KE_47 = "\U0001f3f4\U000e006b\U000e0065\U000e0034\U000e0037\U000e007f" - FLAG_FOR_PREY_VENG_KH_14 = "\U0001f3f4\U000e006b\U000e0068\U000e0031\U000e0034\U000e007f" - FLAG_FOR_TALAS_KG_T = "\U0001f3f4\U000e006b\U000e0067\U000e0074\U000e007f" - FLAG_FOR_HOMA_BAY_KE_08 = "\U0001f3f4\U000e006b\U000e0065\U000e0030\U000e0038\U000e007f" - FLAG_FOR_KRATIE_KH_10 = "\U0001f3f4\U000e006b\U000e0068\U000e0031\U000e0030\U000e007f" - FLAG_FOR_TBONG_KHMUM_KH_25 = "\U0001f3f4\U000e006b\U000e0068\U000e0032\U000e0035\U000e007f" - FLAG_FOR_BISHKEK_KG_GB = "\U0001f3f4\U000e006b\U000e0067\U000e0067\U000e0062\U000e007f" - FLAG_FOR_ODDAR_MEANCHEY_KH_22 = "\U0001f3f4\U000e006b\U000e0068\U000e0032\U000e0032\U000e007f" - FLAG_FOR_PHNOM_PENH_KH_12 = "\U0001f3f4\U000e006b\U000e0068\U000e0031\U000e0032\U000e007f" - FLAG_FOR_NYERI_KE_36 = "\U0001f3f4\U000e006b\U000e0065\U000e0033\U000e0036\U000e007f" - FLAG_FOR_STUNG_TRENG_KH_19 = "\U0001f3f4\U000e006b\U000e0068\U000e0031\U000e0039\U000e007f" - FLAG_FOR_PURSAT_KH_15 = "\U0001f3f4\U000e006b\U000e0068\U000e0031\U000e0035\U000e007f" - FLAG_FOR_NYANDARUA_KE_35 = "\U0001f3f4\U000e006b\U000e0065\U000e0033\U000e0035\U000e007f" - FLAG_FOR_SAMBURU_KE_37 = "\U0001f3f4\U000e006b\U000e0065\U000e0033\U000e0037\U000e007f" - FLAG_FOR_MACHAKOS_KE_22 = "\U0001f3f4\U000e006b\U000e0065\U000e0032\U000e0032\U000e007f" - FLAG_FOR_BATTAMBANG_KH_2 = "\U0001f3f4\U000e006b\U000e0068\U000e0032\U000e007f" - FLAG_FOR_TAITA_TAVETA_KE_39 = "\U0001f3f4\U000e006b\U000e0065\U000e0033\U000e0039\U000e007f" - FLAG_FOR_TAKEO_KH_21 = "\U0001f3f4\U000e006b\U000e0068\U000e0032\U000e0031\U000e007f" - FLAG_FOR_SIHANOUKVILLE_KH_18 = "\U0001f3f4\U000e006b\U000e0068\U000e0031\U000e0038\U000e007f" - FLAG_FOR_NYAMIRA_KE_34 = "\U0001f3f4\U000e006b\U000e0065\U000e0033\U000e0034\U000e007f" - FLAG_FOR_KEP_KH_23 = "\U0001f3f4\U000e006b\U000e0068\U000e0032\U000e0033\U000e007f" - FLAG_FOR_THARAKA_NITHI_KE_41 = "\U0001f3f4\U000e006b\U000e0065\U000e0034\U000e0031\U000e007f" - FAMILY_MAN_MEDIUM_DARK_SKIN_TONE_WOMAN_MEDIUM_DARK_SKIN_TONE_BOY_MEDIUM_DARK_SKIN_TONE_BOY_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f469\U0001f3fe\u200d\U0001f466\U0001f3fe\u200d\U0001f466\U0001f3fe" - FLAG_FOR_SUCHITEPEQUEZ_GT_SU = "\U0001f3f4\U000e0067\U000e0074\U000e0073\U000e0075\U000e007f" - FLAG_FOR_AGUASCALIENTES_MX_AGU = "\U0001f3f4\U000e006d\U000e0078\U000e0061\U000e0067\U000e0075\U000e007f" - FLAG_FOR_COLON_HN_CL = "\U0001f3f4\U000e0068\U000e006e\U000e0063\U000e006c\U000e007f" - FLAG_FOR_KAMPOT_KH_7 = "\U0001f3f4\U000e006b\U000e0068\U000e0037\U000e007f" - FLAG_FOR_SIRNAK_TR_73 = "\U0001f3f4\U000e0074\U000e0072\U000e0037\U000e0033\U000e007f" - FLAG_FOR_ZAGREB_HR_21 = "\U0001f3f4\U000e0068\U000e0072\U000e0032\U000e0031\U000e007f" - FLAG_FOR_GRAND_ANSE_HT_GA = "\U0001f3f4\U000e0068\U000e0074\U000e0067\U000e0061\U000e007f" - FLAG_FOR_LISBON_PT_11 = "\U0001f3f4\U000e0070\U000e0074\U000e0031\U000e0031\U000e007f" - FAMILY_WOMAN_MEDIUM_LIGHT_SKIN_TONE_MAN_MEDIUM_LIGHT_SKIN_TONE_BABY_MEDIUM_LIGHT_SKIN_TONE_GIRL_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f468\U0001f3fc\u200d\U0001f476\U0001f3fc\u200d\U0001f467\U0001f3fc" - FLAG_FOR_CHAGANG_KP_04 = "\U0001f3f4\U000e006b\U000e0070\U000e0030\U000e0034\U000e007f" - FLAG_FOR_SOUTH_HAMGYONG_KP_08 = "\U0001f3f4\U000e006b\U000e0070\U000e0030\U000e0038\U000e007f" - FLAG_FOR_SOUTH_CHUNGCHEONG_KR_44 = "\U0001f3f4\U000e006b\U000e0072\U000e0034\U000e0034\U000e007f" - FLAG_FOR_MOUNT_ATHOS_GR_69 = "\U0001f3f4\U000e0067\U000e0072\U000e0036\U000e0039\U000e007f" - WOMAN_IN_BUSINESS_SUIT_LEVITATING_MEDIUM_SKIN_TONE = "\U0001f574\U0001f3fd\u200d\u2640\ufe0f" - FLAG_FOR_RASON_KP_13 = "\U0001f3f4\U000e006b\U000e0070\U000e0031\U000e0033\U000e007f" - FLAG_FOR_INCHEON_KR_28 = "\U0001f3f4\U000e006b\U000e0072\U000e0032\U000e0038\U000e007f" - FLAG_FOR_KAMPONG_THOM_KH_6 = "\U0001f3f4\U000e006b\U000e0068\U000e0036\U000e007f" - FLAG_FOR_EAST_MACEDONIA_AND_THRACE_GR_A = "\U0001f3f4\U000e0067\U000e0072\U000e0061\U000e007f" - FLAG_FOR_AIN_DEFLA_DZ_44 = "\U0001f3f4\U000e0064\U000e007a\U000e0034\U000e0034\U000e007f" - FLAG_FOR_NORTH_PYONGAN_KP_03 = "\U0001f3f4\U000e006b\U000e0070\U000e0030\U000e0033\U000e007f" - FLAG_FOR_GANGWON_KR_42 = "\U0001f3f4\U000e006b\U000e0072\U000e0034\U000e0032\U000e007f" - FLAG_FOR_KAMPONG_SPEU_KH_5 = "\U0001f3f4\U000e006b\U000e0068\U000e0035\U000e007f" - FLAG_FOR_CENTRAL_OSTROBOTHNIA_FI_07 = "\U0001f3f4\U000e0066\U000e0069\U000e0030\U000e0037\U000e007f" - FLAG_FOR_SOUTH_HWANGHAE_KP_05 = "\U0001f3f4\U000e006b\U000e0070\U000e0030\U000e0035\U000e007f" - FLAG_FOR_ULSAN_KR_31 = "\U0001f3f4\U000e006b\U000e0072\U000e0033\U000e0031\U000e007f" - FLAG_FOR_NORTH_BANK_DIVISION_GM_N = "\U0001f3f4\U000e0067\U000e006d\U000e006e\U000e007f" - COUPLE_WITH_HEART_WOMAN_DARK_SKIN_TONE_MAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc" - FLAG_FOR_AL_FARWANIYAH_KW_FA = "\U0001f3f4\U000e006b\U000e0077\U000e0066\U000e0061\U000e007f" - FLAG_FOR_KARAGANDY_KZ_KAR = "\U0001f3f4\U000e006b\U000e007a\U000e006b\U000e0061\U000e0072\U000e007f" - FLAG_FOR_SERMERSOOQ_GL_SM = "\U0001f3f4\U000e0067\U000e006c\U000e0073\U000e006d\U000e007f" - FLAG_FOR_PHONGSALY_LA_PH = "\U0001f3f4\U000e006c\U000e0061\U000e0070\U000e0068\U000e007f" - FLAG_FOR_OUDOMXAY_LA_OU = "\U0001f3f4\U000e006c\U000e0061\U000e006f\U000e0075\U000e007f" - FLAG_FOR_ATYRAU_KZ_ATY = "\U0001f3f4\U000e006b\U000e007a\U000e0061\U000e0074\U000e0079\U000e007f" - FLAG_FOR_LUANG_NAMTHA_LA_LM = "\U0001f3f4\U000e006c\U000e0061\U000e006c\U000e006d\U000e007f" - FLAG_FOR_LUANG_PRABANG_LA_LP = "\U0001f3f4\U000e006c\U000e0061\U000e006c\U000e0070\U000e007f" - FAMILY_MAN_DARK_SKIN_TONE_WOMAN_DARK_SKIN_TONE_BABY_DARK_SKIN_TONE_BOY_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f469\U0001f3ff\u200d\U0001f476\U0001f3ff\u200d\U0001f466\U0001f3ff" - FLAG_FOR_HONG_KONG_SAR_CHINA_CN_91 = "\U0001f3f4\U000e0063\U000e006e\U000e0039\U000e0031\U000e007f" - FLAG_FOR_VIENTIANE_LA_VT = "\U0001f3f4\U000e006c\U000e0061\U000e0076\U000e0074\U000e007f" - FLAG_FOR_NORTH_CHUNGCHEONG_KR_43 = "\U0001f3f4\U000e006b\U000e0072\U000e0034\U000e0033\U000e007f" - FLAG_FOR_JAMBYL_KZ_ZHA = "\U0001f3f4\U000e006b\U000e007a\U000e007a\U000e0068\U000e0061\U000e007f" - FLAG_FOR_QOM_IR_26 = "\U0001f3f4\U000e0069\U000e0072\U000e0032\U000e0036\U000e007f" - FLAG_FOR_MUBARAK_AL_KABEER_KW_MU = "\U0001f3f4\U000e006b\U000e0077\U000e006d\U000e0075\U000e007f" - FLAG_FOR_KYZYLORDA_KZ_KZY = "\U0001f3f4\U000e006b\U000e007a\U000e006b\U000e007a\U000e0079\U000e007f" - FLAG_FOR_AL_JAHRA_KW_JA = "\U0001f3f4\U000e006b\U000e0077\U000e006a\U000e0061\U000e007f" - FLAG_FOR_AL_ASIMAH_KW_KU = "\U0001f3f4\U000e006b\U000e0077\U000e006b\U000e0075\U000e007f" - FLAG_FOR_GYEONGGI_KR_41 = "\U0001f3f4\U000e006b\U000e0072\U000e0034\U000e0031\U000e007f" - FLAG_FOR_PAVLODAR_KZ_PAV = "\U0001f3f4\U000e006b\U000e007a\U000e0070\U000e0061\U000e0076\U000e007f" - FLAG_FOR_ANJOUAN_KM_A = "\U0001f3f4\U000e006b\U000e006d\U000e0061\U000e007f" - FLAG_FOR_CHAMPASAK_LA_CH = "\U0001f3f4\U000e006c\U000e0061\U000e0063\U000e0068\U000e007f" - FLAG_FOR_SEJONG_KR_50 = "\U0001f3f4\U000e006b\U000e0072\U000e0035\U000e0030\U000e007f" - FLAG_FOR_NORTH_KAZAKHSTAN_KZ_SEV = "\U0001f3f4\U000e006b\U000e007a\U000e0073\U000e0065\U000e0076\U000e007f" - FLAG_FOR_HANOVER_JM_09 = "\U0001f3f4\U000e006a\U000e006d\U000e0030\U000e0039\U000e007f" - FLAG_FOR_UPPER_RIVER_DIVISION_GM_U = "\U0001f3f4\U000e0067\U000e006d\U000e0075\U000e007f" - FLAG_FOR_MAUREN_LI_04 = "\U0001f3f4\U000e006c\U000e0069\U000e0030\U000e0034\U000e007f" - FLAG_FOR_BAALBEK_HERMEL_LB_BH = "\U0001f3f4\U000e006c\U000e0062\U000e0062\U000e0068\U000e007f" - FLAG_FOR_NORTH_CENTRAL_LK_7 = "\U0001f3f4\U000e006c\U000e006b\U000e0037\U000e007f" - FLAG_FOR_SOUFRIERE_LC_10 = "\U0001f3f4\U000e006c\U000e0063\U000e0031\U000e0030\U000e007f" - FLAG_FOR_NORTHERN_LK_4 = "\U0001f3f4\U000e006c\U000e006b\U000e0034\U000e007f" - FLAG_FOR_TRIESEN_LI_09 = "\U0001f3f4\U000e006c\U000e0069\U000e0030\U000e0039\U000e007f" - FLAG_FOR_BJELOVAR_BILOGORA_HR_07 = "\U0001f3f4\U000e0068\U000e0072\U000e0030\U000e0037\U000e007f" - FLAG_FOR_SAINYABULI_LA_XA = "\U0001f3f4\U000e006c\U000e0061\U000e0078\U000e0061\U000e007f" - FLAG_FOR_SOUTHERN_SAVONIA_FI_04 = "\U0001f3f4\U000e0066\U000e0069\U000e0030\U000e0034\U000e007f" - FLAG_FOR_UVA_LK_8 = "\U0001f3f4\U000e006c\U000e006b\U000e0038\U000e007f" - FLAG_FOR_GAMPRIN_LI_03 = "\U0001f3f4\U000e006c\U000e0069\U000e0030\U000e0033\U000e007f" - FLAG_FOR_ESCHEN_LI_02 = "\U0001f3f4\U000e006c\U000e0069\U000e0030\U000e0032\U000e007f" - FLAG_FOR_SOUTH_LB_JA = "\U0001f3f4\U000e006c\U000e0062\U000e006a\U000e0061\U000e007f" - FLAG_FOR_NORTH_LB_AS = "\U0001f3f4\U000e006c\U000e0062\U000e0061\U000e0073\U000e007f" - FLAG_FOR_RUGGELL_LI_06 = "\U0001f3f4\U000e006c\U000e0069\U000e0030\U000e0036\U000e007f" - FLAG_FOR_NORTH_WESTERN_LK_6 = "\U0001f3f4\U000e006c\U000e006b\U000e0036\U000e007f" - FLAG_FOR_GROS_ISLET_LC_06 = "\U0001f3f4\U000e006c\U000e0063\U000e0030\U000e0036\U000e007f" - FLAG_FOR_NABATIEH_LB_NA = "\U0001f3f4\U000e006c\U000e0062\U000e006e\U000e0061\U000e007f" - FLAG_FOR_GRANDE_COMORE_KM_G = "\U0001f3f4\U000e006b\U000e006d\U000e0067\U000e007f" - FLAG_FOR_CASTRIES_LC_02 = "\U0001f3f4\U000e006c\U000e0063\U000e0030\U000e0032\U000e007f" - FLAG_FOR_VADUZ_LI_11 = "\U0001f3f4\U000e006c\U000e0069\U000e0031\U000e0031\U000e007f" - FLAG_FOR_KOSTANAY_KZ_KUS = "\U0001f3f4\U000e006b\U000e007a\U000e006b\U000e0075\U000e0073\U000e007f" - FLAG_FOR_CHOISEUL_LC_03 = "\U0001f3f4\U000e006c\U000e0063\U000e0030\U000e0033\U000e007f" - FLAG_FOR_CENTRAL_LK_2 = "\U0001f3f4\U000e006c\U000e006b\U000e0032\U000e007f" - FLAG_FOR_DENNERY_LC_05 = "\U0001f3f4\U000e006c\U000e0063\U000e0030\U000e0035\U000e007f" - FLAG_FOR_GARISSA_KE_07 = "\U0001f3f4\U000e006b\U000e0065\U000e0030\U000e0037\U000e007f" - FLAG_FOR_ANSE_LA_RAYE_LC_01 = "\U0001f3f4\U000e006c\U000e0063\U000e0030\U000e0031\U000e007f" - FLAG_FOR_RYANGGANG_KP_10 = "\U0001f3f4\U000e006b\U000e0070\U000e0031\U000e0030\U000e007f" - FLAG_FOR_JONAVA_LT_10 = "\U0001f3f4\U000e006c\U000e0074\U000e0031\U000e0030\U000e007f" - FLAG_FOR_MOHALE_S_HOEK_LS_F = "\U0001f3f4\U000e006c\U000e0073\U000e0066\U000e007f" - FLAG_FOR_KALVARIJA_LT_14 = "\U0001f3f4\U000e006c\U000e0074\U000e0031\U000e0034\U000e007f" - FLAG_FOR_GBARPOLU_LR_GP = "\U0001f3f4\U000e006c\U000e0072\U000e0067\U000e0070\U000e007f" - TAG_LATIN_CAPITAL_LETTER_F = "\U000e0046" - FLAG_FOR_NORTH_KIVU_CD_NK = "\U0001f3f4\U000e0063\U000e0064\U000e006e\U000e006b\U000e007f" - FLAG_FOR_IGNALINA_LT_09 = "\U0001f3f4\U000e006c\U000e0074\U000e0030\U000e0039\U000e007f" - FLAG_FOR_MAFETENG_LS_E = "\U0001f3f4\U000e006c\U000e0073\U000e0065\U000e007f" - UNIVERSAL_RECYCLING_SYMBOL = "\u2672" - FLAG_FOR_BIHAR_IN_BR = "\U0001f3f4\U000e0069\U000e006e\U000e0062\U000e0072\U000e007f" - FLAG_FOR_THABA_TSEKA_LS_K = "\U0001f3f4\U000e006c\U000e0073\U000e006b\U000e007f" - FLAG_FOR_ALYTUS_LT_03 = "\U0001f3f4\U000e006c\U000e0074\U000e0030\U000e0033\U000e007f" - FLAG_FOR_DRUSKININKAI_LT_07 = "\U0001f3f4\U000e006c\U000e0074\U000e0030\U000e0037\U000e007f" - FLAG_FOR_MOKHOTLONG_LS_J = "\U0001f3f4\U000e006c\U000e0073\U000e006a\U000e007f" - FLAG_FOR_KAUNO_MUNICIPALITY_LT_15 = "\U0001f3f4\U000e006c\U000e0074\U000e0031\U000e0035\U000e007f" - FLAG_FOR_JONISKIS_LT_11 = "\U0001f3f4\U000e006c\U000e0074\U000e0031\U000e0031\U000e007f" - FLAG_FOR_QACHA_S_NEK_LS_H = "\U0001f3f4\U000e006c\U000e0073\U000e0068\U000e007f" - FLAG_FOR_BEREA_LS_D = "\U0001f3f4\U000e006c\U000e0073\U000e0064\U000e007f" - FLAG_FOR_NIMBA_LR_NI = "\U0001f3f4\U000e006c\U000e0072\U000e006e\U000e0069\U000e007f" - FLAG_FOR_ELEKTRENAI_LT_08 = "\U0001f3f4\U000e006c\U000e0074\U000e0030\U000e0038\U000e007f" - FLAG_FOR_ERBIL_IQ_AR = "\U0001f3f4\U000e0069\U000e0071\U000e0061\U000e0072\U000e007f" - FLAG_FOR_KANGWON_KP_07 = "\U0001f3f4\U000e006b\U000e0070\U000e0030\U000e0037\U000e007f" - FLAG_FOR_KAISIADORYS_LT_13 = "\U0001f3f4\U000e006c\U000e0074\U000e0031\U000e0033\U000e007f" - FLAG_FOR_BUTHA_BUTHE_LS_B = "\U0001f3f4\U000e006c\U000e0073\U000e0062\U000e007f" - FLAG_FOR_BIRSTONAS_LT_05 = "\U0001f3f4\U000e006c\U000e0074\U000e0030\U000e0035\U000e007f" - FLAG_FOR_KIRINYAGA_KE_15 = "\U0001f3f4\U000e006b\U000e0065\U000e0031\U000e0035\U000e007f" - FLAG_FOR_ANYKSCIAI_LT_04 = "\U0001f3f4\U000e006c\U000e0074\U000e0030\U000e0034\U000e007f" - FLAG_FOR_JURBARKAS_LT_12 = "\U0001f3f4\U000e006c\U000e0074\U000e0031\U000e0032\U000e007f" - FLAG_FOR_UTENA_LT_54 = "\U0001f3f4\U000e006c\U000e0074\U000e0035\U000e0034\U000e007f" - FLAG_FOR_PASVALYS_LT_34 = "\U0001f3f4\U000e006c\U000e0074\U000e0033\U000e0034\U000e007f" - FLAG_FOR_PRIENAI_LT_36 = "\U0001f3f4\U000e006c\U000e0074\U000e0033\U000e0036\U000e007f" - FLAG_FOR_KUPISKIS_LT_23 = "\U0001f3f4\U000e006c\U000e0074\U000e0032\U000e0033\U000e007f" - FLAG_FOR_ROKISKIS_LT_40 = "\U0001f3f4\U000e006c\U000e0074\U000e0034\U000e0030\U000e007f" - FLAG_FOR_SIAULIU_MUNICIPALITY_LT_43 = "\U0001f3f4\U000e006c\U000e0074\U000e0034\U000e0033\U000e007f" - FLAG_FOR_KAZLU_RUDA_LT_17 = "\U0001f3f4\U000e006c\U000e0074\U000e0031\U000e0037\U000e007f" - FLAG_FOR_TELSIAI_LT_51 = "\U0001f3f4\U000e006c\U000e0074\U000e0035\U000e0031\U000e007f" - FLAG_FOR_KLAIPEDA_LT_21 = "\U0001f3f4\U000e006c\U000e0074\U000e0032\U000e0031\U000e007f" - FLAG_FOR_KEDAINIAI_LT_18 = "\U0001f3f4\U000e006c\U000e0074\U000e0031\U000e0038\U000e007f" - FLAG_FOR_SIAULIAI_LT_44 = "\U0001f3f4\U000e006c\U000e0074\U000e0034\U000e0034\U000e007f" - FLAG_FOR_PALANGA_LT_31 = "\U0001f3f4\U000e006c\U000e0074\U000e0033\U000e0031\U000e007f" - FLAG_FOR_PANEVEZYS_LT_33 = "\U0001f3f4\U000e006c\U000e0074\U000e0033\U000e0033\U000e007f" - FLAG_FOR_SILALE_LT_45 = "\U0001f3f4\U000e006c\U000e0074\U000e0034\U000e0035\U000e007f" - FLAG_FOR_SAKIAI_LT_41 = "\U0001f3f4\U000e006c\U000e0074\U000e0034\U000e0031\U000e007f" - FLAG_FOR_TAURAGE_LT_50 = "\U0001f3f4\U000e006c\U000e0074\U000e0035\U000e0030\U000e007f" - FLAG_FOR_MOLETAI_LT_27 = "\U0001f3f4\U000e006c\U000e0074\U000e0032\U000e0037\U000e007f" - FLAG_FOR_SKUODAS_LT_48 = "\U0001f3f4\U000e006c\U000e0074\U000e0034\U000e0038\U000e007f" - FLAG_FOR_UKMERGE_LT_53 = "\U0001f3f4\U000e006c\U000e0074\U000e0035\U000e0033\U000e007f" - FLAG_FOR_VILKAVISKIS_LT_56 = "\U0001f3f4\U000e006c\U000e0074\U000e0035\U000e0036\U000e007f" - FLAG_FOR_PAGEGIAI_LT_29 = "\U0001f3f4\U000e006c\U000e0074\U000e0032\U000e0039\U000e007f" - FLAG_FOR_RIETAVAS_LT_39 = "\U0001f3f4\U000e006c\U000e0074\U000e0033\U000e0039\U000e007f" - FLAG_FOR_SILUTE_LT_46 = "\U0001f3f4\U000e006c\U000e0074\U000e0034\U000e0036\U000e007f" - FLAG_FOR_SVENCIONYS_LT_49 = "\U0001f3f4\U000e006c\U000e0074\U000e0034\U000e0039\U000e007f" - FLAG_FOR_GRAND_BASSA_LR_GB = "\U0001f3f4\U000e006c\U000e0072\U000e0067\U000e0062\U000e007f" - FLAG_FOR_TRAKAI_LT_52 = "\U0001f3f4\U000e006c\U000e0074\U000e0035\U000e0032\U000e007f" - FLAG_FOR_LAZDIJAI_LT_24 = "\U0001f3f4\U000e006c\U000e0074\U000e0032\U000e0034\U000e007f" - FLAG_FOR_MARIJAMPOLE_LT_25 = "\U0001f3f4\U000e006c\U000e0074\U000e0032\U000e0035\U000e007f" - FLAG_FOR_VARENA_LT_55 = "\U0001f3f4\U000e006c\U000e0074\U000e0035\U000e0035\U000e007f" - FLAG_FOR_MAZEIKIAI_LT_26 = "\U0001f3f4\U000e006c\U000e0074\U000e0032\U000e0036\U000e007f" - FLAG_FOR_PLUNGE_LT_35 = "\U0001f3f4\U000e006c\U000e0074\U000e0033\U000e0035\U000e007f" - FLAG_FOR_RADVILISKIS_LT_37 = "\U0001f3f4\U000e006c\U000e0074\U000e0033\U000e0037\U000e007f" - FLAG_FOR_KRETINGA_LT_22 = "\U0001f3f4\U000e006c\U000e0074\U000e0032\U000e0032\U000e007f" - FLAG_FOR_KELME_LT_19 = "\U0001f3f4\U000e006c\U000e0074\U000e0031\U000e0039\U000e007f" - FLAG_FOR_SIRVINTOS_LT_47 = "\U0001f3f4\U000e006c\U000e0074\U000e0034\U000e0037\U000e007f" - FLAG_FOR_NERINGA_LT_28 = "\U0001f3f4\U000e006c\U000e0074\U000e0032\U000e0038\U000e007f" - FLAG_FOR_AIZPUTE_LV_003 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0030\U000e0033\U000e007f" - FLAG_FOR_GREVENMACHER_LU_GR = "\U0001f3f4\U000e006c\U000e0075\U000e0067\U000e0072\U000e007f" - FAMILY_WOMAN_MEDIUM_SKIN_TONE_MAN_MEDIUM_SKIN_TONE_BABY_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f468\U0001f3fd\u200d\U0001f476\U0001f3fd" - FLAG_FOR_BALDONE_LV_013 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0031\U000e0033\U000e007f" - FLAG_FOR_AKNISTE_LV_004 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0030\U000e0034\U000e007f" - FLAG_FOR_UTENA_COUNTY_LT_UT = "\U0001f3f4\U000e006c\U000e0074\U000e0075\U000e0074\U000e007f" - FLAG_FOR_ALYTUS_COUNTY_LT_AL = "\U0001f3f4\U000e006c\U000e0074\U000e0061\U000e006c\U000e007f" - FAMILY_WOMAN_MEDIUM_LIGHT_SKIN_TONE_GIRL_MEDIUM_LIGHT_SKIN_TONE_GIRL_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f467\U0001f3fc\u200d\U0001f467\U0001f3fc" - FLAG_FOR_VISAGINAS_LT_59 = "\U0001f3f4\U000e006c\U000e0074\U000e0035\U000e0039\U000e007f" - KISS_WOMAN_MEDIUM_LIGHT_SKIN_TONE_MAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc" - FLAG_FOR_KLAIPEDA_COUNTY_LT_KL = "\U0001f3f4\U000e006c\U000e0074\U000e006b\U000e006c\U000e007f" - FLAG_FOR_REMICH_LU_RM = "\U0001f3f4\U000e006c\U000e0075\U000e0072\U000e006d\U000e007f" - FLAG_FOR_RATANAKIRI_KH_16 = "\U0001f3f4\U000e006b\U000e0068\U000e0031\U000e0036\U000e007f" - FLAG_FOR_AMATA_LV_008 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0030\U000e0038\U000e007f" - FLAG_FOR_RASEINIAI_LT_38 = "\U0001f3f4\U000e006c\U000e0074\U000e0033\U000e0038\U000e007f" - FLAG_FOR_MERSCH_LU_ME = "\U0001f3f4\U000e006c\U000e0075\U000e006d\U000e0065\U000e007f" - FLAG_FOR_VIANDEN_LU_VD = "\U0001f3f4\U000e006c\U000e0075\U000e0076\U000e0064\U000e007f" - FLAG_FOR_CLERVAUX_LU_CL = "\U0001f3f4\U000e006c\U000e0075\U000e0063\U000e006c\U000e007f" - FLAG_FOR_VILNIUS_COUNTY_LT_VL = "\U0001f3f4\U000e006c\U000e0074\U000e0076\U000e006c\U000e007f" - FLAG_FOR_ECHTERNACH_LU_EC = "\U0001f3f4\U000e006c\U000e0075\U000e0065\U000e0063\U000e007f" - FLAG_FOR_DIEKIRCH_LU_DI = "\U0001f3f4\U000e006c\U000e0075\U000e0064\U000e0069\U000e007f" - FLAG_FOR_SIAULIAI_COUNTY_LT_SA = "\U0001f3f4\U000e006c\U000e0074\U000e0073\U000e0061\U000e007f" - FLAG_FOR_TAURAGE_COUNTY_LT_TA = "\U0001f3f4\U000e006c\U000e0074\U000e0074\U000e0061\U000e007f" - FLAG_FOR_MARIJAMPOLE_COUNTY_LT_MR = "\U0001f3f4\U000e006c\U000e0074\U000e006d\U000e0072\U000e007f" - FLAG_FOR_ADAZI_LV_011 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0031\U000e0031\U000e007f" - FLAG_FOR_ALUKSNE_LV_007 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0030\U000e0037\U000e007f" - FLAG_FOR_KULDIGA_LV_050 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0035\U000e0030\U000e007f" - FLAG_FOR_BALVI_LV_015 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0031\U000e0035\U000e007f" - FLAG_FOR_LABORIE_LC_07 = "\U0001f3f4\U000e006c\U000e0063\U000e0030\U000e0037\U000e007f" - FLAG_FOR_KARSAVA_LV_044 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0034\U000e0034\U000e007f" - FLAG_FOR_KEGUMS_LV_051 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0035\U000e0031\U000e007f" - FLAG_FOR_BALTINAVA_LV_014 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0031\U000e0034\U000e007f" - FLAG_FOR_KRUSTPILS_LV_049 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0034\U000e0039\U000e007f" - FLAG_FOR_GROBINA_LV_032 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0033\U000e0032\U000e007f" - FLAG_FOR_BAUSKA_LV_016 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0031\U000e0036\U000e007f" - FLAG_FOR_DOBELE_LV_026 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0032\U000e0036\U000e007f" - FLAG_FOR_BROCENI_LV_018 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0031\U000e0038\U000e007f" - FLAG_FOR_IECAVA_LV_034 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0033\U000e0034\U000e007f" - FLAG_FOR_CESVAINE_LV_021 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0032\U000e0031\U000e007f" - FLAG_FOR_AJLOUN_JO_AJ = "\U0001f3f4\U000e006a\U000e006f\U000e0061\U000e006a\U000e007f" - FLAG_FOR_JAUNJELGAVA_LV_038 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0033\U000e0038\U000e007f" - FLAG_FOR_KOCENI_LV_045 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0034\U000e0035\U000e007f" - FLAG_FOR_GARKALNE_LV_031 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0033\U000e0031\U000e007f" - FLAG_FOR_AIZKRAUKLE_LV_002 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0030\U000e0032\U000e007f" - FLAG_FOR_CIBLA_LV_023 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0032\U000e0033\U000e007f" - FLAG_FOR_JAUNPIEBALGA_LV_039 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0033\U000e0039\U000e007f" - FLAG_FOR_DUNDAGA_LV_027 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0032\U000e0037\U000e007f" - FLAG_FOR_CESIS_LV_022 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0032\U000e0032\U000e007f" - FLAG_FOR_KRIMULDA_LV_048 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0034\U000e0038\U000e007f" - FLAG_FOR_CARNIKAVA_LV_020 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0032\U000e0030\U000e007f" - FLAG_FOR_IKSKILE_LV_035 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0033\U000e0035\U000e007f" - FLAG_FOR_GULBENE_LV_033 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0033\U000e0033\U000e007f" - FLAG_FOR_KRASLAVA_LV_047 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0034\U000e0037\U000e007f" - FLAG_FOR_JAUNPILS_LV_040 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0034\U000e0030\U000e007f" - FLAG_FOR_BURTNIEKI_LV_019 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0031\U000e0039\U000e007f" - FLAG_FOR_BEVERINA_LV_017 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0031\U000e0037\U000e007f" - FLAG_FOR_KANDAVA_LV_043 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0034\U000e0033\U000e007f" - FLAG_FOR_LIGATNE_LV_055 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0035\U000e0035\U000e007f" - FLAG_FOR_SAULKRASTI_LV_089 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0038\U000e0039\U000e007f" - FLAG_FOR_SALASPILS_LV_087 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0038\U000e0037\U000e007f" - FLAG_FOR_PRIEKULI_LV_075 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0037\U000e0035\U000e007f" - FLAG_FOR_RUCAVA_LV_081 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0038\U000e0031\U000e007f" - FLAG_FOR_NAUKSENI_LV_064 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0036\U000e0034\U000e007f" - FLAG_FOR_PRIEKULE_LV_074 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0037\U000e0034\U000e007f" - FLAG_FOR_SEJA_LV_090 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0039\U000e0030\U000e007f" - FLAG_FOR_NERETA_LV_065 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0036\U000e0035\U000e007f" - FLAG_FOR_PARGAUJA_LV_070 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0037\U000e0030\U000e007f" - FLAG_FOR_MARUPE_LV_062 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0036\U000e0032\U000e007f" - FLAG_FOR_PAVILOSTA_LV_071 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0037\U000e0031\U000e007f" - FLAG_FOR_OLAINE_LV_068 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0036\U000e0038\U000e007f" - FLAG_FOR_LIMBAZI_LV_054 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0035\U000e0034\U000e007f" - FLAG_FOR_RUJIENA_LV_084 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0038\U000e0034\U000e007f" - FLAG_FOR_PLAVINAS_LV_072 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0037\U000e0032\U000e007f" - FLAG_FOR_RUNDALE_LV_083 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0038\U000e0033\U000e007f" - FLAG_FOR_PREILI_LV_073 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0037\U000e0033\U000e007f" - FLAG_FOR_RUGAJI_LV_082 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0038\U000e0032\U000e007f" - FLAG_FOR_MERSRAGS_LV_063 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0036\U000e0033\U000e007f" - FLAG_FOR_SALDUS_LV_088 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0038\U000e0038\U000e007f" - FLAG_FOR_LUDZA_LV_058 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0035\U000e0038\U000e007f" - FLAG_FOR_LIVANI_LV_056 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0035\U000e0036\U000e007f" - FLAG_FOR_ROPAZI_LV_080 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0038\U000e0030\U000e007f" - FLAG_FOR_KOH_KONG_KH_9 = "\U0001f3f4\U000e006b\U000e0068\U000e0039\U000e007f" - FLAG_FOR_RIEBINI_LV_078 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0037\U000e0038\U000e007f" - FLAG_FOR_LIELVARDE_LV_053 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0035\U000e0033\U000e007f" - FLAG_FOR_KEKAVA_LV_052 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0035\U000e0032\U000e007f" - FLAG_FOR_NICA_LV_066 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0036\U000e0036\U000e007f" - FLAG_FOR_LUBANA_LV_057 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0035\U000e0037\U000e007f" - FLAG_FOR_GRAND_EST_FR_GES = "\U0001f3f4\U000e0066\U000e0072\U000e0067\U000e0065\U000e0073\U000e007f" - FLAG_FOR_RAUNA_LV_076 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0037\U000e0036\U000e007f" - FLAG_FOR_ROJA_LV_079 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0037\U000e0039\U000e007f" - FLAG_FOR_MALPILS_LV_061 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0036\U000e0031\U000e007f" - FLAG_FOR_SMILTENE_LV_094 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0039\U000e0034\U000e007f" - FLAG_FOR_VARAKLANI_LV_102 = "\U0001f3f4\U000e006c\U000e0076\U000e0031\U000e0030\U000e0032\U000e007f" - FLAG_FOR_JABAL_AL_GHARBI_LY_JG = "\U0001f3f4\U000e006c\U000e0079\U000e006a\U000e0067\U000e007f" - FLAG_FOR_VARKAVA_LV_103 = "\U0001f3f4\U000e006c\U000e0076\U000e0031\U000e0030\U000e0033\U000e007f" - FLAG_FOR_VIESITE_LV_107 = "\U0001f3f4\U000e006c\U000e0076\U000e0031\U000e0030\U000e0037\U000e007f" - FLAG_FOR_DAUGAVPILS_LV_DGV = "\U0001f3f4\U000e006c\U000e0076\U000e0064\U000e0067\U000e0076\U000e007f" - FLAG_FOR_KUFRA_LY_KF = "\U0001f3f4\U000e006c\U000e0079\U000e006b\U000e0066\U000e007f" - FLAG_FOR_VILANI_LV_109 = "\U0001f3f4\U000e006c\U000e0076\U000e0031\U000e0030\U000e0039\U000e007f" - FLAG_FOR_JURMALA_LV_JUR = "\U0001f3f4\U000e006c\U000e0076\U000e006a\U000e0075\U000e0072\U000e007f" - FLAG_FOR_VILAKA_LV_108 = "\U0001f3f4\U000e006c\U000e0076\U000e0031\U000e0030\U000e0038\U000e007f" - FLAG_FOR_VENTSPILS_LV_VEN = "\U0001f3f4\U000e006c\U000e0076\U000e0076\U000e0065\U000e006e\U000e007f" - FLAG_FOR_MURQUB_LY_MB = "\U0001f3f4\U000e006c\U000e0079\U000e006d\U000e0062\U000e007f" - FLAG_FOR_VECPIEBALGA_LV_104 = "\U0001f3f4\U000e006c\U000e0076\U000e0031\U000e0030\U000e0034\U000e007f" - FLAG_FOR_TUKUMS_LV_099 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0039\U000e0039\U000e007f" - FLAG_FOR_BENGHAZI_LY_BA = "\U0001f3f4\U000e006c\U000e0079\U000e0062\U000e0061\U000e007f" - FLAG_FOR_LIEPAJA_LV_LPX = "\U0001f3f4\U000e006c\U000e0076\U000e006c\U000e0070\U000e0078\U000e007f" - FLAG_FOR_JABAL_AL_AKHDAR_LY_JA = "\U0001f3f4\U000e006c\U000e0079\U000e006a\U000e0061\U000e007f" - FLAG_FOR_ZARASAI_LT_60 = "\U0001f3f4\U000e006c\U000e0074\U000e0036\U000e0030\U000e007f" - FLAG_FOR_VECUMNIEKI_LV_105 = "\U0001f3f4\U000e006c\U000e0076\U000e0031\U000e0030\U000e0035\U000e007f" - FLAG_FOR_GHAT_LY_GT = "\U0001f3f4\U000e006c\U000e0079\U000e0067\U000e0074\U000e007f" - FLAG_FOR_TALSI_LV_097 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0039\U000e0037\U000e007f" - FLAG_FOR_JELGAVA_LV_JEL = "\U0001f3f4\U000e006c\U000e0076\U000e006a\U000e0065\U000e006c\U000e007f" - FLAG_FOR_STRENCI_LV_096 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0039\U000e0036\U000e007f" - FLAG_FOR_NORTH_HAMGYONG_KP_09 = "\U0001f3f4\U000e006b\U000e0070\U000e0030\U000e0039\U000e007f" - FLAG_FOR_VALMIERA_LV_VMR = "\U0001f3f4\U000e006c\U000e0076\U000e0076\U000e006d\U000e0072\U000e007f" - FLAG_FOR_TERVETE_LV_098 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0039\U000e0038\U000e007f" - FLAG_FOR_REZEKNE_LV_REZ = "\U0001f3f4\U000e006c\U000e0076\U000e0072\U000e0065\U000e007a\U000e007f" - FLAG_FOR_ZILUPE_LV_110 = "\U0001f3f4\U000e006c\U000e0076\U000e0031\U000e0031\U000e0030\U000e007f" - FLAG_FOR_JEKABPILS_LV_JKB = "\U0001f3f4\U000e006c\U000e0076\U000e006a\U000e006b\U000e0062\U000e007f" - FLAG_FOR_SIGULDA_LV_091 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0039\U000e0031\U000e007f" - FLAG_FOR_STOPINI_LV_095 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0039\U000e0035\U000e007f" - FLAG_FOR_ENGURE_LV_029 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0032\U000e0039\U000e007f" - FLAG_FOR_WADI_AL_HAYAA_LY_WD = "\U0001f3f4\U000e006c\U000e0079\U000e0077\U000e0064\U000e007f" - FLAG_FOR_AL_WAHAT_LY_WA = "\U0001f3f4\U000e006c\U000e0079\U000e0077\U000e0061\U000e007f" - FLAG_FOR_LABE_REGION_GN_L = "\U0001f3f4\U000e0067\U000e006e\U000e006c\U000e007f" - FLAG_FOR_NALUT_LY_NL = "\U0001f3f4\U000e006c\U000e0079\U000e006e\U000e006c\U000e007f" - FLAG_FOR_MONEGHETTI_MC_MG = "\U0001f3f4\U000e006d\U000e0063\U000e006d\U000e0067\U000e007f" - FLAG_FOR_MARRAKESH_TENSIFT_EL_HAOUZ_MA_11 = "\U0001f3f4\U000e006d\U000e0061\U000e0031\U000e0031\U000e007f" - FLAG_FOR_LA_COLLE_MC_CL = "\U0001f3f4\U000e006d\U000e0063\U000e0063\U000e006c\U000e007f" - FLAG_FOR_ZAWIYA_LY_ZA = "\U0001f3f4\U000e006c\U000e0079\U000e007a\U000e0061\U000e007f" - FLAG_FOR_EL_TARF_DZ_36 = "\U0001f3f4\U000e0064\U000e007a\U000e0033\U000e0036\U000e007f" - FAMILY_WOMAN_MEDIUM_LIGHT_SKIN_TONE_WOMAN_MEDIUM_LIGHT_SKIN_TONE_BOY_MEDIUM_LIGHT_SKIN_TONE_BABY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f469\U0001f3fc\u200d\U0001f466\U0001f3fc\u200d\U0001f476\U0001f3fc" - FLAG_FOR_MURZUQ_LY_MQ = "\U0001f3f4\U000e006c\U000e0079\U000e006d\U000e0071\U000e007f" - FLAG_FOR_CHAOUIA_OUARDIGHA_MA_09 = "\U0001f3f4\U000e006d\U000e0061\U000e0030\U000e0039\U000e007f" - FLAG_FOR_SIRTE_LY_SR = "\U0001f3f4\U000e006c\U000e0079\U000e0073\U000e0072\U000e007f" - FLAG_FOR_NUQAT_AL_KHAMS_LY_NQ = "\U0001f3f4\U000e006c\U000e0079\U000e006e\U000e0071\U000e007f" - FLAG_FOR_LA_CONDAMINE_MC_CO = "\U0001f3f4\U000e006d\U000e0063\U000e0063\U000e006f\U000e007f" - FLAG_FOR_MEKNES_TAFILALET_MA_06 = "\U0001f3f4\U000e006d\U000e0061\U000e0030\U000e0036\U000e007f" - FLAG_FOR_LA_GARE_MC_GA = "\U0001f3f4\U000e006d\U000e0063\U000e0067\U000e0061\U000e007f" - FLAG_FOR_GUELMIM_ES_SEMARA_MA_14 = "\U0001f3f4\U000e006d\U000e0061\U000e0031\U000e0034\U000e007f" - COUPLE_WITH_HEART_WOMAN_LIGHT_SKIN_TONE_MAN_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb" - FLAG_FOR_GHARB_CHRARDA_BENI_HSSEN_MA_02 = "\U0001f3f4\U000e006d\U000e0061\U000e0030\U000e0032\U000e007f" - FLAG_FOR_FONTVIEILLE_MC_FO = "\U0001f3f4\U000e006d\U000e0063\U000e0066\U000e006f\U000e007f" - FLAG_FOR_TADLA_AZILAL_MA_12 = "\U0001f3f4\U000e006d\U000e0061\U000e0031\U000e0032\U000e007f" - FAMILY_MAN_DARK_SKIN_TONE_BOY_DARK_SKIN_TONE_GIRL_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f466\U0001f3ff\u200d\U0001f467\U0001f3ff" - FLAG_FOR_RABAT_SALE_ZEMMOUR_ZAER_MA_07 = "\U0001f3f4\U000e006d\U000e0061\U000e0030\U000e0037\U000e007f" - FLAG_FOR_MALBOUSQUET_MC_MA = "\U0001f3f4\U000e006d\U000e0063\U000e006d\U000e0061\U000e007f" - FLAG_FOR_OUED_ED_DAHAB_LAGOUIRA_MA_16 = "\U0001f3f4\U000e006d\U000e0061\U000e0031\U000e0036\U000e007f" - FLAG_FOR_CALARASI_MD_CL = "\U0001f3f4\U000e006d\U000e0064\U000e0063\U000e006c\U000e007f" - FLAG_FOR_SOLDANESTI_MD_SD = "\U0001f3f4\U000e006d\U000e0064\U000e0073\U000e0064\U000e007f" - FLAG_FOR_IALOVENI_MD_IA = "\U0001f3f4\U000e006d\U000e0064\U000e0069\U000e0061\U000e007f" - FLAG_FOR_LEOVA_MD_LE = "\U0001f3f4\U000e006d\U000e0064\U000e006c\U000e0065\U000e007f" - FLAG_FOR_BALTI_MD_BA = "\U0001f3f4\U000e006d\U000e0064\U000e0062\U000e0061\U000e007f" - FLAG_FOR_CRIULENI_MD_CR = "\U0001f3f4\U000e006d\U000e0064\U000e0063\U000e0072\U000e007f" - FLAG_FOR_CENTRE_VAL_DE_LOIRE_FR_CVL = "\U0001f3f4\U000e0066\U000e0072\U000e0063\U000e0076\U000e006c\U000e007f" - FLAG_FOR_CANTEMIR_MD_CT = "\U0001f3f4\U000e006d\U000e0064\U000e0063\U000e0074\U000e007f" - FLAG_FOR_CHISINAU_MD_CU = "\U0001f3f4\U000e006d\U000e0064\U000e0063\U000e0075\U000e007f" - FLAG_FOR_FALESTI_MD_FA = "\U0001f3f4\U000e006d\U000e0064\U000e0066\U000e0061\U000e007f" - FAMILY_MAN_MEDIUM_LIGHT_SKIN_TONE_WOMAN_MEDIUM_LIGHT_SKIN_TONE_GIRL_MEDIUM_LIGHT_SKIN_TONE_BABY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f469\U0001f3fc\u200d\U0001f467\U0001f3fc\u200d\U0001f476\U0001f3fc" - FLAG_FOR_DONDUSENI_MD_DO = "\U0001f3f4\U000e006d\U000e0064\U000e0064\U000e006f\U000e007f" - FLAG_FOR_MELILLA_ES_ML = "\U0001f3f4\U000e0065\U000e0073\U000e006d\U000e006c\U000e007f" - FLAG_FOR_OCNITA_MD_OC = "\U0001f3f4\U000e006d\U000e0064\U000e006f\U000e0063\U000e007f" - FLAG_FOR_SOROCA_MD_SO = "\U0001f3f4\U000e006d\U000e0064\U000e0073\U000e006f\U000e007f" - FLAG_FOR_DUBASARI_MD_DU = "\U0001f3f4\U000e006d\U000e0064\U000e0064\U000e0075\U000e007f" - FLAG_FOR_SINGEREI_MD_SI = "\U0001f3f4\U000e006d\U000e0064\U000e0073\U000e0069\U000e007f" - FLAG_FOR_EDINET_MD_ED = "\U0001f3f4\U000e006d\U000e0064\U000e0065\U000e0064\U000e007f" - FLAG_FOR_SABHA_LY_SB = "\U0001f3f4\U000e006c\U000e0079\U000e0073\U000e0062\U000e007f" - FLAG_FOR_NISPORENI_MD_NI = "\U0001f3f4\U000e006d\U000e0064\U000e006e\U000e0069\U000e007f" - FLAG_FOR_BRICENI_MD_BR = "\U0001f3f4\U000e006d\U000e0064\U000e0062\U000e0072\U000e007f" - FAMILY_WOMAN_MEDIUM_SKIN_TONE_BABY_MEDIUM_SKIN_TONE_BOY_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f476\U0001f3fd\u200d\U0001f466\U0001f3fd" - FLAG_FOR_ORHEI_MD_OR = "\U0001f3f4\U000e006d\U000e0064\U000e006f\U000e0072\U000e007f" - FLAG_FOR_CIMISLIA_MD_CM = "\U0001f3f4\U000e006d\U000e0064\U000e0063\U000e006d\U000e007f" - FLAG_FOR_GLODENI_MD_GL = "\U0001f3f4\U000e006d\U000e0064\U000e0067\U000e006c\U000e007f" - FLAG_FOR_RISCANI_MD_RI = "\U0001f3f4\U000e006d\U000e0064\U000e0072\U000e0069\U000e007f" - FLAG_FOR_BENDER_MD_BD = "\U0001f3f4\U000e006d\U000e0064\U000e0062\U000e0064\U000e007f" - FLAG_FOR_BASARABEASCA_MD_BS = "\U0001f3f4\U000e006d\U000e0064\U000e0062\U000e0073\U000e007f" - FLAG_FOR_JARDIN_EXOTIQUE_DE_MONACO_MC_JE = "\U0001f3f4\U000e006d\U000e0063\U000e006a\U000e0065\U000e007f" - FLAG_FOR_ERGLI_LV_030 = "\U0001f3f4\U000e006c\U000e0076\U000e0030\U000e0033\U000e0030\U000e007f" - FLAG_FOR_CAHUL_MD_CA = "\U0001f3f4\U000e006d\U000e0064\U000e0063\U000e0061\U000e007f" - FLAG_FOR_FLORESTI_MD_FL = "\U0001f3f4\U000e006d\U000e0064\U000e0066\U000e006c\U000e007f" - FLAG_FOR_HINCESTI_MD_HI = "\U0001f3f4\U000e006d\U000e0064\U000e0068\U000e0069\U000e007f" - FLAG_FOR_TOAMASINA_MG_A = "\U0001f3f4\U000e006d\U000e0067\U000e0061\U000e007f" - TAG_APOSTROPHE = "\U000e0027" - FLAG_FOR_TRIPOLI_LY_TB = "\U0001f3f4\U000e006c\U000e0079\U000e0074\U000e0062\U000e007f" - FLAG_FOR_BOGOVINJE_MK_06 = "\U0001f3f4\U000e006d\U000e006b\U000e0030\U000e0036\U000e007f" - FLAG_FOR_MOJKOVAC_ME_11 = "\U0001f3f4\U000e006d\U000e0065\U000e0031\U000e0031\U000e007f" - FLAG_FOR_PLUZINE_ME_15 = "\U0001f3f4\U000e006d\U000e0065\U000e0031\U000e0035\U000e007f" - FLAG_FOR_BOGDANCI_MK_05 = "\U0001f3f4\U000e006d\U000e006b\U000e0030\U000e0035\U000e007f" - FLAG_FOR_ISFAHAN_IR_04 = "\U0001f3f4\U000e0069\U000e0072\U000e0030\U000e0034\U000e007f" - FLAG_FOR_BUDVA_ME_05 = "\U0001f3f4\U000e006d\U000e0065\U000e0030\U000e0035\U000e007f" - FLAG_FOR_FIANARANTSOA_MG_F = "\U0001f3f4\U000e006d\U000e0067\U000e0066\U000e007f" - FLAG_FOR_PLJEVLJA_ME_14 = "\U0001f3f4\U000e006d\U000e0065\U000e0031\U000e0034\U000e007f" - FAMILY_MAN_MEDIUM_LIGHT_SKIN_TONE_BOY_MEDIUM_LIGHT_SKIN_TONE_BABY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f466\U0001f3fc\u200d\U0001f476\U0001f3fc" - FLAG_FOR_BIJELO_POLJE_ME_04 = "\U0001f3f4\U000e006d\U000e0065\U000e0030\U000e0034\U000e007f" - FLAG_FOR_ANTSIRANANA_MG_D = "\U0001f3f4\U000e006d\U000e0067\U000e0064\U000e007f" - FLAG_FOR_SAVNIK_ME_18 = "\U0001f3f4\U000e006d\U000e0065\U000e0031\U000e0038\U000e007f" - FLAG_FOR_BEROVO_MK_03 = "\U0001f3f4\U000e006d\U000e006b\U000e0030\U000e0033\U000e007f" - FLAG_FOR_TOLIARA_MG_U = "\U0001f3f4\U000e006d\U000e0067\U000e0075\U000e007f" - FLAG_FOR_KOLASIN_ME_09 = "\U0001f3f4\U000e006d\U000e0065\U000e0030\U000e0039\U000e007f" - FLAG_FOR_ANDRIJEVICA_ME_01 = "\U0001f3f4\U000e006d\U000e0065\U000e0030\U000e0031\U000e007f" - FLAG_FOR_DERNA_LY_DR = "\U0001f3f4\U000e006c\U000e0079\U000e0064\U000e0072\U000e007f" - FLAG_FOR_ANTANANARIVO_MG_T = "\U0001f3f4\U000e006d\U000e0067\U000e0074\U000e007f" - FLAG_FOR_QEQQATA_GL_QE = "\U0001f3f4\U000e0067\U000e006c\U000e0071\U000e0065\U000e007f" - FLAG_FOR_MAHAJANGA_MG_M = "\U0001f3f4\U000e006d\U000e0067\U000e006d\U000e007f" - FLAG_FOR_NIKSIC_ME_12 = "\U0001f3f4\U000e006d\U000e0065\U000e0031\U000e0032\U000e007f" - FLAG_FOR_PETNJICA_ME_23 = "\U0001f3f4\U000e006d\U000e0065\U000e0032\U000e0033\U000e007f" - FLAG_FOR_BOSILOVO_MK_07 = "\U0001f3f4\U000e006d\U000e006b\U000e0030\U000e0037\U000e007f" - FLAG_FOR_FES_BOULEMANE_MA_05 = "\U0001f3f4\U000e006d\U000e0061\U000e0030\U000e0035\U000e007f" - FLAG_FOR_HOUAPHANH_LA_HO = "\U0001f3f4\U000e006c\U000e0061\U000e0068\U000e006f\U000e007f" - FLAG_FOR_STEFAN_VODA_MD_SV = "\U0001f3f4\U000e006d\U000e0064\U000e0073\U000e0076\U000e007f" - FAMILY_MAN_LIGHT_SKIN_TONE_MAN_LIGHT_SKIN_TONE_BABY_LIGHT_SKIN_TONE_GIRL_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f468\U0001f3fb\u200d\U0001f476\U0001f3fb\u200d\U0001f467\U0001f3fb" - FLAG_FOR_WADI_AL_SHATII_LY_WS = "\U0001f3f4\U000e006c\U000e0079\U000e0077\U000e0073\U000e007f" - FLAG_FOR_PODGORICA_ME_16 = "\U0001f3f4\U000e006d\U000e0065\U000e0031\U000e0036\U000e007f" - FLAG_FOR_KOTOR_ME_10 = "\U0001f3f4\U000e006d\U000e0065\U000e0031\U000e0030\U000e007f" - FLAG_FOR_MARANHAO_BR_MA = "\U0001f3f4\U000e0062\U000e0072\U000e006d\U000e0061\U000e007f" - FLAG_FOR_KRIVOGASTANI_MK_45 = "\U0001f3f4\U000e006d\U000e006b\U000e0034\U000e0035\U000e007f" - FLAG_FOR_KARBINCI_MK_37 = "\U0001f3f4\U000e006d\U000e006b\U000e0033\U000e0037\U000e007f" - FLAG_FOR_KAVADARCI_MK_36 = "\U0001f3f4\U000e006d\U000e006b\U000e0033\U000e0036\U000e007f" - FLAG_FOR_MAVROVO_AND_ROSTUSA_MK_50 = "\U0001f3f4\U000e006d\U000e006b\U000e0035\U000e0030\U000e007f" - FLAG_FOR_ZELINO_MK_30 = "\U0001f3f4\U000e006d\U000e006b\U000e0033\U000e0030\U000e007f" - FLAG_FOR_DOJRAN_MK_26 = "\U0001f3f4\U000e006d\U000e006b\U000e0032\U000e0036\U000e007f" - FLAG_FOR_KOCANI_MK_42 = "\U0001f3f4\U000e006d\U000e006b\U000e0034\U000e0032\U000e007f" - FLAG_FOR_NEGOTINO_MK_54 = "\U0001f3f4\U000e006d\U000e006b\U000e0035\U000e0034\U000e007f" - FLAG_FOR_ILINDEN_MK_34 = "\U0001f3f4\U000e006d\U000e006b\U000e0033\U000e0034\U000e007f" - FLAG_FOR_DEBAR_MK_21 = "\U0001f3f4\U000e006d\U000e006b\U000e0032\U000e0031\U000e007f" - FLAG_FOR_LOZOVO_MK_49 = "\U0001f3f4\U000e006d\U000e006b\U000e0034\U000e0039\U000e007f" - FLAG_FOR_GEVGELIJA_MK_18 = "\U0001f3f4\U000e006d\U000e006b\U000e0031\U000e0038\U000e007f" - FLAG_FOR_NOVACI_MK_55 = "\U0001f3f4\U000e006d\U000e006b\U000e0035\U000e0035\U000e007f" - FLAG_FOR_VINICA_MK_14 = "\U0001f3f4\U000e006d\U000e006b\U000e0031\U000e0034\U000e007f" - FLAG_FOR_VASILEVO_MK_11 = "\U0001f3f4\U000e006d\U000e006b\U000e0031\U000e0031\U000e007f" - FLAG_FOR_NOVO_SELO_MK_56 = "\U0001f3f4\U000e006d\U000e006b\U000e0035\U000e0036\U000e007f" - FLAG_FOR_KUMANOVO_MK_47 = "\U0001f3f4\U000e006d\U000e006b\U000e0034\U000e0037\U000e007f" - FLAG_FOR_GRADSKO_MK_20 = "\U0001f3f4\U000e006d\U000e006b\U000e0032\U000e0030\U000e007f" - FLAG_FOR_KONCE_MK_41 = "\U0001f3f4\U000e006d\U000e006b\U000e0034\U000e0031\U000e007f" - FLAG_FOR_ZELENIKOVO_MK_32 = "\U0001f3f4\U000e006d\U000e006b\U000e0033\U000e0032\U000e007f" - FLAG_FOR_JEGUNOVCE_MK_35 = "\U0001f3f4\U000e006d\U000e006b\U000e0033\U000e0035\U000e007f" - FLAG_FOR_MAKEDONSKA_KAMENICA_MK_51 = "\U0001f3f4\U000e006d\U000e006b\U000e0035\U000e0031\U000e007f" - FLAG_FOR_VEVCANI_MK_12 = "\U0001f3f4\U000e006d\U000e006b\U000e0031\U000e0032\U000e007f" - FLAG_FOR_GOSTIVAR_MK_19 = "\U0001f3f4\U000e006d\U000e006b\U000e0031\U000e0039\U000e007f" - FLAG_FOR_KRUSEVO_MK_46 = "\U0001f3f4\U000e006d\U000e006b\U000e0034\U000e0036\U000e007f" - FLAG_FOR_BRVENICA_MK_08 = "\U0001f3f4\U000e006d\U000e006b\U000e0030\U000e0038\U000e007f" - FLAG_FOR_LIPKOVO_MK_48 = "\U0001f3f4\U000e006d\U000e006b\U000e0034\U000e0038\U000e007f" - FLAG_FOR_VALANDOVO_MK_10 = "\U0001f3f4\U000e006d\U000e006b\U000e0031\U000e0030\U000e007f" - FLAG_FOR_KRATOVO_MK_43 = "\U0001f3f4\U000e006d\U000e006b\U000e0034\U000e0033\U000e007f" - FLAG_FOR_KRIVA_PALANKA_MK_44 = "\U0001f3f4\U000e006d\U000e006b\U000e0034\U000e0034\U000e007f" - FLAG_FOR_ZRNOVCI_MK_33 = "\U0001f3f4\U000e006d\U000e006b\U000e0033\U000e0033\U000e007f" - FLAG_FOR_TEL_AVIV_DISTRICT_IL_TA = "\U0001f3f4\U000e0069\U000e006c\U000e0074\U000e0061\U000e007f" - FLAG_FOR_DANILOVGRAD_ME_07 = "\U0001f3f4\U000e006d\U000e0065\U000e0030\U000e0037\U000e007f" - FLAG_FOR_DEBARCA_MK_22 = "\U0001f3f4\U000e006d\U000e006b\U000e0032\U000e0032\U000e007f" - FLAG_FOR_VRAPCISTE_MK_16 = "\U0001f3f4\U000e006d\U000e006b\U000e0031\U000e0036\U000e007f" - FLAG_FOR_DELCEVO_MK_23 = "\U0001f3f4\U000e006d\U000e006b\U000e0032\U000e0033\U000e007f" - FLAG_FOR_VELES_MK_13 = "\U0001f3f4\U000e006d\U000e006b\U000e0031\U000e0033\U000e007f" - FLAG_FOR_AYEYARWADY_MM_07 = "\U0001f3f4\U000e006d\U000e006d\U000e0030\U000e0037\U000e007f" - FLAG_FOR_KAYAH_MM_12 = "\U0001f3f4\U000e006d\U000e006d\U000e0031\U000e0032\U000e007f" - FLAG_FOR_STARO_NAGORICANE_MK_71 = "\U0001f3f4\U000e006d\U000e006b\U000e0037\U000e0031\U000e007f" - FLAG_FOR_STIP_MK_83 = "\U0001f3f4\U000e006d\U000e006b\U000e0038\U000e0033\U000e007f" - FLAG_FOR_DAEJEON_KR_30 = "\U0001f3f4\U000e006b\U000e0072\U000e0033\U000e0030\U000e007f" - FLAG_FOR_TETOVO_MK_76 = "\U0001f3f4\U000e006d\U000e006b\U000e0037\U000e0036\U000e007f" - FLAG_FOR_SZEGED_HU_SD = "\U0001f3f4\U000e0068\U000e0075\U000e0073\U000e0064\U000e007f" - FLAG_FOR_MOPTI_ML_5 = "\U0001f3f4\U000e006d\U000e006c\U000e0035\U000e007f" - FLAG_FOR_SIKASSO_ML_3 = "\U0001f3f4\U000e006d\U000e006c\U000e0033\U000e007f" - FLAG_FOR_U_S_OUTLYING_ISLANDS_US_UM = "\U0001f3f4\U000e0075\U000e0073\U000e0075\U000e006d\U000e007f" - FLAG_FOR_CASKA_MK_80 = "\U0001f3f4\U000e006d\U000e006b\U000e0038\U000e0030\U000e007f" - FLAG_FOR_STRUGA_MK_72 = "\U0001f3f4\U000e006d\U000e006b\U000e0037\U000e0032\U000e007f" - FLAG_FOR_KOULIKORO_ML_2 = "\U0001f3f4\U000e006d\U000e006c\U000e0032\U000e007f" - FLAG_FOR_PETROVEC_MK_59 = "\U0001f3f4\U000e006d\U000e006b\U000e0035\U000e0039\U000e007f" - KISS_MAN_MAN_MEDIUM_DARK_SKIN_TONE = "\U0001f468\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe" - FLAG_FOR_CUCER_SANDEVO_MK_82 = "\U0001f3f4\U000e006d\U000e006b\U000e0038\U000e0032\U000e007f" - FLAG_FOR_SOPISTE_MK_70 = "\U0001f3f4\U000e006d\U000e006b\U000e0037\U000e0030\U000e007f" - FLAG_FOR_MANDALAY_MM_04 = "\U0001f3f4\U000e006d\U000e006d\U000e0030\U000e0034\U000e007f" - FLAG_FOR_GENEVA_CH_GE = "\U0001f3f4\U000e0063\U000e0068\U000e0067\U000e0065\U000e007f" - FLAG_FOR_KACHIN_MM_11 = "\U0001f3f4\U000e006d\U000e006d\U000e0031\U000e0031\U000e007f" - FLAG_FOR_PROBISTIP_MK_63 = "\U0001f3f4\U000e006d\U000e006b\U000e0036\U000e0033\U000e007f" - FLAG_FOR_RESEN_MK_66 = "\U0001f3f4\U000e006d\U000e006b\U000e0036\U000e0036\U000e007f" - FLAG_FOR_TANINTHARYI_MM_05 = "\U0001f3f4\U000e006d\U000e006d\U000e0030\U000e0035\U000e007f" - FLAG_FOR_ROSOMAN_MK_67 = "\U0001f3f4\U000e006d\U000e006b\U000e0036\U000e0037\U000e007f" - FLAG_FOR_CESINOVO_OBLESEVO_MK_81 = "\U0001f3f4\U000e006d\U000e006b\U000e0038\U000e0031\U000e007f" - FLAG_FOR_PEHCEVO_MK_60 = "\U0001f3f4\U000e006d\U000e006b\U000e0036\U000e0030\U000e007f" - FLAG_FOR_SAGAING_MM_01 = "\U0001f3f4\U000e006d\U000e006d\U000e0030\U000e0031\U000e007f" - FLAG_FOR_ZAVKHAN_MN_057 = "\U0001f3f4\U000e006d\U000e006e\U000e0030\U000e0035\U000e0037\U000e007f" - FLAG_FOR_TOV_MN_047 = "\U0001f3f4\U000e006d\U000e006e\U000e0030\U000e0034\U000e0037\U000e007f" - FLAG_FOR_INCHIRI_MR_12 = "\U0001f3f4\U000e006d\U000e0072\U000e0031\U000e0032\U000e007f" - FLAG_FOR_TAGANT_MR_09 = "\U0001f3f4\U000e006d\U000e0072\U000e0030\U000e0039\U000e007f" - FLAG_FOR_DAKHLET_NOUADHIBOU_MR_08 = "\U0001f3f4\U000e006d\U000e0072\U000e0030\U000e0038\U000e007f" - FLAG_FOR_OVORKHANGAI_MN_055 = "\U0001f3f4\U000e006d\U000e006e\U000e0030\U000e0035\U000e0035\U000e007f" - TAG_DOLLAR_SIGN = "\U000e0024" - FLAG_FOR_HODH_EL_GHARBI_MR_02 = "\U0001f3f4\U000e006d\U000e0072\U000e0030\U000e0032\U000e007f" - FLAG_FOR_BAYAN_OLGII_MN_071 = "\U0001f3f4\U000e006d\U000e006e\U000e0030\U000e0037\U000e0031\U000e007f" - FLAG_FOR_DUNDGOVI_MN_059 = "\U0001f3f4\U000e006d\U000e006e\U000e0030\U000e0035\U000e0039\U000e007f" - FLAG_FOR_SCHWYZ_CH_SZ = "\U0001f3f4\U000e0063\U000e0068\U000e0073\U000e007a\U000e007f" - FLAG_FOR_BULGAN_MN_067 = "\U0001f3f4\U000e006d\U000e006e\U000e0030\U000e0036\U000e0037\U000e007f" - TAG_TILDE = "\U000e007e" - FLAG_FOR_KAYIN_MM_13 = "\U0001f3f4\U000e006d\U000e006d\U000e0031\U000e0033\U000e007f" - FLAG_FOR_GAO_ML_7 = "\U0001f3f4\U000e006d\U000e006c\U000e0037\U000e007f" - FLAG_FOR_DORNOD_MN_061 = "\U0001f3f4\U000e006d\U000e006e\U000e0030\U000e0036\U000e0031\U000e007f" - FLAG_FOR_TRARZA_MR_06 = "\U0001f3f4\U000e006d\U000e0072\U000e0030\U000e0036\U000e007f" - FLAG_FOR_DARKHAN_UUL_MN_037 = "\U0001f3f4\U000e006d\U000e006e\U000e0030\U000e0033\U000e0037\U000e007f" - FLAG_FOR_UVS_MN_046 = "\U0001f3f4\U000e006d\U000e006e\U000e0030\U000e0034\U000e0036\U000e007f" - FLAG_FOR_KHOVSGOL_MN_041 = "\U0001f3f4\U000e006d\U000e006e\U000e0030\U000e0034\U000e0031\U000e007f" - FLAG_FOR_BAYANKHONGOR_MN_069 = "\U0001f3f4\U000e006d\U000e006e\U000e0030\U000e0036\U000e0039\U000e007f" - FLAG_FOR_HODH_ECH_CHARGUI_MR_01 = "\U0001f3f4\U000e006d\U000e0072\U000e0030\U000e0031\U000e007f" - FLAG_FOR_NAYPYIDAW_MM_18 = "\U0001f3f4\U000e006d\U000e006d\U000e0031\U000e0038\U000e007f" - FLAG_FOR_DORNOGOVI_MN_063 = "\U0001f3f4\U000e006d\U000e006e\U000e0030\U000e0036\U000e0033\U000e007f" - FAMILY_WOMAN_MEDIUM_LIGHT_SKIN_TONE_WOMAN_MEDIUM_LIGHT_SKIN_TONE_BOY_MEDIUM_LIGHT_SKIN_TONE_BOY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f469\U0001f3fc\u200d\U0001f466\U0001f3fc\u200d\U0001f466\U0001f3fc" - FLAG_FOR_SAN_MIGUEL_SV_SM = "\U0001f3f4\U000e0073\U000e0076\U000e0073\U000e006d\U000e007f" - FLAG_FOR_MANCHESTER_JM_12 = "\U0001f3f4\U000e006a\U000e006d\U000e0031\U000e0032\U000e007f" - WHITE_RIGHT_POINTING_INDEX_EMOJI_MODIFIER_FITZPATRICK_TYPE_1_2 = "\u261e\U0001f3fb" - REVERSED_THUMBS_DOWN_SIGN_EMOJI_MODIFIER_FITZPATRICK_TYPE_1_2 = "\U0001f593\U0001f3fb" - BLACK_RIGHT_POINTING_INDEX_EMOJI_MODIFIER_FITZPATRICK_TYPE_1_2 = "\u261b\U0001f3fb" - REVERSED_RAISED_HAND_WITH_FINGERS_SPLAYED_EMOJI_MODIFIER_FITZPATRICK_TYPE_1_2 = "\U0001f591\U0001f3fb" - WHITE_DOWN_POINTING_INDEX_EMOJI_MODIFIER_FITZPATRICK_TYPE_1_2 = "\u261f\U0001f3fb" - BLACK_LEFT_POINTING_INDEX_EMOJI_MODIFIER_FITZPATRICK_TYPE_1_2 = "\u261a\U0001f3fb" - REVERSED_VICTORY_HAND_EMOJI_MODIFIER_FITZPATRICK_TYPE_1_2 = "\U0001f594\U0001f3fb" - WHITE_LEFT_POINTING_INDEX_EMOJI_MODIFIER_FITZPATRICK_TYPE_1_2 = "\u261c\U0001f3fb" - REVERSED_THUMBS_UP_SIGN_EMOJI_MODIFIER_FITZPATRICK_TYPE_1_2 = "\U0001f592\U0001f3fb" - LEFT_WRITING_HAND_EMOJI_MODIFIER_FITZPATRICK_TYPE_1_2 = "\U0001f58e\U0001f3fb" - WHITE_LEFT_POINTING_INDEX_EMOJI_MODIFIER_FITZPATRICK_TYPE_3 = "\u261c\U0001f3fc" - BLACK_RIGHT_POINTING_INDEX_EMOJI_MODIFIER_FITZPATRICK_TYPE_3 = "\u261b\U0001f3fc" - REVERSED_THUMBS_UP_SIGN_EMOJI_MODIFIER_FITZPATRICK_TYPE_3 = "\U0001f592\U0001f3fc" - WHITE_RIGHT_POINTING_INDEX_EMOJI_MODIFIER_FITZPATRICK_TYPE_3 = "\u261e\U0001f3fc" - LEFT_WRITING_HAND_EMOJI_MODIFIER_FITZPATRICK_TYPE_3 = "\U0001f58e\U0001f3fc" - REVERSED_VICTORY_HAND_EMOJI_MODIFIER_FITZPATRICK_TYPE_3 = "\U0001f594\U0001f3fc" - WHITE_DOWN_POINTING_INDEX_EMOJI_MODIFIER_FITZPATRICK_TYPE_3 = "\u261f\U0001f3fc" - BLACK_LEFT_POINTING_INDEX_EMOJI_MODIFIER_FITZPATRICK_TYPE_3 = "\u261a\U0001f3fc" - REVERSED_THUMBS_DOWN_SIGN_EMOJI_MODIFIER_FITZPATRICK_TYPE_3 = "\U0001f593\U0001f3fc" - REVERSED_RAISED_HAND_WITH_FINGERS_SPLAYED_EMOJI_MODIFIER_FITZPATRICK_TYPE_3 = "\U0001f591\U0001f3fc" - REVERSED_RAISED_HAND_WITH_FINGERS_SPLAYED_EMOJI_MODIFIER_FITZPATRICK_TYPE_4 = "\U0001f591\U0001f3fd" - BLACK_LEFT_POINTING_INDEX_EMOJI_MODIFIER_FITZPATRICK_TYPE_4 = "\u261a\U0001f3fd" - REVERSED_THUMBS_DOWN_SIGN_EMOJI_MODIFIER_FITZPATRICK_TYPE_4 = "\U0001f593\U0001f3fd" - WHITE_DOWN_POINTING_INDEX_EMOJI_MODIFIER_FITZPATRICK_TYPE_4 = "\u261f\U0001f3fd" - REVERSED_THUMBS_UP_SIGN_EMOJI_MODIFIER_FITZPATRICK_TYPE_4 = "\U0001f592\U0001f3fd" - BLACK_RIGHT_POINTING_INDEX_EMOJI_MODIFIER_FITZPATRICK_TYPE_4 = "\u261b\U0001f3fd" - REVERSED_VICTORY_HAND_EMOJI_MODIFIER_FITZPATRICK_TYPE_4 = "\U0001f594\U0001f3fd" - WHITE_LEFT_POINTING_INDEX_EMOJI_MODIFIER_FITZPATRICK_TYPE_4 = "\u261c\U0001f3fd" - LEFT_WRITING_HAND_EMOJI_MODIFIER_FITZPATRICK_TYPE_4 = "\U0001f58e\U0001f3fd" - WHITE_RIGHT_POINTING_INDEX_EMOJI_MODIFIER_FITZPATRICK_TYPE_4 = "\u261e\U0001f3fd" - LEFT_WRITING_HAND_EMOJI_MODIFIER_FITZPATRICK_TYPE_5 = "\U0001f58e\U0001f3fe" - WHITE_RIGHT_POINTING_INDEX_EMOJI_MODIFIER_FITZPATRICK_TYPE_5 = "\u261e\U0001f3fe" - WHITE_LEFT_POINTING_INDEX_EMOJI_MODIFIER_FITZPATRICK_TYPE_5 = "\u261c\U0001f3fe" - BLACK_LEFT_POINTING_INDEX_EMOJI_MODIFIER_FITZPATRICK_TYPE_5 = "\u261a\U0001f3fe" - WHITE_DOWN_POINTING_INDEX_EMOJI_MODIFIER_FITZPATRICK_TYPE_5 = "\u261f\U0001f3fe" - REVERSED_THUMBS_DOWN_SIGN_EMOJI_MODIFIER_FITZPATRICK_TYPE_5 = "\U0001f593\U0001f3fe" - REVERSED_RAISED_HAND_WITH_FINGERS_SPLAYED_EMOJI_MODIFIER_FITZPATRICK_TYPE_5 = "\U0001f591\U0001f3fe" - REVERSED_VICTORY_HAND_EMOJI_MODIFIER_FITZPATRICK_TYPE_5 = "\U0001f594\U0001f3fe" - BLACK_RIGHT_POINTING_INDEX_EMOJI_MODIFIER_FITZPATRICK_TYPE_5 = "\u261b\U0001f3fe" - REVERSED_THUMBS_UP_SIGN_EMOJI_MODIFIER_FITZPATRICK_TYPE_5 = "\U0001f592\U0001f3fe" - REVERSED_THUMBS_DOWN_SIGN_EMOJI_MODIFIER_FITZPATRICK_TYPE_6 = "\U0001f593\U0001f3ff" - WHITE_LEFT_POINTING_INDEX_EMOJI_MODIFIER_FITZPATRICK_TYPE_6 = "\u261c\U0001f3ff" - WHITE_RIGHT_POINTING_INDEX_EMOJI_MODIFIER_FITZPATRICK_TYPE_6 = "\u261e\U0001f3ff" - BLACK_LEFT_POINTING_INDEX_EMOJI_MODIFIER_FITZPATRICK_TYPE_6 = "\u261a\U0001f3ff" - BLACK_RIGHT_POINTING_INDEX_EMOJI_MODIFIER_FITZPATRICK_TYPE_6 = "\u261b\U0001f3ff" - REVERSED_RAISED_HAND_WITH_FINGERS_SPLAYED_EMOJI_MODIFIER_FITZPATRICK_TYPE_6 = "\U0001f591\U0001f3ff" - WHITE_DOWN_POINTING_INDEX_EMOJI_MODIFIER_FITZPATRICK_TYPE_6 = "\u261f\U0001f3ff" - REVERSED_THUMBS_UP_SIGN_EMOJI_MODIFIER_FITZPATRICK_TYPE_6 = "\U0001f592\U0001f3ff" - LEFT_WRITING_HAND_EMOJI_MODIFIER_FITZPATRICK_TYPE_6 = "\U0001f58e\U0001f3ff" - REVERSED_VICTORY_HAND_EMOJI_MODIFIER_FITZPATRICK_TYPE_6 = "\U0001f594\U0001f3ff" diff --git a/pyrogram/client/ext/file_data.py b/pyrogram/client/ext/file_data.py deleted file mode 100644 index ea9de6e1ba..0000000000 --- a/pyrogram/client/ext/file_data.py +++ /dev/null @@ -1,41 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan -# -# 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 . - -class FileData: - def __init__( - self, *, media_type: int = None, dc_id: int = None, document_id: int = None, access_hash: int = None, - thumb_size: str = None, peer_id: int = None, peer_type: str = None, peer_access_hash: int = None, - volume_id: int = None, local_id: int = None, is_big: bool = None, file_size: int = None, mime_type: str = None, - file_name: str = None, date: int = None, file_ref: str = None - ): - self.media_type = media_type - self.dc_id = dc_id - self.document_id = document_id - self.access_hash = access_hash - self.thumb_size = thumb_size - self.peer_id = peer_id - self.peer_type = peer_type - self.peer_access_hash = peer_access_hash - self.volume_id = volume_id - self.local_id = local_id - self.is_big = is_big - self.file_size = file_size - self.mime_type = mime_type - self.file_name = file_name - self.date = date - self.file_ref = file_ref diff --git a/pyrogram/client/ext/link.py b/pyrogram/client/ext/link.py deleted file mode 100644 index bd2d82cf9a..0000000000 --- a/pyrogram/client/ext/link.py +++ /dev/null @@ -1,52 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan -# -# 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 html - - -class Link(str): - HTML = "{text}" - MD = "[{text}]({url})" - - def __init__(self, url: str, text: str, style: str): - super().__init__() - - self.url = url - self.text = text - self.style = style - - @staticmethod - def format(url: str, text: str, style: str): - if style in ["md", "markdown"]: - fmt = Link.MD - elif style in ["combined", "html", None]: - fmt = Link.HTML - else: - raise ValueError("{} is not a valid style/parse mode".format(style)) - - return fmt.format(url=url, text=html.escape(text)) - - # noinspection PyArgumentList - def __new__(cls, url, text, style): - return str.__new__(cls, Link.format(url, text, style)) - - def __call__(self, other: str = None, *, style: str = None): - return Link.format(self.url, other or self.text, style or self.style) - - def __str__(self): - return Link.format(self.url, self.text, self.style) diff --git a/pyrogram/client/filters/__init__.py b/pyrogram/client/filters/__init__.py deleted file mode 100644 index bdb72abc79..0000000000 --- a/pyrogram/client/filters/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan -# -# 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 .filters import Filters diff --git a/pyrogram/client/filters/filter.py b/pyrogram/client/filters/filter.py deleted file mode 100644 index 67067e0370..0000000000 --- a/pyrogram/client/filters/filter.py +++ /dev/null @@ -1,84 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan -# -# 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 asyncio - - -class Filter: - def __call__(self, message): - raise NotImplementedError - - def __invert__(self): - return InvertFilter(self) - - def __and__(self, other): - return AndFilter(self, other) - - def __or__(self, other): - return OrFilter(self, other) - - -class InvertFilter(Filter): - def __init__(self, base): - self.base = base - - async def __call__(self, message): - if asyncio.iscoroutinefunction(self.base.__call__): - x = await self.base(message) - else: - x = self.base(message) - - return not x - - -class AndFilter(Filter): - def __init__(self, base, other): - self.base = base - self.other = other - - async def __call__(self, message): - if asyncio.iscoroutinefunction(self.base.__call__): - x = await self.base(message) - else: - x = self.base(message) - - if asyncio.iscoroutinefunction(self.other.__call__): - y = await self.other(message) - else: - y = self.other(message) - - return x and y - - -class OrFilter(Filter): - def __init__(self, base, other): - self.base = base - self.other = other - - async def __call__(self, message): - if asyncio.iscoroutinefunction(self.base.__call__): - x = await self.base(message) - else: - x = self.base(message) - - if asyncio.iscoroutinefunction(self.other.__call__): - y = await self.other(message) - else: - y = self.other(message) - - return x or y diff --git a/pyrogram/client/filters/filters.py b/pyrogram/client/filters/filters.py deleted file mode 100644 index 3a798874ea..0000000000 --- a/pyrogram/client/filters/filters.py +++ /dev/null @@ -1,404 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan -# -# 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 re -from typing import Callable, Union - -from .filter import Filter -from ..types import Message, CallbackQuery, InlineQuery -from ..types.bots_and_keyboards import InlineKeyboardMarkup, ReplyKeyboardMarkup - -CUSTOM_FILTER_NAME = "CustomFilter" - - -def create(func: Callable, name: str = None, **kwargs) -> Filter: - """Easily create a custom filter. - - Custom filters give you extra control over which updates are allowed or not to be processed by your handlers. - - Parameters: - func (``callable``): - A function that accepts two positional arguments *(filter, update)* and returns a boolean: True if the - update should be handled, False otherwise. The *filter* argument refers to the filter itself and can be used - to access keyword arguments (read below). The *update* argument type will vary depending on which - `Handler `_ is coming from. For example, in a :obj:`MessageHandler` the *update* argument will be - a :obj:`Message`; in a :obj:`CallbackQueryHandler` the *update* will be a :obj:`CallbackQuery`. Your - function body can then access the incoming update attributes and decide whether to allow it or not. - - name (``str``, *optional*): - Your filter's name. Can be anything you like. - Defaults to "CustomFilter". - - **kwargs (``any``, *optional*): - Any keyword argument you would like to pass. Useful when creating parameterized custom filters, such as - :meth:`~Filters.command` or :meth:`~Filters.regex`. - """ - # TODO: unpack kwargs using **kwargs into the dict itself. For Python 3.5+ only - d = {"__call__": func} - d.update(kwargs) - - return type(name or CUSTOM_FILTER_NAME, (Filter,), d)() - - -class Filters: - """This class provides access to all library-defined Filters available in Pyrogram. - - The Filters listed here are currently intended to be used with the :obj:`MessageHandler` only. - At the moment, if you want to filter updates coming from different `Handlers `_ you have to create - your own filters with :meth:`~Filters.create` and use them in the same way. - """ - - create = create - - all = create(lambda _, m: True, "AllFilter") - """Filter all messages.""" - - me = create(lambda _, m: bool(m.from_user and m.from_user.is_self), "MeFilter") - """Filter messages generated by you yourself.""" - - bot = create(lambda _, m: bool(m.from_user and m.from_user.is_bot), "BotFilter") - """Filter messages coming from bots.""" - - incoming = create(lambda _, m: not m.outgoing, "IncomingFilter") - """Filter incoming messages. Messages sent to your own chat (Saved Messages) are also recognised as incoming.""" - - outgoing = create(lambda _, m: m.outgoing, "OutgoingFilter") - """Filter outgoing messages. Messages sent to your own chat (Saved Messages) are not recognized as outgoing.""" - - text = create(lambda _, m: bool(m.text), "TextFilter") - """Filter text messages.""" - - reply = create(lambda _, m: bool(m.reply_to_message), "ReplyFilter") - """Filter messages that are replies to other messages.""" - - forwarded = create(lambda _, m: bool(m.forward_date), "ForwardedFilter") - """Filter messages that are forwarded.""" - - caption = create(lambda _, m: bool(m.caption), "CaptionFilter") - """Filter media messages that contain captions.""" - - edited = create(lambda _, m: bool(m.edit_date), "EditedFilter") - """Filter edited messages.""" - - audio = create(lambda _, m: bool(m.audio), "AudioFilter") - """Filter messages that contain :obj:`Audio` objects.""" - - document = create(lambda _, m: bool(m.document), "DocumentFilter") - """Filter messages that contain :obj:`Document` objects.""" - - photo = create(lambda _, m: bool(m.photo), "PhotoFilter") - """Filter messages that contain :obj:`Photo` objects.""" - - sticker = create(lambda _, m: bool(m.sticker), "StickerFilter") - """Filter messages that contain :obj:`Sticker` objects.""" - - animation = create(lambda _, m: bool(m.animation), "AnimationFilter") - """Filter messages that contain :obj:`Animation` objects.""" - - game = create(lambda _, m: bool(m.game), "GameFilter") - """Filter messages that contain :obj:`Game` objects.""" - - video = create(lambda _, m: bool(m.video), "VideoFilter") - """Filter messages that contain :obj:`Video` objects.""" - - media_group = create(lambda _, m: bool(m.media_group_id), "MediaGroupFilter") - """Filter messages containing photos or videos being part of an album.""" - - voice = create(lambda _, m: bool(m.voice), "VoiceFilter") - """Filter messages that contain :obj:`Voice` note objects.""" - - video_note = create(lambda _, m: bool(m.video_note), "VideoNoteFilter") - """Filter messages that contain :obj:`VideoNote` objects.""" - - contact = create(lambda _, m: bool(m.contact), "ContactFilter") - """Filter messages that contain :obj:`Contact` objects.""" - - location = create(lambda _, m: bool(m.location), "LocationFilter") - """Filter messages that contain :obj:`Location` objects.""" - - venue = create(lambda _, m: bool(m.venue), "VenueFilter") - """Filter messages that contain :obj:`Venue` objects.""" - - web_page = create(lambda _, m: m.web_page, "WebPageFilter") - """Filter messages sent with a webpage preview.""" - - poll = create(lambda _, m: m.poll, "PollFilter") - """Filter messages that contain :obj:`Poll` objects.""" - - private = create(lambda _, m: bool(m.chat and m.chat.type in {"private", "bot"}), "PrivateFilter") - """Filter messages sent in private chats.""" - - group = create(lambda _, m: bool(m.chat and m.chat.type in {"group", "supergroup"}), "GroupFilter") - """Filter messages sent in group or supergroup chats.""" - - channel = create(lambda _, m: bool(m.chat and m.chat.type == "channel"), "ChannelFilter") - """Filter messages sent in channels.""" - - new_chat_members = create(lambda _, m: bool(m.new_chat_members), "NewChatMembersFilter") - """Filter service messages for new chat members.""" - - left_chat_member = create(lambda _, m: bool(m.left_chat_member), "LeftChatMemberFilter") - """Filter service messages for members that left the chat.""" - - new_chat_title = create(lambda _, m: bool(m.new_chat_title), "NewChatTitleFilter") - """Filter service messages for new chat titles.""" - - new_chat_photo = create(lambda _, m: bool(m.new_chat_photo), "NewChatPhotoFilter") - """Filter service messages for new chat photos.""" - - delete_chat_photo = create(lambda _, m: bool(m.delete_chat_photo), "DeleteChatPhotoFilter") - """Filter service messages for deleted photos.""" - - group_chat_created = create(lambda _, m: bool(m.group_chat_created), "GroupChatCreatedFilter") - """Filter service messages for group chat creations.""" - - supergroup_chat_created = create(lambda _, m: bool(m.supergroup_chat_created), "SupergroupChatCreatedFilter") - """Filter service messages for supergroup chat creations.""" - - channel_chat_created = create(lambda _, m: bool(m.channel_chat_created), "ChannelChatCreatedFilter") - """Filter service messages for channel chat creations.""" - - migrate_to_chat_id = create(lambda _, m: bool(m.migrate_to_chat_id), "MigrateToChatIdFilter") - """Filter service messages that contain migrate_to_chat_id.""" - - migrate_from_chat_id = create(lambda _, m: bool(m.migrate_from_chat_id), "MigrateFromChatIdFilter") - """Filter service messages that contain migrate_from_chat_id.""" - - pinned_message = create(lambda _, m: bool(m.pinned_message), "PinnedMessageFilter") - """Filter service messages for pinned messages.""" - - game_high_score = create(lambda _, m: bool(m.game_high_score), "GameHighScoreFilter") - """Filter service messages for game high scores.""" - - reply_keyboard = create(lambda _, m: isinstance(m.reply_markup, ReplyKeyboardMarkup), "ReplyKeyboardFilter") - """Filter messages containing reply keyboard markups""" - - inline_keyboard = create(lambda _, m: isinstance(m.reply_markup, InlineKeyboardMarkup), "InlineKeyboardFilter") - """Filter messages containing inline keyboard markups""" - - mentioned = create(lambda _, m: bool(m.mentioned), "MentionedFilter") - """Filter messages containing mentions""" - - via_bot = create(lambda _, m: bool(m.via_bot), "ViaBotFilter") - """Filter messages sent via inline bots""" - - service = create(lambda _, m: bool(m.service), "ServiceFilter") - """Filter service messages. - - 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(lambda _, m: bool(m.media), "MediaFilter") - """Filter media messages. - - A media message contains any of the following fields set: *audio*, *document*, *photo*, *sticker*, *video*, - *animation*, *voice*, *video_note*, *contact*, *location*, *venue*, *poll*. - """ - - scheduled = create(lambda _, m: bool(m.scheduled), "ScheduledFilter") - """Filter messages that have been scheduled (not yet sent).""" - - from_scheduled = create(lambda _, m: bool(m.from_scheduled), "FromScheduledFilter") - """Filter new automatically sent messages that were previously scheduled.""" - - # Messages from linked channels are forwarded automatically by Telegram and have no sender (from_user is None). - linked_channel = create(lambda _, m: bool(m.forward_from_chat and not m.from_user), "LinkedChannelFilter") - """Filter messages that are automatically forwarded from the linked channel to the group chat.""" - - @staticmethod - def command( - commands: str or list, - prefixes: str or list = "/", - case_sensitive: bool = False - ): - """Filter commands, i.e.: text messages starting with "/" or any other custom prefix. - - Parameters: - commands (``str`` | ``list``): - The command or list of commands as string the filter should look for. - Examples: "start", ["start", "help", "settings"]. When a message text containing - a command arrives, the command itself and its arguments will be stored in the *command* - field of the :obj:`Message`. - - prefixes (``str`` | ``list``, *optional*): - A prefix or a list of prefixes as string the filter should look for. - Defaults to "/" (slash). Examples: ".", "!", ["/", "!", "."], list(".:!"). - Pass None or "" (empty string) to allow commands with no prefix at all. - - case_sensitive (``bool``, *optional*): - Pass True if you want your command(s) to be case sensitive. Defaults to False. - Examples: when True, command="Start" would trigger /Start but not /start. - """ - command_re = re.compile(r"([\"'])(.*?)(?`_ are - stored in the ``matches`` field of the update object itself. - - Parameters: - pattern (``str`` | ``Pattern``): - The regex pattern as string or as pre-compiled pattern. - - flags (``int``, *optional*): - Regex flags. - """ - - def func(flt, update): - if isinstance(update, Message): - value = update.text or update.caption - elif isinstance(update, CallbackQuery): - value = update.data - elif isinstance(update, InlineQuery): - value = update.query - else: - raise ValueError("Regex filter doesn't work with {}".format(type(update))) - - if value: - update.matches = list(flt.p.finditer(value)) or None - - return bool(update.matches) - - return create( - func, - "RegexFilter", - p=pattern if isinstance(pattern, re.Pattern) else re.compile(pattern, flags) - ) - - # noinspection PyPep8Naming - class user(Filter, set): - """Filter messages coming from one or more users. - - You can use `set bound methods `_ to manipulate the - users container. - - Parameters: - users (``int`` | ``str`` | ``list``): - Pass one or more user ids/usernames to filter users. - For you yourself, "me" or "self" can be used as well. - Defaults to None (no users). - """ - - def __init__(self, users: int or str or list = None): - users = [] if users is None else users if isinstance(users, list) else [users] - - super().__init__( - "me" if u in ["me", "self"] - else u.lower().strip("@") if isinstance(u, str) - else u for u in users - ) - - def __call__(self, message): - return (message.from_user - and (message.from_user.id in self - or (message.from_user.username - and message.from_user.username.lower() in self) - or ("me" in self - and message.from_user.is_self))) - - # noinspection PyPep8Naming - class chat(Filter, set): - """Filter messages coming from one or more chats. - - You can use `set bound methods `_ to manipulate the - chats container. - - Parameters: - chats (``int`` | ``str`` | ``list``): - Pass one or more chat ids/usernames to filter chats. - For your personal cloud (Saved Messages) you can simply use "me" or "self". - Defaults to None (no chats). - """ - - def __init__(self, chats: int or str or list = None): - chats = [] if chats is None else chats if isinstance(chats, list) else [chats] - - super().__init__( - "me" if c in ["me", "self"] - else c.lower().strip("@") if isinstance(c, str) - else c for c in chats - ) - - def __call__(self, message): - return (message.chat - and (message.chat.id in self - or (message.chat.username - and message.chat.username.lower() in self) - or ("me" in self - and message.from_user - and message.from_user.is_self - and not message.outgoing))) - - dan = create(lambda _, m: bool(m.from_user and m.from_user.id == 23122162), "DanFilter") diff --git a/pyrogram/client/methods/password/utils.py b/pyrogram/client/methods/password/utils.py deleted file mode 100644 index 30c3679684..0000000000 --- a/pyrogram/client/methods/password/utils.py +++ /dev/null @@ -1,104 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan -# -# 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 hashlib -import os - -from pyrogram.api import types - - -def btoi(b: bytes) -> int: - return int.from_bytes(b, "big") - - -def itob(i: int) -> bytes: - return i.to_bytes(256, "big") - - -def sha256(data: bytes) -> bytes: - return hashlib.sha256(data).digest() - - -def xor(a: bytes, b: bytes) -> bytes: - return bytes(i ^ j for i, j in zip(a, b)) - - -def compute_hash(algo: types.PasswordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow, password: str) -> bytes: - hash1 = sha256(algo.salt1 + password.encode() + algo.salt1) - hash2 = sha256(algo.salt2 + hash1 + algo.salt2) - hash3 = hashlib.pbkdf2_hmac("sha512", hash2, algo.salt1, 100000) - - return sha256(algo.salt2 + hash3 + algo.salt2) - - -# noinspection PyPep8Naming -def compute_check(r: types.account.Password, password: str) -> types.InputCheckPasswordSRP: - algo = r.current_algo - - p_bytes = algo.p - p = btoi(algo.p) - - g_bytes = itob(algo.g) - g = algo.g - - B_bytes = r.srp_B - B = btoi(B_bytes) - - srp_id = r.srp_id - - x_bytes = compute_hash(algo, password) - x = btoi(x_bytes) - - g_x = pow(g, x, p) - - k_bytes = sha256(p_bytes + g_bytes) - k = btoi(k_bytes) - - kg_x = (k * g_x) % p - - while True: - a_bytes = os.urandom(256) - a = btoi(a_bytes) - - A = pow(g, a, p) - A_bytes = itob(A) - - u = btoi(sha256(A_bytes + B_bytes)) - - if u > 0: - break - - g_b = (B - kg_x) % p - - ux = u * x - a_ux = a + ux - S = pow(g_b, a_ux, p) - S_bytes = itob(S) - - K_bytes = sha256(S_bytes) - - M1_bytes = sha256( - xor(sha256(p_bytes), sha256(g_bytes)) - + sha256(algo.salt1) - + sha256(algo.salt2) - + A_bytes - + B_bytes - + K_bytes - ) - - return types.InputCheckPasswordSRP(srp_id=srp_id, A=A_bytes, M1=M1_bytes) diff --git a/pyrogram/client/types/inline_mode/inline_query_result.py b/pyrogram/client/types/inline_mode/inline_query_result.py deleted file mode 100644 index 6525585bf4..0000000000 --- a/pyrogram/client/types/inline_mode/inline_query_result.py +++ /dev/null @@ -1,71 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan -# -# 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 uuid import uuid4 - -from ..bots_and_keyboards import InlineKeyboardMarkup -from ..input_message_content import InputMessageContent -from ..object import Object - -"""- :obj:`InlineQueryResultCachedAudio` - - :obj:`InlineQueryResultCachedDocument` - - :obj:`InlineQueryResultCachedGif` - - :obj:`InlineQueryResultCachedMpeg4Gif` - - :obj:`InlineQueryResultCachedPhoto` - - :obj:`InlineQueryResultCachedSticker` - - :obj:`InlineQueryResultCachedVideo` - - :obj:`InlineQueryResultCachedVoice` - - :obj:`InlineQueryResultAudio` - - :obj:`InlineQueryResultContact` - - :obj:`InlineQueryResultGame` - - :obj:`InlineQueryResultDocument` - - :obj:`InlineQueryResultGif` - - :obj:`InlineQueryResultLocation` - - :obj:`InlineQueryResultMpeg4Gif` - - :obj:`InlineQueryResultPhoto` - - :obj:`InlineQueryResultVenue` - - :obj:`InlineQueryResultVideo` - - :obj:`InlineQueryResultVoice`""" - - -class InlineQueryResult(Object): - """One result of an inline query. - - Pyrogram currently supports results of the following types: - - - :obj:`InlineQueryResultArticle` - - :obj:`InlineQueryResultPhoto` - - :obj:`InlineQueryResultAnimation` - """ - - def __init__( - self, - type: str, - id: str, - input_message_content: InputMessageContent, - reply_markup: InlineKeyboardMarkup - ): - super().__init__() - - self.type = type - self.id = str(uuid4()) if id is None else str(id) - self.input_message_content = input_message_content - self.reply_markup = reply_markup - - async def write(self): - pass diff --git a/pyrogram/connection/transport/tcp/tcp.py b/pyrogram/connection/transport/tcp/tcp.py index 070907f469..b2ff539184 100644 --- a/pyrogram/connection/transport/tcp/tcp.py +++ b/pyrogram/connection/transport/tcp/tcp.py @@ -68,7 +68,7 @@ def __init__(self, ipv6: bool, proxy: dict): password=proxy.get("password", None) ) - log.info("Using proxy {}:{}".format(hostname, port)) + log.info(f"Using proxy {hostname}:{port}") else: self.socket = socks.socksocket( socket.AF_INET6 if ipv6 diff --git a/pyrogram/connection/transport/tcp/tcp_abridged_o.py b/pyrogram/connection/transport/tcp/tcp_abridged_o.py index e8f8fba0d9..c7b241593f 100644 --- a/pyrogram/connection/transport/tcp/tcp_abridged_o.py +++ b/pyrogram/connection/transport/tcp/tcp_abridged_o.py @@ -19,8 +19,8 @@ import logging import os +from pyrogram.crypto import aes from .tcp import TCP -from ....crypto.aes import AES log = logging.getLogger(__name__) @@ -49,7 +49,7 @@ async def connect(self, address: tuple): self.encrypt = (nonce[8:40], nonce[40:56], bytearray(1)) self.decrypt = (temp[0:32], temp[32:48], bytearray(1)) - nonce[56:64] = AES.ctr256_encrypt(nonce, *self.encrypt)[56:64] + nonce[56:64] = aes.ctr256_encrypt(nonce, *self.encrypt)[56:64] await super().send(nonce) @@ -57,7 +57,7 @@ async def send(self, data: bytes, *args): length = len(data) // 4 await super().send( - AES.ctr256_encrypt( + aes.ctr256_encrypt( (bytes([length]) if length <= 126 else b"\x7f" + length.to_bytes(3, "little")) @@ -72,7 +72,7 @@ async def recv(self, length: int = 0) -> bytes or None: if length is None: return None - length = AES.ctr256_decrypt(length, *self.decrypt) + length = aes.ctr256_decrypt(length, *self.decrypt) if length == b"\x7f": length = await super().recv(3) @@ -80,11 +80,11 @@ async def recv(self, length: int = 0) -> bytes or None: if length is None: return None - length = AES.ctr256_decrypt(length, *self.decrypt) + length = aes.ctr256_decrypt(length, *self.decrypt) data = await super().recv(int.from_bytes(length, "little") * 4) if data is None: return None - return AES.ctr256_decrypt(data, *self.decrypt) + return aes.ctr256_decrypt(data, *self.decrypt) diff --git a/pyrogram/connection/transport/tcp/tcp_intermediate_o.py b/pyrogram/connection/transport/tcp/tcp_intermediate_o.py index 4b4f4610af..c0c1d915b8 100644 --- a/pyrogram/connection/transport/tcp/tcp_intermediate_o.py +++ b/pyrogram/connection/transport/tcp/tcp_intermediate_o.py @@ -20,8 +20,8 @@ import os from struct import pack, unpack +from pyrogram.crypto import aes from .tcp import TCP -from ....crypto.aes import AES log = logging.getLogger(__name__) @@ -50,13 +50,13 @@ async def connect(self, address: tuple): self.encrypt = (nonce[8:40], nonce[40:56], bytearray(1)) self.decrypt = (temp[0:32], temp[32:48], bytearray(1)) - nonce[56:64] = AES.ctr256_encrypt(nonce, *self.encrypt)[56:64] + nonce[56:64] = aes.ctr256_encrypt(nonce, *self.encrypt)[56:64] await super().send(nonce) async def send(self, data: bytes, *args): await super().send( - AES.ctr256_encrypt( + aes.ctr256_encrypt( pack(" bytes or None: if length is None: return None - length = AES.ctr256_decrypt(length, *self.decrypt) + length = aes.ctr256_decrypt(length, *self.decrypt) data = await super().recv(unpack(". - -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/aes.py b/pyrogram/crypto/aes.py index 47c9c0949c..c094fd2214 100644 --- a/pyrogram/crypto/aes.py +++ b/pyrogram/crypto/aes.py @@ -26,30 +26,28 @@ log.info("Using TgCrypto") - class AES: - @classmethod - def ige256_encrypt(cls, data: bytes, key: bytes, iv: bytes) -> bytes: - return tgcrypto.ige256_encrypt(data, key, iv) - - @classmethod - def ige256_decrypt(cls, data: bytes, key: bytes, iv: bytes) -> bytes: - return tgcrypto.ige256_decrypt(data, key, iv) - - @staticmethod - def ctr256_encrypt(data: bytes, key: bytes, iv: bytearray, state: bytearray = None) -> bytes: - return tgcrypto.ctr256_encrypt(data, key, iv, state or bytearray(1)) - - @staticmethod - def ctr256_decrypt(data: bytes, key: bytes, iv: bytearray, state: bytearray = None) -> bytes: - return tgcrypto.ctr256_decrypt(data, key, iv, state or bytearray(1)) - - @staticmethod - def xor(a: bytes, b: bytes) -> bytes: - return int.to_bytes( - int.from_bytes(a, "big") ^ int.from_bytes(b, "big"), - len(a), - "big", - ) + def ige256_encrypt(data: bytes, key: bytes, iv: bytes) -> bytes: + return tgcrypto.ige256_encrypt(data, key, iv) + + + def ige256_decrypt(data: bytes, key: bytes, iv: bytes) -> bytes: + return tgcrypto.ige256_decrypt(data, key, iv) + + + def ctr256_encrypt(data: bytes, key: bytes, iv: bytearray, state: bytearray = None) -> bytes: + return tgcrypto.ctr256_encrypt(data, key, iv, state or bytearray(1)) + + + def ctr256_decrypt(data: bytes, key: bytes, iv: bytearray, state: bytearray = None) -> bytes: + return tgcrypto.ctr256_decrypt(data, key, iv, state or bytearray(1)) + + + def xor(a: bytes, b: bytes) -> bytes: + return int.to_bytes( + int.from_bytes(a, "big") ^ int.from_bytes(b, "big"), + len(a), + "big", + ) except ImportError: import pyaes @@ -60,75 +58,73 @@ def xor(a: bytes, b: bytes) -> bytes: ) - class AES: - @classmethod - def ige256_encrypt(cls, data: bytes, key: bytes, iv: bytes) -> bytes: - return cls.ige(data, key, iv, True) + def ige256_encrypt(data: bytes, key: bytes, iv: bytes) -> bytes: + return ige(data, key, iv, True) + + + def ige256_decrypt(data: bytes, key: bytes, iv: bytes) -> bytes: + return ige(data, key, iv, False) + + + def ctr256_encrypt(data: bytes, key: bytes, iv: bytearray, state: bytearray = None) -> bytes: + return ctr(data, key, iv, state or bytearray(1)) + + + def ctr256_decrypt(data: bytes, key: bytes, iv: bytearray, state: bytearray = None) -> bytes: + return ctr(data, key, iv, state or bytearray(1)) - @classmethod - def ige256_decrypt(cls, data: bytes, key: bytes, iv: bytes) -> bytes: - return cls.ige(data, key, iv, False) - @classmethod - def ctr256_encrypt(cls, data: bytes, key: bytes, iv: bytearray, state: bytearray = None) -> bytes: - return cls.ctr(data, key, iv, state or bytearray(1)) + def xor(a: bytes, b: bytes) -> bytes: + return int.to_bytes( + int.from_bytes(a, "big") ^ int.from_bytes(b, "big"), + len(a), + "big", + ) - @classmethod - def ctr256_decrypt(cls, data: bytes, key: bytes, iv: bytearray, state: bytearray = None) -> bytes: - return cls.ctr(data, key, iv, state or bytearray(1)) - @staticmethod - def xor(a: bytes, b: bytes) -> bytes: - return int.to_bytes( - int.from_bytes(a, "big") ^ int.from_bytes(b, "big"), - len(a), - "big", - ) + def ige(data: bytes, key: bytes, iv: bytes, encrypt: bool) -> bytes: + cipher = pyaes.AES(key) - @classmethod - def ige(cls, data: bytes, key: bytes, iv: bytes, encrypt: bool) -> bytes: - cipher = pyaes.AES(key) + iv_1 = iv[:16] + iv_2 = iv[16:] - iv_1 = iv[:16] - iv_2 = iv[16:] + data = [data[i: i + 16] for i in range(0, len(data), 16)] - data = [data[i: i + 16] for i in range(0, len(data), 16)] + if encrypt: + for i, chunk in enumerate(data): + iv_1 = data[i] = xor(cipher.encrypt(xor(chunk, iv_1)), iv_2) + iv_2 = chunk + else: + for i, chunk in enumerate(data): + iv_2 = data[i] = xor(cipher.decrypt(xor(chunk, iv_2)), iv_1) + iv_1 = chunk - if encrypt: - for i, chunk in enumerate(data): - iv_1 = data[i] = cls.xor(cipher.encrypt(cls.xor(chunk, iv_1)), iv_2) - iv_2 = chunk - else: - for i, chunk in enumerate(data): - iv_2 = data[i] = cls.xor(cipher.decrypt(cls.xor(chunk, iv_2)), iv_1) - iv_1 = chunk + return b"".join(data) - return b"".join(data) - @classmethod - def ctr(cls, data: bytes, key: bytes, iv: bytearray, state: bytearray) -> bytes: - cipher = pyaes.AES(key) + def ctr(data: bytes, key: bytes, iv: bytearray, state: bytearray) -> bytes: + cipher = pyaes.AES(key) - out = bytearray(data) - chunk = cipher.encrypt(iv) + out = bytearray(data) + chunk = cipher.encrypt(iv) - for i in range(0, len(data), 16): - for j in range(0, min(len(data) - i, 16)): - out[i + j] ^= chunk[state[0]] + for i in range(0, len(data), 16): + for j in range(0, min(len(data) - i, 16)): + out[i + j] ^= chunk[state[0]] - state[0] += 1 + state[0] += 1 - if state[0] >= 16: - state[0] = 0 + if state[0] >= 16: + state[0] = 0 - if state[0] == 0: - for k in range(15, -1, -1): - try: - iv[k] += 1 - break - except ValueError: - iv[k] = 0 + if state[0] == 0: + for k in range(15, -1, -1): + try: + iv[k] += 1 + break + except ValueError: + iv[k] = 0 - chunk = cipher.encrypt(iv) + chunk = cipher.encrypt(iv) - return out + return out diff --git a/pyrogram/crypto/mtproto.py b/pyrogram/crypto/mtproto.py index 539976d66e..b67b49f128 100644 --- a/pyrogram/crypto/mtproto.py +++ b/pyrogram/crypto/mtproto.py @@ -20,43 +20,54 @@ from io import BytesIO from os import urandom -from pyrogram.api.core import Message, Long -from . import AES, KDF +from pyrogram.raw.core import Message, Long +from . import aes -class MTProto: - @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) +def kdf(auth_key: bytes, msg_key: bytes, outgoing: bool) -> tuple: + # https://core.telegram.org/mtproto/description#defining-aes-key-and-initialization-vector + x = 0 if outgoing else 8 - # 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) + sha256_a = sha256(msg_key + auth_key[x: x + 36]).digest() + sha256_b = sha256(auth_key[x + 40:x + 76] + msg_key).digest() # 76 = 40 + 36 - return auth_key_id + msg_key + AES.ige256_encrypt(data + padding, aes_key, aes_iv) + aes_key = sha256_a[:8] + sha256_b[8:24] + sha256_a[24:32] + aes_iv = sha256_b[:8] + sha256_a[8:24] + sha256_b[24:32] - @staticmethod - def unpack(b: BytesIO, session_id: bytes, auth_key: bytes, auth_key_id: bytes) -> Message: - assert b.read(8) == auth_key_id, b.getvalue() + return aes_key, aes_iv - 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)) - data.read(8) - # https://core.telegram.org/mtproto/security_guidelines#checking-session-id - assert data.read(8) == session_id +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) - message = Message.read(data) + # 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) - # 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] + return auth_key_id + msg_key + aes.ige256_encrypt(data + padding, aes_key, aes_iv) - # https://core.telegram.org/mtproto/security_guidelines#checking-msg-id - assert message.msg_id % 2 != 0 - return 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)) + data.read(8) + + # 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 diff --git a/pyrogram/crypto/prime.py b/pyrogram/crypto/prime.py index 0ca59ea75d..22210726af 100644 --- a/pyrogram/crypto/prime.py +++ b/pyrogram/crypto/prime.py @@ -18,68 +18,65 @@ from random import randint +CURRENT_DH_PRIME = int( + "C71CAEB9C6B1C9048E6C522F70F13F73980D40238E3E21C14934D037563D930F" + "48198A0AA7C14058229493D22530F4DBFA336F6E0AC925139543AED44CCE7C37" + "20FD51F69458705AC68CD4FE6B6B13ABDC9746512969328454F18FAF8C595F64" + "2477FE96BB2A941D5BCD1D4AC8CC49880708FA9B378E3C4F3A9060BEE67CF9A4" + "A4A695811051907E162753B56B0F6B410DBA74D8A84B2A14B3144E0EF1284754" + "FD17ED950D5965B4B9DD46582DB1178D169C6BC465B0D6FF9CA3928FEF5B9AE4" + "E418FC15E83EBEA0F87FA9FF5EED70050DED2849F47BF959D956850CE929851F" + "0D8115F635B105EE2E4E15D04B2454BF6F4FADF034B10403119CD8E3B92FCC5B", + 16 +) + + +# Recursive variant +# def gcd(cls, a: int, b: int) -> int: +# return cls.gcd(b, a % b) if b else a + +def gcd(a: int, b: int) -> int: + while b: + a, b = b, a % b + + return a -class Prime: - CURRENT_DH_PRIME = int( - "C71CAEB9C6B1C9048E6C522F70F13F73980D40238E3E21C14934D037563D930F" - "48198A0AA7C14058229493D22530F4DBFA336F6E0AC925139543AED44CCE7C37" - "20FD51F69458705AC68CD4FE6B6B13ABDC9746512969328454F18FAF8C595F64" - "2477FE96BB2A941D5BCD1D4AC8CC49880708FA9B378E3C4F3A9060BEE67CF9A4" - "A4A695811051907E162753B56B0F6B410DBA74D8A84B2A14B3144E0EF1284754" - "FD17ED950D5965B4B9DD46582DB1178D169C6BC465B0D6FF9CA3928FEF5B9AE4" - "E418FC15E83EBEA0F87FA9FF5EED70050DED2849F47BF959D956850CE929851F" - "0D8115F635B105EE2E4E15D04B2454BF6F4FADF034B10403119CD8E3B92FCC5B", - 16 - ) - - # Recursive variant - # @classmethod - # def gcd(cls, a: int, b: int) -> int: - # return cls.gcd(b, a % b) if b else a - - @staticmethod - def gcd(a: int, b: int) -> int: - while b: - a, b = b, a % b - - return a - - @classmethod - def decompose(cls, pq: int) -> int: - # https://comeoncodeon.wordpress.com/2010/09/18/pollard-rho-brent-integer-factorization/ - if pq % 2 == 0: - return 2 - - y, c, m = randint(1, pq - 1), randint(1, pq - 1), randint(1, pq - 1) - g = r = q = 1 - x = ys = 0 - - while g == 1: - x = y - - for i in range(r): - y = (pow(y, 2, pq) + c) % pq - k = 0 +def decompose(pq: int) -> int: + # https://comeoncodeon.wordpress.com/2010/09/18/pollard-rho-brent-integer-factorization/ + if pq % 2 == 0: + return 2 - while k < r and g == 1: - ys = y + y, c, m = randint(1, pq - 1), randint(1, pq - 1), randint(1, pq - 1) + g = r = q = 1 + x = ys = 0 - for i in range(min(m, r - k)): - y = (pow(y, 2, pq) + c) % pq - q = q * (abs(x - y)) % pq + while g == 1: + x = y + + for i in range(r): + y = (pow(y, 2, pq) + c) % pq + + k = 0 + + while k < r and g == 1: + ys = y + + for i in range(min(m, r - k)): + y = (pow(y, 2, pq) + c) % pq + q = q * (abs(x - y)) % pq - g = cls.gcd(q, pq) - k += m + g = gcd(q, pq) + k += m - r *= 2 + r *= 2 - if g == pq: - while True: - ys = (pow(ys, 2, pq) + c) % pq - g = cls.gcd(abs(x - ys), pq) + if g == pq: + while True: + ys = (pow(ys, 2, pq) + c) % pq + g = gcd(abs(x - ys), pq) - if g > 1: - break + if g > 1: + break - return g + return g diff --git a/pyrogram/crypto/rsa.py b/pyrogram/crypto/rsa.py index 268a91e476..aaec8df6ca 100644 --- a/pyrogram/crypto/rsa.py +++ b/pyrogram/crypto/rsa.py @@ -20,194 +20,192 @@ PublicKey = namedtuple("PublicKey", ["m", "e"]) +# To get modulus and exponent: +# +# [RSA PUBLIC KEY]: +# grep -v -- - public.key | tr -d \\n | base64 -d | openssl asn1parse -inform DER -i +# +# [PUBLIC KEY]: +# openssl rsa -pubin -in key -text -noout -class RSA: - # To get modulus and exponent: - # - # [RSA PUBLIC KEY]: - # grep -v -- - public.key | tr -d \\n | base64 -d | openssl asn1parse -inform DER -i - # - # [PUBLIC KEY]: - # openssl rsa -pubin -in key -text -noout +server_public_keys = { + # -4344800451088585951 + 0xc3b42b026ce86b21 - (1 << 64): PublicKey( # Telegram servers #1 + # -----BEGIN RSA PUBLIC KEY----- + # MIIBCgKCAQEAwVACPi9w23mF3tBkdZz+zwrzKOaaQdr01vAbU4E1pvkfj4sqDsm6 + # lyDONS789sVoD/xCS9Y0hkkC3gtL1tSfTlgCMOOul9lcixlEKzwKENj1Yz/s7daS + # an9tqw3bfUV/nqgbhGX81v/+7RFAEd+RwFnK7a+XYl9sluzHRyVVaTTveB2GazTw + # Efzk2DWgkBluml8OREmvfraX3bkHZJTKX4EQSjBbbdJ2ZXIsRrYOXfaA+xayEGB+ + # 8hdlLmAjbCVfaigxX0CDqWeR1yFL9kwd9P0NsZRPsmoqVwMbMu7mStFai6aIhc3n + # Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB + # -----END RSA PUBLIC KEY----- + int( + "C150023E2F70DB7985DED064759CFECF0AF328E69A41DAF4D6F01B538135A6F9" + "1F8F8B2A0EC9BA9720CE352EFCF6C5680FFC424BD634864902DE0B4BD6D49F4E" + "580230E3AE97D95C8B19442B3C0A10D8F5633FECEDD6926A7F6DAB0DDB7D457F" + "9EA81B8465FCD6FFFEED114011DF91C059CAEDAF97625F6C96ECC74725556934" + "EF781D866B34F011FCE4D835A090196E9A5F0E4449AF7EB697DDB9076494CA5F" + "81104A305B6DD27665722C46B60E5DF680FB16B210607EF217652E60236C255F" + "6A28315F4083A96791D7214BF64C1DF4FD0DB1944FB26A2A57031B32EEE64AD1" + "5A8BA68885CDE74A5BFC920F6ABF59BA5C75506373E7130F9042DA922179251F", + 16 + ), # Modulus + int("010001", 16) # Exponent + ), - server_public_keys = { - # -4344800451088585951 - 0xc3b42b026ce86b21 - (1 << 64): PublicKey( # Telegram servers #1 - # -----BEGIN RSA PUBLIC KEY----- - # MIIBCgKCAQEAwVACPi9w23mF3tBkdZz+zwrzKOaaQdr01vAbU4E1pvkfj4sqDsm6 - # lyDONS789sVoD/xCS9Y0hkkC3gtL1tSfTlgCMOOul9lcixlEKzwKENj1Yz/s7daS - # an9tqw3bfUV/nqgbhGX81v/+7RFAEd+RwFnK7a+XYl9sluzHRyVVaTTveB2GazTw - # Efzk2DWgkBluml8OREmvfraX3bkHZJTKX4EQSjBbbdJ2ZXIsRrYOXfaA+xayEGB+ - # 8hdlLmAjbCVfaigxX0CDqWeR1yFL9kwd9P0NsZRPsmoqVwMbMu7mStFai6aIhc3n - # Slv8kg9qv1m6XHVQY3PnEw+QQtqSIXklHwIDAQAB - # -----END RSA PUBLIC KEY----- - int( - "C150023E2F70DB7985DED064759CFECF0AF328E69A41DAF4D6F01B538135A6F9" - "1F8F8B2A0EC9BA9720CE352EFCF6C5680FFC424BD634864902DE0B4BD6D49F4E" - "580230E3AE97D95C8B19442B3C0A10D8F5633FECEDD6926A7F6DAB0DDB7D457F" - "9EA81B8465FCD6FFFEED114011DF91C059CAEDAF97625F6C96ECC74725556934" - "EF781D866B34F011FCE4D835A090196E9A5F0E4449AF7EB697DDB9076494CA5F" - "81104A305B6DD27665722C46B60E5DF680FB16B210607EF217652E60236C255F" - "6A28315F4083A96791D7214BF64C1DF4FD0DB1944FB26A2A57031B32EEE64AD1" - "5A8BA68885CDE74A5BFC920F6ABF59BA5C75506373E7130F9042DA922179251F", - 16 - ), # Modulus - int("010001", 16) # Exponent - ), + # 847625836280919973 + 0x10bc35f3509f7b7a5 - (1 << 64): PublicKey( # Telegram servers #2 + # -----BEGIN PUBLIC KEY----- + # MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAruw2yP/BCcsJliRoW5eB + # VBVle9dtjJw+OYED160Wybum9SXtBBLXriwt4rROd9csv0t0OHCaTmRqBcQ0J8fx + # hN6/cpR1GWgOZRUAiQxoMnlt0R93LCX/j1dnVa/gVbCjdSxpbrfY2g2L4frzjJvd + # l84Kd9ORYjDEAyFnEA7dD556OptgLQQ2e2iVNq8NZLYTzLp5YpOdO1doK+ttrltg + # gTCy5SrKeLoCPPbOgGsdxJxyz5KKcZnSLj16yE5HvJQn0CNpRdENvRUXe6tBP78O + # 39oJ8BTHp9oIjd6XWXAsp2CvK45Ol8wFXGF710w9lwCGNbmNxNYhtIkdqfsEcwR5 + # JwIDAQAB + # -----END PUBLIC KEY----- + int( + "AEEC36C8FFC109CB099624685B97815415657BD76D8C9C3E398103D7AD16C9BB" + "A6F525ED0412D7AE2C2DE2B44E77D72CBF4B7438709A4E646A05C43427C7F184" + "DEBF72947519680E651500890C6832796DD11F772C25FF8F576755AFE055B0A3" + "752C696EB7D8DA0D8BE1FAF38C9BDD97CE0A77D3916230C4032167100EDD0F9E" + "7A3A9B602D04367B689536AF0D64B613CCBA7962939D3B57682BEB6DAE5B6081" + "30B2E52ACA78BA023CF6CE806B1DC49C72CF928A7199D22E3D7AC84E47BC9427" + "D0236945D10DBD15177BAB413FBF0EDFDA09F014C7A7DA088DDE9759702CA760" + "AF2B8E4E97CC055C617BD74C3D97008635B98DC4D621B4891DA9FB0473047927", + 16 + ), # Modulus + int("010001", 16) # Exponent + ), - # 847625836280919973 - 0x10bc35f3509f7b7a5 - (1 << 64): PublicKey( # Telegram servers #2 - # -----BEGIN PUBLIC KEY----- - # MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAruw2yP/BCcsJliRoW5eB - # VBVle9dtjJw+OYED160Wybum9SXtBBLXriwt4rROd9csv0t0OHCaTmRqBcQ0J8fx - # hN6/cpR1GWgOZRUAiQxoMnlt0R93LCX/j1dnVa/gVbCjdSxpbrfY2g2L4frzjJvd - # l84Kd9ORYjDEAyFnEA7dD556OptgLQQ2e2iVNq8NZLYTzLp5YpOdO1doK+ttrltg - # gTCy5SrKeLoCPPbOgGsdxJxyz5KKcZnSLj16yE5HvJQn0CNpRdENvRUXe6tBP78O - # 39oJ8BTHp9oIjd6XWXAsp2CvK45Ol8wFXGF710w9lwCGNbmNxNYhtIkdqfsEcwR5 - # JwIDAQAB - # -----END PUBLIC KEY----- - int( - "AEEC36C8FFC109CB099624685B97815415657BD76D8C9C3E398103D7AD16C9BB" - "A6F525ED0412D7AE2C2DE2B44E77D72CBF4B7438709A4E646A05C43427C7F184" - "DEBF72947519680E651500890C6832796DD11F772C25FF8F576755AFE055B0A3" - "752C696EB7D8DA0D8BE1FAF38C9BDD97CE0A77D3916230C4032167100EDD0F9E" - "7A3A9B602D04367B689536AF0D64B613CCBA7962939D3B57682BEB6DAE5B6081" - "30B2E52ACA78BA023CF6CE806B1DC49C72CF928A7199D22E3D7AC84E47BC9427" - "D0236945D10DBD15177BAB413FBF0EDFDA09F014C7A7DA088DDE9759702CA760" - "AF2B8E4E97CC055C617BD74C3D97008635B98DC4D621B4891DA9FB0473047927", - 16 - ), # Modulus - int("010001", 16) # Exponent - ), + # 1562291298945373506 + 0x115ae5fa8b5529542 - (1 << 64): PublicKey( # Telegram servers #3 + # -----BEGIN PUBLIC KEY----- + # MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvfLHfYH2r9R70w8prHbl + # Wt/nDkh+XkgpflqQVcnAfSuTtO05lNPspQmL8Y2XjVT4t8cT6xAkdgfmmvnvRPOO + # KPi0OfJXoRVylFzAQG/j83u5K3kRLbae7fLccVhKZhY46lvsueI1hQdLgNV9n1cQ + # 3TDS2pQOCtovG4eDl9wacrXOJTG2990VjgnIKNA0UMoP+KF03qzryqIt3oTvZq03 + # DyWdGK+AZjgBLaDKSnC6qD2cFY81UryRWOab8zKkWAnhw2kFpcqhI0jdV5QaSCEx + # vnsjVaX0Y1N0870931/5Jb9ICe4nweZ9kSDF/gip3kWLG0o8XQpChDfyvsqB9OLV + # /wIDAQAB + # -----END PUBLIC KEY----- + int( + "BDF2C77D81F6AFD47BD30F29AC76E55ADFE70E487E5E48297E5A9055C9C07D2B" + "93B4ED3994D3ECA5098BF18D978D54F8B7C713EB10247607E69AF9EF44F38E28" + "F8B439F257A11572945CC0406FE3F37BB92B79112DB69EEDF2DC71584A661638" + "EA5BECB9E23585074B80D57D9F5710DD30D2DA940E0ADA2F1B878397DC1A72B5" + "CE2531B6F7DD158E09C828D03450CA0FF8A174DEACEBCAA22DDE84EF66AD370F" + "259D18AF806638012DA0CA4A70BAA83D9C158F3552BC9158E69BF332A45809E1" + "C36905A5CAA12348DD57941A482131BE7B2355A5F4635374F3BD3DDF5FF925BF" + "4809EE27C1E67D9120C5FE08A9DE458B1B4A3C5D0A428437F2BECA81F4E2D5FF", + 16 + ), # Modulus + int("010001", 16) # Exponent + ), - # 1562291298945373506 - 0x115ae5fa8b5529542 - (1 << 64): PublicKey( # Telegram servers #3 - # -----BEGIN PUBLIC KEY----- - # MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvfLHfYH2r9R70w8prHbl - # Wt/nDkh+XkgpflqQVcnAfSuTtO05lNPspQmL8Y2XjVT4t8cT6xAkdgfmmvnvRPOO - # KPi0OfJXoRVylFzAQG/j83u5K3kRLbae7fLccVhKZhY46lvsueI1hQdLgNV9n1cQ - # 3TDS2pQOCtovG4eDl9wacrXOJTG2990VjgnIKNA0UMoP+KF03qzryqIt3oTvZq03 - # DyWdGK+AZjgBLaDKSnC6qD2cFY81UryRWOab8zKkWAnhw2kFpcqhI0jdV5QaSCEx - # vnsjVaX0Y1N0870931/5Jb9ICe4nweZ9kSDF/gip3kWLG0o8XQpChDfyvsqB9OLV - # /wIDAQAB - # -----END PUBLIC KEY----- - int( - "BDF2C77D81F6AFD47BD30F29AC76E55ADFE70E487E5E48297E5A9055C9C07D2B" - "93B4ED3994D3ECA5098BF18D978D54F8B7C713EB10247607E69AF9EF44F38E28" - "F8B439F257A11572945CC0406FE3F37BB92B79112DB69EEDF2DC71584A661638" - "EA5BECB9E23585074B80D57D9F5710DD30D2DA940E0ADA2F1B878397DC1A72B5" - "CE2531B6F7DD158E09C828D03450CA0FF8A174DEACEBCAA22DDE84EF66AD370F" - "259D18AF806638012DA0CA4A70BAA83D9C158F3552BC9158E69BF332A45809E1" - "C36905A5CAA12348DD57941A482131BE7B2355A5F4635374F3BD3DDF5FF925BF" - "4809EE27C1E67D9120C5FE08A9DE458B1B4A3C5D0A428437F2BECA81F4E2D5FF", - 16 - ), # Modulus - int("010001", 16) # Exponent - ), + # -5859577972006586033 + 0xaeae98e13cd7f94f - (1 << 64): PublicKey( # Telegram servers #4 + # -----BEGIN PUBLIC KEY----- + # MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs/ditzm+mPND6xkhzwFI + # z6J/968CtkcSE/7Z2qAJiXbmZ3UDJPGrzqTDHkO30R8VeRM/Kz2f4nR05GIFiITl + # 4bEjvpy7xqRDspJcCFIOcyXm8abVDhF+th6knSU0yLtNKuQVP6voMrnt9MV1X92L + # GZQLgdHZbPQz0Z5qIpaKhdyA8DEvWWvSUwwc+yi1/gGaybwlzZwqXYoPOhwMebzK + # Uk0xW14htcJrRrq+PXXQbRzTMynseCoPIoke0dtCodbA3qQxQovE16q9zz4Otv2k + # 4j63cz53J+mhkVWAeWxVGI0lltJmWtEYK6er8VqqWot3nqmWMXogrgRLggv/Nbbo + # oQIDAQAB + # -----END PUBLIC KEY----- + int( + "B3F762B739BE98F343EB1921CF0148CFA27FF7AF02B6471213FED9DAA0098976" + "E667750324F1ABCEA4C31E43B7D11F1579133F2B3D9FE27474E462058884E5E1" + "B123BE9CBBC6A443B2925C08520E7325E6F1A6D50E117EB61EA49D2534C8BB4D" + "2AE4153FABE832B9EDF4C5755FDD8B19940B81D1D96CF433D19E6A22968A85DC" + "80F0312F596BD2530C1CFB28B5FE019AC9BC25CD9C2A5D8A0F3A1C0C79BCCA52" + "4D315B5E21B5C26B46BABE3D75D06D1CD33329EC782A0F22891ED1DB42A1D6C0" + "DEA431428BC4D7AABDCF3E0EB6FDA4E23EB7733E7727E9A1915580796C55188D" + "2596D2665AD1182BA7ABF15AAA5A8B779EA996317A20AE044B820BFF35B6E8A1", + 16 + ), # Modulus + int("010001", 16) # Exponent + ), - # -5859577972006586033 - 0xaeae98e13cd7f94f - (1 << 64): PublicKey( # Telegram servers #4 - # -----BEGIN PUBLIC KEY----- - # MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAs/ditzm+mPND6xkhzwFI - # z6J/968CtkcSE/7Z2qAJiXbmZ3UDJPGrzqTDHkO30R8VeRM/Kz2f4nR05GIFiITl - # 4bEjvpy7xqRDspJcCFIOcyXm8abVDhF+th6knSU0yLtNKuQVP6voMrnt9MV1X92L - # GZQLgdHZbPQz0Z5qIpaKhdyA8DEvWWvSUwwc+yi1/gGaybwlzZwqXYoPOhwMebzK - # Uk0xW14htcJrRrq+PXXQbRzTMynseCoPIoke0dtCodbA3qQxQovE16q9zz4Otv2k - # 4j63cz53J+mhkVWAeWxVGI0lltJmWtEYK6er8VqqWot3nqmWMXogrgRLggv/Nbbo - # oQIDAQAB - # -----END PUBLIC KEY----- - int( - "B3F762B739BE98F343EB1921CF0148CFA27FF7AF02B6471213FED9DAA0098976" - "E667750324F1ABCEA4C31E43B7D11F1579133F2B3D9FE27474E462058884E5E1" - "B123BE9CBBC6A443B2925C08520E7325E6F1A6D50E117EB61EA49D2534C8BB4D" - "2AE4153FABE832B9EDF4C5755FDD8B19940B81D1D96CF433D19E6A22968A85DC" - "80F0312F596BD2530C1CFB28B5FE019AC9BC25CD9C2A5D8A0F3A1C0C79BCCA52" - "4D315B5E21B5C26B46BABE3D75D06D1CD33329EC782A0F22891ED1DB42A1D6C0" - "DEA431428BC4D7AABDCF3E0EB6FDA4E23EB7733E7727E9A1915580796C55188D" - "2596D2665AD1182BA7ABF15AAA5A8B779EA996317A20AE044B820BFF35B6E8A1", - 16 - ), # Modulus - int("010001", 16) # Exponent - ), + # 6491968696586960280 + 0x15a181b2235057d98 - (1 << 64): PublicKey( # Telegram servers #5 + # -----BEGIN PUBLIC KEY----- + # MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvmpxVY7ld/8DAjz6F6q0 + # 5shjg8/4p6047bn6/m8yPy1RBsvIyvuDuGnP/RzPEhzXQ9UJ5Ynmh2XJZgHoE9xb + # nfxL5BXHplJhMtADXKM9bWB11PU1Eioc3+AXBB8QiNFBn2XI5UkO5hPhbb9mJpjA + # 9Uhw8EdfqJP8QetVsI/xrCEbwEXe0xvifRLJbY08/Gp66KpQvy7g8w7VB8wlgePe + # xW3pT13Ap6vuC+mQuJPyiHvSxjEKHgqePji9NP3tJUFQjcECqcm0yV7/2d0t/pbC + # m+ZH1sadZspQCEPPrtbkQBlvHb4OLiIWPGHKSMeRFvp3IWcmdJqXahxLCUS1Eh6M + # AQIDAQAB + # -----END PUBLIC KEY----- + int( + "BE6A71558EE577FF03023CFA17AAB4E6C86383CFF8A7AD38EDB9FAFE6F323F2D" + "5106CBC8CAFB83B869CFFD1CCF121CD743D509E589E68765C96601E813DC5B9D" + "FC4BE415C7A6526132D0035CA33D6D6075D4F535122A1CDFE017041F1088D141" + "9F65C8E5490EE613E16DBF662698C0F54870F0475FA893FC41EB55B08FF1AC21" + "1BC045DED31BE27D12C96D8D3CFC6A7AE8AA50BF2EE0F30ED507CC2581E3DEC5" + "6DE94F5DC0A7ABEE0BE990B893F2887BD2C6310A1E0A9E3E38BD34FDED254150" + "8DC102A9C9B4C95EFFD9DD2DFE96C29BE647D6C69D66CA500843CFAED6E44019" + "6F1DBE0E2E22163C61CA48C79116FA77216726749A976A1C4B0944B5121E8C01", + 16 + ), # Modulus + int("010001", 16) # Exponent + ), - # 6491968696586960280 - 0x15a181b2235057d98 - (1 << 64): PublicKey( # Telegram servers #5 - # -----BEGIN PUBLIC KEY----- - # MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvmpxVY7ld/8DAjz6F6q0 - # 5shjg8/4p6047bn6/m8yPy1RBsvIyvuDuGnP/RzPEhzXQ9UJ5Ynmh2XJZgHoE9xb - # nfxL5BXHplJhMtADXKM9bWB11PU1Eioc3+AXBB8QiNFBn2XI5UkO5hPhbb9mJpjA - # 9Uhw8EdfqJP8QetVsI/xrCEbwEXe0xvifRLJbY08/Gp66KpQvy7g8w7VB8wlgePe - # xW3pT13Ap6vuC+mQuJPyiHvSxjEKHgqePji9NP3tJUFQjcECqcm0yV7/2d0t/pbC - # m+ZH1sadZspQCEPPrtbkQBlvHb4OLiIWPGHKSMeRFvp3IWcmdJqXahxLCUS1Eh6M - # AQIDAQAB - # -----END PUBLIC KEY----- - int( - "BE6A71558EE577FF03023CFA17AAB4E6C86383CFF8A7AD38EDB9FAFE6F323F2D" - "5106CBC8CAFB83B869CFFD1CCF121CD743D509E589E68765C96601E813DC5B9D" - "FC4BE415C7A6526132D0035CA33D6D6075D4F535122A1CDFE017041F1088D141" - "9F65C8E5490EE613E16DBF662698C0F54870F0475FA893FC41EB55B08FF1AC21" - "1BC045DED31BE27D12C96D8D3CFC6A7AE8AA50BF2EE0F30ED507CC2581E3DEC5" - "6DE94F5DC0A7ABEE0BE990B893F2887BD2C6310A1E0A9E3E38BD34FDED254150" - "8DC102A9C9B4C95EFFD9DD2DFE96C29BE647D6C69D66CA500843CFAED6E44019" - "6F1DBE0E2E22163C61CA48C79116FA77216726749A976A1C4B0944B5121E8C01", - 16 - ), # Modulus - int("010001", 16) # Exponent - ), + # 6427105915145367799 + 0x15931aac70e0d30f7 - (1 << 64): PublicKey( # CDN DC-121 + # -----BEGIN RSA PUBLIC KEY----- + # MIIBCgKCAQEA+Lf3PvgE1yxbJUCMaEAkV0QySTVpnaDjiednB5RbtNWjCeqSVakY + # HbqqGMIIv5WCGdFdrqOfMNcNSstPtSU6R9UmRw6tquOIykpSuUOje9H+4XVIKquj + # yL2ISdK+4ZOMl4hCMkqauw4bP1Sbr03vZRQbU6qEA04V4j879BAyBVhr3WG9+Zi+ + # t5XfGSTgSExPYEl8rZNHYNV5RB+BuroVH2HLTOpT/mJVfikYpgjfWF5ldezV4Wo9 + # LSH0cZGSFIaeJl8d0A8Eiy5B9gtBO8mL+XfQRKOOmr7a4BM4Ro2de5rr2i2od7hY + # Xd3DO9FRSl4y1zA8Am48Rfd95WHF3N/OmQIDAQAB + # -----END RSA PUBLIC KEY----- + int( + "F8B7F73EF804D72C5B25408C6840245744324935699DA0E389E76707945BB4D5" + "A309EA9255A9181DBAAA18C208BF958219D15DAEA39F30D70D4ACB4FB5253A47" + "D526470EADAAE388CA4A52B943A37BD1FEE175482AABA3C8BD8849D2BEE1938C" + "978842324A9ABB0E1B3F549BAF4DEF65141B53AA84034E15E23F3BF410320558" + "6BDD61BDF998BEB795DF1924E0484C4F60497CAD934760D579441F81BABA151F" + "61CB4CEA53FE62557E2918A608DF585E6575ECD5E16A3D2D21F471919214869E" + "265F1DD00F048B2E41F60B413BC98BF977D044A38E9ABEDAE01338468D9D7B9A" + "EBDA2DA877B8585DDDC33BD1514A5E32D7303C026E3C45F77DE561C5DCDFCE99", + 16 + ), # Modulus + int("010001", 16) # Exponent + ), - # 6427105915145367799 - 0x15931aac70e0d30f7 - (1 << 64): PublicKey( # CDN DC-121 - # -----BEGIN RSA PUBLIC KEY----- - # MIIBCgKCAQEA+Lf3PvgE1yxbJUCMaEAkV0QySTVpnaDjiednB5RbtNWjCeqSVakY - # HbqqGMIIv5WCGdFdrqOfMNcNSstPtSU6R9UmRw6tquOIykpSuUOje9H+4XVIKquj - # yL2ISdK+4ZOMl4hCMkqauw4bP1Sbr03vZRQbU6qEA04V4j879BAyBVhr3WG9+Zi+ - # t5XfGSTgSExPYEl8rZNHYNV5RB+BuroVH2HLTOpT/mJVfikYpgjfWF5ldezV4Wo9 - # LSH0cZGSFIaeJl8d0A8Eiy5B9gtBO8mL+XfQRKOOmr7a4BM4Ro2de5rr2i2od7hY - # Xd3DO9FRSl4y1zA8Am48Rfd95WHF3N/OmQIDAQAB - # -----END RSA PUBLIC KEY----- - int( - "F8B7F73EF804D72C5B25408C6840245744324935699DA0E389E76707945BB4D5" - "A309EA9255A9181DBAAA18C208BF958219D15DAEA39F30D70D4ACB4FB5253A47" - "D526470EADAAE388CA4A52B943A37BD1FEE175482AABA3C8BD8849D2BEE1938C" - "978842324A9ABB0E1B3F549BAF4DEF65141B53AA84034E15E23F3BF410320558" - "6BDD61BDF998BEB795DF1924E0484C4F60497CAD934760D579441F81BABA151F" - "61CB4CEA53FE62557E2918A608DF585E6575ECD5E16A3D2D21F471919214869E" - "265F1DD00F048B2E41F60B413BC98BF977D044A38E9ABEDAE01338468D9D7B9A" - "EBDA2DA877B8585DDDC33BD1514A5E32D7303C026E3C45F77DE561C5DCDFCE99", - 16 - ), # Modulus - int("010001", 16) # Exponent - ), + # 2685959930972952888 + 0x1254672538e935938 - (1 << 64): PublicKey( # CDN DC-140 + # -----BEGIN RSA PUBLIC KEY----- + # MIIBCgKCAQEAzuHVC7sE50Kho/yDVZtWnlmA5Bf/aM8KZY3WzS16w6w1sBqipj8o + # gMGG7ULbGBtYmKEaI7IIJO6WM2m1MaXVnsqS8d7PaGAZiy8rSN3S7S2a8wp4RXZe + # hs0JAXvZeIz45iByCMBfycbJKmSweYkesRUI7hUO8eQhmm/UYUEpJY7VOt0Iemiu + # URSpqlRQ2FlcyHahYUNcvbICb4+/AP7coKBn6cB5FyzM7MCcKxbEKOx3Y3MUnbZq + # q5pN6/eRazkegyrlp4kuJ94KsbRFHFX5Dx8uzjrO9wi8LF7gIgZu5DRMcmjXJKq6 + # rGZ2Z9cnrD8pVu1L2vcInd4K6ximZS2hbwIDAQAB + # -----END RSA PUBLIC KEY----- + int( + "CEE1D50BBB04E742A1A3FC83559B569E5980E417FF68CF0A658DD6CD2D7AC3AC" + "35B01AA2A63F2880C186ED42DB181B5898A11A23B20824EE963369B531A5D59E" + "CA92F1DECF6860198B2F2B48DDD2ED2D9AF30A7845765E86CD09017BD9788CF8" + "E6207208C05FC9C6C92A64B079891EB11508EE150EF1E4219A6FD4614129258E" + "D53ADD087A68AE5114A9AA5450D8595CC876A161435CBDB2026F8FBF00FEDCA0" + "A067E9C079172CCCECC09C2B16C428EC776373149DB66AAB9A4DEBF7916B391E" + "832AE5A7892E27DE0AB1B4451C55F90F1F2ECE3ACEF708BC2C5EE022066EE434" + "4C7268D724AABAAC667667D727AC3F2956ED4BDAF7089DDE0AEB18A6652DA16F", + 16 + ), # Modulus + int("010001", 16) # Exponent + ) +} - # 2685959930972952888 - 0x1254672538e935938 - (1 << 64): PublicKey( # CDN DC-140 - # -----BEGIN RSA PUBLIC KEY----- - # MIIBCgKCAQEAzuHVC7sE50Kho/yDVZtWnlmA5Bf/aM8KZY3WzS16w6w1sBqipj8o - # gMGG7ULbGBtYmKEaI7IIJO6WM2m1MaXVnsqS8d7PaGAZiy8rSN3S7S2a8wp4RXZe - # hs0JAXvZeIz45iByCMBfycbJKmSweYkesRUI7hUO8eQhmm/UYUEpJY7VOt0Iemiu - # URSpqlRQ2FlcyHahYUNcvbICb4+/AP7coKBn6cB5FyzM7MCcKxbEKOx3Y3MUnbZq - # q5pN6/eRazkegyrlp4kuJ94KsbRFHFX5Dx8uzjrO9wi8LF7gIgZu5DRMcmjXJKq6 - # rGZ2Z9cnrD8pVu1L2vcInd4K6ximZS2hbwIDAQAB - # -----END RSA PUBLIC KEY----- - int( - "CEE1D50BBB04E742A1A3FC83559B569E5980E417FF68CF0A658DD6CD2D7AC3AC" - "35B01AA2A63F2880C186ED42DB181B5898A11A23B20824EE963369B531A5D59E" - "CA92F1DECF6860198B2F2B48DDD2ED2D9AF30A7845765E86CD09017BD9788CF8" - "E6207208C05FC9C6C92A64B079891EB11508EE150EF1E4219A6FD4614129258E" - "D53ADD087A68AE5114A9AA5450D8595CC876A161435CBDB2026F8FBF00FEDCA0" - "A067E9C079172CCCECC09C2B16C428EC776373149DB66AAB9A4DEBF7916B391E" - "832AE5A7892E27DE0AB1B4451C55F90F1F2ECE3ACEF708BC2C5EE022066EE434" - "4C7268D724AABAAC667667D727AC3F2956ED4BDAF7089DDE0AEB18A6652DA16F", - 16 - ), # Modulus - int("010001", 16) # Exponent - ) - } - @classmethod - def encrypt(cls, data: bytes, fingerprint: int) -> bytes: - return pow( - int.from_bytes(data, "big"), - cls.server_public_keys[fingerprint].e, - cls.server_public_keys[fingerprint].m - ).to_bytes(256, "big") +def encrypt(data: bytes, fingerprint: int) -> bytes: + return pow( + int.from_bytes(data, "big"), + server_public_keys[fingerprint].e, + server_public_keys[fingerprint].m + ).to_bytes(256, "big") diff --git a/pyrogram/client/ext/dispatcher.py b/pyrogram/dispatcher.py similarity index 72% rename from pyrogram/client/ext/dispatcher.py rename to pyrogram/dispatcher.py index d8308944db..40a6dcf48f 100644 --- a/pyrogram/client/ext/dispatcher.py +++ b/pyrogram/dispatcher.py @@ -21,7 +21,13 @@ from collections import OrderedDict import pyrogram -from pyrogram.api.types import ( +from pyrogram import utils +from pyrogram.handlers import ( + CallbackQueryHandler, MessageHandler, DeletedMessagesHandler, + UserStatusHandler, RawUpdateHandler, InlineQueryHandler, PollHandler, + ChosenInlineResultHandler +) +from pyrogram.raw.types import ( UpdateNewMessage, UpdateNewChannelMessage, UpdateNewScheduledMessage, UpdateEditMessage, UpdateEditChannelMessage, UpdateDeleteMessages, UpdateDeleteChannelMessages, @@ -29,12 +35,6 @@ UpdateUserStatus, UpdateBotInlineQuery, UpdateMessagePoll, UpdateBotInlineSend ) -from . import utils -from ..handlers import ( - CallbackQueryHandler, MessageHandler, DeletedMessagesHandler, - UserStatusHandler, RawUpdateHandler, InlineQueryHandler, PollHandler, - ChosenInlineResultHandler -) log = logging.getLogger(__name__) @@ -63,18 +63,18 @@ class Dispatcher: MESSAGE_UPDATES = NEW_MESSAGE_UPDATES + EDIT_MESSAGE_UPDATES - def __init__(self, client, workers: int): + def __init__(self, client: "pyrogram.Client"): self.client = client - self.workers = workers + self.loop = asyncio.get_event_loop() - self.update_worker_tasks = [] + self.handler_worker_tasks = [] self.locks_list = [] self.updates_queue = asyncio.Queue() self.groups = OrderedDict() async def message_parser(update, users, chats): - return await pyrogram.Message._parse( + return await pyrogram.types.Message._parse( self.client, update.message, users, chats, isinstance(update, UpdateNewScheduledMessage) ), MessageHandler @@ -83,19 +83,19 @@ async def deleted_messages_parser(update, users, chats): return utils.parse_deleted_messages(self.client, update), DeletedMessagesHandler async def callback_query_parser(update, users, chats): - return await pyrogram.CallbackQuery._parse(self.client, update, users), CallbackQueryHandler + return await pyrogram.types.CallbackQuery._parse(self.client, update, users), CallbackQueryHandler async def user_status_parser(update, users, chats): - return pyrogram.User._parse_user_status(self.client, update), UserStatusHandler + return pyrogram.types.User._parse_user_status(self.client, update), UserStatusHandler async def inline_query_parser(update, users, chats): - return pyrogram.InlineQuery._parse(self.client, update, users), InlineQueryHandler + return pyrogram.types.InlineQuery._parse(self.client, update, users), InlineQueryHandler async def poll_parser(update, users, chats): - return pyrogram.Poll._parse_update(self.client, update), PollHandler + return pyrogram.types.Poll._parse_update(self.client, update), PollHandler async def chosen_inline_result_parser(update, users, chats): - return pyrogram.ChosenInlineResult._parse(self.client, update, users), ChosenInlineResultHandler + return pyrogram.types.ChosenInlineResult._parse(self.client, update, users), ChosenInlineResultHandler self.update_parsers = { Dispatcher.MESSAGE_UPDATES: message_parser, @@ -110,26 +110,28 @@ async def chosen_inline_result_parser(update, users, chats): self.update_parsers = {key: value for key_tuple, value in self.update_parsers.items() for key in key_tuple} async def start(self): - for i in range(self.workers): - self.locks_list.append(asyncio.Lock()) + if not self.client.no_updates: + for i in range(self.client.workers): + self.locks_list.append(asyncio.Lock()) - self.update_worker_tasks.append( - asyncio.ensure_future(self.update_worker(self.locks_list[-1])) - ) + self.handler_worker_tasks.append( + asyncio.ensure_future(self.handler_worker(self.locks_list[-1])) + ) - logging.info("Started {} UpdateWorkerTasks".format(self.workers)) + logging.info(f"Started {self.client.workers} HandlerTasks") async def stop(self): - for i in range(self.workers): - self.updates_queue.put_nowait(None) + if not self.client.no_updates: + for i in range(self.client.workers): + self.updates_queue.put_nowait(None) - for i in self.update_worker_tasks: - await i + for i in self.handler_worker_tasks: + await i - self.update_worker_tasks.clear() - self.groups.clear() + self.handler_worker_tasks.clear() + self.groups.clear() - logging.info("Stopped {} UpdateWorkerTasks".format(self.workers)) + logging.info(f"Stopped {self.client.workers} HandlerTasks") def add_handler(self, handler, group: int): async def fn(): @@ -155,7 +157,7 @@ async def fn(): try: if group not in self.groups: - raise ValueError("Group {} does not exist. Handler was not removed.".format(group)) + raise ValueError(f"Group {group} does not exist. Handler was not removed.") self.groups[group].remove(handler) finally: @@ -164,7 +166,7 @@ async def fn(): asyncio.ensure_future(fn()) - async def update_worker(self, lock): + async def handler_worker(self, lock): while True: packet = await self.updates_queue.get() @@ -188,9 +190,8 @@ async def update_worker(self, lock): if isinstance(handler, handler_type): try: - if (await handler.check(parsed_update)): + if await handler.check(self.client, parsed_update): args = (parsed_update,) - except Exception as e: log.error(e, exc_info=True) continue @@ -202,7 +203,15 @@ async def update_worker(self, lock): continue try: - await handler.callback(self.client, *args) + if asyncio.iscoroutinefunction(handler.callback): + await handler.callback(self.client, *args) + else: + await self.loop.run_in_executor( + self.client.executor, + handler.callback, + self.client, + *args + ) except pyrogram.StopPropagation: raise except pyrogram.ContinuePropagation: diff --git a/pyrogram/emoji.py b/pyrogram/emoji.py new file mode 100644 index 0000000000..7c55df601f --- /dev/null +++ b/pyrogram/emoji.py @@ -0,0 +1,3521 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan +# +# 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 . + +GRINNING_FACE = "\U0001f600" +GRINNING_FACE_WITH_BIG_EYES = "\U0001f603" +GRINNING_FACE_WITH_SMILING_EYES = "\U0001f604" +BEAMING_FACE_WITH_SMILING_EYES = "\U0001f601" +GRINNING_SQUINTING_FACE = "\U0001f606" +GRINNING_FACE_WITH_SWEAT = "\U0001f605" +ROLLING_ON_THE_FLOOR_LAUGHING = "\U0001f923" +FACE_WITH_TEARS_OF_JOY = "\U0001f602" +SLIGHTLY_SMILING_FACE = "\U0001f642" +UPSIDE_DOWN_FACE = "\U0001f643" +WINKING_FACE = "\U0001f609" +SMILING_FACE_WITH_SMILING_EYES = "\U0001f60a" +SMILING_FACE_WITH_HALO = "\U0001f607" +SMILING_FACE_WITH_HEARTS = "\U0001f970" +SMILING_FACE_WITH_HEART_EYES = "\U0001f60d" +STAR_STRUCK = "\U0001f929" +FACE_BLOWING_A_KISS = "\U0001f618" +KISSING_FACE = "\U0001f617" +SMILING_FACE = "\u263a\ufe0f" +KISSING_FACE_WITH_CLOSED_EYES = "\U0001f61a" +KISSING_FACE_WITH_SMILING_EYES = "\U0001f619" +SMILING_FACE_WITH_TEAR = "\U0001f972" +FACE_SAVORING_FOOD = "\U0001f60b" +FACE_WITH_TONGUE = "\U0001f61b" +WINKING_FACE_WITH_TONGUE = "\U0001f61c" +ZANY_FACE = "\U0001f92a" +SQUINTING_FACE_WITH_TONGUE = "\U0001f61d" +MONEY_MOUTH_FACE = "\U0001f911" +HUGGING_FACE = "\U0001f917" +FACE_WITH_HAND_OVER_MOUTH = "\U0001f92d" +SHUSHING_FACE = "\U0001f92b" +THINKING_FACE = "\U0001f914" +ZIPPER_MOUTH_FACE = "\U0001f910" +FACE_WITH_RAISED_EYEBROW = "\U0001f928" +NEUTRAL_FACE = "\U0001f610" +EXPRESSIONLESS_FACE = "\U0001f611" +FACE_WITHOUT_MOUTH = "\U0001f636" +SMIRKING_FACE = "\U0001f60f" +UNAMUSED_FACE = "\U0001f612" +FACE_WITH_ROLLING_EYES = "\U0001f644" +GRIMACING_FACE = "\U0001f62c" +LYING_FACE = "\U0001f925" +RELIEVED_FACE = "\U0001f60c" +PENSIVE_FACE = "\U0001f614" +SLEEPY_FACE = "\U0001f62a" +DROOLING_FACE = "\U0001f924" +SLEEPING_FACE = "\U0001f634" +FACE_WITH_MEDICAL_MASK = "\U0001f637" +FACE_WITH_THERMOMETER = "\U0001f912" +FACE_WITH_HEAD_BANDAGE = "\U0001f915" +NAUSEATED_FACE = "\U0001f922" +FACE_VOMITING = "\U0001f92e" +SNEEZING_FACE = "\U0001f927" +HOT_FACE = "\U0001f975" +COLD_FACE = "\U0001f976" +WOOZY_FACE = "\U0001f974" +DIZZY_FACE = "\U0001f635" +EXPLODING_HEAD = "\U0001f92f" +COWBOY_HAT_FACE = "\U0001f920" +PARTYING_FACE = "\U0001f973" +DISGUISED_FACE = "\U0001f978" +SMILING_FACE_WITH_SUNGLASSES = "\U0001f60e" +NERD_FACE = "\U0001f913" +FACE_WITH_MONOCLE = "\U0001f9d0" +CONFUSED_FACE = "\U0001f615" +WORRIED_FACE = "\U0001f61f" +SLIGHTLY_FROWNING_FACE = "\U0001f641" +FROWNING_FACE = "\u2639\ufe0f" +FACE_WITH_OPEN_MOUTH = "\U0001f62e" +HUSHED_FACE = "\U0001f62f" +ASTONISHED_FACE = "\U0001f632" +FLUSHED_FACE = "\U0001f633" +PLEADING_FACE = "\U0001f97a" +FROWNING_FACE_WITH_OPEN_MOUTH = "\U0001f626" +ANGUISHED_FACE = "\U0001f627" +FEARFUL_FACE = "\U0001f628" +ANXIOUS_FACE_WITH_SWEAT = "\U0001f630" +SAD_BUT_RELIEVED_FACE = "\U0001f625" +CRYING_FACE = "\U0001f622" +LOUDLY_CRYING_FACE = "\U0001f62d" +FACE_SCREAMING_IN_FEAR = "\U0001f631" +CONFOUNDED_FACE = "\U0001f616" +PERSEVERING_FACE = "\U0001f623" +DISAPPOINTED_FACE = "\U0001f61e" +DOWNCAST_FACE_WITH_SWEAT = "\U0001f613" +WEARY_FACE = "\U0001f629" +TIRED_FACE = "\U0001f62b" +YAWNING_FACE = "\U0001f971" +FACE_WITH_STEAM_FROM_NOSE = "\U0001f624" +POUTING_FACE = "\U0001f621" +ANGRY_FACE = "\U0001f620" +FACE_WITH_SYMBOLS_ON_MOUTH = "\U0001f92c" +SMILING_FACE_WITH_HORNS = "\U0001f608" +ANGRY_FACE_WITH_HORNS = "\U0001f47f" +SKULL = "\U0001f480" +SKULL_AND_CROSSBONES = "\u2620\ufe0f" +PILE_OF_POO = "\U0001f4a9" +CLOWN_FACE = "\U0001f921" +OGRE = "\U0001f479" +GOBLIN = "\U0001f47a" +GHOST = "\U0001f47b" +ALIEN = "\U0001f47d" +ALIEN_MONSTER = "\U0001f47e" +ROBOT = "\U0001f916" +GRINNING_CAT = "\U0001f63a" +GRINNING_CAT_WITH_SMILING_EYES = "\U0001f638" +CAT_WITH_TEARS_OF_JOY = "\U0001f639" +SMILING_CAT_WITH_HEART_EYES = "\U0001f63b" +CAT_WITH_WRY_SMILE = "\U0001f63c" +KISSING_CAT = "\U0001f63d" +WEARY_CAT = "\U0001f640" +CRYING_CAT = "\U0001f63f" +POUTING_CAT = "\U0001f63e" +SEE_NO_EVIL_MONKEY = "\U0001f648" +HEAR_NO_EVIL_MONKEY = "\U0001f649" +SPEAK_NO_EVIL_MONKEY = "\U0001f64a" +KISS_MARK = "\U0001f48b" +LOVE_LETTER = "\U0001f48c" +HEART_WITH_ARROW = "\U0001f498" +HEART_WITH_RIBBON = "\U0001f49d" +SPARKLING_HEART = "\U0001f496" +GROWING_HEART = "\U0001f497" +BEATING_HEART = "\U0001f493" +REVOLVING_HEARTS = "\U0001f49e" +TWO_HEARTS = "\U0001f495" +HEART_DECORATION = "\U0001f49f" +HEART_EXCLAMATION = "\u2763\ufe0f" +BROKEN_HEART = "\U0001f494" +RED_HEART = "\u2764\ufe0f" +ORANGE_HEART = "\U0001f9e1" +YELLOW_HEART = "\U0001f49b" +GREEN_HEART = "\U0001f49a" +BLUE_HEART = "\U0001f499" +PURPLE_HEART = "\U0001f49c" +BROWN_HEART = "\U0001f90e" +BLACK_HEART = "\U0001f5a4" +WHITE_HEART = "\U0001f90d" +HUNDRED_POINTS = "\U0001f4af" +ANGER_SYMBOL = "\U0001f4a2" +COLLISION = "\U0001f4a5" +DIZZY = "\U0001f4ab" +SWEAT_DROPLETS = "\U0001f4a6" +DASHING_AWAY = "\U0001f4a8" +HOLE = "\U0001f573\ufe0f" +BOMB = "\U0001f4a3" +SPEECH_BALLOON = "\U0001f4ac" +EYE_IN_SPEECH_BUBBLE = "\U0001f441\ufe0f\u200d\U0001f5e8\ufe0f" +LEFT_SPEECH_BUBBLE = "\U0001f5e8\ufe0f" +RIGHT_ANGER_BUBBLE = "\U0001f5ef\ufe0f" +THOUGHT_BALLOON = "\U0001f4ad" +ZZZ = "\U0001f4a4" +WAVING_HAND = "\U0001f44b" +WAVING_HAND_LIGHT_SKIN_TONE = "\U0001f44b\U0001f3fb" +WAVING_HAND_MEDIUM_LIGHT_SKIN_TONE = "\U0001f44b\U0001f3fc" +WAVING_HAND_MEDIUM_SKIN_TONE = "\U0001f44b\U0001f3fd" +WAVING_HAND_MEDIUM_DARK_SKIN_TONE = "\U0001f44b\U0001f3fe" +WAVING_HAND_DARK_SKIN_TONE = "\U0001f44b\U0001f3ff" +RAISED_BACK_OF_HAND = "\U0001f91a" +RAISED_BACK_OF_HAND_LIGHT_SKIN_TONE = "\U0001f91a\U0001f3fb" +RAISED_BACK_OF_HAND_MEDIUM_LIGHT_SKIN_TONE = "\U0001f91a\U0001f3fc" +RAISED_BACK_OF_HAND_MEDIUM_SKIN_TONE = "\U0001f91a\U0001f3fd" +RAISED_BACK_OF_HAND_MEDIUM_DARK_SKIN_TONE = "\U0001f91a\U0001f3fe" +RAISED_BACK_OF_HAND_DARK_SKIN_TONE = "\U0001f91a\U0001f3ff" +HAND_WITH_FINGERS_SPLAYED = "\U0001f590\ufe0f" +HAND_WITH_FINGERS_SPLAYED_LIGHT_SKIN_TONE = "\U0001f590\U0001f3fb" +HAND_WITH_FINGERS_SPLAYED_MEDIUM_LIGHT_SKIN_TONE = "\U0001f590\U0001f3fc" +HAND_WITH_FINGERS_SPLAYED_MEDIUM_SKIN_TONE = "\U0001f590\U0001f3fd" +HAND_WITH_FINGERS_SPLAYED_MEDIUM_DARK_SKIN_TONE = "\U0001f590\U0001f3fe" +HAND_WITH_FINGERS_SPLAYED_DARK_SKIN_TONE = "\U0001f590\U0001f3ff" +RAISED_HAND = "\u270b" +RAISED_HAND_LIGHT_SKIN_TONE = "\u270b\U0001f3fb" +RAISED_HAND_MEDIUM_LIGHT_SKIN_TONE = "\u270b\U0001f3fc" +RAISED_HAND_MEDIUM_SKIN_TONE = "\u270b\U0001f3fd" +RAISED_HAND_MEDIUM_DARK_SKIN_TONE = "\u270b\U0001f3fe" +RAISED_HAND_DARK_SKIN_TONE = "\u270b\U0001f3ff" +VULCAN_SALUTE = "\U0001f596" +VULCAN_SALUTE_LIGHT_SKIN_TONE = "\U0001f596\U0001f3fb" +VULCAN_SALUTE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f596\U0001f3fc" +VULCAN_SALUTE_MEDIUM_SKIN_TONE = "\U0001f596\U0001f3fd" +VULCAN_SALUTE_MEDIUM_DARK_SKIN_TONE = "\U0001f596\U0001f3fe" +VULCAN_SALUTE_DARK_SKIN_TONE = "\U0001f596\U0001f3ff" +OK_HAND = "\U0001f44c" +OK_HAND_LIGHT_SKIN_TONE = "\U0001f44c\U0001f3fb" +OK_HAND_MEDIUM_LIGHT_SKIN_TONE = "\U0001f44c\U0001f3fc" +OK_HAND_MEDIUM_SKIN_TONE = "\U0001f44c\U0001f3fd" +OK_HAND_MEDIUM_DARK_SKIN_TONE = "\U0001f44c\U0001f3fe" +OK_HAND_DARK_SKIN_TONE = "\U0001f44c\U0001f3ff" +PINCHED_FINGERS = "\U0001f90c" +PINCHED_FINGERS_LIGHT_SKIN_TONE = "\U0001f90c\U0001f3fb" +PINCHED_FINGERS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f90c\U0001f3fc" +PINCHED_FINGERS_MEDIUM_SKIN_TONE = "\U0001f90c\U0001f3fd" +PINCHED_FINGERS_MEDIUM_DARK_SKIN_TONE = "\U0001f90c\U0001f3fe" +PINCHED_FINGERS_DARK_SKIN_TONE = "\U0001f90c\U0001f3ff" +PINCHING_HAND = "\U0001f90f" +PINCHING_HAND_LIGHT_SKIN_TONE = "\U0001f90f\U0001f3fb" +PINCHING_HAND_MEDIUM_LIGHT_SKIN_TONE = "\U0001f90f\U0001f3fc" +PINCHING_HAND_MEDIUM_SKIN_TONE = "\U0001f90f\U0001f3fd" +PINCHING_HAND_MEDIUM_DARK_SKIN_TONE = "\U0001f90f\U0001f3fe" +PINCHING_HAND_DARK_SKIN_TONE = "\U0001f90f\U0001f3ff" +VICTORY_HAND = "\u270c\ufe0f" +VICTORY_HAND_LIGHT_SKIN_TONE = "\u270c\U0001f3fb" +VICTORY_HAND_MEDIUM_LIGHT_SKIN_TONE = "\u270c\U0001f3fc" +VICTORY_HAND_MEDIUM_SKIN_TONE = "\u270c\U0001f3fd" +VICTORY_HAND_MEDIUM_DARK_SKIN_TONE = "\u270c\U0001f3fe" +VICTORY_HAND_DARK_SKIN_TONE = "\u270c\U0001f3ff" +CROSSED_FINGERS = "\U0001f91e" +CROSSED_FINGERS_LIGHT_SKIN_TONE = "\U0001f91e\U0001f3fb" +CROSSED_FINGERS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f91e\U0001f3fc" +CROSSED_FINGERS_MEDIUM_SKIN_TONE = "\U0001f91e\U0001f3fd" +CROSSED_FINGERS_MEDIUM_DARK_SKIN_TONE = "\U0001f91e\U0001f3fe" +CROSSED_FINGERS_DARK_SKIN_TONE = "\U0001f91e\U0001f3ff" +LOVE_YOU_GESTURE = "\U0001f91f" +LOVE_YOU_GESTURE_LIGHT_SKIN_TONE = "\U0001f91f\U0001f3fb" +LOVE_YOU_GESTURE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f91f\U0001f3fc" +LOVE_YOU_GESTURE_MEDIUM_SKIN_TONE = "\U0001f91f\U0001f3fd" +LOVE_YOU_GESTURE_MEDIUM_DARK_SKIN_TONE = "\U0001f91f\U0001f3fe" +LOVE_YOU_GESTURE_DARK_SKIN_TONE = "\U0001f91f\U0001f3ff" +SIGN_OF_THE_HORNS = "\U0001f918" +SIGN_OF_THE_HORNS_LIGHT_SKIN_TONE = "\U0001f918\U0001f3fb" +SIGN_OF_THE_HORNS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f918\U0001f3fc" +SIGN_OF_THE_HORNS_MEDIUM_SKIN_TONE = "\U0001f918\U0001f3fd" +SIGN_OF_THE_HORNS_MEDIUM_DARK_SKIN_TONE = "\U0001f918\U0001f3fe" +SIGN_OF_THE_HORNS_DARK_SKIN_TONE = "\U0001f918\U0001f3ff" +CALL_ME_HAND = "\U0001f919" +CALL_ME_HAND_LIGHT_SKIN_TONE = "\U0001f919\U0001f3fb" +CALL_ME_HAND_MEDIUM_LIGHT_SKIN_TONE = "\U0001f919\U0001f3fc" +CALL_ME_HAND_MEDIUM_SKIN_TONE = "\U0001f919\U0001f3fd" +CALL_ME_HAND_MEDIUM_DARK_SKIN_TONE = "\U0001f919\U0001f3fe" +CALL_ME_HAND_DARK_SKIN_TONE = "\U0001f919\U0001f3ff" +BACKHAND_INDEX_POINTING_LEFT = "\U0001f448" +BACKHAND_INDEX_POINTING_LEFT_LIGHT_SKIN_TONE = "\U0001f448\U0001f3fb" +BACKHAND_INDEX_POINTING_LEFT_MEDIUM_LIGHT_SKIN_TONE = "\U0001f448\U0001f3fc" +BACKHAND_INDEX_POINTING_LEFT_MEDIUM_SKIN_TONE = "\U0001f448\U0001f3fd" +BACKHAND_INDEX_POINTING_LEFT_MEDIUM_DARK_SKIN_TONE = "\U0001f448\U0001f3fe" +BACKHAND_INDEX_POINTING_LEFT_DARK_SKIN_TONE = "\U0001f448\U0001f3ff" +BACKHAND_INDEX_POINTING_RIGHT = "\U0001f449" +BACKHAND_INDEX_POINTING_RIGHT_LIGHT_SKIN_TONE = "\U0001f449\U0001f3fb" +BACKHAND_INDEX_POINTING_RIGHT_MEDIUM_LIGHT_SKIN_TONE = "\U0001f449\U0001f3fc" +BACKHAND_INDEX_POINTING_RIGHT_MEDIUM_SKIN_TONE = "\U0001f449\U0001f3fd" +BACKHAND_INDEX_POINTING_RIGHT_MEDIUM_DARK_SKIN_TONE = "\U0001f449\U0001f3fe" +BACKHAND_INDEX_POINTING_RIGHT_DARK_SKIN_TONE = "\U0001f449\U0001f3ff" +BACKHAND_INDEX_POINTING_UP = "\U0001f446" +BACKHAND_INDEX_POINTING_UP_LIGHT_SKIN_TONE = "\U0001f446\U0001f3fb" +BACKHAND_INDEX_POINTING_UP_MEDIUM_LIGHT_SKIN_TONE = "\U0001f446\U0001f3fc" +BACKHAND_INDEX_POINTING_UP_MEDIUM_SKIN_TONE = "\U0001f446\U0001f3fd" +BACKHAND_INDEX_POINTING_UP_MEDIUM_DARK_SKIN_TONE = "\U0001f446\U0001f3fe" +BACKHAND_INDEX_POINTING_UP_DARK_SKIN_TONE = "\U0001f446\U0001f3ff" +MIDDLE_FINGER = "\U0001f595" +MIDDLE_FINGER_LIGHT_SKIN_TONE = "\U0001f595\U0001f3fb" +MIDDLE_FINGER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f595\U0001f3fc" +MIDDLE_FINGER_MEDIUM_SKIN_TONE = "\U0001f595\U0001f3fd" +MIDDLE_FINGER_MEDIUM_DARK_SKIN_TONE = "\U0001f595\U0001f3fe" +MIDDLE_FINGER_DARK_SKIN_TONE = "\U0001f595\U0001f3ff" +BACKHAND_INDEX_POINTING_DOWN = "\U0001f447" +BACKHAND_INDEX_POINTING_DOWN_LIGHT_SKIN_TONE = "\U0001f447\U0001f3fb" +BACKHAND_INDEX_POINTING_DOWN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f447\U0001f3fc" +BACKHAND_INDEX_POINTING_DOWN_MEDIUM_SKIN_TONE = "\U0001f447\U0001f3fd" +BACKHAND_INDEX_POINTING_DOWN_MEDIUM_DARK_SKIN_TONE = "\U0001f447\U0001f3fe" +BACKHAND_INDEX_POINTING_DOWN_DARK_SKIN_TONE = "\U0001f447\U0001f3ff" +INDEX_POINTING_UP = "\u261d\ufe0f" +INDEX_POINTING_UP_LIGHT_SKIN_TONE = "\u261d\U0001f3fb" +INDEX_POINTING_UP_MEDIUM_LIGHT_SKIN_TONE = "\u261d\U0001f3fc" +INDEX_POINTING_UP_MEDIUM_SKIN_TONE = "\u261d\U0001f3fd" +INDEX_POINTING_UP_MEDIUM_DARK_SKIN_TONE = "\u261d\U0001f3fe" +INDEX_POINTING_UP_DARK_SKIN_TONE = "\u261d\U0001f3ff" +THUMBS_UP = "\U0001f44d" +THUMBS_UP_LIGHT_SKIN_TONE = "\U0001f44d\U0001f3fb" +THUMBS_UP_MEDIUM_LIGHT_SKIN_TONE = "\U0001f44d\U0001f3fc" +THUMBS_UP_MEDIUM_SKIN_TONE = "\U0001f44d\U0001f3fd" +THUMBS_UP_MEDIUM_DARK_SKIN_TONE = "\U0001f44d\U0001f3fe" +THUMBS_UP_DARK_SKIN_TONE = "\U0001f44d\U0001f3ff" +THUMBS_DOWN = "\U0001f44e" +THUMBS_DOWN_LIGHT_SKIN_TONE = "\U0001f44e\U0001f3fb" +THUMBS_DOWN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f44e\U0001f3fc" +THUMBS_DOWN_MEDIUM_SKIN_TONE = "\U0001f44e\U0001f3fd" +THUMBS_DOWN_MEDIUM_DARK_SKIN_TONE = "\U0001f44e\U0001f3fe" +THUMBS_DOWN_DARK_SKIN_TONE = "\U0001f44e\U0001f3ff" +RAISED_FIST = "\u270a" +RAISED_FIST_LIGHT_SKIN_TONE = "\u270a\U0001f3fb" +RAISED_FIST_MEDIUM_LIGHT_SKIN_TONE = "\u270a\U0001f3fc" +RAISED_FIST_MEDIUM_SKIN_TONE = "\u270a\U0001f3fd" +RAISED_FIST_MEDIUM_DARK_SKIN_TONE = "\u270a\U0001f3fe" +RAISED_FIST_DARK_SKIN_TONE = "\u270a\U0001f3ff" +ONCOMING_FIST = "\U0001f44a" +ONCOMING_FIST_LIGHT_SKIN_TONE = "\U0001f44a\U0001f3fb" +ONCOMING_FIST_MEDIUM_LIGHT_SKIN_TONE = "\U0001f44a\U0001f3fc" +ONCOMING_FIST_MEDIUM_SKIN_TONE = "\U0001f44a\U0001f3fd" +ONCOMING_FIST_MEDIUM_DARK_SKIN_TONE = "\U0001f44a\U0001f3fe" +ONCOMING_FIST_DARK_SKIN_TONE = "\U0001f44a\U0001f3ff" +LEFT_FACING_FIST = "\U0001f91b" +LEFT_FACING_FIST_LIGHT_SKIN_TONE = "\U0001f91b\U0001f3fb" +LEFT_FACING_FIST_MEDIUM_LIGHT_SKIN_TONE = "\U0001f91b\U0001f3fc" +LEFT_FACING_FIST_MEDIUM_SKIN_TONE = "\U0001f91b\U0001f3fd" +LEFT_FACING_FIST_MEDIUM_DARK_SKIN_TONE = "\U0001f91b\U0001f3fe" +LEFT_FACING_FIST_DARK_SKIN_TONE = "\U0001f91b\U0001f3ff" +RIGHT_FACING_FIST = "\U0001f91c" +RIGHT_FACING_FIST_LIGHT_SKIN_TONE = "\U0001f91c\U0001f3fb" +RIGHT_FACING_FIST_MEDIUM_LIGHT_SKIN_TONE = "\U0001f91c\U0001f3fc" +RIGHT_FACING_FIST_MEDIUM_SKIN_TONE = "\U0001f91c\U0001f3fd" +RIGHT_FACING_FIST_MEDIUM_DARK_SKIN_TONE = "\U0001f91c\U0001f3fe" +RIGHT_FACING_FIST_DARK_SKIN_TONE = "\U0001f91c\U0001f3ff" +CLAPPING_HANDS = "\U0001f44f" +CLAPPING_HANDS_LIGHT_SKIN_TONE = "\U0001f44f\U0001f3fb" +CLAPPING_HANDS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f44f\U0001f3fc" +CLAPPING_HANDS_MEDIUM_SKIN_TONE = "\U0001f44f\U0001f3fd" +CLAPPING_HANDS_MEDIUM_DARK_SKIN_TONE = "\U0001f44f\U0001f3fe" +CLAPPING_HANDS_DARK_SKIN_TONE = "\U0001f44f\U0001f3ff" +RAISING_HANDS = "\U0001f64c" +RAISING_HANDS_LIGHT_SKIN_TONE = "\U0001f64c\U0001f3fb" +RAISING_HANDS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f64c\U0001f3fc" +RAISING_HANDS_MEDIUM_SKIN_TONE = "\U0001f64c\U0001f3fd" +RAISING_HANDS_MEDIUM_DARK_SKIN_TONE = "\U0001f64c\U0001f3fe" +RAISING_HANDS_DARK_SKIN_TONE = "\U0001f64c\U0001f3ff" +OPEN_HANDS = "\U0001f450" +OPEN_HANDS_LIGHT_SKIN_TONE = "\U0001f450\U0001f3fb" +OPEN_HANDS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f450\U0001f3fc" +OPEN_HANDS_MEDIUM_SKIN_TONE = "\U0001f450\U0001f3fd" +OPEN_HANDS_MEDIUM_DARK_SKIN_TONE = "\U0001f450\U0001f3fe" +OPEN_HANDS_DARK_SKIN_TONE = "\U0001f450\U0001f3ff" +PALMS_UP_TOGETHER = "\U0001f932" +PALMS_UP_TOGETHER_LIGHT_SKIN_TONE = "\U0001f932\U0001f3fb" +PALMS_UP_TOGETHER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f932\U0001f3fc" +PALMS_UP_TOGETHER_MEDIUM_SKIN_TONE = "\U0001f932\U0001f3fd" +PALMS_UP_TOGETHER_MEDIUM_DARK_SKIN_TONE = "\U0001f932\U0001f3fe" +PALMS_UP_TOGETHER_DARK_SKIN_TONE = "\U0001f932\U0001f3ff" +HANDSHAKE = "\U0001f91d" +FOLDED_HANDS = "\U0001f64f" +FOLDED_HANDS_LIGHT_SKIN_TONE = "\U0001f64f\U0001f3fb" +FOLDED_HANDS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f64f\U0001f3fc" +FOLDED_HANDS_MEDIUM_SKIN_TONE = "\U0001f64f\U0001f3fd" +FOLDED_HANDS_MEDIUM_DARK_SKIN_TONE = "\U0001f64f\U0001f3fe" +FOLDED_HANDS_DARK_SKIN_TONE = "\U0001f64f\U0001f3ff" +WRITING_HAND = "\u270d\ufe0f" +WRITING_HAND_LIGHT_SKIN_TONE = "\u270d\U0001f3fb" +WRITING_HAND_MEDIUM_LIGHT_SKIN_TONE = "\u270d\U0001f3fc" +WRITING_HAND_MEDIUM_SKIN_TONE = "\u270d\U0001f3fd" +WRITING_HAND_MEDIUM_DARK_SKIN_TONE = "\u270d\U0001f3fe" +WRITING_HAND_DARK_SKIN_TONE = "\u270d\U0001f3ff" +NAIL_POLISH = "\U0001f485" +NAIL_POLISH_LIGHT_SKIN_TONE = "\U0001f485\U0001f3fb" +NAIL_POLISH_MEDIUM_LIGHT_SKIN_TONE = "\U0001f485\U0001f3fc" +NAIL_POLISH_MEDIUM_SKIN_TONE = "\U0001f485\U0001f3fd" +NAIL_POLISH_MEDIUM_DARK_SKIN_TONE = "\U0001f485\U0001f3fe" +NAIL_POLISH_DARK_SKIN_TONE = "\U0001f485\U0001f3ff" +SELFIE = "\U0001f933" +SELFIE_LIGHT_SKIN_TONE = "\U0001f933\U0001f3fb" +SELFIE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f933\U0001f3fc" +SELFIE_MEDIUM_SKIN_TONE = "\U0001f933\U0001f3fd" +SELFIE_MEDIUM_DARK_SKIN_TONE = "\U0001f933\U0001f3fe" +SELFIE_DARK_SKIN_TONE = "\U0001f933\U0001f3ff" +FLEXED_BICEPS = "\U0001f4aa" +FLEXED_BICEPS_LIGHT_SKIN_TONE = "\U0001f4aa\U0001f3fb" +FLEXED_BICEPS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f4aa\U0001f3fc" +FLEXED_BICEPS_MEDIUM_SKIN_TONE = "\U0001f4aa\U0001f3fd" +FLEXED_BICEPS_MEDIUM_DARK_SKIN_TONE = "\U0001f4aa\U0001f3fe" +FLEXED_BICEPS_DARK_SKIN_TONE = "\U0001f4aa\U0001f3ff" +MECHANICAL_ARM = "\U0001f9be" +MECHANICAL_LEG = "\U0001f9bf" +LEG = "\U0001f9b5" +LEG_LIGHT_SKIN_TONE = "\U0001f9b5\U0001f3fb" +LEG_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9b5\U0001f3fc" +LEG_MEDIUM_SKIN_TONE = "\U0001f9b5\U0001f3fd" +LEG_MEDIUM_DARK_SKIN_TONE = "\U0001f9b5\U0001f3fe" +LEG_DARK_SKIN_TONE = "\U0001f9b5\U0001f3ff" +FOOT = "\U0001f9b6" +FOOT_LIGHT_SKIN_TONE = "\U0001f9b6\U0001f3fb" +FOOT_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9b6\U0001f3fc" +FOOT_MEDIUM_SKIN_TONE = "\U0001f9b6\U0001f3fd" +FOOT_MEDIUM_DARK_SKIN_TONE = "\U0001f9b6\U0001f3fe" +FOOT_DARK_SKIN_TONE = "\U0001f9b6\U0001f3ff" +EAR = "\U0001f442" +EAR_LIGHT_SKIN_TONE = "\U0001f442\U0001f3fb" +EAR_MEDIUM_LIGHT_SKIN_TONE = "\U0001f442\U0001f3fc" +EAR_MEDIUM_SKIN_TONE = "\U0001f442\U0001f3fd" +EAR_MEDIUM_DARK_SKIN_TONE = "\U0001f442\U0001f3fe" +EAR_DARK_SKIN_TONE = "\U0001f442\U0001f3ff" +EAR_WITH_HEARING_AID = "\U0001f9bb" +EAR_WITH_HEARING_AID_LIGHT_SKIN_TONE = "\U0001f9bb\U0001f3fb" +EAR_WITH_HEARING_AID_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9bb\U0001f3fc" +EAR_WITH_HEARING_AID_MEDIUM_SKIN_TONE = "\U0001f9bb\U0001f3fd" +EAR_WITH_HEARING_AID_MEDIUM_DARK_SKIN_TONE = "\U0001f9bb\U0001f3fe" +EAR_WITH_HEARING_AID_DARK_SKIN_TONE = "\U0001f9bb\U0001f3ff" +NOSE = "\U0001f443" +NOSE_LIGHT_SKIN_TONE = "\U0001f443\U0001f3fb" +NOSE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f443\U0001f3fc" +NOSE_MEDIUM_SKIN_TONE = "\U0001f443\U0001f3fd" +NOSE_MEDIUM_DARK_SKIN_TONE = "\U0001f443\U0001f3fe" +NOSE_DARK_SKIN_TONE = "\U0001f443\U0001f3ff" +BRAIN = "\U0001f9e0" +ANATOMICAL_HEART = "\U0001fac0" +LUNGS = "\U0001fac1" +TOOTH = "\U0001f9b7" +BONE = "\U0001f9b4" +EYES = "\U0001f440" +EYE = "\U0001f441\ufe0f" +TONGUE = "\U0001f445" +MOUTH = "\U0001f444" +BABY = "\U0001f476" +BABY_LIGHT_SKIN_TONE = "\U0001f476\U0001f3fb" +BABY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f476\U0001f3fc" +BABY_MEDIUM_SKIN_TONE = "\U0001f476\U0001f3fd" +BABY_MEDIUM_DARK_SKIN_TONE = "\U0001f476\U0001f3fe" +BABY_DARK_SKIN_TONE = "\U0001f476\U0001f3ff" +CHILD = "\U0001f9d2" +CHILD_LIGHT_SKIN_TONE = "\U0001f9d2\U0001f3fb" +CHILD_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d2\U0001f3fc" +CHILD_MEDIUM_SKIN_TONE = "\U0001f9d2\U0001f3fd" +CHILD_MEDIUM_DARK_SKIN_TONE = "\U0001f9d2\U0001f3fe" +CHILD_DARK_SKIN_TONE = "\U0001f9d2\U0001f3ff" +BOY = "\U0001f466" +BOY_LIGHT_SKIN_TONE = "\U0001f466\U0001f3fb" +BOY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f466\U0001f3fc" +BOY_MEDIUM_SKIN_TONE = "\U0001f466\U0001f3fd" +BOY_MEDIUM_DARK_SKIN_TONE = "\U0001f466\U0001f3fe" +BOY_DARK_SKIN_TONE = "\U0001f466\U0001f3ff" +GIRL = "\U0001f467" +GIRL_LIGHT_SKIN_TONE = "\U0001f467\U0001f3fb" +GIRL_MEDIUM_LIGHT_SKIN_TONE = "\U0001f467\U0001f3fc" +GIRL_MEDIUM_SKIN_TONE = "\U0001f467\U0001f3fd" +GIRL_MEDIUM_DARK_SKIN_TONE = "\U0001f467\U0001f3fe" +GIRL_DARK_SKIN_TONE = "\U0001f467\U0001f3ff" +PERSON = "\U0001f9d1" +PERSON_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fb" +PERSON_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fc" +PERSON_MEDIUM_SKIN_TONE = "\U0001f9d1\U0001f3fd" +PERSON_MEDIUM_DARK_SKIN_TONE = "\U0001f9d1\U0001f3fe" +PERSON_DARK_SKIN_TONE = "\U0001f9d1\U0001f3ff" +PERSON_BLOND_HAIR = "\U0001f471" +PERSON_LIGHT_SKIN_TONE_BLOND_HAIR = "\U0001f471\U0001f3fb" +PERSON_MEDIUM_LIGHT_SKIN_TONE_BLOND_HAIR = "\U0001f471\U0001f3fc" +PERSON_MEDIUM_SKIN_TONE_BLOND_HAIR = "\U0001f471\U0001f3fd" +PERSON_MEDIUM_DARK_SKIN_TONE_BLOND_HAIR = "\U0001f471\U0001f3fe" +PERSON_DARK_SKIN_TONE_BLOND_HAIR = "\U0001f471\U0001f3ff" +MAN = "\U0001f468" +MAN_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb" +MAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc" +MAN_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd" +MAN_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe" +MAN_DARK_SKIN_TONE = "\U0001f468\U0001f3ff" +MAN_BEARD = "\U0001f9d4" +MAN_LIGHT_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fb" +MAN_MEDIUM_LIGHT_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fc" +MAN_MEDIUM_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fd" +MAN_MEDIUM_DARK_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fe" +MAN_DARK_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3ff" +MAN_RED_HAIR = "\U0001f468\u200d\U0001f9b0" +MAN_LIGHT_SKIN_TONE_RED_HAIR = "\U0001f468\U0001f3fb\u200d\U0001f9b0" +MAN_MEDIUM_LIGHT_SKIN_TONE_RED_HAIR = "\U0001f468\U0001f3fc\u200d\U0001f9b0" +MAN_MEDIUM_SKIN_TONE_RED_HAIR = "\U0001f468\U0001f3fd\u200d\U0001f9b0" +MAN_MEDIUM_DARK_SKIN_TONE_RED_HAIR = "\U0001f468\U0001f3fe\u200d\U0001f9b0" +MAN_DARK_SKIN_TONE_RED_HAIR = "\U0001f468\U0001f3ff\u200d\U0001f9b0" +MAN_CURLY_HAIR = "\U0001f468\u200d\U0001f9b1" +MAN_LIGHT_SKIN_TONE_CURLY_HAIR = "\U0001f468\U0001f3fb\u200d\U0001f9b1" +MAN_MEDIUM_LIGHT_SKIN_TONE_CURLY_HAIR = "\U0001f468\U0001f3fc\u200d\U0001f9b1" +MAN_MEDIUM_SKIN_TONE_CURLY_HAIR = "\U0001f468\U0001f3fd\u200d\U0001f9b1" +MAN_MEDIUM_DARK_SKIN_TONE_CURLY_HAIR = "\U0001f468\U0001f3fe\u200d\U0001f9b1" +MAN_DARK_SKIN_TONE_CURLY_HAIR = "\U0001f468\U0001f3ff\u200d\U0001f9b1" +MAN_WHITE_HAIR = "\U0001f468\u200d\U0001f9b3" +MAN_LIGHT_SKIN_TONE_WHITE_HAIR = "\U0001f468\U0001f3fb\u200d\U0001f9b3" +MAN_MEDIUM_LIGHT_SKIN_TONE_WHITE_HAIR = "\U0001f468\U0001f3fc\u200d\U0001f9b3" +MAN_MEDIUM_SKIN_TONE_WHITE_HAIR = "\U0001f468\U0001f3fd\u200d\U0001f9b3" +MAN_MEDIUM_DARK_SKIN_TONE_WHITE_HAIR = "\U0001f468\U0001f3fe\u200d\U0001f9b3" +MAN_DARK_SKIN_TONE_WHITE_HAIR = "\U0001f468\U0001f3ff\u200d\U0001f9b3" +MAN_BALD = "\U0001f468\u200d\U0001f9b2" +MAN_LIGHT_SKIN_TONE_BALD = "\U0001f468\U0001f3fb\u200d\U0001f9b2" +MAN_MEDIUM_LIGHT_SKIN_TONE_BALD = "\U0001f468\U0001f3fc\u200d\U0001f9b2" +MAN_MEDIUM_SKIN_TONE_BALD = "\U0001f468\U0001f3fd\u200d\U0001f9b2" +MAN_MEDIUM_DARK_SKIN_TONE_BALD = "\U0001f468\U0001f3fe\u200d\U0001f9b2" +MAN_DARK_SKIN_TONE_BALD = "\U0001f468\U0001f3ff\u200d\U0001f9b2" +WOMAN = "\U0001f469" +WOMAN_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb" +WOMAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc" +WOMAN_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd" +WOMAN_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe" +WOMAN_DARK_SKIN_TONE = "\U0001f469\U0001f3ff" +WOMAN_RED_HAIR = "\U0001f469\u200d\U0001f9b0" +WOMAN_LIGHT_SKIN_TONE_RED_HAIR = "\U0001f469\U0001f3fb\u200d\U0001f9b0" +WOMAN_MEDIUM_LIGHT_SKIN_TONE_RED_HAIR = "\U0001f469\U0001f3fc\u200d\U0001f9b0" +WOMAN_MEDIUM_SKIN_TONE_RED_HAIR = "\U0001f469\U0001f3fd\u200d\U0001f9b0" +WOMAN_MEDIUM_DARK_SKIN_TONE_RED_HAIR = "\U0001f469\U0001f3fe\u200d\U0001f9b0" +WOMAN_DARK_SKIN_TONE_RED_HAIR = "\U0001f469\U0001f3ff\u200d\U0001f9b0" +PERSON_RED_HAIR = "\U0001f9d1\u200d\U0001f9b0" +PERSON_LIGHT_SKIN_TONE_RED_HAIR = "\U0001f9d1\U0001f3fb\u200d\U0001f9b0" +PERSON_MEDIUM_LIGHT_SKIN_TONE_RED_HAIR = "\U0001f9d1\U0001f3fc\u200d\U0001f9b0" +PERSON_MEDIUM_SKIN_TONE_RED_HAIR = "\U0001f9d1\U0001f3fd\u200d\U0001f9b0" +PERSON_MEDIUM_DARK_SKIN_TONE_RED_HAIR = "\U0001f9d1\U0001f3fe\u200d\U0001f9b0" +PERSON_DARK_SKIN_TONE_RED_HAIR = "\U0001f9d1\U0001f3ff\u200d\U0001f9b0" +WOMAN_CURLY_HAIR = "\U0001f469\u200d\U0001f9b1" +WOMAN_LIGHT_SKIN_TONE_CURLY_HAIR = "\U0001f469\U0001f3fb\u200d\U0001f9b1" +WOMAN_MEDIUM_LIGHT_SKIN_TONE_CURLY_HAIR = "\U0001f469\U0001f3fc\u200d\U0001f9b1" +WOMAN_MEDIUM_SKIN_TONE_CURLY_HAIR = "\U0001f469\U0001f3fd\u200d\U0001f9b1" +WOMAN_MEDIUM_DARK_SKIN_TONE_CURLY_HAIR = "\U0001f469\U0001f3fe\u200d\U0001f9b1" +WOMAN_DARK_SKIN_TONE_CURLY_HAIR = "\U0001f469\U0001f3ff\u200d\U0001f9b1" +PERSON_CURLY_HAIR = "\U0001f9d1\u200d\U0001f9b1" +PERSON_LIGHT_SKIN_TONE_CURLY_HAIR = "\U0001f9d1\U0001f3fb\u200d\U0001f9b1" +PERSON_MEDIUM_LIGHT_SKIN_TONE_CURLY_HAIR = "\U0001f9d1\U0001f3fc\u200d\U0001f9b1" +PERSON_MEDIUM_SKIN_TONE_CURLY_HAIR = "\U0001f9d1\U0001f3fd\u200d\U0001f9b1" +PERSON_MEDIUM_DARK_SKIN_TONE_CURLY_HAIR = "\U0001f9d1\U0001f3fe\u200d\U0001f9b1" +PERSON_DARK_SKIN_TONE_CURLY_HAIR = "\U0001f9d1\U0001f3ff\u200d\U0001f9b1" +WOMAN_WHITE_HAIR = "\U0001f469\u200d\U0001f9b3" +WOMAN_LIGHT_SKIN_TONE_WHITE_HAIR = "\U0001f469\U0001f3fb\u200d\U0001f9b3" +WOMAN_MEDIUM_LIGHT_SKIN_TONE_WHITE_HAIR = "\U0001f469\U0001f3fc\u200d\U0001f9b3" +WOMAN_MEDIUM_SKIN_TONE_WHITE_HAIR = "\U0001f469\U0001f3fd\u200d\U0001f9b3" +WOMAN_MEDIUM_DARK_SKIN_TONE_WHITE_HAIR = "\U0001f469\U0001f3fe\u200d\U0001f9b3" +WOMAN_DARK_SKIN_TONE_WHITE_HAIR = "\U0001f469\U0001f3ff\u200d\U0001f9b3" +PERSON_WHITE_HAIR = "\U0001f9d1\u200d\U0001f9b3" +PERSON_LIGHT_SKIN_TONE_WHITE_HAIR = "\U0001f9d1\U0001f3fb\u200d\U0001f9b3" +PERSON_MEDIUM_LIGHT_SKIN_TONE_WHITE_HAIR = "\U0001f9d1\U0001f3fc\u200d\U0001f9b3" +PERSON_MEDIUM_SKIN_TONE_WHITE_HAIR = "\U0001f9d1\U0001f3fd\u200d\U0001f9b3" +PERSON_MEDIUM_DARK_SKIN_TONE_WHITE_HAIR = "\U0001f9d1\U0001f3fe\u200d\U0001f9b3" +PERSON_DARK_SKIN_TONE_WHITE_HAIR = "\U0001f9d1\U0001f3ff\u200d\U0001f9b3" +WOMAN_BALD = "\U0001f469\u200d\U0001f9b2" +WOMAN_LIGHT_SKIN_TONE_BALD = "\U0001f469\U0001f3fb\u200d\U0001f9b2" +WOMAN_MEDIUM_LIGHT_SKIN_TONE_BALD = "\U0001f469\U0001f3fc\u200d\U0001f9b2" +WOMAN_MEDIUM_SKIN_TONE_BALD = "\U0001f469\U0001f3fd\u200d\U0001f9b2" +WOMAN_MEDIUM_DARK_SKIN_TONE_BALD = "\U0001f469\U0001f3fe\u200d\U0001f9b2" +WOMAN_DARK_SKIN_TONE_BALD = "\U0001f469\U0001f3ff\u200d\U0001f9b2" +PERSON_BALD = "\U0001f9d1\u200d\U0001f9b2" +PERSON_LIGHT_SKIN_TONE_BALD = "\U0001f9d1\U0001f3fb\u200d\U0001f9b2" +PERSON_MEDIUM_LIGHT_SKIN_TONE_BALD = "\U0001f9d1\U0001f3fc\u200d\U0001f9b2" +PERSON_MEDIUM_SKIN_TONE_BALD = "\U0001f9d1\U0001f3fd\u200d\U0001f9b2" +PERSON_MEDIUM_DARK_SKIN_TONE_BALD = "\U0001f9d1\U0001f3fe\u200d\U0001f9b2" +PERSON_DARK_SKIN_TONE_BALD = "\U0001f9d1\U0001f3ff\u200d\U0001f9b2" +WOMAN_BLOND_HAIR = "\U0001f471\u200d\u2640\ufe0f" +WOMAN_LIGHT_SKIN_TONE_BLOND_HAIR = "\U0001f471\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_MEDIUM_LIGHT_SKIN_TONE_BLOND_HAIR = "\U0001f471\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_MEDIUM_SKIN_TONE_BLOND_HAIR = "\U0001f471\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_MEDIUM_DARK_SKIN_TONE_BLOND_HAIR = "\U0001f471\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_DARK_SKIN_TONE_BLOND_HAIR = "\U0001f471\U0001f3ff\u200d\u2640\ufe0f" +MAN_BLOND_HAIR = "\U0001f471\u200d\u2642\ufe0f" +MAN_LIGHT_SKIN_TONE_BLOND_HAIR = "\U0001f471\U0001f3fb\u200d\u2642\ufe0f" +MAN_MEDIUM_LIGHT_SKIN_TONE_BLOND_HAIR = "\U0001f471\U0001f3fc\u200d\u2642\ufe0f" +MAN_MEDIUM_SKIN_TONE_BLOND_HAIR = "\U0001f471\U0001f3fd\u200d\u2642\ufe0f" +MAN_MEDIUM_DARK_SKIN_TONE_BLOND_HAIR = "\U0001f471\U0001f3fe\u200d\u2642\ufe0f" +MAN_DARK_SKIN_TONE_BLOND_HAIR = "\U0001f471\U0001f3ff\u200d\u2642\ufe0f" +OLDER_PERSON = "\U0001f9d3" +OLDER_PERSON_LIGHT_SKIN_TONE = "\U0001f9d3\U0001f3fb" +OLDER_PERSON_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d3\U0001f3fc" +OLDER_PERSON_MEDIUM_SKIN_TONE = "\U0001f9d3\U0001f3fd" +OLDER_PERSON_MEDIUM_DARK_SKIN_TONE = "\U0001f9d3\U0001f3fe" +OLDER_PERSON_DARK_SKIN_TONE = "\U0001f9d3\U0001f3ff" +OLD_MAN = "\U0001f474" +OLD_MAN_LIGHT_SKIN_TONE = "\U0001f474\U0001f3fb" +OLD_MAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f474\U0001f3fc" +OLD_MAN_MEDIUM_SKIN_TONE = "\U0001f474\U0001f3fd" +OLD_MAN_MEDIUM_DARK_SKIN_TONE = "\U0001f474\U0001f3fe" +OLD_MAN_DARK_SKIN_TONE = "\U0001f474\U0001f3ff" +OLD_WOMAN = "\U0001f475" +OLD_WOMAN_LIGHT_SKIN_TONE = "\U0001f475\U0001f3fb" +OLD_WOMAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f475\U0001f3fc" +OLD_WOMAN_MEDIUM_SKIN_TONE = "\U0001f475\U0001f3fd" +OLD_WOMAN_MEDIUM_DARK_SKIN_TONE = "\U0001f475\U0001f3fe" +OLD_WOMAN_DARK_SKIN_TONE = "\U0001f475\U0001f3ff" +PERSON_FROWNING = "\U0001f64d" +PERSON_FROWNING_LIGHT_SKIN_TONE = "\U0001f64d\U0001f3fb" +PERSON_FROWNING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f64d\U0001f3fc" +PERSON_FROWNING_MEDIUM_SKIN_TONE = "\U0001f64d\U0001f3fd" +PERSON_FROWNING_MEDIUM_DARK_SKIN_TONE = "\U0001f64d\U0001f3fe" +PERSON_FROWNING_DARK_SKIN_TONE = "\U0001f64d\U0001f3ff" +MAN_FROWNING = "\U0001f64d\u200d\u2642\ufe0f" +MAN_FROWNING_LIGHT_SKIN_TONE = "\U0001f64d\U0001f3fb\u200d\u2642\ufe0f" +MAN_FROWNING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f64d\U0001f3fc\u200d\u2642\ufe0f" +MAN_FROWNING_MEDIUM_SKIN_TONE = "\U0001f64d\U0001f3fd\u200d\u2642\ufe0f" +MAN_FROWNING_MEDIUM_DARK_SKIN_TONE = "\U0001f64d\U0001f3fe\u200d\u2642\ufe0f" +MAN_FROWNING_DARK_SKIN_TONE = "\U0001f64d\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_FROWNING = "\U0001f64d\u200d\u2640\ufe0f" +WOMAN_FROWNING_LIGHT_SKIN_TONE = "\U0001f64d\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_FROWNING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f64d\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_FROWNING_MEDIUM_SKIN_TONE = "\U0001f64d\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_FROWNING_MEDIUM_DARK_SKIN_TONE = "\U0001f64d\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_FROWNING_DARK_SKIN_TONE = "\U0001f64d\U0001f3ff\u200d\u2640\ufe0f" +PERSON_POUTING = "\U0001f64e" +PERSON_POUTING_LIGHT_SKIN_TONE = "\U0001f64e\U0001f3fb" +PERSON_POUTING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f64e\U0001f3fc" +PERSON_POUTING_MEDIUM_SKIN_TONE = "\U0001f64e\U0001f3fd" +PERSON_POUTING_MEDIUM_DARK_SKIN_TONE = "\U0001f64e\U0001f3fe" +PERSON_POUTING_DARK_SKIN_TONE = "\U0001f64e\U0001f3ff" +MAN_POUTING = "\U0001f64e\u200d\u2642\ufe0f" +MAN_POUTING_LIGHT_SKIN_TONE = "\U0001f64e\U0001f3fb\u200d\u2642\ufe0f" +MAN_POUTING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f64e\U0001f3fc\u200d\u2642\ufe0f" +MAN_POUTING_MEDIUM_SKIN_TONE = "\U0001f64e\U0001f3fd\u200d\u2642\ufe0f" +MAN_POUTING_MEDIUM_DARK_SKIN_TONE = "\U0001f64e\U0001f3fe\u200d\u2642\ufe0f" +MAN_POUTING_DARK_SKIN_TONE = "\U0001f64e\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_POUTING = "\U0001f64e\u200d\u2640\ufe0f" +WOMAN_POUTING_LIGHT_SKIN_TONE = "\U0001f64e\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_POUTING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f64e\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_POUTING_MEDIUM_SKIN_TONE = "\U0001f64e\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_POUTING_MEDIUM_DARK_SKIN_TONE = "\U0001f64e\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_POUTING_DARK_SKIN_TONE = "\U0001f64e\U0001f3ff\u200d\u2640\ufe0f" +PERSON_GESTURING_NO = "\U0001f645" +PERSON_GESTURING_NO_LIGHT_SKIN_TONE = "\U0001f645\U0001f3fb" +PERSON_GESTURING_NO_MEDIUM_LIGHT_SKIN_TONE = "\U0001f645\U0001f3fc" +PERSON_GESTURING_NO_MEDIUM_SKIN_TONE = "\U0001f645\U0001f3fd" +PERSON_GESTURING_NO_MEDIUM_DARK_SKIN_TONE = "\U0001f645\U0001f3fe" +PERSON_GESTURING_NO_DARK_SKIN_TONE = "\U0001f645\U0001f3ff" +MAN_GESTURING_NO = "\U0001f645\u200d\u2642\ufe0f" +MAN_GESTURING_NO_LIGHT_SKIN_TONE = "\U0001f645\U0001f3fb\u200d\u2642\ufe0f" +MAN_GESTURING_NO_MEDIUM_LIGHT_SKIN_TONE = "\U0001f645\U0001f3fc\u200d\u2642\ufe0f" +MAN_GESTURING_NO_MEDIUM_SKIN_TONE = "\U0001f645\U0001f3fd\u200d\u2642\ufe0f" +MAN_GESTURING_NO_MEDIUM_DARK_SKIN_TONE = "\U0001f645\U0001f3fe\u200d\u2642\ufe0f" +MAN_GESTURING_NO_DARK_SKIN_TONE = "\U0001f645\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_GESTURING_NO = "\U0001f645\u200d\u2640\ufe0f" +WOMAN_GESTURING_NO_LIGHT_SKIN_TONE = "\U0001f645\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_GESTURING_NO_MEDIUM_LIGHT_SKIN_TONE = "\U0001f645\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_GESTURING_NO_MEDIUM_SKIN_TONE = "\U0001f645\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_GESTURING_NO_MEDIUM_DARK_SKIN_TONE = "\U0001f645\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_GESTURING_NO_DARK_SKIN_TONE = "\U0001f645\U0001f3ff\u200d\u2640\ufe0f" +PERSON_GESTURING_OK = "\U0001f646" +PERSON_GESTURING_OK_LIGHT_SKIN_TONE = "\U0001f646\U0001f3fb" +PERSON_GESTURING_OK_MEDIUM_LIGHT_SKIN_TONE = "\U0001f646\U0001f3fc" +PERSON_GESTURING_OK_MEDIUM_SKIN_TONE = "\U0001f646\U0001f3fd" +PERSON_GESTURING_OK_MEDIUM_DARK_SKIN_TONE = "\U0001f646\U0001f3fe" +PERSON_GESTURING_OK_DARK_SKIN_TONE = "\U0001f646\U0001f3ff" +MAN_GESTURING_OK = "\U0001f646\u200d\u2642\ufe0f" +MAN_GESTURING_OK_LIGHT_SKIN_TONE = "\U0001f646\U0001f3fb\u200d\u2642\ufe0f" +MAN_GESTURING_OK_MEDIUM_LIGHT_SKIN_TONE = "\U0001f646\U0001f3fc\u200d\u2642\ufe0f" +MAN_GESTURING_OK_MEDIUM_SKIN_TONE = "\U0001f646\U0001f3fd\u200d\u2642\ufe0f" +MAN_GESTURING_OK_MEDIUM_DARK_SKIN_TONE = "\U0001f646\U0001f3fe\u200d\u2642\ufe0f" +MAN_GESTURING_OK_DARK_SKIN_TONE = "\U0001f646\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_GESTURING_OK = "\U0001f646\u200d\u2640\ufe0f" +WOMAN_GESTURING_OK_LIGHT_SKIN_TONE = "\U0001f646\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_GESTURING_OK_MEDIUM_LIGHT_SKIN_TONE = "\U0001f646\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_GESTURING_OK_MEDIUM_SKIN_TONE = "\U0001f646\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_GESTURING_OK_MEDIUM_DARK_SKIN_TONE = "\U0001f646\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_GESTURING_OK_DARK_SKIN_TONE = "\U0001f646\U0001f3ff\u200d\u2640\ufe0f" +PERSON_TIPPING_HAND = "\U0001f481" +PERSON_TIPPING_HAND_LIGHT_SKIN_TONE = "\U0001f481\U0001f3fb" +PERSON_TIPPING_HAND_MEDIUM_LIGHT_SKIN_TONE = "\U0001f481\U0001f3fc" +PERSON_TIPPING_HAND_MEDIUM_SKIN_TONE = "\U0001f481\U0001f3fd" +PERSON_TIPPING_HAND_MEDIUM_DARK_SKIN_TONE = "\U0001f481\U0001f3fe" +PERSON_TIPPING_HAND_DARK_SKIN_TONE = "\U0001f481\U0001f3ff" +MAN_TIPPING_HAND = "\U0001f481\u200d\u2642\ufe0f" +MAN_TIPPING_HAND_LIGHT_SKIN_TONE = "\U0001f481\U0001f3fb\u200d\u2642\ufe0f" +MAN_TIPPING_HAND_MEDIUM_LIGHT_SKIN_TONE = "\U0001f481\U0001f3fc\u200d\u2642\ufe0f" +MAN_TIPPING_HAND_MEDIUM_SKIN_TONE = "\U0001f481\U0001f3fd\u200d\u2642\ufe0f" +MAN_TIPPING_HAND_MEDIUM_DARK_SKIN_TONE = "\U0001f481\U0001f3fe\u200d\u2642\ufe0f" +MAN_TIPPING_HAND_DARK_SKIN_TONE = "\U0001f481\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_TIPPING_HAND = "\U0001f481\u200d\u2640\ufe0f" +WOMAN_TIPPING_HAND_LIGHT_SKIN_TONE = "\U0001f481\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_TIPPING_HAND_MEDIUM_LIGHT_SKIN_TONE = "\U0001f481\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_TIPPING_HAND_MEDIUM_SKIN_TONE = "\U0001f481\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_TIPPING_HAND_MEDIUM_DARK_SKIN_TONE = "\U0001f481\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_TIPPING_HAND_DARK_SKIN_TONE = "\U0001f481\U0001f3ff\u200d\u2640\ufe0f" +PERSON_RAISING_HAND = "\U0001f64b" +PERSON_RAISING_HAND_LIGHT_SKIN_TONE = "\U0001f64b\U0001f3fb" +PERSON_RAISING_HAND_MEDIUM_LIGHT_SKIN_TONE = "\U0001f64b\U0001f3fc" +PERSON_RAISING_HAND_MEDIUM_SKIN_TONE = "\U0001f64b\U0001f3fd" +PERSON_RAISING_HAND_MEDIUM_DARK_SKIN_TONE = "\U0001f64b\U0001f3fe" +PERSON_RAISING_HAND_DARK_SKIN_TONE = "\U0001f64b\U0001f3ff" +MAN_RAISING_HAND = "\U0001f64b\u200d\u2642\ufe0f" +MAN_RAISING_HAND_LIGHT_SKIN_TONE = "\U0001f64b\U0001f3fb\u200d\u2642\ufe0f" +MAN_RAISING_HAND_MEDIUM_LIGHT_SKIN_TONE = "\U0001f64b\U0001f3fc\u200d\u2642\ufe0f" +MAN_RAISING_HAND_MEDIUM_SKIN_TONE = "\U0001f64b\U0001f3fd\u200d\u2642\ufe0f" +MAN_RAISING_HAND_MEDIUM_DARK_SKIN_TONE = "\U0001f64b\U0001f3fe\u200d\u2642\ufe0f" +MAN_RAISING_HAND_DARK_SKIN_TONE = "\U0001f64b\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_RAISING_HAND = "\U0001f64b\u200d\u2640\ufe0f" +WOMAN_RAISING_HAND_LIGHT_SKIN_TONE = "\U0001f64b\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_RAISING_HAND_MEDIUM_LIGHT_SKIN_TONE = "\U0001f64b\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_RAISING_HAND_MEDIUM_SKIN_TONE = "\U0001f64b\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_RAISING_HAND_MEDIUM_DARK_SKIN_TONE = "\U0001f64b\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_RAISING_HAND_DARK_SKIN_TONE = "\U0001f64b\U0001f3ff\u200d\u2640\ufe0f" +DEAF_PERSON = "\U0001f9cf" +DEAF_PERSON_LIGHT_SKIN_TONE = "\U0001f9cf\U0001f3fb" +DEAF_PERSON_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9cf\U0001f3fc" +DEAF_PERSON_MEDIUM_SKIN_TONE = "\U0001f9cf\U0001f3fd" +DEAF_PERSON_MEDIUM_DARK_SKIN_TONE = "\U0001f9cf\U0001f3fe" +DEAF_PERSON_DARK_SKIN_TONE = "\U0001f9cf\U0001f3ff" +DEAF_MAN = "\U0001f9cf\u200d\u2642\ufe0f" +DEAF_MAN_LIGHT_SKIN_TONE = "\U0001f9cf\U0001f3fb\u200d\u2642\ufe0f" +DEAF_MAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9cf\U0001f3fc\u200d\u2642\ufe0f" +DEAF_MAN_MEDIUM_SKIN_TONE = "\U0001f9cf\U0001f3fd\u200d\u2642\ufe0f" +DEAF_MAN_MEDIUM_DARK_SKIN_TONE = "\U0001f9cf\U0001f3fe\u200d\u2642\ufe0f" +DEAF_MAN_DARK_SKIN_TONE = "\U0001f9cf\U0001f3ff\u200d\u2642\ufe0f" +DEAF_WOMAN = "\U0001f9cf\u200d\u2640\ufe0f" +DEAF_WOMAN_LIGHT_SKIN_TONE = "\U0001f9cf\U0001f3fb\u200d\u2640\ufe0f" +DEAF_WOMAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9cf\U0001f3fc\u200d\u2640\ufe0f" +DEAF_WOMAN_MEDIUM_SKIN_TONE = "\U0001f9cf\U0001f3fd\u200d\u2640\ufe0f" +DEAF_WOMAN_MEDIUM_DARK_SKIN_TONE = "\U0001f9cf\U0001f3fe\u200d\u2640\ufe0f" +DEAF_WOMAN_DARK_SKIN_TONE = "\U0001f9cf\U0001f3ff\u200d\u2640\ufe0f" +PERSON_BOWING = "\U0001f647" +PERSON_BOWING_LIGHT_SKIN_TONE = "\U0001f647\U0001f3fb" +PERSON_BOWING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f647\U0001f3fc" +PERSON_BOWING_MEDIUM_SKIN_TONE = "\U0001f647\U0001f3fd" +PERSON_BOWING_MEDIUM_DARK_SKIN_TONE = "\U0001f647\U0001f3fe" +PERSON_BOWING_DARK_SKIN_TONE = "\U0001f647\U0001f3ff" +MAN_BOWING = "\U0001f647\u200d\u2642\ufe0f" +MAN_BOWING_LIGHT_SKIN_TONE = "\U0001f647\U0001f3fb\u200d\u2642\ufe0f" +MAN_BOWING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f647\U0001f3fc\u200d\u2642\ufe0f" +MAN_BOWING_MEDIUM_SKIN_TONE = "\U0001f647\U0001f3fd\u200d\u2642\ufe0f" +MAN_BOWING_MEDIUM_DARK_SKIN_TONE = "\U0001f647\U0001f3fe\u200d\u2642\ufe0f" +MAN_BOWING_DARK_SKIN_TONE = "\U0001f647\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_BOWING = "\U0001f647\u200d\u2640\ufe0f" +WOMAN_BOWING_LIGHT_SKIN_TONE = "\U0001f647\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_BOWING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f647\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_BOWING_MEDIUM_SKIN_TONE = "\U0001f647\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_BOWING_MEDIUM_DARK_SKIN_TONE = "\U0001f647\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_BOWING_DARK_SKIN_TONE = "\U0001f647\U0001f3ff\u200d\u2640\ufe0f" +PERSON_FACEPALMING = "\U0001f926" +PERSON_FACEPALMING_LIGHT_SKIN_TONE = "\U0001f926\U0001f3fb" +PERSON_FACEPALMING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f926\U0001f3fc" +PERSON_FACEPALMING_MEDIUM_SKIN_TONE = "\U0001f926\U0001f3fd" +PERSON_FACEPALMING_MEDIUM_DARK_SKIN_TONE = "\U0001f926\U0001f3fe" +PERSON_FACEPALMING_DARK_SKIN_TONE = "\U0001f926\U0001f3ff" +MAN_FACEPALMING = "\U0001f926\u200d\u2642\ufe0f" +MAN_FACEPALMING_LIGHT_SKIN_TONE = "\U0001f926\U0001f3fb\u200d\u2642\ufe0f" +MAN_FACEPALMING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f926\U0001f3fc\u200d\u2642\ufe0f" +MAN_FACEPALMING_MEDIUM_SKIN_TONE = "\U0001f926\U0001f3fd\u200d\u2642\ufe0f" +MAN_FACEPALMING_MEDIUM_DARK_SKIN_TONE = "\U0001f926\U0001f3fe\u200d\u2642\ufe0f" +MAN_FACEPALMING_DARK_SKIN_TONE = "\U0001f926\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_FACEPALMING = "\U0001f926\u200d\u2640\ufe0f" +WOMAN_FACEPALMING_LIGHT_SKIN_TONE = "\U0001f926\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_FACEPALMING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f926\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_FACEPALMING_MEDIUM_SKIN_TONE = "\U0001f926\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_FACEPALMING_MEDIUM_DARK_SKIN_TONE = "\U0001f926\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_FACEPALMING_DARK_SKIN_TONE = "\U0001f926\U0001f3ff\u200d\u2640\ufe0f" +PERSON_SHRUGGING = "\U0001f937" +PERSON_SHRUGGING_LIGHT_SKIN_TONE = "\U0001f937\U0001f3fb" +PERSON_SHRUGGING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f937\U0001f3fc" +PERSON_SHRUGGING_MEDIUM_SKIN_TONE = "\U0001f937\U0001f3fd" +PERSON_SHRUGGING_MEDIUM_DARK_SKIN_TONE = "\U0001f937\U0001f3fe" +PERSON_SHRUGGING_DARK_SKIN_TONE = "\U0001f937\U0001f3ff" +MAN_SHRUGGING = "\U0001f937\u200d\u2642\ufe0f" +MAN_SHRUGGING_LIGHT_SKIN_TONE = "\U0001f937\U0001f3fb\u200d\u2642\ufe0f" +MAN_SHRUGGING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f937\U0001f3fc\u200d\u2642\ufe0f" +MAN_SHRUGGING_MEDIUM_SKIN_TONE = "\U0001f937\U0001f3fd\u200d\u2642\ufe0f" +MAN_SHRUGGING_MEDIUM_DARK_SKIN_TONE = "\U0001f937\U0001f3fe\u200d\u2642\ufe0f" +MAN_SHRUGGING_DARK_SKIN_TONE = "\U0001f937\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_SHRUGGING = "\U0001f937\u200d\u2640\ufe0f" +WOMAN_SHRUGGING_LIGHT_SKIN_TONE = "\U0001f937\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_SHRUGGING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f937\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_SHRUGGING_MEDIUM_SKIN_TONE = "\U0001f937\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_SHRUGGING_MEDIUM_DARK_SKIN_TONE = "\U0001f937\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_SHRUGGING_DARK_SKIN_TONE = "\U0001f937\U0001f3ff\u200d\u2640\ufe0f" +HEALTH_WORKER = "\U0001f9d1\u200d\u2695\ufe0f" +HEALTH_WORKER_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fb\u200d\u2695\ufe0f" +HEALTH_WORKER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fc\u200d\u2695\ufe0f" +HEALTH_WORKER_MEDIUM_SKIN_TONE = "\U0001f9d1\U0001f3fd\u200d\u2695\ufe0f" +HEALTH_WORKER_MEDIUM_DARK_SKIN_TONE = "\U0001f9d1\U0001f3fe\u200d\u2695\ufe0f" +HEALTH_WORKER_DARK_SKIN_TONE = "\U0001f9d1\U0001f3ff\u200d\u2695\ufe0f" +MAN_HEALTH_WORKER = "\U0001f468\u200d\u2695\ufe0f" +MAN_HEALTH_WORKER_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\u2695\ufe0f" +MAN_HEALTH_WORKER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\u2695\ufe0f" +MAN_HEALTH_WORKER_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\u2695\ufe0f" +MAN_HEALTH_WORKER_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\u2695\ufe0f" +MAN_HEALTH_WORKER_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\u2695\ufe0f" +WOMAN_HEALTH_WORKER = "\U0001f469\u200d\u2695\ufe0f" +WOMAN_HEALTH_WORKER_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\u2695\ufe0f" +WOMAN_HEALTH_WORKER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\u2695\ufe0f" +WOMAN_HEALTH_WORKER_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\u2695\ufe0f" +WOMAN_HEALTH_WORKER_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\u2695\ufe0f" +WOMAN_HEALTH_WORKER_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\u2695\ufe0f" +STUDENT = "\U0001f9d1\u200d\U0001f393" +STUDENT_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fb\u200d\U0001f393" +STUDENT_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fc\u200d\U0001f393" +STUDENT_MEDIUM_SKIN_TONE = "\U0001f9d1\U0001f3fd\u200d\U0001f393" +STUDENT_MEDIUM_DARK_SKIN_TONE = "\U0001f9d1\U0001f3fe\u200d\U0001f393" +STUDENT_DARK_SKIN_TONE = "\U0001f9d1\U0001f3ff\u200d\U0001f393" +MAN_STUDENT = "\U0001f468\u200d\U0001f393" +MAN_STUDENT_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f393" +MAN_STUDENT_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f393" +MAN_STUDENT_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f393" +MAN_STUDENT_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f393" +MAN_STUDENT_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f393" +WOMAN_STUDENT = "\U0001f469\u200d\U0001f393" +WOMAN_STUDENT_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f393" +WOMAN_STUDENT_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f393" +WOMAN_STUDENT_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f393" +WOMAN_STUDENT_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f393" +WOMAN_STUDENT_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f393" +TEACHER = "\U0001f9d1\u200d\U0001f3eb" +TEACHER_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fb\u200d\U0001f3eb" +TEACHER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fc\u200d\U0001f3eb" +TEACHER_MEDIUM_SKIN_TONE = "\U0001f9d1\U0001f3fd\u200d\U0001f3eb" +TEACHER_MEDIUM_DARK_SKIN_TONE = "\U0001f9d1\U0001f3fe\u200d\U0001f3eb" +TEACHER_DARK_SKIN_TONE = "\U0001f9d1\U0001f3ff\u200d\U0001f3eb" +MAN_TEACHER = "\U0001f468\u200d\U0001f3eb" +MAN_TEACHER_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f3eb" +MAN_TEACHER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f3eb" +MAN_TEACHER_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f3eb" +MAN_TEACHER_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f3eb" +MAN_TEACHER_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f3eb" +WOMAN_TEACHER = "\U0001f469\u200d\U0001f3eb" +WOMAN_TEACHER_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f3eb" +WOMAN_TEACHER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f3eb" +WOMAN_TEACHER_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f3eb" +WOMAN_TEACHER_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f3eb" +WOMAN_TEACHER_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f3eb" +JUDGE = "\U0001f9d1\u200d\u2696\ufe0f" +JUDGE_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fb\u200d\u2696\ufe0f" +JUDGE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fc\u200d\u2696\ufe0f" +JUDGE_MEDIUM_SKIN_TONE = "\U0001f9d1\U0001f3fd\u200d\u2696\ufe0f" +JUDGE_MEDIUM_DARK_SKIN_TONE = "\U0001f9d1\U0001f3fe\u200d\u2696\ufe0f" +JUDGE_DARK_SKIN_TONE = "\U0001f9d1\U0001f3ff\u200d\u2696\ufe0f" +MAN_JUDGE = "\U0001f468\u200d\u2696\ufe0f" +MAN_JUDGE_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\u2696\ufe0f" +MAN_JUDGE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\u2696\ufe0f" +MAN_JUDGE_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\u2696\ufe0f" +MAN_JUDGE_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\u2696\ufe0f" +MAN_JUDGE_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\u2696\ufe0f" +WOMAN_JUDGE = "\U0001f469\u200d\u2696\ufe0f" +WOMAN_JUDGE_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\u2696\ufe0f" +WOMAN_JUDGE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\u2696\ufe0f" +WOMAN_JUDGE_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\u2696\ufe0f" +WOMAN_JUDGE_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\u2696\ufe0f" +WOMAN_JUDGE_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\u2696\ufe0f" +FARMER = "\U0001f9d1\u200d\U0001f33e" +FARMER_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fb\u200d\U0001f33e" +FARMER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fc\u200d\U0001f33e" +FARMER_MEDIUM_SKIN_TONE = "\U0001f9d1\U0001f3fd\u200d\U0001f33e" +FARMER_MEDIUM_DARK_SKIN_TONE = "\U0001f9d1\U0001f3fe\u200d\U0001f33e" +FARMER_DARK_SKIN_TONE = "\U0001f9d1\U0001f3ff\u200d\U0001f33e" +MAN_FARMER = "\U0001f468\u200d\U0001f33e" +MAN_FARMER_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f33e" +MAN_FARMER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f33e" +MAN_FARMER_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f33e" +MAN_FARMER_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f33e" +MAN_FARMER_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f33e" +WOMAN_FARMER = "\U0001f469\u200d\U0001f33e" +WOMAN_FARMER_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f33e" +WOMAN_FARMER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f33e" +WOMAN_FARMER_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f33e" +WOMAN_FARMER_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f33e" +WOMAN_FARMER_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f33e" +COOK = "\U0001f9d1\u200d\U0001f373" +COOK_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fb\u200d\U0001f373" +COOK_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fc\u200d\U0001f373" +COOK_MEDIUM_SKIN_TONE = "\U0001f9d1\U0001f3fd\u200d\U0001f373" +COOK_MEDIUM_DARK_SKIN_TONE = "\U0001f9d1\U0001f3fe\u200d\U0001f373" +COOK_DARK_SKIN_TONE = "\U0001f9d1\U0001f3ff\u200d\U0001f373" +MAN_COOK = "\U0001f468\u200d\U0001f373" +MAN_COOK_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f373" +MAN_COOK_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f373" +MAN_COOK_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f373" +MAN_COOK_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f373" +MAN_COOK_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f373" +WOMAN_COOK = "\U0001f469\u200d\U0001f373" +WOMAN_COOK_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f373" +WOMAN_COOK_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f373" +WOMAN_COOK_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f373" +WOMAN_COOK_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f373" +WOMAN_COOK_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f373" +MECHANIC = "\U0001f9d1\u200d\U0001f527" +MECHANIC_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fb\u200d\U0001f527" +MECHANIC_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fc\u200d\U0001f527" +MECHANIC_MEDIUM_SKIN_TONE = "\U0001f9d1\U0001f3fd\u200d\U0001f527" +MECHANIC_MEDIUM_DARK_SKIN_TONE = "\U0001f9d1\U0001f3fe\u200d\U0001f527" +MECHANIC_DARK_SKIN_TONE = "\U0001f9d1\U0001f3ff\u200d\U0001f527" +MAN_MECHANIC = "\U0001f468\u200d\U0001f527" +MAN_MECHANIC_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f527" +MAN_MECHANIC_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f527" +MAN_MECHANIC_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f527" +MAN_MECHANIC_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f527" +MAN_MECHANIC_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f527" +WOMAN_MECHANIC = "\U0001f469\u200d\U0001f527" +WOMAN_MECHANIC_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f527" +WOMAN_MECHANIC_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f527" +WOMAN_MECHANIC_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f527" +WOMAN_MECHANIC_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f527" +WOMAN_MECHANIC_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f527" +FACTORY_WORKER = "\U0001f9d1\u200d\U0001f3ed" +FACTORY_WORKER_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fb\u200d\U0001f3ed" +FACTORY_WORKER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fc\u200d\U0001f3ed" +FACTORY_WORKER_MEDIUM_SKIN_TONE = "\U0001f9d1\U0001f3fd\u200d\U0001f3ed" +FACTORY_WORKER_MEDIUM_DARK_SKIN_TONE = "\U0001f9d1\U0001f3fe\u200d\U0001f3ed" +FACTORY_WORKER_DARK_SKIN_TONE = "\U0001f9d1\U0001f3ff\u200d\U0001f3ed" +MAN_FACTORY_WORKER = "\U0001f468\u200d\U0001f3ed" +MAN_FACTORY_WORKER_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f3ed" +MAN_FACTORY_WORKER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f3ed" +MAN_FACTORY_WORKER_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f3ed" +MAN_FACTORY_WORKER_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f3ed" +MAN_FACTORY_WORKER_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f3ed" +WOMAN_FACTORY_WORKER = "\U0001f469\u200d\U0001f3ed" +WOMAN_FACTORY_WORKER_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f3ed" +WOMAN_FACTORY_WORKER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f3ed" +WOMAN_FACTORY_WORKER_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f3ed" +WOMAN_FACTORY_WORKER_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f3ed" +WOMAN_FACTORY_WORKER_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f3ed" +OFFICE_WORKER = "\U0001f9d1\u200d\U0001f4bc" +OFFICE_WORKER_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fb\u200d\U0001f4bc" +OFFICE_WORKER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fc\u200d\U0001f4bc" +OFFICE_WORKER_MEDIUM_SKIN_TONE = "\U0001f9d1\U0001f3fd\u200d\U0001f4bc" +OFFICE_WORKER_MEDIUM_DARK_SKIN_TONE = "\U0001f9d1\U0001f3fe\u200d\U0001f4bc" +OFFICE_WORKER_DARK_SKIN_TONE = "\U0001f9d1\U0001f3ff\u200d\U0001f4bc" +MAN_OFFICE_WORKER = "\U0001f468\u200d\U0001f4bc" +MAN_OFFICE_WORKER_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f4bc" +MAN_OFFICE_WORKER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f4bc" +MAN_OFFICE_WORKER_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f4bc" +MAN_OFFICE_WORKER_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f4bc" +MAN_OFFICE_WORKER_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f4bc" +WOMAN_OFFICE_WORKER = "\U0001f469\u200d\U0001f4bc" +WOMAN_OFFICE_WORKER_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f4bc" +WOMAN_OFFICE_WORKER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f4bc" +WOMAN_OFFICE_WORKER_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f4bc" +WOMAN_OFFICE_WORKER_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f4bc" +WOMAN_OFFICE_WORKER_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f4bc" +SCIENTIST = "\U0001f9d1\u200d\U0001f52c" +SCIENTIST_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fb\u200d\U0001f52c" +SCIENTIST_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fc\u200d\U0001f52c" +SCIENTIST_MEDIUM_SKIN_TONE = "\U0001f9d1\U0001f3fd\u200d\U0001f52c" +SCIENTIST_MEDIUM_DARK_SKIN_TONE = "\U0001f9d1\U0001f3fe\u200d\U0001f52c" +SCIENTIST_DARK_SKIN_TONE = "\U0001f9d1\U0001f3ff\u200d\U0001f52c" +MAN_SCIENTIST = "\U0001f468\u200d\U0001f52c" +MAN_SCIENTIST_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f52c" +MAN_SCIENTIST_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f52c" +MAN_SCIENTIST_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f52c" +MAN_SCIENTIST_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f52c" +MAN_SCIENTIST_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f52c" +WOMAN_SCIENTIST = "\U0001f469\u200d\U0001f52c" +WOMAN_SCIENTIST_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f52c" +WOMAN_SCIENTIST_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f52c" +WOMAN_SCIENTIST_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f52c" +WOMAN_SCIENTIST_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f52c" +WOMAN_SCIENTIST_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f52c" +TECHNOLOGIST = "\U0001f9d1\u200d\U0001f4bb" +TECHNOLOGIST_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fb\u200d\U0001f4bb" +TECHNOLOGIST_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fc\u200d\U0001f4bb" +TECHNOLOGIST_MEDIUM_SKIN_TONE = "\U0001f9d1\U0001f3fd\u200d\U0001f4bb" +TECHNOLOGIST_MEDIUM_DARK_SKIN_TONE = "\U0001f9d1\U0001f3fe\u200d\U0001f4bb" +TECHNOLOGIST_DARK_SKIN_TONE = "\U0001f9d1\U0001f3ff\u200d\U0001f4bb" +MAN_TECHNOLOGIST = "\U0001f468\u200d\U0001f4bb" +MAN_TECHNOLOGIST_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f4bb" +MAN_TECHNOLOGIST_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f4bb" +MAN_TECHNOLOGIST_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f4bb" +MAN_TECHNOLOGIST_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f4bb" +MAN_TECHNOLOGIST_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f4bb" +WOMAN_TECHNOLOGIST = "\U0001f469\u200d\U0001f4bb" +WOMAN_TECHNOLOGIST_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f4bb" +WOMAN_TECHNOLOGIST_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f4bb" +WOMAN_TECHNOLOGIST_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f4bb" +WOMAN_TECHNOLOGIST_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f4bb" +WOMAN_TECHNOLOGIST_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f4bb" +SINGER = "\U0001f9d1\u200d\U0001f3a4" +SINGER_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fb\u200d\U0001f3a4" +SINGER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fc\u200d\U0001f3a4" +SINGER_MEDIUM_SKIN_TONE = "\U0001f9d1\U0001f3fd\u200d\U0001f3a4" +SINGER_MEDIUM_DARK_SKIN_TONE = "\U0001f9d1\U0001f3fe\u200d\U0001f3a4" +SINGER_DARK_SKIN_TONE = "\U0001f9d1\U0001f3ff\u200d\U0001f3a4" +MAN_SINGER = "\U0001f468\u200d\U0001f3a4" +MAN_SINGER_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f3a4" +MAN_SINGER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f3a4" +MAN_SINGER_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f3a4" +MAN_SINGER_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f3a4" +MAN_SINGER_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f3a4" +WOMAN_SINGER = "\U0001f469\u200d\U0001f3a4" +WOMAN_SINGER_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f3a4" +WOMAN_SINGER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f3a4" +WOMAN_SINGER_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f3a4" +WOMAN_SINGER_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f3a4" +WOMAN_SINGER_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f3a4" +ARTIST = "\U0001f9d1\u200d\U0001f3a8" +ARTIST_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fb\u200d\U0001f3a8" +ARTIST_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fc\u200d\U0001f3a8" +ARTIST_MEDIUM_SKIN_TONE = "\U0001f9d1\U0001f3fd\u200d\U0001f3a8" +ARTIST_MEDIUM_DARK_SKIN_TONE = "\U0001f9d1\U0001f3fe\u200d\U0001f3a8" +ARTIST_DARK_SKIN_TONE = "\U0001f9d1\U0001f3ff\u200d\U0001f3a8" +MAN_ARTIST = "\U0001f468\u200d\U0001f3a8" +MAN_ARTIST_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f3a8" +MAN_ARTIST_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f3a8" +MAN_ARTIST_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f3a8" +MAN_ARTIST_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f3a8" +MAN_ARTIST_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f3a8" +WOMAN_ARTIST = "\U0001f469\u200d\U0001f3a8" +WOMAN_ARTIST_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f3a8" +WOMAN_ARTIST_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f3a8" +WOMAN_ARTIST_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f3a8" +WOMAN_ARTIST_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f3a8" +WOMAN_ARTIST_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f3a8" +PILOT = "\U0001f9d1\u200d\u2708\ufe0f" +PILOT_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fb\u200d\u2708\ufe0f" +PILOT_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fc\u200d\u2708\ufe0f" +PILOT_MEDIUM_SKIN_TONE = "\U0001f9d1\U0001f3fd\u200d\u2708\ufe0f" +PILOT_MEDIUM_DARK_SKIN_TONE = "\U0001f9d1\U0001f3fe\u200d\u2708\ufe0f" +PILOT_DARK_SKIN_TONE = "\U0001f9d1\U0001f3ff\u200d\u2708\ufe0f" +MAN_PILOT = "\U0001f468\u200d\u2708\ufe0f" +MAN_PILOT_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\u2708\ufe0f" +MAN_PILOT_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\u2708\ufe0f" +MAN_PILOT_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\u2708\ufe0f" +MAN_PILOT_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\u2708\ufe0f" +MAN_PILOT_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\u2708\ufe0f" +WOMAN_PILOT = "\U0001f469\u200d\u2708\ufe0f" +WOMAN_PILOT_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\u2708\ufe0f" +WOMAN_PILOT_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\u2708\ufe0f" +WOMAN_PILOT_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\u2708\ufe0f" +WOMAN_PILOT_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\u2708\ufe0f" +WOMAN_PILOT_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\u2708\ufe0f" +ASTRONAUT = "\U0001f9d1\u200d\U0001f680" +ASTRONAUT_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fb\u200d\U0001f680" +ASTRONAUT_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fc\u200d\U0001f680" +ASTRONAUT_MEDIUM_SKIN_TONE = "\U0001f9d1\U0001f3fd\u200d\U0001f680" +ASTRONAUT_MEDIUM_DARK_SKIN_TONE = "\U0001f9d1\U0001f3fe\u200d\U0001f680" +ASTRONAUT_DARK_SKIN_TONE = "\U0001f9d1\U0001f3ff\u200d\U0001f680" +MAN_ASTRONAUT = "\U0001f468\u200d\U0001f680" +MAN_ASTRONAUT_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f680" +MAN_ASTRONAUT_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f680" +MAN_ASTRONAUT_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f680" +MAN_ASTRONAUT_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f680" +MAN_ASTRONAUT_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f680" +WOMAN_ASTRONAUT = "\U0001f469\u200d\U0001f680" +WOMAN_ASTRONAUT_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f680" +WOMAN_ASTRONAUT_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f680" +WOMAN_ASTRONAUT_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f680" +WOMAN_ASTRONAUT_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f680" +WOMAN_ASTRONAUT_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f680" +FIREFIGHTER = "\U0001f9d1\u200d\U0001f692" +FIREFIGHTER_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fb\u200d\U0001f692" +FIREFIGHTER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fc\u200d\U0001f692" +FIREFIGHTER_MEDIUM_SKIN_TONE = "\U0001f9d1\U0001f3fd\u200d\U0001f692" +FIREFIGHTER_MEDIUM_DARK_SKIN_TONE = "\U0001f9d1\U0001f3fe\u200d\U0001f692" +FIREFIGHTER_DARK_SKIN_TONE = "\U0001f9d1\U0001f3ff\u200d\U0001f692" +MAN_FIREFIGHTER = "\U0001f468\u200d\U0001f692" +MAN_FIREFIGHTER_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f692" +MAN_FIREFIGHTER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f692" +MAN_FIREFIGHTER_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f692" +MAN_FIREFIGHTER_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f692" +MAN_FIREFIGHTER_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f692" +WOMAN_FIREFIGHTER = "\U0001f469\u200d\U0001f692" +WOMAN_FIREFIGHTER_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f692" +WOMAN_FIREFIGHTER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f692" +WOMAN_FIREFIGHTER_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f692" +WOMAN_FIREFIGHTER_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f692" +WOMAN_FIREFIGHTER_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f692" +POLICE_OFFICER = "\U0001f46e" +POLICE_OFFICER_LIGHT_SKIN_TONE = "\U0001f46e\U0001f3fb" +POLICE_OFFICER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f46e\U0001f3fc" +POLICE_OFFICER_MEDIUM_SKIN_TONE = "\U0001f46e\U0001f3fd" +POLICE_OFFICER_MEDIUM_DARK_SKIN_TONE = "\U0001f46e\U0001f3fe" +POLICE_OFFICER_DARK_SKIN_TONE = "\U0001f46e\U0001f3ff" +MAN_POLICE_OFFICER = "\U0001f46e\u200d\u2642\ufe0f" +MAN_POLICE_OFFICER_LIGHT_SKIN_TONE = "\U0001f46e\U0001f3fb\u200d\u2642\ufe0f" +MAN_POLICE_OFFICER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f46e\U0001f3fc\u200d\u2642\ufe0f" +MAN_POLICE_OFFICER_MEDIUM_SKIN_TONE = "\U0001f46e\U0001f3fd\u200d\u2642\ufe0f" +MAN_POLICE_OFFICER_MEDIUM_DARK_SKIN_TONE = "\U0001f46e\U0001f3fe\u200d\u2642\ufe0f" +MAN_POLICE_OFFICER_DARK_SKIN_TONE = "\U0001f46e\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_POLICE_OFFICER = "\U0001f46e\u200d\u2640\ufe0f" +WOMAN_POLICE_OFFICER_LIGHT_SKIN_TONE = "\U0001f46e\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_POLICE_OFFICER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f46e\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_POLICE_OFFICER_MEDIUM_SKIN_TONE = "\U0001f46e\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_POLICE_OFFICER_MEDIUM_DARK_SKIN_TONE = "\U0001f46e\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_POLICE_OFFICER_DARK_SKIN_TONE = "\U0001f46e\U0001f3ff\u200d\u2640\ufe0f" +DETECTIVE = "\U0001f575\ufe0f" +DETECTIVE_LIGHT_SKIN_TONE = "\U0001f575\U0001f3fb" +DETECTIVE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f575\U0001f3fc" +DETECTIVE_MEDIUM_SKIN_TONE = "\U0001f575\U0001f3fd" +DETECTIVE_MEDIUM_DARK_SKIN_TONE = "\U0001f575\U0001f3fe" +DETECTIVE_DARK_SKIN_TONE = "\U0001f575\U0001f3ff" +MAN_DETECTIVE = "\U0001f575\ufe0f\u200d\u2642\ufe0f" +MAN_DETECTIVE_LIGHT_SKIN_TONE = "\U0001f575\U0001f3fb\u200d\u2642\ufe0f" +MAN_DETECTIVE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f575\U0001f3fc\u200d\u2642\ufe0f" +MAN_DETECTIVE_MEDIUM_SKIN_TONE = "\U0001f575\U0001f3fd\u200d\u2642\ufe0f" +MAN_DETECTIVE_MEDIUM_DARK_SKIN_TONE = "\U0001f575\U0001f3fe\u200d\u2642\ufe0f" +MAN_DETECTIVE_DARK_SKIN_TONE = "\U0001f575\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_DETECTIVE = "\U0001f575\ufe0f\u200d\u2640\ufe0f" +WOMAN_DETECTIVE_LIGHT_SKIN_TONE = "\U0001f575\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_DETECTIVE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f575\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_DETECTIVE_MEDIUM_SKIN_TONE = "\U0001f575\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_DETECTIVE_MEDIUM_DARK_SKIN_TONE = "\U0001f575\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_DETECTIVE_DARK_SKIN_TONE = "\U0001f575\U0001f3ff\u200d\u2640\ufe0f" +GUARD = "\U0001f482" +GUARD_LIGHT_SKIN_TONE = "\U0001f482\U0001f3fb" +GUARD_MEDIUM_LIGHT_SKIN_TONE = "\U0001f482\U0001f3fc" +GUARD_MEDIUM_SKIN_TONE = "\U0001f482\U0001f3fd" +GUARD_MEDIUM_DARK_SKIN_TONE = "\U0001f482\U0001f3fe" +GUARD_DARK_SKIN_TONE = "\U0001f482\U0001f3ff" +MAN_GUARD = "\U0001f482\u200d\u2642\ufe0f" +MAN_GUARD_LIGHT_SKIN_TONE = "\U0001f482\U0001f3fb\u200d\u2642\ufe0f" +MAN_GUARD_MEDIUM_LIGHT_SKIN_TONE = "\U0001f482\U0001f3fc\u200d\u2642\ufe0f" +MAN_GUARD_MEDIUM_SKIN_TONE = "\U0001f482\U0001f3fd\u200d\u2642\ufe0f" +MAN_GUARD_MEDIUM_DARK_SKIN_TONE = "\U0001f482\U0001f3fe\u200d\u2642\ufe0f" +MAN_GUARD_DARK_SKIN_TONE = "\U0001f482\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_GUARD = "\U0001f482\u200d\u2640\ufe0f" +WOMAN_GUARD_LIGHT_SKIN_TONE = "\U0001f482\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_GUARD_MEDIUM_LIGHT_SKIN_TONE = "\U0001f482\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_GUARD_MEDIUM_SKIN_TONE = "\U0001f482\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_GUARD_MEDIUM_DARK_SKIN_TONE = "\U0001f482\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_GUARD_DARK_SKIN_TONE = "\U0001f482\U0001f3ff\u200d\u2640\ufe0f" +NINJA = "\U0001f977" +NINJA_LIGHT_SKIN_TONE = "\U0001f977\U0001f3fb" +NINJA_MEDIUM_LIGHT_SKIN_TONE = "\U0001f977\U0001f3fc" +NINJA_MEDIUM_SKIN_TONE = "\U0001f977\U0001f3fd" +NINJA_MEDIUM_DARK_SKIN_TONE = "\U0001f977\U0001f3fe" +NINJA_DARK_SKIN_TONE = "\U0001f977\U0001f3ff" +CONSTRUCTION_WORKER = "\U0001f477" +CONSTRUCTION_WORKER_LIGHT_SKIN_TONE = "\U0001f477\U0001f3fb" +CONSTRUCTION_WORKER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f477\U0001f3fc" +CONSTRUCTION_WORKER_MEDIUM_SKIN_TONE = "\U0001f477\U0001f3fd" +CONSTRUCTION_WORKER_MEDIUM_DARK_SKIN_TONE = "\U0001f477\U0001f3fe" +CONSTRUCTION_WORKER_DARK_SKIN_TONE = "\U0001f477\U0001f3ff" +MAN_CONSTRUCTION_WORKER = "\U0001f477\u200d\u2642\ufe0f" +MAN_CONSTRUCTION_WORKER_LIGHT_SKIN_TONE = "\U0001f477\U0001f3fb\u200d\u2642\ufe0f" +MAN_CONSTRUCTION_WORKER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f477\U0001f3fc\u200d\u2642\ufe0f" +MAN_CONSTRUCTION_WORKER_MEDIUM_SKIN_TONE = "\U0001f477\U0001f3fd\u200d\u2642\ufe0f" +MAN_CONSTRUCTION_WORKER_MEDIUM_DARK_SKIN_TONE = "\U0001f477\U0001f3fe\u200d\u2642\ufe0f" +MAN_CONSTRUCTION_WORKER_DARK_SKIN_TONE = "\U0001f477\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_CONSTRUCTION_WORKER = "\U0001f477\u200d\u2640\ufe0f" +WOMAN_CONSTRUCTION_WORKER_LIGHT_SKIN_TONE = "\U0001f477\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_CONSTRUCTION_WORKER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f477\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_CONSTRUCTION_WORKER_MEDIUM_SKIN_TONE = "\U0001f477\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_CONSTRUCTION_WORKER_MEDIUM_DARK_SKIN_TONE = "\U0001f477\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_CONSTRUCTION_WORKER_DARK_SKIN_TONE = "\U0001f477\U0001f3ff\u200d\u2640\ufe0f" +PRINCE = "\U0001f934" +PRINCE_LIGHT_SKIN_TONE = "\U0001f934\U0001f3fb" +PRINCE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f934\U0001f3fc" +PRINCE_MEDIUM_SKIN_TONE = "\U0001f934\U0001f3fd" +PRINCE_MEDIUM_DARK_SKIN_TONE = "\U0001f934\U0001f3fe" +PRINCE_DARK_SKIN_TONE = "\U0001f934\U0001f3ff" +PRINCESS = "\U0001f478" +PRINCESS_LIGHT_SKIN_TONE = "\U0001f478\U0001f3fb" +PRINCESS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f478\U0001f3fc" +PRINCESS_MEDIUM_SKIN_TONE = "\U0001f478\U0001f3fd" +PRINCESS_MEDIUM_DARK_SKIN_TONE = "\U0001f478\U0001f3fe" +PRINCESS_DARK_SKIN_TONE = "\U0001f478\U0001f3ff" +PERSON_WEARING_TURBAN = "\U0001f473" +PERSON_WEARING_TURBAN_LIGHT_SKIN_TONE = "\U0001f473\U0001f3fb" +PERSON_WEARING_TURBAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f473\U0001f3fc" +PERSON_WEARING_TURBAN_MEDIUM_SKIN_TONE = "\U0001f473\U0001f3fd" +PERSON_WEARING_TURBAN_MEDIUM_DARK_SKIN_TONE = "\U0001f473\U0001f3fe" +PERSON_WEARING_TURBAN_DARK_SKIN_TONE = "\U0001f473\U0001f3ff" +MAN_WEARING_TURBAN = "\U0001f473\u200d\u2642\ufe0f" +MAN_WEARING_TURBAN_LIGHT_SKIN_TONE = "\U0001f473\U0001f3fb\u200d\u2642\ufe0f" +MAN_WEARING_TURBAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f473\U0001f3fc\u200d\u2642\ufe0f" +MAN_WEARING_TURBAN_MEDIUM_SKIN_TONE = "\U0001f473\U0001f3fd\u200d\u2642\ufe0f" +MAN_WEARING_TURBAN_MEDIUM_DARK_SKIN_TONE = "\U0001f473\U0001f3fe\u200d\u2642\ufe0f" +MAN_WEARING_TURBAN_DARK_SKIN_TONE = "\U0001f473\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_WEARING_TURBAN = "\U0001f473\u200d\u2640\ufe0f" +WOMAN_WEARING_TURBAN_LIGHT_SKIN_TONE = "\U0001f473\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_WEARING_TURBAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f473\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_WEARING_TURBAN_MEDIUM_SKIN_TONE = "\U0001f473\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_WEARING_TURBAN_MEDIUM_DARK_SKIN_TONE = "\U0001f473\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_WEARING_TURBAN_DARK_SKIN_TONE = "\U0001f473\U0001f3ff\u200d\u2640\ufe0f" +PERSON_WITH_SKULLCAP = "\U0001f472" +PERSON_WITH_SKULLCAP_LIGHT_SKIN_TONE = "\U0001f472\U0001f3fb" +PERSON_WITH_SKULLCAP_MEDIUM_LIGHT_SKIN_TONE = "\U0001f472\U0001f3fc" +PERSON_WITH_SKULLCAP_MEDIUM_SKIN_TONE = "\U0001f472\U0001f3fd" +PERSON_WITH_SKULLCAP_MEDIUM_DARK_SKIN_TONE = "\U0001f472\U0001f3fe" +PERSON_WITH_SKULLCAP_DARK_SKIN_TONE = "\U0001f472\U0001f3ff" +WOMAN_WITH_HEADSCARF = "\U0001f9d5" +WOMAN_WITH_HEADSCARF_LIGHT_SKIN_TONE = "\U0001f9d5\U0001f3fb" +WOMAN_WITH_HEADSCARF_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d5\U0001f3fc" +WOMAN_WITH_HEADSCARF_MEDIUM_SKIN_TONE = "\U0001f9d5\U0001f3fd" +WOMAN_WITH_HEADSCARF_MEDIUM_DARK_SKIN_TONE = "\U0001f9d5\U0001f3fe" +WOMAN_WITH_HEADSCARF_DARK_SKIN_TONE = "\U0001f9d5\U0001f3ff" +PERSON_IN_TUXEDO = "\U0001f935" +PERSON_IN_TUXEDO_LIGHT_SKIN_TONE = "\U0001f935\U0001f3fb" +PERSON_IN_TUXEDO_MEDIUM_LIGHT_SKIN_TONE = "\U0001f935\U0001f3fc" +PERSON_IN_TUXEDO_MEDIUM_SKIN_TONE = "\U0001f935\U0001f3fd" +PERSON_IN_TUXEDO_MEDIUM_DARK_SKIN_TONE = "\U0001f935\U0001f3fe" +PERSON_IN_TUXEDO_DARK_SKIN_TONE = "\U0001f935\U0001f3ff" +MAN_IN_TUXEDO = "\U0001f935\u200d\u2642\ufe0f" +MAN_IN_TUXEDO_LIGHT_SKIN_TONE = "\U0001f935\U0001f3fb\u200d\u2642\ufe0f" +MAN_IN_TUXEDO_MEDIUM_LIGHT_SKIN_TONE = "\U0001f935\U0001f3fc\u200d\u2642\ufe0f" +MAN_IN_TUXEDO_MEDIUM_SKIN_TONE = "\U0001f935\U0001f3fd\u200d\u2642\ufe0f" +MAN_IN_TUXEDO_MEDIUM_DARK_SKIN_TONE = "\U0001f935\U0001f3fe\u200d\u2642\ufe0f" +MAN_IN_TUXEDO_DARK_SKIN_TONE = "\U0001f935\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_IN_TUXEDO = "\U0001f935\u200d\u2640\ufe0f" +WOMAN_IN_TUXEDO_LIGHT_SKIN_TONE = "\U0001f935\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_IN_TUXEDO_MEDIUM_LIGHT_SKIN_TONE = "\U0001f935\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_IN_TUXEDO_MEDIUM_SKIN_TONE = "\U0001f935\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_IN_TUXEDO_MEDIUM_DARK_SKIN_TONE = "\U0001f935\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_IN_TUXEDO_DARK_SKIN_TONE = "\U0001f935\U0001f3ff\u200d\u2640\ufe0f" +PERSON_WITH_VEIL = "\U0001f470" +PERSON_WITH_VEIL_LIGHT_SKIN_TONE = "\U0001f470\U0001f3fb" +PERSON_WITH_VEIL_MEDIUM_LIGHT_SKIN_TONE = "\U0001f470\U0001f3fc" +PERSON_WITH_VEIL_MEDIUM_SKIN_TONE = "\U0001f470\U0001f3fd" +PERSON_WITH_VEIL_MEDIUM_DARK_SKIN_TONE = "\U0001f470\U0001f3fe" +PERSON_WITH_VEIL_DARK_SKIN_TONE = "\U0001f470\U0001f3ff" +MAN_WITH_VEIL = "\U0001f470\u200d\u2642\ufe0f" +MAN_WITH_VEIL_LIGHT_SKIN_TONE = "\U0001f470\U0001f3fb\u200d\u2642\ufe0f" +MAN_WITH_VEIL_MEDIUM_LIGHT_SKIN_TONE = "\U0001f470\U0001f3fc\u200d\u2642\ufe0f" +MAN_WITH_VEIL_MEDIUM_SKIN_TONE = "\U0001f470\U0001f3fd\u200d\u2642\ufe0f" +MAN_WITH_VEIL_MEDIUM_DARK_SKIN_TONE = "\U0001f470\U0001f3fe\u200d\u2642\ufe0f" +MAN_WITH_VEIL_DARK_SKIN_TONE = "\U0001f470\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_WITH_VEIL = "\U0001f470\u200d\u2640\ufe0f" +WOMAN_WITH_VEIL_LIGHT_SKIN_TONE = "\U0001f470\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_WITH_VEIL_MEDIUM_LIGHT_SKIN_TONE = "\U0001f470\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_WITH_VEIL_MEDIUM_SKIN_TONE = "\U0001f470\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_WITH_VEIL_MEDIUM_DARK_SKIN_TONE = "\U0001f470\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_WITH_VEIL_DARK_SKIN_TONE = "\U0001f470\U0001f3ff\u200d\u2640\ufe0f" +PREGNANT_WOMAN = "\U0001f930" +PREGNANT_WOMAN_LIGHT_SKIN_TONE = "\U0001f930\U0001f3fb" +PREGNANT_WOMAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f930\U0001f3fc" +PREGNANT_WOMAN_MEDIUM_SKIN_TONE = "\U0001f930\U0001f3fd" +PREGNANT_WOMAN_MEDIUM_DARK_SKIN_TONE = "\U0001f930\U0001f3fe" +PREGNANT_WOMAN_DARK_SKIN_TONE = "\U0001f930\U0001f3ff" +BREAST_FEEDING = "\U0001f931" +BREAST_FEEDING_LIGHT_SKIN_TONE = "\U0001f931\U0001f3fb" +BREAST_FEEDING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f931\U0001f3fc" +BREAST_FEEDING_MEDIUM_SKIN_TONE = "\U0001f931\U0001f3fd" +BREAST_FEEDING_MEDIUM_DARK_SKIN_TONE = "\U0001f931\U0001f3fe" +BREAST_FEEDING_DARK_SKIN_TONE = "\U0001f931\U0001f3ff" +WOMAN_FEEDING_BABY = "\U0001f469\u200d\U0001f37c" +WOMAN_FEEDING_BABY_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f37c" +WOMAN_FEEDING_BABY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f37c" +WOMAN_FEEDING_BABY_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f37c" +WOMAN_FEEDING_BABY_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f37c" +WOMAN_FEEDING_BABY_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f37c" +MAN_FEEDING_BABY = "\U0001f468\u200d\U0001f37c" +MAN_FEEDING_BABY_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f37c" +MAN_FEEDING_BABY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f37c" +MAN_FEEDING_BABY_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f37c" +MAN_FEEDING_BABY_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f37c" +MAN_FEEDING_BABY_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f37c" +PERSON_FEEDING_BABY = "\U0001f9d1\u200d\U0001f37c" +PERSON_FEEDING_BABY_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fb\u200d\U0001f37c" +PERSON_FEEDING_BABY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fc\u200d\U0001f37c" +PERSON_FEEDING_BABY_MEDIUM_SKIN_TONE = "\U0001f9d1\U0001f3fd\u200d\U0001f37c" +PERSON_FEEDING_BABY_MEDIUM_DARK_SKIN_TONE = "\U0001f9d1\U0001f3fe\u200d\U0001f37c" +PERSON_FEEDING_BABY_DARK_SKIN_TONE = "\U0001f9d1\U0001f3ff\u200d\U0001f37c" +BABY_ANGEL = "\U0001f47c" +BABY_ANGEL_LIGHT_SKIN_TONE = "\U0001f47c\U0001f3fb" +BABY_ANGEL_MEDIUM_LIGHT_SKIN_TONE = "\U0001f47c\U0001f3fc" +BABY_ANGEL_MEDIUM_SKIN_TONE = "\U0001f47c\U0001f3fd" +BABY_ANGEL_MEDIUM_DARK_SKIN_TONE = "\U0001f47c\U0001f3fe" +BABY_ANGEL_DARK_SKIN_TONE = "\U0001f47c\U0001f3ff" +SANTA_CLAUS = "\U0001f385" +SANTA_CLAUS_LIGHT_SKIN_TONE = "\U0001f385\U0001f3fb" +SANTA_CLAUS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f385\U0001f3fc" +SANTA_CLAUS_MEDIUM_SKIN_TONE = "\U0001f385\U0001f3fd" +SANTA_CLAUS_MEDIUM_DARK_SKIN_TONE = "\U0001f385\U0001f3fe" +SANTA_CLAUS_DARK_SKIN_TONE = "\U0001f385\U0001f3ff" +MRS_CLAUS = "\U0001f936" +MRS_CLAUS_LIGHT_SKIN_TONE = "\U0001f936\U0001f3fb" +MRS_CLAUS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f936\U0001f3fc" +MRS_CLAUS_MEDIUM_SKIN_TONE = "\U0001f936\U0001f3fd" +MRS_CLAUS_MEDIUM_DARK_SKIN_TONE = "\U0001f936\U0001f3fe" +MRS_CLAUS_DARK_SKIN_TONE = "\U0001f936\U0001f3ff" +MX_CLAUS = "\U0001f9d1\u200d\U0001f384" +MX_CLAUS_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fb\u200d\U0001f384" +MX_CLAUS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fc\u200d\U0001f384" +MX_CLAUS_MEDIUM_SKIN_TONE = "\U0001f9d1\U0001f3fd\u200d\U0001f384" +MX_CLAUS_MEDIUM_DARK_SKIN_TONE = "\U0001f9d1\U0001f3fe\u200d\U0001f384" +MX_CLAUS_DARK_SKIN_TONE = "\U0001f9d1\U0001f3ff\u200d\U0001f384" +SUPERHERO = "\U0001f9b8" +SUPERHERO_LIGHT_SKIN_TONE = "\U0001f9b8\U0001f3fb" +SUPERHERO_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9b8\U0001f3fc" +SUPERHERO_MEDIUM_SKIN_TONE = "\U0001f9b8\U0001f3fd" +SUPERHERO_MEDIUM_DARK_SKIN_TONE = "\U0001f9b8\U0001f3fe" +SUPERHERO_DARK_SKIN_TONE = "\U0001f9b8\U0001f3ff" +MAN_SUPERHERO = "\U0001f9b8\u200d\u2642\ufe0f" +MAN_SUPERHERO_LIGHT_SKIN_TONE = "\U0001f9b8\U0001f3fb\u200d\u2642\ufe0f" +MAN_SUPERHERO_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9b8\U0001f3fc\u200d\u2642\ufe0f" +MAN_SUPERHERO_MEDIUM_SKIN_TONE = "\U0001f9b8\U0001f3fd\u200d\u2642\ufe0f" +MAN_SUPERHERO_MEDIUM_DARK_SKIN_TONE = "\U0001f9b8\U0001f3fe\u200d\u2642\ufe0f" +MAN_SUPERHERO_DARK_SKIN_TONE = "\U0001f9b8\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_SUPERHERO = "\U0001f9b8\u200d\u2640\ufe0f" +WOMAN_SUPERHERO_LIGHT_SKIN_TONE = "\U0001f9b8\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_SUPERHERO_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9b8\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_SUPERHERO_MEDIUM_SKIN_TONE = "\U0001f9b8\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_SUPERHERO_MEDIUM_DARK_SKIN_TONE = "\U0001f9b8\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_SUPERHERO_DARK_SKIN_TONE = "\U0001f9b8\U0001f3ff\u200d\u2640\ufe0f" +SUPERVILLAIN = "\U0001f9b9" +SUPERVILLAIN_LIGHT_SKIN_TONE = "\U0001f9b9\U0001f3fb" +SUPERVILLAIN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9b9\U0001f3fc" +SUPERVILLAIN_MEDIUM_SKIN_TONE = "\U0001f9b9\U0001f3fd" +SUPERVILLAIN_MEDIUM_DARK_SKIN_TONE = "\U0001f9b9\U0001f3fe" +SUPERVILLAIN_DARK_SKIN_TONE = "\U0001f9b9\U0001f3ff" +MAN_SUPERVILLAIN = "\U0001f9b9\u200d\u2642\ufe0f" +MAN_SUPERVILLAIN_LIGHT_SKIN_TONE = "\U0001f9b9\U0001f3fb\u200d\u2642\ufe0f" +MAN_SUPERVILLAIN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9b9\U0001f3fc\u200d\u2642\ufe0f" +MAN_SUPERVILLAIN_MEDIUM_SKIN_TONE = "\U0001f9b9\U0001f3fd\u200d\u2642\ufe0f" +MAN_SUPERVILLAIN_MEDIUM_DARK_SKIN_TONE = "\U0001f9b9\U0001f3fe\u200d\u2642\ufe0f" +MAN_SUPERVILLAIN_DARK_SKIN_TONE = "\U0001f9b9\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_SUPERVILLAIN = "\U0001f9b9\u200d\u2640\ufe0f" +WOMAN_SUPERVILLAIN_LIGHT_SKIN_TONE = "\U0001f9b9\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_SUPERVILLAIN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9b9\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_SUPERVILLAIN_MEDIUM_SKIN_TONE = "\U0001f9b9\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_SUPERVILLAIN_MEDIUM_DARK_SKIN_TONE = "\U0001f9b9\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_SUPERVILLAIN_DARK_SKIN_TONE = "\U0001f9b9\U0001f3ff\u200d\u2640\ufe0f" +MAGE = "\U0001f9d9" +MAGE_LIGHT_SKIN_TONE = "\U0001f9d9\U0001f3fb" +MAGE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d9\U0001f3fc" +MAGE_MEDIUM_SKIN_TONE = "\U0001f9d9\U0001f3fd" +MAGE_MEDIUM_DARK_SKIN_TONE = "\U0001f9d9\U0001f3fe" +MAGE_DARK_SKIN_TONE = "\U0001f9d9\U0001f3ff" +MAN_MAGE = "\U0001f9d9\u200d\u2642\ufe0f" +MAN_MAGE_LIGHT_SKIN_TONE = "\U0001f9d9\U0001f3fb\u200d\u2642\ufe0f" +MAN_MAGE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d9\U0001f3fc\u200d\u2642\ufe0f" +MAN_MAGE_MEDIUM_SKIN_TONE = "\U0001f9d9\U0001f3fd\u200d\u2642\ufe0f" +MAN_MAGE_MEDIUM_DARK_SKIN_TONE = "\U0001f9d9\U0001f3fe\u200d\u2642\ufe0f" +MAN_MAGE_DARK_SKIN_TONE = "\U0001f9d9\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_MAGE = "\U0001f9d9\u200d\u2640\ufe0f" +WOMAN_MAGE_LIGHT_SKIN_TONE = "\U0001f9d9\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_MAGE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d9\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_MAGE_MEDIUM_SKIN_TONE = "\U0001f9d9\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_MAGE_MEDIUM_DARK_SKIN_TONE = "\U0001f9d9\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_MAGE_DARK_SKIN_TONE = "\U0001f9d9\U0001f3ff\u200d\u2640\ufe0f" +FAIRY = "\U0001f9da" +FAIRY_LIGHT_SKIN_TONE = "\U0001f9da\U0001f3fb" +FAIRY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9da\U0001f3fc" +FAIRY_MEDIUM_SKIN_TONE = "\U0001f9da\U0001f3fd" +FAIRY_MEDIUM_DARK_SKIN_TONE = "\U0001f9da\U0001f3fe" +FAIRY_DARK_SKIN_TONE = "\U0001f9da\U0001f3ff" +MAN_FAIRY = "\U0001f9da\u200d\u2642\ufe0f" +MAN_FAIRY_LIGHT_SKIN_TONE = "\U0001f9da\U0001f3fb\u200d\u2642\ufe0f" +MAN_FAIRY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9da\U0001f3fc\u200d\u2642\ufe0f" +MAN_FAIRY_MEDIUM_SKIN_TONE = "\U0001f9da\U0001f3fd\u200d\u2642\ufe0f" +MAN_FAIRY_MEDIUM_DARK_SKIN_TONE = "\U0001f9da\U0001f3fe\u200d\u2642\ufe0f" +MAN_FAIRY_DARK_SKIN_TONE = "\U0001f9da\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_FAIRY = "\U0001f9da\u200d\u2640\ufe0f" +WOMAN_FAIRY_LIGHT_SKIN_TONE = "\U0001f9da\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_FAIRY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9da\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_FAIRY_MEDIUM_SKIN_TONE = "\U0001f9da\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_FAIRY_MEDIUM_DARK_SKIN_TONE = "\U0001f9da\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_FAIRY_DARK_SKIN_TONE = "\U0001f9da\U0001f3ff\u200d\u2640\ufe0f" +VAMPIRE = "\U0001f9db" +VAMPIRE_LIGHT_SKIN_TONE = "\U0001f9db\U0001f3fb" +VAMPIRE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9db\U0001f3fc" +VAMPIRE_MEDIUM_SKIN_TONE = "\U0001f9db\U0001f3fd" +VAMPIRE_MEDIUM_DARK_SKIN_TONE = "\U0001f9db\U0001f3fe" +VAMPIRE_DARK_SKIN_TONE = "\U0001f9db\U0001f3ff" +MAN_VAMPIRE = "\U0001f9db\u200d\u2642\ufe0f" +MAN_VAMPIRE_LIGHT_SKIN_TONE = "\U0001f9db\U0001f3fb\u200d\u2642\ufe0f" +MAN_VAMPIRE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9db\U0001f3fc\u200d\u2642\ufe0f" +MAN_VAMPIRE_MEDIUM_SKIN_TONE = "\U0001f9db\U0001f3fd\u200d\u2642\ufe0f" +MAN_VAMPIRE_MEDIUM_DARK_SKIN_TONE = "\U0001f9db\U0001f3fe\u200d\u2642\ufe0f" +MAN_VAMPIRE_DARK_SKIN_TONE = "\U0001f9db\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_VAMPIRE = "\U0001f9db\u200d\u2640\ufe0f" +WOMAN_VAMPIRE_LIGHT_SKIN_TONE = "\U0001f9db\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_VAMPIRE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9db\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_VAMPIRE_MEDIUM_SKIN_TONE = "\U0001f9db\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_VAMPIRE_MEDIUM_DARK_SKIN_TONE = "\U0001f9db\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_VAMPIRE_DARK_SKIN_TONE = "\U0001f9db\U0001f3ff\u200d\u2640\ufe0f" +MERPERSON = "\U0001f9dc" +MERPERSON_LIGHT_SKIN_TONE = "\U0001f9dc\U0001f3fb" +MERPERSON_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9dc\U0001f3fc" +MERPERSON_MEDIUM_SKIN_TONE = "\U0001f9dc\U0001f3fd" +MERPERSON_MEDIUM_DARK_SKIN_TONE = "\U0001f9dc\U0001f3fe" +MERPERSON_DARK_SKIN_TONE = "\U0001f9dc\U0001f3ff" +MERMAN = "\U0001f9dc\u200d\u2642\ufe0f" +MERMAN_LIGHT_SKIN_TONE = "\U0001f9dc\U0001f3fb\u200d\u2642\ufe0f" +MERMAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9dc\U0001f3fc\u200d\u2642\ufe0f" +MERMAN_MEDIUM_SKIN_TONE = "\U0001f9dc\U0001f3fd\u200d\u2642\ufe0f" +MERMAN_MEDIUM_DARK_SKIN_TONE = "\U0001f9dc\U0001f3fe\u200d\u2642\ufe0f" +MERMAN_DARK_SKIN_TONE = "\U0001f9dc\U0001f3ff\u200d\u2642\ufe0f" +MERMAID = "\U0001f9dc\u200d\u2640\ufe0f" +MERMAID_LIGHT_SKIN_TONE = "\U0001f9dc\U0001f3fb\u200d\u2640\ufe0f" +MERMAID_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9dc\U0001f3fc\u200d\u2640\ufe0f" +MERMAID_MEDIUM_SKIN_TONE = "\U0001f9dc\U0001f3fd\u200d\u2640\ufe0f" +MERMAID_MEDIUM_DARK_SKIN_TONE = "\U0001f9dc\U0001f3fe\u200d\u2640\ufe0f" +MERMAID_DARK_SKIN_TONE = "\U0001f9dc\U0001f3ff\u200d\u2640\ufe0f" +ELF = "\U0001f9dd" +ELF_LIGHT_SKIN_TONE = "\U0001f9dd\U0001f3fb" +ELF_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9dd\U0001f3fc" +ELF_MEDIUM_SKIN_TONE = "\U0001f9dd\U0001f3fd" +ELF_MEDIUM_DARK_SKIN_TONE = "\U0001f9dd\U0001f3fe" +ELF_DARK_SKIN_TONE = "\U0001f9dd\U0001f3ff" +MAN_ELF = "\U0001f9dd\u200d\u2642\ufe0f" +MAN_ELF_LIGHT_SKIN_TONE = "\U0001f9dd\U0001f3fb\u200d\u2642\ufe0f" +MAN_ELF_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9dd\U0001f3fc\u200d\u2642\ufe0f" +MAN_ELF_MEDIUM_SKIN_TONE = "\U0001f9dd\U0001f3fd\u200d\u2642\ufe0f" +MAN_ELF_MEDIUM_DARK_SKIN_TONE = "\U0001f9dd\U0001f3fe\u200d\u2642\ufe0f" +MAN_ELF_DARK_SKIN_TONE = "\U0001f9dd\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_ELF = "\U0001f9dd\u200d\u2640\ufe0f" +WOMAN_ELF_LIGHT_SKIN_TONE = "\U0001f9dd\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_ELF_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9dd\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_ELF_MEDIUM_SKIN_TONE = "\U0001f9dd\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_ELF_MEDIUM_DARK_SKIN_TONE = "\U0001f9dd\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_ELF_DARK_SKIN_TONE = "\U0001f9dd\U0001f3ff\u200d\u2640\ufe0f" +GENIE = "\U0001f9de" +MAN_GENIE = "\U0001f9de\u200d\u2642\ufe0f" +WOMAN_GENIE = "\U0001f9de\u200d\u2640\ufe0f" +ZOMBIE = "\U0001f9df" +MAN_ZOMBIE = "\U0001f9df\u200d\u2642\ufe0f" +WOMAN_ZOMBIE = "\U0001f9df\u200d\u2640\ufe0f" +PERSON_GETTING_MASSAGE = "\U0001f486" +PERSON_GETTING_MASSAGE_LIGHT_SKIN_TONE = "\U0001f486\U0001f3fb" +PERSON_GETTING_MASSAGE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f486\U0001f3fc" +PERSON_GETTING_MASSAGE_MEDIUM_SKIN_TONE = "\U0001f486\U0001f3fd" +PERSON_GETTING_MASSAGE_MEDIUM_DARK_SKIN_TONE = "\U0001f486\U0001f3fe" +PERSON_GETTING_MASSAGE_DARK_SKIN_TONE = "\U0001f486\U0001f3ff" +MAN_GETTING_MASSAGE = "\U0001f486\u200d\u2642\ufe0f" +MAN_GETTING_MASSAGE_LIGHT_SKIN_TONE = "\U0001f486\U0001f3fb\u200d\u2642\ufe0f" +MAN_GETTING_MASSAGE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f486\U0001f3fc\u200d\u2642\ufe0f" +MAN_GETTING_MASSAGE_MEDIUM_SKIN_TONE = "\U0001f486\U0001f3fd\u200d\u2642\ufe0f" +MAN_GETTING_MASSAGE_MEDIUM_DARK_SKIN_TONE = "\U0001f486\U0001f3fe\u200d\u2642\ufe0f" +MAN_GETTING_MASSAGE_DARK_SKIN_TONE = "\U0001f486\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_GETTING_MASSAGE = "\U0001f486\u200d\u2640\ufe0f" +WOMAN_GETTING_MASSAGE_LIGHT_SKIN_TONE = "\U0001f486\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_GETTING_MASSAGE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f486\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_GETTING_MASSAGE_MEDIUM_SKIN_TONE = "\U0001f486\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_GETTING_MASSAGE_MEDIUM_DARK_SKIN_TONE = "\U0001f486\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_GETTING_MASSAGE_DARK_SKIN_TONE = "\U0001f486\U0001f3ff\u200d\u2640\ufe0f" +PERSON_GETTING_HAIRCUT = "\U0001f487" +PERSON_GETTING_HAIRCUT_LIGHT_SKIN_TONE = "\U0001f487\U0001f3fb" +PERSON_GETTING_HAIRCUT_MEDIUM_LIGHT_SKIN_TONE = "\U0001f487\U0001f3fc" +PERSON_GETTING_HAIRCUT_MEDIUM_SKIN_TONE = "\U0001f487\U0001f3fd" +PERSON_GETTING_HAIRCUT_MEDIUM_DARK_SKIN_TONE = "\U0001f487\U0001f3fe" +PERSON_GETTING_HAIRCUT_DARK_SKIN_TONE = "\U0001f487\U0001f3ff" +MAN_GETTING_HAIRCUT = "\U0001f487\u200d\u2642\ufe0f" +MAN_GETTING_HAIRCUT_LIGHT_SKIN_TONE = "\U0001f487\U0001f3fb\u200d\u2642\ufe0f" +MAN_GETTING_HAIRCUT_MEDIUM_LIGHT_SKIN_TONE = "\U0001f487\U0001f3fc\u200d\u2642\ufe0f" +MAN_GETTING_HAIRCUT_MEDIUM_SKIN_TONE = "\U0001f487\U0001f3fd\u200d\u2642\ufe0f" +MAN_GETTING_HAIRCUT_MEDIUM_DARK_SKIN_TONE = "\U0001f487\U0001f3fe\u200d\u2642\ufe0f" +MAN_GETTING_HAIRCUT_DARK_SKIN_TONE = "\U0001f487\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_GETTING_HAIRCUT = "\U0001f487\u200d\u2640\ufe0f" +WOMAN_GETTING_HAIRCUT_LIGHT_SKIN_TONE = "\U0001f487\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_GETTING_HAIRCUT_MEDIUM_LIGHT_SKIN_TONE = "\U0001f487\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_GETTING_HAIRCUT_MEDIUM_SKIN_TONE = "\U0001f487\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_GETTING_HAIRCUT_MEDIUM_DARK_SKIN_TONE = "\U0001f487\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_GETTING_HAIRCUT_DARK_SKIN_TONE = "\U0001f487\U0001f3ff\u200d\u2640\ufe0f" +PERSON_WALKING = "\U0001f6b6" +PERSON_WALKING_LIGHT_SKIN_TONE = "\U0001f6b6\U0001f3fb" +PERSON_WALKING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f6b6\U0001f3fc" +PERSON_WALKING_MEDIUM_SKIN_TONE = "\U0001f6b6\U0001f3fd" +PERSON_WALKING_MEDIUM_DARK_SKIN_TONE = "\U0001f6b6\U0001f3fe" +PERSON_WALKING_DARK_SKIN_TONE = "\U0001f6b6\U0001f3ff" +MAN_WALKING = "\U0001f6b6\u200d\u2642\ufe0f" +MAN_WALKING_LIGHT_SKIN_TONE = "\U0001f6b6\U0001f3fb\u200d\u2642\ufe0f" +MAN_WALKING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f6b6\U0001f3fc\u200d\u2642\ufe0f" +MAN_WALKING_MEDIUM_SKIN_TONE = "\U0001f6b6\U0001f3fd\u200d\u2642\ufe0f" +MAN_WALKING_MEDIUM_DARK_SKIN_TONE = "\U0001f6b6\U0001f3fe\u200d\u2642\ufe0f" +MAN_WALKING_DARK_SKIN_TONE = "\U0001f6b6\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_WALKING = "\U0001f6b6\u200d\u2640\ufe0f" +WOMAN_WALKING_LIGHT_SKIN_TONE = "\U0001f6b6\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_WALKING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f6b6\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_WALKING_MEDIUM_SKIN_TONE = "\U0001f6b6\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_WALKING_MEDIUM_DARK_SKIN_TONE = "\U0001f6b6\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_WALKING_DARK_SKIN_TONE = "\U0001f6b6\U0001f3ff\u200d\u2640\ufe0f" +PERSON_STANDING = "\U0001f9cd" +PERSON_STANDING_LIGHT_SKIN_TONE = "\U0001f9cd\U0001f3fb" +PERSON_STANDING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9cd\U0001f3fc" +PERSON_STANDING_MEDIUM_SKIN_TONE = "\U0001f9cd\U0001f3fd" +PERSON_STANDING_MEDIUM_DARK_SKIN_TONE = "\U0001f9cd\U0001f3fe" +PERSON_STANDING_DARK_SKIN_TONE = "\U0001f9cd\U0001f3ff" +MAN_STANDING = "\U0001f9cd\u200d\u2642\ufe0f" +MAN_STANDING_LIGHT_SKIN_TONE = "\U0001f9cd\U0001f3fb\u200d\u2642\ufe0f" +MAN_STANDING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9cd\U0001f3fc\u200d\u2642\ufe0f" +MAN_STANDING_MEDIUM_SKIN_TONE = "\U0001f9cd\U0001f3fd\u200d\u2642\ufe0f" +MAN_STANDING_MEDIUM_DARK_SKIN_TONE = "\U0001f9cd\U0001f3fe\u200d\u2642\ufe0f" +MAN_STANDING_DARK_SKIN_TONE = "\U0001f9cd\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_STANDING = "\U0001f9cd\u200d\u2640\ufe0f" +WOMAN_STANDING_LIGHT_SKIN_TONE = "\U0001f9cd\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_STANDING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9cd\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_STANDING_MEDIUM_SKIN_TONE = "\U0001f9cd\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_STANDING_MEDIUM_DARK_SKIN_TONE = "\U0001f9cd\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_STANDING_DARK_SKIN_TONE = "\U0001f9cd\U0001f3ff\u200d\u2640\ufe0f" +PERSON_KNEELING = "\U0001f9ce" +PERSON_KNEELING_LIGHT_SKIN_TONE = "\U0001f9ce\U0001f3fb" +PERSON_KNEELING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9ce\U0001f3fc" +PERSON_KNEELING_MEDIUM_SKIN_TONE = "\U0001f9ce\U0001f3fd" +PERSON_KNEELING_MEDIUM_DARK_SKIN_TONE = "\U0001f9ce\U0001f3fe" +PERSON_KNEELING_DARK_SKIN_TONE = "\U0001f9ce\U0001f3ff" +MAN_KNEELING = "\U0001f9ce\u200d\u2642\ufe0f" +MAN_KNEELING_LIGHT_SKIN_TONE = "\U0001f9ce\U0001f3fb\u200d\u2642\ufe0f" +MAN_KNEELING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9ce\U0001f3fc\u200d\u2642\ufe0f" +MAN_KNEELING_MEDIUM_SKIN_TONE = "\U0001f9ce\U0001f3fd\u200d\u2642\ufe0f" +MAN_KNEELING_MEDIUM_DARK_SKIN_TONE = "\U0001f9ce\U0001f3fe\u200d\u2642\ufe0f" +MAN_KNEELING_DARK_SKIN_TONE = "\U0001f9ce\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_KNEELING = "\U0001f9ce\u200d\u2640\ufe0f" +WOMAN_KNEELING_LIGHT_SKIN_TONE = "\U0001f9ce\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_KNEELING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9ce\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_KNEELING_MEDIUM_SKIN_TONE = "\U0001f9ce\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_KNEELING_MEDIUM_DARK_SKIN_TONE = "\U0001f9ce\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_KNEELING_DARK_SKIN_TONE = "\U0001f9ce\U0001f3ff\u200d\u2640\ufe0f" +PERSON_WITH_WHITE_CANE = "\U0001f9d1\u200d\U0001f9af" +PERSON_WITH_WHITE_CANE_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fb\u200d\U0001f9af" +PERSON_WITH_WHITE_CANE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fc\u200d\U0001f9af" +PERSON_WITH_WHITE_CANE_MEDIUM_SKIN_TONE = "\U0001f9d1\U0001f3fd\u200d\U0001f9af" +PERSON_WITH_WHITE_CANE_MEDIUM_DARK_SKIN_TONE = "\U0001f9d1\U0001f3fe\u200d\U0001f9af" +PERSON_WITH_WHITE_CANE_DARK_SKIN_TONE = "\U0001f9d1\U0001f3ff\u200d\U0001f9af" +MAN_WITH_WHITE_CANE = "\U0001f468\u200d\U0001f9af" +MAN_WITH_WHITE_CANE_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f9af" +MAN_WITH_WHITE_CANE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f9af" +MAN_WITH_WHITE_CANE_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f9af" +MAN_WITH_WHITE_CANE_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f9af" +MAN_WITH_WHITE_CANE_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f9af" +WOMAN_WITH_WHITE_CANE = "\U0001f469\u200d\U0001f9af" +WOMAN_WITH_WHITE_CANE_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f9af" +WOMAN_WITH_WHITE_CANE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f9af" +WOMAN_WITH_WHITE_CANE_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f9af" +WOMAN_WITH_WHITE_CANE_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f9af" +WOMAN_WITH_WHITE_CANE_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f9af" +PERSON_IN_MOTORIZED_WHEELCHAIR = "\U0001f9d1\u200d\U0001f9bc" +PERSON_IN_MOTORIZED_WHEELCHAIR_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fb\u200d\U0001f9bc" +PERSON_IN_MOTORIZED_WHEELCHAIR_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fc\u200d\U0001f9bc" +PERSON_IN_MOTORIZED_WHEELCHAIR_MEDIUM_SKIN_TONE = "\U0001f9d1\U0001f3fd\u200d\U0001f9bc" +PERSON_IN_MOTORIZED_WHEELCHAIR_MEDIUM_DARK_SKIN_TONE = "\U0001f9d1\U0001f3fe\u200d\U0001f9bc" +PERSON_IN_MOTORIZED_WHEELCHAIR_DARK_SKIN_TONE = "\U0001f9d1\U0001f3ff\u200d\U0001f9bc" +MAN_IN_MOTORIZED_WHEELCHAIR = "\U0001f468\u200d\U0001f9bc" +MAN_IN_MOTORIZED_WHEELCHAIR_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f9bc" +MAN_IN_MOTORIZED_WHEELCHAIR_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f9bc" +MAN_IN_MOTORIZED_WHEELCHAIR_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f9bc" +MAN_IN_MOTORIZED_WHEELCHAIR_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f9bc" +MAN_IN_MOTORIZED_WHEELCHAIR_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f9bc" +WOMAN_IN_MOTORIZED_WHEELCHAIR = "\U0001f469\u200d\U0001f9bc" +WOMAN_IN_MOTORIZED_WHEELCHAIR_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f9bc" +WOMAN_IN_MOTORIZED_WHEELCHAIR_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f9bc" +WOMAN_IN_MOTORIZED_WHEELCHAIR_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f9bc" +WOMAN_IN_MOTORIZED_WHEELCHAIR_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f9bc" +WOMAN_IN_MOTORIZED_WHEELCHAIR_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f9bc" +PERSON_IN_MANUAL_WHEELCHAIR = "\U0001f9d1\u200d\U0001f9bd" +PERSON_IN_MANUAL_WHEELCHAIR_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fb\u200d\U0001f9bd" +PERSON_IN_MANUAL_WHEELCHAIR_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fc\u200d\U0001f9bd" +PERSON_IN_MANUAL_WHEELCHAIR_MEDIUM_SKIN_TONE = "\U0001f9d1\U0001f3fd\u200d\U0001f9bd" +PERSON_IN_MANUAL_WHEELCHAIR_MEDIUM_DARK_SKIN_TONE = "\U0001f9d1\U0001f3fe\u200d\U0001f9bd" +PERSON_IN_MANUAL_WHEELCHAIR_DARK_SKIN_TONE = "\U0001f9d1\U0001f3ff\u200d\U0001f9bd" +MAN_IN_MANUAL_WHEELCHAIR = "\U0001f468\u200d\U0001f9bd" +MAN_IN_MANUAL_WHEELCHAIR_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f9bd" +MAN_IN_MANUAL_WHEELCHAIR_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\U0001f9bd" +MAN_IN_MANUAL_WHEELCHAIR_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f9bd" +MAN_IN_MANUAL_WHEELCHAIR_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\U0001f9bd" +MAN_IN_MANUAL_WHEELCHAIR_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f9bd" +WOMAN_IN_MANUAL_WHEELCHAIR = "\U0001f469\u200d\U0001f9bd" +WOMAN_IN_MANUAL_WHEELCHAIR_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f9bd" +WOMAN_IN_MANUAL_WHEELCHAIR_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\U0001f9bd" +WOMAN_IN_MANUAL_WHEELCHAIR_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f9bd" +WOMAN_IN_MANUAL_WHEELCHAIR_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\U0001f9bd" +WOMAN_IN_MANUAL_WHEELCHAIR_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f9bd" +PERSON_RUNNING = "\U0001f3c3" +PERSON_RUNNING_LIGHT_SKIN_TONE = "\U0001f3c3\U0001f3fb" +PERSON_RUNNING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f3c3\U0001f3fc" +PERSON_RUNNING_MEDIUM_SKIN_TONE = "\U0001f3c3\U0001f3fd" +PERSON_RUNNING_MEDIUM_DARK_SKIN_TONE = "\U0001f3c3\U0001f3fe" +PERSON_RUNNING_DARK_SKIN_TONE = "\U0001f3c3\U0001f3ff" +MAN_RUNNING = "\U0001f3c3\u200d\u2642\ufe0f" +MAN_RUNNING_LIGHT_SKIN_TONE = "\U0001f3c3\U0001f3fb\u200d\u2642\ufe0f" +MAN_RUNNING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f3c3\U0001f3fc\u200d\u2642\ufe0f" +MAN_RUNNING_MEDIUM_SKIN_TONE = "\U0001f3c3\U0001f3fd\u200d\u2642\ufe0f" +MAN_RUNNING_MEDIUM_DARK_SKIN_TONE = "\U0001f3c3\U0001f3fe\u200d\u2642\ufe0f" +MAN_RUNNING_DARK_SKIN_TONE = "\U0001f3c3\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_RUNNING = "\U0001f3c3\u200d\u2640\ufe0f" +WOMAN_RUNNING_LIGHT_SKIN_TONE = "\U0001f3c3\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_RUNNING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f3c3\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_RUNNING_MEDIUM_SKIN_TONE = "\U0001f3c3\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_RUNNING_MEDIUM_DARK_SKIN_TONE = "\U0001f3c3\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_RUNNING_DARK_SKIN_TONE = "\U0001f3c3\U0001f3ff\u200d\u2640\ufe0f" +WOMAN_DANCING = "\U0001f483" +WOMAN_DANCING_LIGHT_SKIN_TONE = "\U0001f483\U0001f3fb" +WOMAN_DANCING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f483\U0001f3fc" +WOMAN_DANCING_MEDIUM_SKIN_TONE = "\U0001f483\U0001f3fd" +WOMAN_DANCING_MEDIUM_DARK_SKIN_TONE = "\U0001f483\U0001f3fe" +WOMAN_DANCING_DARK_SKIN_TONE = "\U0001f483\U0001f3ff" +MAN_DANCING = "\U0001f57a" +MAN_DANCING_LIGHT_SKIN_TONE = "\U0001f57a\U0001f3fb" +MAN_DANCING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f57a\U0001f3fc" +MAN_DANCING_MEDIUM_SKIN_TONE = "\U0001f57a\U0001f3fd" +MAN_DANCING_MEDIUM_DARK_SKIN_TONE = "\U0001f57a\U0001f3fe" +MAN_DANCING_DARK_SKIN_TONE = "\U0001f57a\U0001f3ff" +PERSON_IN_SUIT_LEVITATING = "\U0001f574\ufe0f" +PERSON_IN_SUIT_LEVITATING_LIGHT_SKIN_TONE = "\U0001f574\U0001f3fb" +PERSON_IN_SUIT_LEVITATING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f574\U0001f3fc" +PERSON_IN_SUIT_LEVITATING_MEDIUM_SKIN_TONE = "\U0001f574\U0001f3fd" +PERSON_IN_SUIT_LEVITATING_MEDIUM_DARK_SKIN_TONE = "\U0001f574\U0001f3fe" +PERSON_IN_SUIT_LEVITATING_DARK_SKIN_TONE = "\U0001f574\U0001f3ff" +PEOPLE_WITH_BUNNY_EARS = "\U0001f46f" +MEN_WITH_BUNNY_EARS = "\U0001f46f\u200d\u2642\ufe0f" +WOMEN_WITH_BUNNY_EARS = "\U0001f46f\u200d\u2640\ufe0f" +PERSON_IN_STEAMY_ROOM = "\U0001f9d6" +PERSON_IN_STEAMY_ROOM_LIGHT_SKIN_TONE = "\U0001f9d6\U0001f3fb" +PERSON_IN_STEAMY_ROOM_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d6\U0001f3fc" +PERSON_IN_STEAMY_ROOM_MEDIUM_SKIN_TONE = "\U0001f9d6\U0001f3fd" +PERSON_IN_STEAMY_ROOM_MEDIUM_DARK_SKIN_TONE = "\U0001f9d6\U0001f3fe" +PERSON_IN_STEAMY_ROOM_DARK_SKIN_TONE = "\U0001f9d6\U0001f3ff" +MAN_IN_STEAMY_ROOM = "\U0001f9d6\u200d\u2642\ufe0f" +MAN_IN_STEAMY_ROOM_LIGHT_SKIN_TONE = "\U0001f9d6\U0001f3fb\u200d\u2642\ufe0f" +MAN_IN_STEAMY_ROOM_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d6\U0001f3fc\u200d\u2642\ufe0f" +MAN_IN_STEAMY_ROOM_MEDIUM_SKIN_TONE = "\U0001f9d6\U0001f3fd\u200d\u2642\ufe0f" +MAN_IN_STEAMY_ROOM_MEDIUM_DARK_SKIN_TONE = "\U0001f9d6\U0001f3fe\u200d\u2642\ufe0f" +MAN_IN_STEAMY_ROOM_DARK_SKIN_TONE = "\U0001f9d6\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_IN_STEAMY_ROOM = "\U0001f9d6\u200d\u2640\ufe0f" +WOMAN_IN_STEAMY_ROOM_LIGHT_SKIN_TONE = "\U0001f9d6\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_IN_STEAMY_ROOM_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d6\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_IN_STEAMY_ROOM_MEDIUM_SKIN_TONE = "\U0001f9d6\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_IN_STEAMY_ROOM_MEDIUM_DARK_SKIN_TONE = "\U0001f9d6\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_IN_STEAMY_ROOM_DARK_SKIN_TONE = "\U0001f9d6\U0001f3ff\u200d\u2640\ufe0f" +PERSON_CLIMBING = "\U0001f9d7" +PERSON_CLIMBING_LIGHT_SKIN_TONE = "\U0001f9d7\U0001f3fb" +PERSON_CLIMBING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d7\U0001f3fc" +PERSON_CLIMBING_MEDIUM_SKIN_TONE = "\U0001f9d7\U0001f3fd" +PERSON_CLIMBING_MEDIUM_DARK_SKIN_TONE = "\U0001f9d7\U0001f3fe" +PERSON_CLIMBING_DARK_SKIN_TONE = "\U0001f9d7\U0001f3ff" +MAN_CLIMBING = "\U0001f9d7\u200d\u2642\ufe0f" +MAN_CLIMBING_LIGHT_SKIN_TONE = "\U0001f9d7\U0001f3fb\u200d\u2642\ufe0f" +MAN_CLIMBING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d7\U0001f3fc\u200d\u2642\ufe0f" +MAN_CLIMBING_MEDIUM_SKIN_TONE = "\U0001f9d7\U0001f3fd\u200d\u2642\ufe0f" +MAN_CLIMBING_MEDIUM_DARK_SKIN_TONE = "\U0001f9d7\U0001f3fe\u200d\u2642\ufe0f" +MAN_CLIMBING_DARK_SKIN_TONE = "\U0001f9d7\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_CLIMBING = "\U0001f9d7\u200d\u2640\ufe0f" +WOMAN_CLIMBING_LIGHT_SKIN_TONE = "\U0001f9d7\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_CLIMBING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d7\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_CLIMBING_MEDIUM_SKIN_TONE = "\U0001f9d7\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_CLIMBING_MEDIUM_DARK_SKIN_TONE = "\U0001f9d7\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_CLIMBING_DARK_SKIN_TONE = "\U0001f9d7\U0001f3ff\u200d\u2640\ufe0f" +PERSON_FENCING = "\U0001f93a" +HORSE_RACING = "\U0001f3c7" +HORSE_RACING_LIGHT_SKIN_TONE = "\U0001f3c7\U0001f3fb" +HORSE_RACING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f3c7\U0001f3fc" +HORSE_RACING_MEDIUM_SKIN_TONE = "\U0001f3c7\U0001f3fd" +HORSE_RACING_MEDIUM_DARK_SKIN_TONE = "\U0001f3c7\U0001f3fe" +HORSE_RACING_DARK_SKIN_TONE = "\U0001f3c7\U0001f3ff" +SKIER = "\u26f7\ufe0f" +SNOWBOARDER = "\U0001f3c2" +SNOWBOARDER_LIGHT_SKIN_TONE = "\U0001f3c2\U0001f3fb" +SNOWBOARDER_MEDIUM_LIGHT_SKIN_TONE = "\U0001f3c2\U0001f3fc" +SNOWBOARDER_MEDIUM_SKIN_TONE = "\U0001f3c2\U0001f3fd" +SNOWBOARDER_MEDIUM_DARK_SKIN_TONE = "\U0001f3c2\U0001f3fe" +SNOWBOARDER_DARK_SKIN_TONE = "\U0001f3c2\U0001f3ff" +PERSON_GOLFING = "\U0001f3cc\ufe0f" +PERSON_GOLFING_LIGHT_SKIN_TONE = "\U0001f3cc\U0001f3fb" +PERSON_GOLFING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f3cc\U0001f3fc" +PERSON_GOLFING_MEDIUM_SKIN_TONE = "\U0001f3cc\U0001f3fd" +PERSON_GOLFING_MEDIUM_DARK_SKIN_TONE = "\U0001f3cc\U0001f3fe" +PERSON_GOLFING_DARK_SKIN_TONE = "\U0001f3cc\U0001f3ff" +MAN_GOLFING = "\U0001f3cc\ufe0f\u200d\u2642\ufe0f" +MAN_GOLFING_LIGHT_SKIN_TONE = "\U0001f3cc\U0001f3fb\u200d\u2642\ufe0f" +MAN_GOLFING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f3cc\U0001f3fc\u200d\u2642\ufe0f" +MAN_GOLFING_MEDIUM_SKIN_TONE = "\U0001f3cc\U0001f3fd\u200d\u2642\ufe0f" +MAN_GOLFING_MEDIUM_DARK_SKIN_TONE = "\U0001f3cc\U0001f3fe\u200d\u2642\ufe0f" +MAN_GOLFING_DARK_SKIN_TONE = "\U0001f3cc\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_GOLFING = "\U0001f3cc\ufe0f\u200d\u2640\ufe0f" +WOMAN_GOLFING_LIGHT_SKIN_TONE = "\U0001f3cc\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_GOLFING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f3cc\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_GOLFING_MEDIUM_SKIN_TONE = "\U0001f3cc\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_GOLFING_MEDIUM_DARK_SKIN_TONE = "\U0001f3cc\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_GOLFING_DARK_SKIN_TONE = "\U0001f3cc\U0001f3ff\u200d\u2640\ufe0f" +PERSON_SURFING = "\U0001f3c4" +PERSON_SURFING_LIGHT_SKIN_TONE = "\U0001f3c4\U0001f3fb" +PERSON_SURFING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f3c4\U0001f3fc" +PERSON_SURFING_MEDIUM_SKIN_TONE = "\U0001f3c4\U0001f3fd" +PERSON_SURFING_MEDIUM_DARK_SKIN_TONE = "\U0001f3c4\U0001f3fe" +PERSON_SURFING_DARK_SKIN_TONE = "\U0001f3c4\U0001f3ff" +MAN_SURFING = "\U0001f3c4\u200d\u2642\ufe0f" +MAN_SURFING_LIGHT_SKIN_TONE = "\U0001f3c4\U0001f3fb\u200d\u2642\ufe0f" +MAN_SURFING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f3c4\U0001f3fc\u200d\u2642\ufe0f" +MAN_SURFING_MEDIUM_SKIN_TONE = "\U0001f3c4\U0001f3fd\u200d\u2642\ufe0f" +MAN_SURFING_MEDIUM_DARK_SKIN_TONE = "\U0001f3c4\U0001f3fe\u200d\u2642\ufe0f" +MAN_SURFING_DARK_SKIN_TONE = "\U0001f3c4\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_SURFING = "\U0001f3c4\u200d\u2640\ufe0f" +WOMAN_SURFING_LIGHT_SKIN_TONE = "\U0001f3c4\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_SURFING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f3c4\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_SURFING_MEDIUM_SKIN_TONE = "\U0001f3c4\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_SURFING_MEDIUM_DARK_SKIN_TONE = "\U0001f3c4\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_SURFING_DARK_SKIN_TONE = "\U0001f3c4\U0001f3ff\u200d\u2640\ufe0f" +PERSON_ROWING_BOAT = "\U0001f6a3" +PERSON_ROWING_BOAT_LIGHT_SKIN_TONE = "\U0001f6a3\U0001f3fb" +PERSON_ROWING_BOAT_MEDIUM_LIGHT_SKIN_TONE = "\U0001f6a3\U0001f3fc" +PERSON_ROWING_BOAT_MEDIUM_SKIN_TONE = "\U0001f6a3\U0001f3fd" +PERSON_ROWING_BOAT_MEDIUM_DARK_SKIN_TONE = "\U0001f6a3\U0001f3fe" +PERSON_ROWING_BOAT_DARK_SKIN_TONE = "\U0001f6a3\U0001f3ff" +MAN_ROWING_BOAT = "\U0001f6a3\u200d\u2642\ufe0f" +MAN_ROWING_BOAT_LIGHT_SKIN_TONE = "\U0001f6a3\U0001f3fb\u200d\u2642\ufe0f" +MAN_ROWING_BOAT_MEDIUM_LIGHT_SKIN_TONE = "\U0001f6a3\U0001f3fc\u200d\u2642\ufe0f" +MAN_ROWING_BOAT_MEDIUM_SKIN_TONE = "\U0001f6a3\U0001f3fd\u200d\u2642\ufe0f" +MAN_ROWING_BOAT_MEDIUM_DARK_SKIN_TONE = "\U0001f6a3\U0001f3fe\u200d\u2642\ufe0f" +MAN_ROWING_BOAT_DARK_SKIN_TONE = "\U0001f6a3\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_ROWING_BOAT = "\U0001f6a3\u200d\u2640\ufe0f" +WOMAN_ROWING_BOAT_LIGHT_SKIN_TONE = "\U0001f6a3\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_ROWING_BOAT_MEDIUM_LIGHT_SKIN_TONE = "\U0001f6a3\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_ROWING_BOAT_MEDIUM_SKIN_TONE = "\U0001f6a3\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_ROWING_BOAT_MEDIUM_DARK_SKIN_TONE = "\U0001f6a3\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_ROWING_BOAT_DARK_SKIN_TONE = "\U0001f6a3\U0001f3ff\u200d\u2640\ufe0f" +PERSON_SWIMMING = "\U0001f3ca" +PERSON_SWIMMING_LIGHT_SKIN_TONE = "\U0001f3ca\U0001f3fb" +PERSON_SWIMMING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f3ca\U0001f3fc" +PERSON_SWIMMING_MEDIUM_SKIN_TONE = "\U0001f3ca\U0001f3fd" +PERSON_SWIMMING_MEDIUM_DARK_SKIN_TONE = "\U0001f3ca\U0001f3fe" +PERSON_SWIMMING_DARK_SKIN_TONE = "\U0001f3ca\U0001f3ff" +MAN_SWIMMING = "\U0001f3ca\u200d\u2642\ufe0f" +MAN_SWIMMING_LIGHT_SKIN_TONE = "\U0001f3ca\U0001f3fb\u200d\u2642\ufe0f" +MAN_SWIMMING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f3ca\U0001f3fc\u200d\u2642\ufe0f" +MAN_SWIMMING_MEDIUM_SKIN_TONE = "\U0001f3ca\U0001f3fd\u200d\u2642\ufe0f" +MAN_SWIMMING_MEDIUM_DARK_SKIN_TONE = "\U0001f3ca\U0001f3fe\u200d\u2642\ufe0f" +MAN_SWIMMING_DARK_SKIN_TONE = "\U0001f3ca\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_SWIMMING = "\U0001f3ca\u200d\u2640\ufe0f" +WOMAN_SWIMMING_LIGHT_SKIN_TONE = "\U0001f3ca\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_SWIMMING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f3ca\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_SWIMMING_MEDIUM_SKIN_TONE = "\U0001f3ca\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_SWIMMING_MEDIUM_DARK_SKIN_TONE = "\U0001f3ca\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_SWIMMING_DARK_SKIN_TONE = "\U0001f3ca\U0001f3ff\u200d\u2640\ufe0f" +PERSON_BOUNCING_BALL = "\u26f9\ufe0f" +PERSON_BOUNCING_BALL_LIGHT_SKIN_TONE = "\u26f9\U0001f3fb" +PERSON_BOUNCING_BALL_MEDIUM_LIGHT_SKIN_TONE = "\u26f9\U0001f3fc" +PERSON_BOUNCING_BALL_MEDIUM_SKIN_TONE = "\u26f9\U0001f3fd" +PERSON_BOUNCING_BALL_MEDIUM_DARK_SKIN_TONE = "\u26f9\U0001f3fe" +PERSON_BOUNCING_BALL_DARK_SKIN_TONE = "\u26f9\U0001f3ff" +MAN_BOUNCING_BALL = "\u26f9\ufe0f\u200d\u2642\ufe0f" +MAN_BOUNCING_BALL_LIGHT_SKIN_TONE = "\u26f9\U0001f3fb\u200d\u2642\ufe0f" +MAN_BOUNCING_BALL_MEDIUM_LIGHT_SKIN_TONE = "\u26f9\U0001f3fc\u200d\u2642\ufe0f" +MAN_BOUNCING_BALL_MEDIUM_SKIN_TONE = "\u26f9\U0001f3fd\u200d\u2642\ufe0f" +MAN_BOUNCING_BALL_MEDIUM_DARK_SKIN_TONE = "\u26f9\U0001f3fe\u200d\u2642\ufe0f" +MAN_BOUNCING_BALL_DARK_SKIN_TONE = "\u26f9\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_BOUNCING_BALL = "\u26f9\ufe0f\u200d\u2640\ufe0f" +WOMAN_BOUNCING_BALL_LIGHT_SKIN_TONE = "\u26f9\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_BOUNCING_BALL_MEDIUM_LIGHT_SKIN_TONE = "\u26f9\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_BOUNCING_BALL_MEDIUM_SKIN_TONE = "\u26f9\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_BOUNCING_BALL_MEDIUM_DARK_SKIN_TONE = "\u26f9\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_BOUNCING_BALL_DARK_SKIN_TONE = "\u26f9\U0001f3ff\u200d\u2640\ufe0f" +PERSON_LIFTING_WEIGHTS = "\U0001f3cb\ufe0f" +PERSON_LIFTING_WEIGHTS_LIGHT_SKIN_TONE = "\U0001f3cb\U0001f3fb" +PERSON_LIFTING_WEIGHTS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f3cb\U0001f3fc" +PERSON_LIFTING_WEIGHTS_MEDIUM_SKIN_TONE = "\U0001f3cb\U0001f3fd" +PERSON_LIFTING_WEIGHTS_MEDIUM_DARK_SKIN_TONE = "\U0001f3cb\U0001f3fe" +PERSON_LIFTING_WEIGHTS_DARK_SKIN_TONE = "\U0001f3cb\U0001f3ff" +MAN_LIFTING_WEIGHTS = "\U0001f3cb\ufe0f\u200d\u2642\ufe0f" +MAN_LIFTING_WEIGHTS_LIGHT_SKIN_TONE = "\U0001f3cb\U0001f3fb\u200d\u2642\ufe0f" +MAN_LIFTING_WEIGHTS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f3cb\U0001f3fc\u200d\u2642\ufe0f" +MAN_LIFTING_WEIGHTS_MEDIUM_SKIN_TONE = "\U0001f3cb\U0001f3fd\u200d\u2642\ufe0f" +MAN_LIFTING_WEIGHTS_MEDIUM_DARK_SKIN_TONE = "\U0001f3cb\U0001f3fe\u200d\u2642\ufe0f" +MAN_LIFTING_WEIGHTS_DARK_SKIN_TONE = "\U0001f3cb\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_LIFTING_WEIGHTS = "\U0001f3cb\ufe0f\u200d\u2640\ufe0f" +WOMAN_LIFTING_WEIGHTS_LIGHT_SKIN_TONE = "\U0001f3cb\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_LIFTING_WEIGHTS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f3cb\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_LIFTING_WEIGHTS_MEDIUM_SKIN_TONE = "\U0001f3cb\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_LIFTING_WEIGHTS_MEDIUM_DARK_SKIN_TONE = "\U0001f3cb\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_LIFTING_WEIGHTS_DARK_SKIN_TONE = "\U0001f3cb\U0001f3ff\u200d\u2640\ufe0f" +PERSON_BIKING = "\U0001f6b4" +PERSON_BIKING_LIGHT_SKIN_TONE = "\U0001f6b4\U0001f3fb" +PERSON_BIKING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f6b4\U0001f3fc" +PERSON_BIKING_MEDIUM_SKIN_TONE = "\U0001f6b4\U0001f3fd" +PERSON_BIKING_MEDIUM_DARK_SKIN_TONE = "\U0001f6b4\U0001f3fe" +PERSON_BIKING_DARK_SKIN_TONE = "\U0001f6b4\U0001f3ff" +MAN_BIKING = "\U0001f6b4\u200d\u2642\ufe0f" +MAN_BIKING_LIGHT_SKIN_TONE = "\U0001f6b4\U0001f3fb\u200d\u2642\ufe0f" +MAN_BIKING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f6b4\U0001f3fc\u200d\u2642\ufe0f" +MAN_BIKING_MEDIUM_SKIN_TONE = "\U0001f6b4\U0001f3fd\u200d\u2642\ufe0f" +MAN_BIKING_MEDIUM_DARK_SKIN_TONE = "\U0001f6b4\U0001f3fe\u200d\u2642\ufe0f" +MAN_BIKING_DARK_SKIN_TONE = "\U0001f6b4\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_BIKING = "\U0001f6b4\u200d\u2640\ufe0f" +WOMAN_BIKING_LIGHT_SKIN_TONE = "\U0001f6b4\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_BIKING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f6b4\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_BIKING_MEDIUM_SKIN_TONE = "\U0001f6b4\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_BIKING_MEDIUM_DARK_SKIN_TONE = "\U0001f6b4\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_BIKING_DARK_SKIN_TONE = "\U0001f6b4\U0001f3ff\u200d\u2640\ufe0f" +PERSON_MOUNTAIN_BIKING = "\U0001f6b5" +PERSON_MOUNTAIN_BIKING_LIGHT_SKIN_TONE = "\U0001f6b5\U0001f3fb" +PERSON_MOUNTAIN_BIKING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f6b5\U0001f3fc" +PERSON_MOUNTAIN_BIKING_MEDIUM_SKIN_TONE = "\U0001f6b5\U0001f3fd" +PERSON_MOUNTAIN_BIKING_MEDIUM_DARK_SKIN_TONE = "\U0001f6b5\U0001f3fe" +PERSON_MOUNTAIN_BIKING_DARK_SKIN_TONE = "\U0001f6b5\U0001f3ff" +MAN_MOUNTAIN_BIKING = "\U0001f6b5\u200d\u2642\ufe0f" +MAN_MOUNTAIN_BIKING_LIGHT_SKIN_TONE = "\U0001f6b5\U0001f3fb\u200d\u2642\ufe0f" +MAN_MOUNTAIN_BIKING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f6b5\U0001f3fc\u200d\u2642\ufe0f" +MAN_MOUNTAIN_BIKING_MEDIUM_SKIN_TONE = "\U0001f6b5\U0001f3fd\u200d\u2642\ufe0f" +MAN_MOUNTAIN_BIKING_MEDIUM_DARK_SKIN_TONE = "\U0001f6b5\U0001f3fe\u200d\u2642\ufe0f" +MAN_MOUNTAIN_BIKING_DARK_SKIN_TONE = "\U0001f6b5\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_MOUNTAIN_BIKING = "\U0001f6b5\u200d\u2640\ufe0f" +WOMAN_MOUNTAIN_BIKING_LIGHT_SKIN_TONE = "\U0001f6b5\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_MOUNTAIN_BIKING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f6b5\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_MOUNTAIN_BIKING_MEDIUM_SKIN_TONE = "\U0001f6b5\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_MOUNTAIN_BIKING_MEDIUM_DARK_SKIN_TONE = "\U0001f6b5\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_MOUNTAIN_BIKING_DARK_SKIN_TONE = "\U0001f6b5\U0001f3ff\u200d\u2640\ufe0f" +PERSON_CARTWHEELING = "\U0001f938" +PERSON_CARTWHEELING_LIGHT_SKIN_TONE = "\U0001f938\U0001f3fb" +PERSON_CARTWHEELING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f938\U0001f3fc" +PERSON_CARTWHEELING_MEDIUM_SKIN_TONE = "\U0001f938\U0001f3fd" +PERSON_CARTWHEELING_MEDIUM_DARK_SKIN_TONE = "\U0001f938\U0001f3fe" +PERSON_CARTWHEELING_DARK_SKIN_TONE = "\U0001f938\U0001f3ff" +MAN_CARTWHEELING = "\U0001f938\u200d\u2642\ufe0f" +MAN_CARTWHEELING_LIGHT_SKIN_TONE = "\U0001f938\U0001f3fb\u200d\u2642\ufe0f" +MAN_CARTWHEELING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f938\U0001f3fc\u200d\u2642\ufe0f" +MAN_CARTWHEELING_MEDIUM_SKIN_TONE = "\U0001f938\U0001f3fd\u200d\u2642\ufe0f" +MAN_CARTWHEELING_MEDIUM_DARK_SKIN_TONE = "\U0001f938\U0001f3fe\u200d\u2642\ufe0f" +MAN_CARTWHEELING_DARK_SKIN_TONE = "\U0001f938\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_CARTWHEELING = "\U0001f938\u200d\u2640\ufe0f" +WOMAN_CARTWHEELING_LIGHT_SKIN_TONE = "\U0001f938\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_CARTWHEELING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f938\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_CARTWHEELING_MEDIUM_SKIN_TONE = "\U0001f938\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_CARTWHEELING_MEDIUM_DARK_SKIN_TONE = "\U0001f938\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_CARTWHEELING_DARK_SKIN_TONE = "\U0001f938\U0001f3ff\u200d\u2640\ufe0f" +PEOPLE_WRESTLING = "\U0001f93c" +PERSON_PLAYING_WATER_POLO = "\U0001f93d" +PERSON_PLAYING_WATER_POLO_LIGHT_SKIN_TONE = "\U0001f93d\U0001f3fb" +PERSON_PLAYING_WATER_POLO_MEDIUM_LIGHT_SKIN_TONE = "\U0001f93d\U0001f3fc" +PERSON_PLAYING_WATER_POLO_MEDIUM_SKIN_TONE = "\U0001f93d\U0001f3fd" +PERSON_PLAYING_WATER_POLO_MEDIUM_DARK_SKIN_TONE = "\U0001f93d\U0001f3fe" +PERSON_PLAYING_WATER_POLO_DARK_SKIN_TONE = "\U0001f93d\U0001f3ff" +MAN_PLAYING_WATER_POLO = "\U0001f93d\u200d\u2642\ufe0f" +MAN_PLAYING_WATER_POLO_LIGHT_SKIN_TONE = "\U0001f93d\U0001f3fb\u200d\u2642\ufe0f" +MAN_PLAYING_WATER_POLO_MEDIUM_LIGHT_SKIN_TONE = "\U0001f93d\U0001f3fc\u200d\u2642\ufe0f" +MAN_PLAYING_WATER_POLO_MEDIUM_SKIN_TONE = "\U0001f93d\U0001f3fd\u200d\u2642\ufe0f" +MAN_PLAYING_WATER_POLO_MEDIUM_DARK_SKIN_TONE = "\U0001f93d\U0001f3fe\u200d\u2642\ufe0f" +MAN_PLAYING_WATER_POLO_DARK_SKIN_TONE = "\U0001f93d\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_PLAYING_WATER_POLO = "\U0001f93d\u200d\u2640\ufe0f" +WOMAN_PLAYING_WATER_POLO_LIGHT_SKIN_TONE = "\U0001f93d\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_PLAYING_WATER_POLO_MEDIUM_LIGHT_SKIN_TONE = "\U0001f93d\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_PLAYING_WATER_POLO_MEDIUM_SKIN_TONE = "\U0001f93d\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_PLAYING_WATER_POLO_MEDIUM_DARK_SKIN_TONE = "\U0001f93d\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_PLAYING_WATER_POLO_DARK_SKIN_TONE = "\U0001f93d\U0001f3ff\u200d\u2640\ufe0f" +PERSON_PLAYING_HANDBALL = "\U0001f93e" +PERSON_PLAYING_HANDBALL_LIGHT_SKIN_TONE = "\U0001f93e\U0001f3fb" +PERSON_PLAYING_HANDBALL_MEDIUM_LIGHT_SKIN_TONE = "\U0001f93e\U0001f3fc" +PERSON_PLAYING_HANDBALL_MEDIUM_SKIN_TONE = "\U0001f93e\U0001f3fd" +PERSON_PLAYING_HANDBALL_MEDIUM_DARK_SKIN_TONE = "\U0001f93e\U0001f3fe" +PERSON_PLAYING_HANDBALL_DARK_SKIN_TONE = "\U0001f93e\U0001f3ff" +MAN_PLAYING_HANDBALL = "\U0001f93e\u200d\u2642\ufe0f" +MAN_PLAYING_HANDBALL_LIGHT_SKIN_TONE = "\U0001f93e\U0001f3fb\u200d\u2642\ufe0f" +MAN_PLAYING_HANDBALL_MEDIUM_LIGHT_SKIN_TONE = "\U0001f93e\U0001f3fc\u200d\u2642\ufe0f" +MAN_PLAYING_HANDBALL_MEDIUM_SKIN_TONE = "\U0001f93e\U0001f3fd\u200d\u2642\ufe0f" +MAN_PLAYING_HANDBALL_MEDIUM_DARK_SKIN_TONE = "\U0001f93e\U0001f3fe\u200d\u2642\ufe0f" +MAN_PLAYING_HANDBALL_DARK_SKIN_TONE = "\U0001f93e\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_PLAYING_HANDBALL = "\U0001f93e\u200d\u2640\ufe0f" +WOMAN_PLAYING_HANDBALL_LIGHT_SKIN_TONE = "\U0001f93e\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_PLAYING_HANDBALL_MEDIUM_LIGHT_SKIN_TONE = "\U0001f93e\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_PLAYING_HANDBALL_MEDIUM_SKIN_TONE = "\U0001f93e\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_PLAYING_HANDBALL_MEDIUM_DARK_SKIN_TONE = "\U0001f93e\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_PLAYING_HANDBALL_DARK_SKIN_TONE = "\U0001f93e\U0001f3ff\u200d\u2640\ufe0f" +PERSON_JUGGLING = "\U0001f939" +PERSON_JUGGLING_LIGHT_SKIN_TONE = "\U0001f939\U0001f3fb" +PERSON_JUGGLING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f939\U0001f3fc" +PERSON_JUGGLING_MEDIUM_SKIN_TONE = "\U0001f939\U0001f3fd" +PERSON_JUGGLING_MEDIUM_DARK_SKIN_TONE = "\U0001f939\U0001f3fe" +PERSON_JUGGLING_DARK_SKIN_TONE = "\U0001f939\U0001f3ff" +MAN_JUGGLING = "\U0001f939\u200d\u2642\ufe0f" +MAN_JUGGLING_LIGHT_SKIN_TONE = "\U0001f939\U0001f3fb\u200d\u2642\ufe0f" +MAN_JUGGLING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f939\U0001f3fc\u200d\u2642\ufe0f" +MAN_JUGGLING_MEDIUM_SKIN_TONE = "\U0001f939\U0001f3fd\u200d\u2642\ufe0f" +MAN_JUGGLING_MEDIUM_DARK_SKIN_TONE = "\U0001f939\U0001f3fe\u200d\u2642\ufe0f" +MAN_JUGGLING_DARK_SKIN_TONE = "\U0001f939\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_JUGGLING = "\U0001f939\u200d\u2640\ufe0f" +WOMAN_JUGGLING_LIGHT_SKIN_TONE = "\U0001f939\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_JUGGLING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f939\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_JUGGLING_MEDIUM_SKIN_TONE = "\U0001f939\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_JUGGLING_MEDIUM_DARK_SKIN_TONE = "\U0001f939\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_JUGGLING_DARK_SKIN_TONE = "\U0001f939\U0001f3ff\u200d\u2640\ufe0f" +PERSON_IN_LOTUS_POSITION = "\U0001f9d8" +PERSON_IN_LOTUS_POSITION_LIGHT_SKIN_TONE = "\U0001f9d8\U0001f3fb" +PERSON_IN_LOTUS_POSITION_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d8\U0001f3fc" +PERSON_IN_LOTUS_POSITION_MEDIUM_SKIN_TONE = "\U0001f9d8\U0001f3fd" +PERSON_IN_LOTUS_POSITION_MEDIUM_DARK_SKIN_TONE = "\U0001f9d8\U0001f3fe" +PERSON_IN_LOTUS_POSITION_DARK_SKIN_TONE = "\U0001f9d8\U0001f3ff" +MAN_IN_LOTUS_POSITION = "\U0001f9d8\u200d\u2642\ufe0f" +MAN_IN_LOTUS_POSITION_LIGHT_SKIN_TONE = "\U0001f9d8\U0001f3fb\u200d\u2642\ufe0f" +MAN_IN_LOTUS_POSITION_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d8\U0001f3fc\u200d\u2642\ufe0f" +MAN_IN_LOTUS_POSITION_MEDIUM_SKIN_TONE = "\U0001f9d8\U0001f3fd\u200d\u2642\ufe0f" +MAN_IN_LOTUS_POSITION_MEDIUM_DARK_SKIN_TONE = "\U0001f9d8\U0001f3fe\u200d\u2642\ufe0f" +MAN_IN_LOTUS_POSITION_DARK_SKIN_TONE = "\U0001f9d8\U0001f3ff\u200d\u2642\ufe0f" +WOMAN_IN_LOTUS_POSITION = "\U0001f9d8\u200d\u2640\ufe0f" +WOMAN_IN_LOTUS_POSITION_LIGHT_SKIN_TONE = "\U0001f9d8\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_IN_LOTUS_POSITION_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d8\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_IN_LOTUS_POSITION_MEDIUM_SKIN_TONE = "\U0001f9d8\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_IN_LOTUS_POSITION_MEDIUM_DARK_SKIN_TONE = "\U0001f9d8\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_IN_LOTUS_POSITION_DARK_SKIN_TONE = "\U0001f9d8\U0001f3ff\u200d\u2640\ufe0f" +PERSON_TAKING_BATH = "\U0001f6c0" +PERSON_TAKING_BATH_LIGHT_SKIN_TONE = "\U0001f6c0\U0001f3fb" +PERSON_TAKING_BATH_MEDIUM_LIGHT_SKIN_TONE = "\U0001f6c0\U0001f3fc" +PERSON_TAKING_BATH_MEDIUM_SKIN_TONE = "\U0001f6c0\U0001f3fd" +PERSON_TAKING_BATH_MEDIUM_DARK_SKIN_TONE = "\U0001f6c0\U0001f3fe" +PERSON_TAKING_BATH_DARK_SKIN_TONE = "\U0001f6c0\U0001f3ff" +PERSON_IN_BED = "\U0001f6cc" +PERSON_IN_BED_LIGHT_SKIN_TONE = "\U0001f6cc\U0001f3fb" +PERSON_IN_BED_MEDIUM_LIGHT_SKIN_TONE = "\U0001f6cc\U0001f3fc" +PERSON_IN_BED_MEDIUM_SKIN_TONE = "\U0001f6cc\U0001f3fd" +PERSON_IN_BED_MEDIUM_DARK_SKIN_TONE = "\U0001f6cc\U0001f3fe" +PERSON_IN_BED_DARK_SKIN_TONE = "\U0001f6cc\U0001f3ff" +PEOPLE_HOLDING_HANDS = "\U0001f9d1\u200d\U0001f91d\u200d\U0001f9d1" +PEOPLE_HOLDING_HANDS_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fb\u200d\U0001f91d\u200d\U0001f9d1\U0001f3fb" +PEOPLE_HOLDING_HANDS_LIGHT_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3fb\u200d\U0001f91d\u200d\U0001f9d1\U0001f3fc" +PEOPLE_HOLDING_HANDS_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f9d1\U0001f3fb\u200d\U0001f91d\u200d\U0001f9d1\U0001f3fd" +PEOPLE_HOLDING_HANDS_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f9d1\U0001f3fb\u200d\U0001f91d\u200d\U0001f9d1\U0001f3fe" +PEOPLE_HOLDING_HANDS_LIGHT_SKIN_TONE_DARK_SKIN_TONE = "\U0001f9d1\U0001f3fb\u200d\U0001f91d\u200d\U0001f9d1\U0001f3ff" +PEOPLE_HOLDING_HANDS_MEDIUM_LIGHT_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3fc\u200d\U0001f91d\u200d\U0001f9d1\U0001f3fb" +PEOPLE_HOLDING_HANDS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3fc\u200d\U0001f91d\u200d\U0001f9d1\U0001f3fc" +PEOPLE_HOLDING_HANDS_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f9d1\U0001f3fc\u200d\U0001f91d\u200d\U0001f9d1\U0001f3fd" +PEOPLE_HOLDING_HANDS_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f9d1\U0001f3fc\u200d\U0001f91d\u200d\U0001f9d1\U0001f3fe" +PEOPLE_HOLDING_HANDS_MEDIUM_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f9d1\U0001f3fc\u200d\U0001f91d\u200d\U0001f9d1\U0001f3ff" +PEOPLE_HOLDING_HANDS_MEDIUM_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3fd\u200d\U0001f91d\u200d\U0001f9d1\U0001f3fb" +PEOPLE_HOLDING_HANDS_MEDIUM_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3fd\u200d\U0001f91d\u200d\U0001f9d1\U0001f3fc" +PEOPLE_HOLDING_HANDS_MEDIUM_SKIN_TONE = "\U0001f9d1\U0001f3fd\u200d\U0001f91d\u200d\U0001f9d1\U0001f3fd" +PEOPLE_HOLDING_HANDS_MEDIUM_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f9d1\U0001f3fd\u200d\U0001f91d\u200d\U0001f9d1\U0001f3fe" +PEOPLE_HOLDING_HANDS_MEDIUM_SKIN_TONE_DARK_SKIN_TONE = "\U0001f9d1\U0001f3fd\u200d\U0001f91d\u200d\U0001f9d1\U0001f3ff" +PEOPLE_HOLDING_HANDS_MEDIUM_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3fe\u200d\U0001f91d\u200d\U0001f9d1\U0001f3fb" +PEOPLE_HOLDING_HANDS_MEDIUM_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3fe\u200d\U0001f91d\u200d\U0001f9d1\U0001f3fc" +PEOPLE_HOLDING_HANDS_MEDIUM_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f9d1\U0001f3fe\u200d\U0001f91d\u200d\U0001f9d1\U0001f3fd" +PEOPLE_HOLDING_HANDS_MEDIUM_DARK_SKIN_TONE = "\U0001f9d1\U0001f3fe\u200d\U0001f91d\u200d\U0001f9d1\U0001f3fe" +PEOPLE_HOLDING_HANDS_MEDIUM_DARK_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f9d1\U0001f3fe\u200d\U0001f91d\u200d\U0001f9d1\U0001f3ff" +PEOPLE_HOLDING_HANDS_DARK_SKIN_TONE_LIGHT_SKIN_TONE = "\U0001f9d1\U0001f3ff\u200d\U0001f91d\u200d\U0001f9d1\U0001f3fb" +PEOPLE_HOLDING_HANDS_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3ff\u200d\U0001f91d\u200d\U0001f9d1\U0001f3fc" +PEOPLE_HOLDING_HANDS_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = "\U0001f9d1\U0001f3ff\u200d\U0001f91d\u200d\U0001f9d1\U0001f3fd" +PEOPLE_HOLDING_HANDS_DARK_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f9d1\U0001f3ff\u200d\U0001f91d\u200d\U0001f9d1\U0001f3fe" +PEOPLE_HOLDING_HANDS_DARK_SKIN_TONE = "\U0001f9d1\U0001f3ff\u200d\U0001f91d\u200d\U0001f9d1\U0001f3ff" +WOMEN_HOLDING_HANDS = "\U0001f46d" +WOMEN_HOLDING_HANDS_LIGHT_SKIN_TONE = "\U0001f46d\U0001f3fb" +WOMEN_HOLDING_HANDS_LIGHT_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fb\u200d\U0001f91d\u200d\U0001f469\U0001f3fc" +WOMEN_HOLDING_HANDS_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f91d\u200d\U0001f469\U0001f3fd" +WOMEN_HOLDING_HANDS_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fb\u200d\U0001f91d\u200d\U0001f469\U0001f3fe" +WOMEN_HOLDING_HANDS_LIGHT_SKIN_TONE_DARK_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\U0001f91d\u200d\U0001f469\U0001f3ff" +WOMEN_HOLDING_HANDS_MEDIUM_LIGHT_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\U0001f91d\u200d\U0001f469\U0001f3fb" +WOMEN_HOLDING_HANDS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f46d\U0001f3fc" +WOMEN_HOLDING_HANDS_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\U0001f91d\u200d\U0001f469\U0001f3fd" +WOMEN_HOLDING_HANDS_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\U0001f91d\u200d\U0001f469\U0001f3fe" +WOMEN_HOLDING_HANDS_MEDIUM_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\U0001f91d\u200d\U0001f469\U0001f3ff" +WOMEN_HOLDING_HANDS_MEDIUM_SKIN_TONE_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f91d\u200d\U0001f469\U0001f3fb" +WOMEN_HOLDING_HANDS_MEDIUM_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fd\u200d\U0001f91d\u200d\U0001f469\U0001f3fc" +WOMEN_HOLDING_HANDS_MEDIUM_SKIN_TONE = "\U0001f46d\U0001f3fd" +WOMEN_HOLDING_HANDS_MEDIUM_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fd\u200d\U0001f91d\u200d\U0001f469\U0001f3fe" +WOMEN_HOLDING_HANDS_MEDIUM_SKIN_TONE_DARK_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\U0001f91d\u200d\U0001f469\U0001f3ff" +WOMEN_HOLDING_HANDS_MEDIUM_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\U0001f91d\u200d\U0001f469\U0001f3fb" +WOMEN_HOLDING_HANDS_MEDIUM_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\U0001f91d\u200d\U0001f469\U0001f3fc" +WOMEN_HOLDING_HANDS_MEDIUM_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\U0001f91d\u200d\U0001f469\U0001f3fd" +WOMEN_HOLDING_HANDS_MEDIUM_DARK_SKIN_TONE = "\U0001f46d\U0001f3fe" +WOMEN_HOLDING_HANDS_MEDIUM_DARK_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\U0001f91d\u200d\U0001f469\U0001f3ff" +WOMEN_HOLDING_HANDS_DARK_SKIN_TONE_LIGHT_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f91d\u200d\U0001f469\U0001f3fb" +WOMEN_HOLDING_HANDS_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3ff\u200d\U0001f91d\u200d\U0001f469\U0001f3fc" +WOMEN_HOLDING_HANDS_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\U0001f91d\u200d\U0001f469\U0001f3fd" +WOMEN_HOLDING_HANDS_DARK_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3ff\u200d\U0001f91d\u200d\U0001f469\U0001f3fe" +WOMEN_HOLDING_HANDS_DARK_SKIN_TONE = "\U0001f46d\U0001f3ff" +WOMAN_AND_MAN_HOLDING_HANDS = "\U0001f46b" +WOMAN_AND_MAN_HOLDING_HANDS_LIGHT_SKIN_TONE = "\U0001f46b\U0001f3fb" +WOMAN_AND_MAN_HOLDING_HANDS_LIGHT_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fb\u200d\U0001f91d\u200d\U0001f468\U0001f3fc" +WOMAN_AND_MAN_HOLDING_HANDS_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f469\U0001f3fb\u200d\U0001f91d\u200d\U0001f468\U0001f3fd" +WOMAN_AND_MAN_HOLDING_HANDS_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fb\u200d\U0001f91d\u200d\U0001f468\U0001f3fe" +WOMAN_AND_MAN_HOLDING_HANDS_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fb\u200d\U0001f91d\u200d\U0001f468\U0001f3ff" +WOMAN_AND_MAN_HOLDING_HANDS_MEDIUM_LIGHT_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\U0001f91d\u200d\U0001f468\U0001f3fb" +WOMAN_AND_MAN_HOLDING_HANDS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f46b\U0001f3fc" +WOMAN_AND_MAN_HOLDING_HANDS_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\U0001f91d\u200d\U0001f468\U0001f3fd" +WOMAN_AND_MAN_HOLDING_HANDS_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\U0001f91d\u200d\U0001f468\U0001f3fe" +WOMAN_AND_MAN_HOLDING_HANDS_MEDIUM_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\U0001f91d\u200d\U0001f468\U0001f3ff" +WOMAN_AND_MAN_HOLDING_HANDS_MEDIUM_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fd\u200d\U0001f91d\u200d\U0001f468\U0001f3fb" +WOMAN_AND_MAN_HOLDING_HANDS_MEDIUM_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fd\u200d\U0001f91d\u200d\U0001f468\U0001f3fc" +WOMAN_AND_MAN_HOLDING_HANDS_MEDIUM_SKIN_TONE = "\U0001f46b\U0001f3fd" +WOMAN_AND_MAN_HOLDING_HANDS_MEDIUM_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fd\u200d\U0001f91d\u200d\U0001f468\U0001f3fe" +WOMAN_AND_MAN_HOLDING_HANDS_MEDIUM_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fd\u200d\U0001f91d\u200d\U0001f468\U0001f3ff" +WOMAN_AND_MAN_HOLDING_HANDS_MEDIUM_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\U0001f91d\u200d\U0001f468\U0001f3fb" +WOMAN_AND_MAN_HOLDING_HANDS_MEDIUM_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\U0001f91d\u200d\U0001f468\U0001f3fc" +WOMAN_AND_MAN_HOLDING_HANDS_MEDIUM_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\U0001f91d\u200d\U0001f468\U0001f3fd" +WOMAN_AND_MAN_HOLDING_HANDS_MEDIUM_DARK_SKIN_TONE = "\U0001f46b\U0001f3fe" +WOMAN_AND_MAN_HOLDING_HANDS_MEDIUM_DARK_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\U0001f91d\u200d\U0001f468\U0001f3ff" +WOMAN_AND_MAN_HOLDING_HANDS_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3ff\u200d\U0001f91d\u200d\U0001f468\U0001f3fb" +WOMAN_AND_MAN_HOLDING_HANDS_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3ff\u200d\U0001f91d\u200d\U0001f468\U0001f3fc" +WOMAN_AND_MAN_HOLDING_HANDS_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f469\U0001f3ff\u200d\U0001f91d\u200d\U0001f468\U0001f3fd" +WOMAN_AND_MAN_HOLDING_HANDS_DARK_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3ff\u200d\U0001f91d\u200d\U0001f468\U0001f3fe" +WOMAN_AND_MAN_HOLDING_HANDS_DARK_SKIN_TONE = "\U0001f46b\U0001f3ff" +MEN_HOLDING_HANDS = "\U0001f46c" +MEN_HOLDING_HANDS_LIGHT_SKIN_TONE = "\U0001f46c\U0001f3fb" +MEN_HOLDING_HANDS_LIGHT_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3fb\u200d\U0001f91d\u200d\U0001f468\U0001f3fc" +MEN_HOLDING_HANDS_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f91d\u200d\U0001f468\U0001f3fd" +MEN_HOLDING_HANDS_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f468\U0001f3fb\u200d\U0001f91d\u200d\U0001f468\U0001f3fe" +MEN_HOLDING_HANDS_LIGHT_SKIN_TONE_DARK_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\U0001f91d\u200d\U0001f468\U0001f3ff" +MEN_HOLDING_HANDS_MEDIUM_LIGHT_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3fc\u200d\U0001f91d\u200d\U0001f468\U0001f3fb" +MEN_HOLDING_HANDS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f46c\U0001f3fc" +MEN_HOLDING_HANDS_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f468\U0001f3fc\u200d\U0001f91d\u200d\U0001f468\U0001f3fd" +MEN_HOLDING_HANDS_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f468\U0001f3fc\u200d\U0001f91d\u200d\U0001f468\U0001f3fe" +MEN_HOLDING_HANDS_MEDIUM_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f468\U0001f3fc\u200d\U0001f91d\u200d\U0001f468\U0001f3ff" +MEN_HOLDING_HANDS_MEDIUM_SKIN_TONE_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f91d\u200d\U0001f468\U0001f3fb" +MEN_HOLDING_HANDS_MEDIUM_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3fd\u200d\U0001f91d\u200d\U0001f468\U0001f3fc" +MEN_HOLDING_HANDS_MEDIUM_SKIN_TONE = "\U0001f46c\U0001f3fd" +MEN_HOLDING_HANDS_MEDIUM_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f468\U0001f3fd\u200d\U0001f91d\u200d\U0001f468\U0001f3fe" +MEN_HOLDING_HANDS_MEDIUM_SKIN_TONE_DARK_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\U0001f91d\u200d\U0001f468\U0001f3ff" +MEN_HOLDING_HANDS_MEDIUM_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3fe\u200d\U0001f91d\u200d\U0001f468\U0001f3fb" +MEN_HOLDING_HANDS_MEDIUM_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3fe\u200d\U0001f91d\u200d\U0001f468\U0001f3fc" +MEN_HOLDING_HANDS_MEDIUM_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f468\U0001f3fe\u200d\U0001f91d\u200d\U0001f468\U0001f3fd" +MEN_HOLDING_HANDS_MEDIUM_DARK_SKIN_TONE = "\U0001f46c\U0001f3fe" +MEN_HOLDING_HANDS_MEDIUM_DARK_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f468\U0001f3fe\u200d\U0001f91d\u200d\U0001f468\U0001f3ff" +MEN_HOLDING_HANDS_DARK_SKIN_TONE_LIGHT_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f91d\u200d\U0001f468\U0001f3fb" +MEN_HOLDING_HANDS_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3ff\u200d\U0001f91d\u200d\U0001f468\U0001f3fc" +MEN_HOLDING_HANDS_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\U0001f91d\u200d\U0001f468\U0001f3fd" +MEN_HOLDING_HANDS_DARK_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f468\U0001f3ff\u200d\U0001f91d\u200d\U0001f468\U0001f3fe" +MEN_HOLDING_HANDS_DARK_SKIN_TONE = "\U0001f46c\U0001f3ff" +KISS = "\U0001f48f" +KISS_WOMAN_MAN = "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468" +KISS_MAN_MAN = "\U0001f468\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468" +KISS_WOMAN_WOMAN = "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469" +COUPLE_WITH_HEART = "\U0001f491" +COUPLE_WITH_HEART_WOMAN_MAN = "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f468" +COUPLE_WITH_HEART_MAN_MAN = "\U0001f468\u200d\u2764\ufe0f\u200d\U0001f468" +COUPLE_WITH_HEART_WOMAN_WOMAN = "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f469" +FAMILY = "\U0001f46a" +FAMILY_MAN_WOMAN_BOY = "\U0001f468\u200d\U0001f469\u200d\U0001f466" +FAMILY_MAN_WOMAN_GIRL = "\U0001f468\u200d\U0001f469\u200d\U0001f467" +FAMILY_MAN_WOMAN_GIRL_BOY = "\U0001f468\u200d\U0001f469\u200d\U0001f467\u200d\U0001f466" +FAMILY_MAN_WOMAN_BOY_BOY = "\U0001f468\u200d\U0001f469\u200d\U0001f466\u200d\U0001f466" +FAMILY_MAN_WOMAN_GIRL_GIRL = "\U0001f468\u200d\U0001f469\u200d\U0001f467\u200d\U0001f467" +FAMILY_MAN_MAN_BOY = "\U0001f468\u200d\U0001f468\u200d\U0001f466" +FAMILY_MAN_MAN_GIRL = "\U0001f468\u200d\U0001f468\u200d\U0001f467" +FAMILY_MAN_MAN_GIRL_BOY = "\U0001f468\u200d\U0001f468\u200d\U0001f467\u200d\U0001f466" +FAMILY_MAN_MAN_BOY_BOY = "\U0001f468\u200d\U0001f468\u200d\U0001f466\u200d\U0001f466" +FAMILY_MAN_MAN_GIRL_GIRL = "\U0001f468\u200d\U0001f468\u200d\U0001f467\u200d\U0001f467" +FAMILY_WOMAN_WOMAN_BOY = "\U0001f469\u200d\U0001f469\u200d\U0001f466" +FAMILY_WOMAN_WOMAN_GIRL = "\U0001f469\u200d\U0001f469\u200d\U0001f467" +FAMILY_WOMAN_WOMAN_GIRL_BOY = "\U0001f469\u200d\U0001f469\u200d\U0001f467\u200d\U0001f466" +FAMILY_WOMAN_WOMAN_BOY_BOY = "\U0001f469\u200d\U0001f469\u200d\U0001f466\u200d\U0001f466" +FAMILY_WOMAN_WOMAN_GIRL_GIRL = "\U0001f469\u200d\U0001f469\u200d\U0001f467\u200d\U0001f467" +FAMILY_MAN_BOY = "\U0001f468\u200d\U0001f466" +FAMILY_MAN_BOY_BOY = "\U0001f468\u200d\U0001f466\u200d\U0001f466" +FAMILY_MAN_GIRL = "\U0001f468\u200d\U0001f467" +FAMILY_MAN_GIRL_BOY = "\U0001f468\u200d\U0001f467\u200d\U0001f466" +FAMILY_MAN_GIRL_GIRL = "\U0001f468\u200d\U0001f467\u200d\U0001f467" +FAMILY_WOMAN_BOY = "\U0001f469\u200d\U0001f466" +FAMILY_WOMAN_BOY_BOY = "\U0001f469\u200d\U0001f466\u200d\U0001f466" +FAMILY_WOMAN_GIRL = "\U0001f469\u200d\U0001f467" +FAMILY_WOMAN_GIRL_BOY = "\U0001f469\u200d\U0001f467\u200d\U0001f466" +FAMILY_WOMAN_GIRL_GIRL = "\U0001f469\u200d\U0001f467\u200d\U0001f467" +SPEAKING_HEAD = "\U0001f5e3\ufe0f" +BUST_IN_SILHOUETTE = "\U0001f464" +BUSTS_IN_SILHOUETTE = "\U0001f465" +PEOPLE_HUGGING = "\U0001fac2" +FOOTPRINTS = "\U0001f463" +LIGHT_SKIN_TONE = "\U0001f3fb" +MEDIUM_LIGHT_SKIN_TONE = "\U0001f3fc" +MEDIUM_SKIN_TONE = "\U0001f3fd" +MEDIUM_DARK_SKIN_TONE = "\U0001f3fe" +DARK_SKIN_TONE = "\U0001f3ff" +RED_HAIR = "\U0001f9b0" +CURLY_HAIR = "\U0001f9b1" +WHITE_HAIR = "\U0001f9b3" +BALD = "\U0001f9b2" +MONKEY_FACE = "\U0001f435" +MONKEY = "\U0001f412" +GORILLA = "\U0001f98d" +ORANGUTAN = "\U0001f9a7" +DOG_FACE = "\U0001f436" +DOG = "\U0001f415" +GUIDE_DOG = "\U0001f9ae" +SERVICE_DOG = "\U0001f415\u200d\U0001f9ba" +POODLE = "\U0001f429" +WOLF = "\U0001f43a" +FOX = "\U0001f98a" +RACCOON = "\U0001f99d" +CAT_FACE = "\U0001f431" +CAT = "\U0001f408" +BLACK_CAT = "\U0001f408\u200d\u2b1b" +LION = "\U0001f981" +TIGER_FACE = "\U0001f42f" +TIGER = "\U0001f405" +LEOPARD = "\U0001f406" +HORSE_FACE = "\U0001f434" +HORSE = "\U0001f40e" +UNICORN = "\U0001f984" +ZEBRA = "\U0001f993" +DEER = "\U0001f98c" +BISON = "\U0001f9ac" +COW_FACE = "\U0001f42e" +OX = "\U0001f402" +WATER_BUFFALO = "\U0001f403" +COW = "\U0001f404" +PIG_FACE = "\U0001f437" +PIG = "\U0001f416" +BOAR = "\U0001f417" +PIG_NOSE = "\U0001f43d" +RAM = "\U0001f40f" +EWE = "\U0001f411" +GOAT = "\U0001f410" +CAMEL = "\U0001f42a" +TWO_HUMP_CAMEL = "\U0001f42b" +LLAMA = "\U0001f999" +GIRAFFE = "\U0001f992" +ELEPHANT = "\U0001f418" +MAMMOTH = "\U0001f9a3" +RHINOCEROS = "\U0001f98f" +HIPPOPOTAMUS = "\U0001f99b" +MOUSE_FACE = "\U0001f42d" +MOUSE = "\U0001f401" +RAT = "\U0001f400" +HAMSTER = "\U0001f439" +RABBIT_FACE = "\U0001f430" +RABBIT = "\U0001f407" +CHIPMUNK = "\U0001f43f\ufe0f" +BEAVER = "\U0001f9ab" +HEDGEHOG = "\U0001f994" +BAT = "\U0001f987" +BEAR = "\U0001f43b" +POLAR_BEAR = "\U0001f43b\u200d\u2744\ufe0f" +KOALA = "\U0001f428" +PANDA = "\U0001f43c" +SLOTH = "\U0001f9a5" +OTTER = "\U0001f9a6" +SKUNK = "\U0001f9a8" +KANGAROO = "\U0001f998" +BADGER = "\U0001f9a1" +PAW_PRINTS = "\U0001f43e" +TURKEY = "\U0001f983" +CHICKEN = "\U0001f414" +ROOSTER = "\U0001f413" +HATCHING_CHICK = "\U0001f423" +BABY_CHICK = "\U0001f424" +FRONT_FACING_BABY_CHICK = "\U0001f425" +BIRD = "\U0001f426" +PENGUIN = "\U0001f427" +DOVE = "\U0001f54a\ufe0f" +EAGLE = "\U0001f985" +DUCK = "\U0001f986" +SWAN = "\U0001f9a2" +OWL = "\U0001f989" +DODO = "\U0001f9a4" +FEATHER = "\U0001fab6" +FLAMINGO = "\U0001f9a9" +PEACOCK = "\U0001f99a" +PARROT = "\U0001f99c" +FROG = "\U0001f438" +CROCODILE = "\U0001f40a" +TURTLE = "\U0001f422" +LIZARD = "\U0001f98e" +SNAKE = "\U0001f40d" +DRAGON_FACE = "\U0001f432" +DRAGON = "\U0001f409" +SAUROPOD = "\U0001f995" +T_REX = "\U0001f996" +SPOUTING_WHALE = "\U0001f433" +WHALE = "\U0001f40b" +DOLPHIN = "\U0001f42c" +SEAL = "\U0001f9ad" +FISH = "\U0001f41f" +TROPICAL_FISH = "\U0001f420" +BLOWFISH = "\U0001f421" +SHARK = "\U0001f988" +OCTOPUS = "\U0001f419" +SPIRAL_SHELL = "\U0001f41a" +SNAIL = "\U0001f40c" +BUTTERFLY = "\U0001f98b" +BUG = "\U0001f41b" +ANT = "\U0001f41c" +HONEYBEE = "\U0001f41d" +BEETLE = "\U0001fab2" +LADY_BEETLE = "\U0001f41e" +CRICKET = "\U0001f997" +COCKROACH = "\U0001fab3" +SPIDER = "\U0001f577\ufe0f" +SPIDER_WEB = "\U0001f578\ufe0f" +SCORPION = "\U0001f982" +MOSQUITO = "\U0001f99f" +FLY = "\U0001fab0" +WORM = "\U0001fab1" +MICROBE = "\U0001f9a0" +BOUQUET = "\U0001f490" +CHERRY_BLOSSOM = "\U0001f338" +WHITE_FLOWER = "\U0001f4ae" +ROSETTE = "\U0001f3f5\ufe0f" +ROSE = "\U0001f339" +WILTED_FLOWER = "\U0001f940" +HIBISCUS = "\U0001f33a" +SUNFLOWER = "\U0001f33b" +BLOSSOM = "\U0001f33c" +TULIP = "\U0001f337" +SEEDLING = "\U0001f331" +POTTED_PLANT = "\U0001fab4" +EVERGREEN_TREE = "\U0001f332" +DECIDUOUS_TREE = "\U0001f333" +PALM_TREE = "\U0001f334" +CACTUS = "\U0001f335" +SHEAF_OF_RICE = "\U0001f33e" +HERB = "\U0001f33f" +SHAMROCK = "\u2618\ufe0f" +FOUR_LEAF_CLOVER = "\U0001f340" +MAPLE_LEAF = "\U0001f341" +FALLEN_LEAF = "\U0001f342" +LEAF_FLUTTERING_IN_WIND = "\U0001f343" +GRAPES = "\U0001f347" +MELON = "\U0001f348" +WATERMELON = "\U0001f349" +TANGERINE = "\U0001f34a" +LEMON = "\U0001f34b" +BANANA = "\U0001f34c" +PINEAPPLE = "\U0001f34d" +MANGO = "\U0001f96d" +RED_APPLE = "\U0001f34e" +GREEN_APPLE = "\U0001f34f" +PEAR = "\U0001f350" +PEACH = "\U0001f351" +CHERRIES = "\U0001f352" +STRAWBERRY = "\U0001f353" +BLUEBERRIES = "\U0001fad0" +KIWI_FRUIT = "\U0001f95d" +TOMATO = "\U0001f345" +OLIVE = "\U0001fad2" +COCONUT = "\U0001f965" +AVOCADO = "\U0001f951" +EGGPLANT = "\U0001f346" +POTATO = "\U0001f954" +CARROT = "\U0001f955" +EAR_OF_CORN = "\U0001f33d" +HOT_PEPPER = "\U0001f336\ufe0f" +BELL_PEPPER = "\U0001fad1" +CUCUMBER = "\U0001f952" +LEAFY_GREEN = "\U0001f96c" +BROCCOLI = "\U0001f966" +GARLIC = "\U0001f9c4" +ONION = "\U0001f9c5" +MUSHROOM = "\U0001f344" +PEANUTS = "\U0001f95c" +CHESTNUT = "\U0001f330" +BREAD = "\U0001f35e" +CROISSANT = "\U0001f950" +BAGUETTE_BREAD = "\U0001f956" +FLATBREAD = "\U0001fad3" +PRETZEL = "\U0001f968" +BAGEL = "\U0001f96f" +PANCAKES = "\U0001f95e" +WAFFLE = "\U0001f9c7" +CHEESE_WEDGE = "\U0001f9c0" +MEAT_ON_BONE = "\U0001f356" +POULTRY_LEG = "\U0001f357" +CUT_OF_MEAT = "\U0001f969" +BACON = "\U0001f953" +HAMBURGER = "\U0001f354" +FRENCH_FRIES = "\U0001f35f" +PIZZA = "\U0001f355" +HOT_DOG = "\U0001f32d" +SANDWICH = "\U0001f96a" +TACO = "\U0001f32e" +BURRITO = "\U0001f32f" +TAMALE = "\U0001fad4" +STUFFED_FLATBREAD = "\U0001f959" +FALAFEL = "\U0001f9c6" +EGG = "\U0001f95a" +COOKING = "\U0001f373" +SHALLOW_PAN_OF_FOOD = "\U0001f958" +POT_OF_FOOD = "\U0001f372" +FONDUE = "\U0001fad5" +BOWL_WITH_SPOON = "\U0001f963" +GREEN_SALAD = "\U0001f957" +POPCORN = "\U0001f37f" +BUTTER = "\U0001f9c8" +SALT = "\U0001f9c2" +CANNED_FOOD = "\U0001f96b" +BENTO_BOX = "\U0001f371" +RICE_CRACKER = "\U0001f358" +RICE_BALL = "\U0001f359" +COOKED_RICE = "\U0001f35a" +CURRY_RICE = "\U0001f35b" +STEAMING_BOWL = "\U0001f35c" +SPAGHETTI = "\U0001f35d" +ROASTED_SWEET_POTATO = "\U0001f360" +ODEN = "\U0001f362" +SUSHI = "\U0001f363" +FRIED_SHRIMP = "\U0001f364" +FISH_CAKE_WITH_SWIRL = "\U0001f365" +MOON_CAKE = "\U0001f96e" +DANGO = "\U0001f361" +DUMPLING = "\U0001f95f" +FORTUNE_COOKIE = "\U0001f960" +TAKEOUT_BOX = "\U0001f961" +CRAB = "\U0001f980" +LOBSTER = "\U0001f99e" +SHRIMP = "\U0001f990" +SQUID = "\U0001f991" +OYSTER = "\U0001f9aa" +SOFT_ICE_CREAM = "\U0001f366" +SHAVED_ICE = "\U0001f367" +ICE_CREAM = "\U0001f368" +DOUGHNUT = "\U0001f369" +COOKIE = "\U0001f36a" +BIRTHDAY_CAKE = "\U0001f382" +SHORTCAKE = "\U0001f370" +CUPCAKE = "\U0001f9c1" +PIE = "\U0001f967" +CHOCOLATE_BAR = "\U0001f36b" +CANDY = "\U0001f36c" +LOLLIPOP = "\U0001f36d" +CUSTARD = "\U0001f36e" +HONEY_POT = "\U0001f36f" +BABY_BOTTLE = "\U0001f37c" +GLASS_OF_MILK = "\U0001f95b" +HOT_BEVERAGE = "\u2615" +TEAPOT = "\U0001fad6" +TEACUP_WITHOUT_HANDLE = "\U0001f375" +SAKE = "\U0001f376" +BOTTLE_WITH_POPPING_CORK = "\U0001f37e" +WINE_GLASS = "\U0001f377" +COCKTAIL_GLASS = "\U0001f378" +TROPICAL_DRINK = "\U0001f379" +BEER_MUG = "\U0001f37a" +CLINKING_BEER_MUGS = "\U0001f37b" +CLINKING_GLASSES = "\U0001f942" +TUMBLER_GLASS = "\U0001f943" +CUP_WITH_STRAW = "\U0001f964" +BUBBLE_TEA = "\U0001f9cb" +BEVERAGE_BOX = "\U0001f9c3" +MATE = "\U0001f9c9" +ICE = "\U0001f9ca" +CHOPSTICKS = "\U0001f962" +FORK_AND_KNIFE_WITH_PLATE = "\U0001f37d\ufe0f" +FORK_AND_KNIFE = "\U0001f374" +SPOON = "\U0001f944" +KITCHEN_KNIFE = "\U0001f52a" +AMPHORA = "\U0001f3fa" +GLOBE_SHOWING_EUROPE_AFRICA = "\U0001f30d" +GLOBE_SHOWING_AMERICAS = "\U0001f30e" +GLOBE_SHOWING_ASIA_AUSTRALIA = "\U0001f30f" +GLOBE_WITH_MERIDIANS = "\U0001f310" +WORLD_MAP = "\U0001f5fa\ufe0f" +MAP_OF_JAPAN = "\U0001f5fe" +COMPASS = "\U0001f9ed" +SNOW_CAPPED_MOUNTAIN = "\U0001f3d4\ufe0f" +MOUNTAIN = "\u26f0\ufe0f" +VOLCANO = "\U0001f30b" +MOUNT_FUJI = "\U0001f5fb" +CAMPING = "\U0001f3d5\ufe0f" +BEACH_WITH_UMBRELLA = "\U0001f3d6\ufe0f" +DESERT = "\U0001f3dc\ufe0f" +DESERT_ISLAND = "\U0001f3dd\ufe0f" +NATIONAL_PARK = "\U0001f3de\ufe0f" +STADIUM = "\U0001f3df\ufe0f" +CLASSICAL_BUILDING = "\U0001f3db\ufe0f" +BUILDING_CONSTRUCTION = "\U0001f3d7\ufe0f" +BRICK = "\U0001f9f1" +ROCK = "\U0001faa8" +WOOD = "\U0001fab5" +HUT = "\U0001f6d6" +HOUSES = "\U0001f3d8\ufe0f" +DERELICT_HOUSE = "\U0001f3da\ufe0f" +HOUSE = "\U0001f3e0" +HOUSE_WITH_GARDEN = "\U0001f3e1" +OFFICE_BUILDING = "\U0001f3e2" +JAPANESE_POST_OFFICE = "\U0001f3e3" +POST_OFFICE = "\U0001f3e4" +HOSPITAL = "\U0001f3e5" +BANK = "\U0001f3e6" +HOTEL = "\U0001f3e8" +LOVE_HOTEL = "\U0001f3e9" +CONVENIENCE_STORE = "\U0001f3ea" +SCHOOL = "\U0001f3eb" +DEPARTMENT_STORE = "\U0001f3ec" +FACTORY = "\U0001f3ed" +JAPANESE_CASTLE = "\U0001f3ef" +CASTLE = "\U0001f3f0" +WEDDING = "\U0001f492" +TOKYO_TOWER = "\U0001f5fc" +STATUE_OF_LIBERTY = "\U0001f5fd" +CHURCH = "\u26ea" +MOSQUE = "\U0001f54c" +HINDU_TEMPLE = "\U0001f6d5" +SYNAGOGUE = "\U0001f54d" +SHINTO_SHRINE = "\u26e9\ufe0f" +KAABA = "\U0001f54b" +FOUNTAIN = "\u26f2" +TENT = "\u26fa" +FOGGY = "\U0001f301" +NIGHT_WITH_STARS = "\U0001f303" +CITYSCAPE = "\U0001f3d9\ufe0f" +SUNRISE_OVER_MOUNTAINS = "\U0001f304" +SUNRISE = "\U0001f305" +CITYSCAPE_AT_DUSK = "\U0001f306" +SUNSET = "\U0001f307" +BRIDGE_AT_NIGHT = "\U0001f309" +HOT_SPRINGS = "\u2668\ufe0f" +CAROUSEL_HORSE = "\U0001f3a0" +FERRIS_WHEEL = "\U0001f3a1" +ROLLER_COASTER = "\U0001f3a2" +BARBER_POLE = "\U0001f488" +CIRCUS_TENT = "\U0001f3aa" +LOCOMOTIVE = "\U0001f682" +RAILWAY_CAR = "\U0001f683" +HIGH_SPEED_TRAIN = "\U0001f684" +BULLET_TRAIN = "\U0001f685" +TRAIN = "\U0001f686" +METRO = "\U0001f687" +LIGHT_RAIL = "\U0001f688" +STATION = "\U0001f689" +TRAM = "\U0001f68a" +MONORAIL = "\U0001f69d" +MOUNTAIN_RAILWAY = "\U0001f69e" +TRAM_CAR = "\U0001f68b" +BUS = "\U0001f68c" +ONCOMING_BUS = "\U0001f68d" +TROLLEYBUS = "\U0001f68e" +MINIBUS = "\U0001f690" +AMBULANCE = "\U0001f691" +FIRE_ENGINE = "\U0001f692" +POLICE_CAR = "\U0001f693" +ONCOMING_POLICE_CAR = "\U0001f694" +TAXI = "\U0001f695" +ONCOMING_TAXI = "\U0001f696" +AUTOMOBILE = "\U0001f697" +ONCOMING_AUTOMOBILE = "\U0001f698" +SPORT_UTILITY_VEHICLE = "\U0001f699" +PICKUP_TRUCK = "\U0001f6fb" +DELIVERY_TRUCK = "\U0001f69a" +ARTICULATED_LORRY = "\U0001f69b" +TRACTOR = "\U0001f69c" +RACING_CAR = "\U0001f3ce\ufe0f" +MOTORCYCLE = "\U0001f3cd\ufe0f" +MOTOR_SCOOTER = "\U0001f6f5" +MANUAL_WHEELCHAIR = "\U0001f9bd" +MOTORIZED_WHEELCHAIR = "\U0001f9bc" +AUTO_RICKSHAW = "\U0001f6fa" +BICYCLE = "\U0001f6b2" +KICK_SCOOTER = "\U0001f6f4" +SKATEBOARD = "\U0001f6f9" +ROLLER_SKATE = "\U0001f6fc" +BUS_STOP = "\U0001f68f" +MOTORWAY = "\U0001f6e3\ufe0f" +RAILWAY_TRACK = "\U0001f6e4\ufe0f" +OIL_DRUM = "\U0001f6e2\ufe0f" +FUEL_PUMP = "\u26fd" +POLICE_CAR_LIGHT = "\U0001f6a8" +HORIZONTAL_TRAFFIC_LIGHT = "\U0001f6a5" +VERTICAL_TRAFFIC_LIGHT = "\U0001f6a6" +STOP_SIGN = "\U0001f6d1" +CONSTRUCTION = "\U0001f6a7" +ANCHOR = "\u2693" +SAILBOAT = "\u26f5" +CANOE = "\U0001f6f6" +SPEEDBOAT = "\U0001f6a4" +PASSENGER_SHIP = "\U0001f6f3\ufe0f" +FERRY = "\u26f4\ufe0f" +MOTOR_BOAT = "\U0001f6e5\ufe0f" +SHIP = "\U0001f6a2" +AIRPLANE = "\u2708\ufe0f" +SMALL_AIRPLANE = "\U0001f6e9\ufe0f" +AIRPLANE_DEPARTURE = "\U0001f6eb" +AIRPLANE_ARRIVAL = "\U0001f6ec" +PARACHUTE = "\U0001fa82" +SEAT = "\U0001f4ba" +HELICOPTER = "\U0001f681" +SUSPENSION_RAILWAY = "\U0001f69f" +MOUNTAIN_CABLEWAY = "\U0001f6a0" +AERIAL_TRAMWAY = "\U0001f6a1" +SATELLITE = "\U0001f6f0\ufe0f" +ROCKET = "\U0001f680" +FLYING_SAUCER = "\U0001f6f8" +BELLHOP_BELL = "\U0001f6ce\ufe0f" +LUGGAGE = "\U0001f9f3" +HOURGLASS_DONE = "\u231b" +HOURGLASS_NOT_DONE = "\u23f3" +WATCH = "\u231a" +ALARM_CLOCK = "\u23f0" +STOPWATCH = "\u23f1\ufe0f" +TIMER_CLOCK = "\u23f2\ufe0f" +MANTELPIECE_CLOCK = "\U0001f570\ufe0f" +TWELVE_O_CLOCK = "\U0001f55b" +TWELVE_THIRTY = "\U0001f567" +ONE_O_CLOCK = "\U0001f550" +ONE_THIRTY = "\U0001f55c" +TWO_O_CLOCK = "\U0001f551" +TWO_THIRTY = "\U0001f55d" +THREE_O_CLOCK = "\U0001f552" +THREE_THIRTY = "\U0001f55e" +FOUR_O_CLOCK = "\U0001f553" +FOUR_THIRTY = "\U0001f55f" +FIVE_O_CLOCK = "\U0001f554" +FIVE_THIRTY = "\U0001f560" +SIX_O_CLOCK = "\U0001f555" +SIX_THIRTY = "\U0001f561" +SEVEN_O_CLOCK = "\U0001f556" +SEVEN_THIRTY = "\U0001f562" +EIGHT_O_CLOCK = "\U0001f557" +EIGHT_THIRTY = "\U0001f563" +NINE_O_CLOCK = "\U0001f558" +NINE_THIRTY = "\U0001f564" +TEN_O_CLOCK = "\U0001f559" +TEN_THIRTY = "\U0001f565" +ELEVEN_O_CLOCK = "\U0001f55a" +ELEVEN_THIRTY = "\U0001f566" +NEW_MOON = "\U0001f311" +WAXING_CRESCENT_MOON = "\U0001f312" +FIRST_QUARTER_MOON = "\U0001f313" +WAXING_GIBBOUS_MOON = "\U0001f314" +FULL_MOON = "\U0001f315" +WANING_GIBBOUS_MOON = "\U0001f316" +LAST_QUARTER_MOON = "\U0001f317" +WANING_CRESCENT_MOON = "\U0001f318" +CRESCENT_MOON = "\U0001f319" +NEW_MOON_FACE = "\U0001f31a" +FIRST_QUARTER_MOON_FACE = "\U0001f31b" +LAST_QUARTER_MOON_FACE = "\U0001f31c" +THERMOMETER = "\U0001f321\ufe0f" +SUN = "\u2600\ufe0f" +FULL_MOON_FACE = "\U0001f31d" +SUN_WITH_FACE = "\U0001f31e" +RINGED_PLANET = "\U0001fa90" +STAR = "\u2b50" +GLOWING_STAR = "\U0001f31f" +SHOOTING_STAR = "\U0001f320" +MILKY_WAY = "\U0001f30c" +CLOUD = "\u2601\ufe0f" +SUN_BEHIND_CLOUD = "\u26c5" +CLOUD_WITH_LIGHTNING_AND_RAIN = "\u26c8\ufe0f" +SUN_BEHIND_SMALL_CLOUD = "\U0001f324\ufe0f" +SUN_BEHIND_LARGE_CLOUD = "\U0001f325\ufe0f" +SUN_BEHIND_RAIN_CLOUD = "\U0001f326\ufe0f" +CLOUD_WITH_RAIN = "\U0001f327\ufe0f" +CLOUD_WITH_SNOW = "\U0001f328\ufe0f" +CLOUD_WITH_LIGHTNING = "\U0001f329\ufe0f" +TORNADO = "\U0001f32a\ufe0f" +FOG = "\U0001f32b\ufe0f" +WIND_FACE = "\U0001f32c\ufe0f" +CYCLONE = "\U0001f300" +RAINBOW = "\U0001f308" +CLOSED_UMBRELLA = "\U0001f302" +UMBRELLA = "\u2602\ufe0f" +UMBRELLA_WITH_RAIN_DROPS = "\u2614" +UMBRELLA_ON_GROUND = "\u26f1\ufe0f" +HIGH_VOLTAGE = "\u26a1" +SNOWFLAKE = "\u2744\ufe0f" +SNOWMAN = "\u2603\ufe0f" +SNOWMAN_WITHOUT_SNOW = "\u26c4" +COMET = "\u2604\ufe0f" +FIRE = "\U0001f525" +DROPLET = "\U0001f4a7" +WATER_WAVE = "\U0001f30a" +JACK_O_LANTERN = "\U0001f383" +CHRISTMAS_TREE = "\U0001f384" +FIREWORKS = "\U0001f386" +SPARKLER = "\U0001f387" +FIRECRACKER = "\U0001f9e8" +SPARKLES = "\u2728" +BALLOON = "\U0001f388" +PARTY_POPPER = "\U0001f389" +CONFETTI_BALL = "\U0001f38a" +TANABATA_TREE = "\U0001f38b" +PINE_DECORATION = "\U0001f38d" +JAPANESE_DOLLS = "\U0001f38e" +CARP_STREAMER = "\U0001f38f" +WIND_CHIME = "\U0001f390" +MOON_VIEWING_CEREMONY = "\U0001f391" +RED_ENVELOPE = "\U0001f9e7" +RIBBON = "\U0001f380" +WRAPPED_GIFT = "\U0001f381" +REMINDER_RIBBON = "\U0001f397\ufe0f" +ADMISSION_TICKETS = "\U0001f39f\ufe0f" +TICKET = "\U0001f3ab" +MILITARY_MEDAL = "\U0001f396\ufe0f" +TROPHY = "\U0001f3c6" +SPORTS_MEDAL = "\U0001f3c5" +FIRST_PLACE_MEDAL = "\U0001f947" +SECOND_PLACE_MEDAL = "\U0001f948" +THIRD_PLACE_MEDAL = "\U0001f949" +SOCCER_BALL = "\u26bd" +BASEBALL = "\u26be" +SOFTBALL = "\U0001f94e" +BASKETBALL = "\U0001f3c0" +VOLLEYBALL = "\U0001f3d0" +AMERICAN_FOOTBALL = "\U0001f3c8" +RUGBY_FOOTBALL = "\U0001f3c9" +TENNIS = "\U0001f3be" +FLYING_DISC = "\U0001f94f" +BOWLING = "\U0001f3b3" +CRICKET_GAME = "\U0001f3cf" +FIELD_HOCKEY = "\U0001f3d1" +ICE_HOCKEY = "\U0001f3d2" +LACROSSE = "\U0001f94d" +PING_PONG = "\U0001f3d3" +BADMINTON = "\U0001f3f8" +BOXING_GLOVE = "\U0001f94a" +MARTIAL_ARTS_UNIFORM = "\U0001f94b" +GOAL_NET = "\U0001f945" +FLAG_IN_HOLE = "\u26f3" +ICE_SKATE = "\u26f8\ufe0f" +FISHING_POLE = "\U0001f3a3" +DIVING_MASK = "\U0001f93f" +RUNNING_SHIRT = "\U0001f3bd" +SKIS = "\U0001f3bf" +SLED = "\U0001f6f7" +CURLING_STONE = "\U0001f94c" +DIRECT_HIT = "\U0001f3af" +YO_YO = "\U0001fa80" +KITE = "\U0001fa81" +POOL_8_BALL = "\U0001f3b1" +CRYSTAL_BALL = "\U0001f52e" +MAGIC_WAND = "\U0001fa84" +NAZAR_AMULET = "\U0001f9ff" +VIDEO_GAME = "\U0001f3ae" +JOYSTICK = "\U0001f579\ufe0f" +SLOT_MACHINE = "\U0001f3b0" +GAME_DIE = "\U0001f3b2" +PUZZLE_PIECE = "\U0001f9e9" +TEDDY_BEAR = "\U0001f9f8" +PINATA = "\U0001fa85" +NESTING_DOLLS = "\U0001fa86" +SPADE_SUIT = "\u2660\ufe0f" +HEART_SUIT = "\u2665\ufe0f" +DIAMOND_SUIT = "\u2666\ufe0f" +CLUB_SUIT = "\u2663\ufe0f" +CHESS_PAWN = "\u265f\ufe0f" +JOKER = "\U0001f0cf" +MAHJONG_RED_DRAGON = "\U0001f004" +FLOWER_PLAYING_CARDS = "\U0001f3b4" +PERFORMING_ARTS = "\U0001f3ad" +FRAMED_PICTURE = "\U0001f5bc\ufe0f" +ARTIST_PALETTE = "\U0001f3a8" +THREAD = "\U0001f9f5" +SEWING_NEEDLE = "\U0001faa1" +YARN = "\U0001f9f6" +KNOT = "\U0001faa2" +GLASSES = "\U0001f453" +SUNGLASSES = "\U0001f576\ufe0f" +GOGGLES = "\U0001f97d" +LAB_COAT = "\U0001f97c" +SAFETY_VEST = "\U0001f9ba" +NECKTIE = "\U0001f454" +T_SHIRT = "\U0001f455" +JEANS = "\U0001f456" +SCARF = "\U0001f9e3" +GLOVES = "\U0001f9e4" +COAT = "\U0001f9e5" +SOCKS = "\U0001f9e6" +DRESS = "\U0001f457" +KIMONO = "\U0001f458" +SARI = "\U0001f97b" +ONE_PIECE_SWIMSUIT = "\U0001fa71" +BRIEFS = "\U0001fa72" +SHORTS = "\U0001fa73" +BIKINI = "\U0001f459" +WOMAN_S_CLOTHES = "\U0001f45a" +PURSE = "\U0001f45b" +HANDBAG = "\U0001f45c" +CLUTCH_BAG = "\U0001f45d" +SHOPPING_BAGS = "\U0001f6cd\ufe0f" +BACKPACK = "\U0001f392" +THONG_SANDAL = "\U0001fa74" +MAN_S_SHOE = "\U0001f45e" +RUNNING_SHOE = "\U0001f45f" +HIKING_BOOT = "\U0001f97e" +FLAT_SHOE = "\U0001f97f" +HIGH_HEELED_SHOE = "\U0001f460" +WOMAN_S_SANDAL = "\U0001f461" +BALLET_SHOES = "\U0001fa70" +WOMAN_S_BOOT = "\U0001f462" +CROWN = "\U0001f451" +WOMAN_S_HAT = "\U0001f452" +TOP_HAT = "\U0001f3a9" +GRADUATION_CAP = "\U0001f393" +BILLED_CAP = "\U0001f9e2" +MILITARY_HELMET = "\U0001fa96" +RESCUE_WORKER_S_HELMET = "\u26d1\ufe0f" +PRAYER_BEADS = "\U0001f4ff" +LIPSTICK = "\U0001f484" +RING = "\U0001f48d" +GEM_STONE = "\U0001f48e" +MUTED_SPEAKER = "\U0001f507" +SPEAKER_LOW_VOLUME = "\U0001f508" +SPEAKER_MEDIUM_VOLUME = "\U0001f509" +SPEAKER_HIGH_VOLUME = "\U0001f50a" +LOUDSPEAKER = "\U0001f4e2" +MEGAPHONE = "\U0001f4e3" +POSTAL_HORN = "\U0001f4ef" +BELL = "\U0001f514" +BELL_WITH_SLASH = "\U0001f515" +MUSICAL_SCORE = "\U0001f3bc" +MUSICAL_NOTE = "\U0001f3b5" +MUSICAL_NOTES = "\U0001f3b6" +STUDIO_MICROPHONE = "\U0001f399\ufe0f" +LEVEL_SLIDER = "\U0001f39a\ufe0f" +CONTROL_KNOBS = "\U0001f39b\ufe0f" +MICROPHONE = "\U0001f3a4" +HEADPHONE = "\U0001f3a7" +RADIO = "\U0001f4fb" +SAXOPHONE = "\U0001f3b7" +ACCORDION = "\U0001fa97" +GUITAR = "\U0001f3b8" +MUSICAL_KEYBOARD = "\U0001f3b9" +TRUMPET = "\U0001f3ba" +VIOLIN = "\U0001f3bb" +BANJO = "\U0001fa95" +DRUM = "\U0001f941" +LONG_DRUM = "\U0001fa98" +MOBILE_PHONE = "\U0001f4f1" +MOBILE_PHONE_WITH_ARROW = "\U0001f4f2" +TELEPHONE = "\u260e\ufe0f" +TELEPHONE_RECEIVER = "\U0001f4de" +PAGER = "\U0001f4df" +FAX_MACHINE = "\U0001f4e0" +BATTERY = "\U0001f50b" +ELECTRIC_PLUG = "\U0001f50c" +LAPTOP = "\U0001f4bb" +DESKTOP_COMPUTER = "\U0001f5a5\ufe0f" +PRINTER = "\U0001f5a8\ufe0f" +KEYBOARD = "\u2328\ufe0f" +COMPUTER_MOUSE = "\U0001f5b1\ufe0f" +TRACKBALL = "\U0001f5b2\ufe0f" +COMPUTER_DISK = "\U0001f4bd" +FLOPPY_DISK = "\U0001f4be" +OPTICAL_DISK = "\U0001f4bf" +DVD = "\U0001f4c0" +ABACUS = "\U0001f9ee" +MOVIE_CAMERA = "\U0001f3a5" +FILM_FRAMES = "\U0001f39e\ufe0f" +FILM_PROJECTOR = "\U0001f4fd\ufe0f" +CLAPPER_BOARD = "\U0001f3ac" +TELEVISION = "\U0001f4fa" +CAMERA = "\U0001f4f7" +CAMERA_WITH_FLASH = "\U0001f4f8" +VIDEO_CAMERA = "\U0001f4f9" +VIDEOCASSETTE = "\U0001f4fc" +MAGNIFYING_GLASS_TILTED_LEFT = "\U0001f50d" +MAGNIFYING_GLASS_TILTED_RIGHT = "\U0001f50e" +CANDLE = "\U0001f56f\ufe0f" +LIGHT_BULB = "\U0001f4a1" +FLASHLIGHT = "\U0001f526" +RED_PAPER_LANTERN = "\U0001f3ee" +DIYA_LAMP = "\U0001fa94" +NOTEBOOK_WITH_DECORATIVE_COVER = "\U0001f4d4" +CLOSED_BOOK = "\U0001f4d5" +OPEN_BOOK = "\U0001f4d6" +GREEN_BOOK = "\U0001f4d7" +BLUE_BOOK = "\U0001f4d8" +ORANGE_BOOK = "\U0001f4d9" +BOOKS = "\U0001f4da" +NOTEBOOK = "\U0001f4d3" +LEDGER = "\U0001f4d2" +PAGE_WITH_CURL = "\U0001f4c3" +SCROLL = "\U0001f4dc" +PAGE_FACING_UP = "\U0001f4c4" +NEWSPAPER = "\U0001f4f0" +ROLLED_UP_NEWSPAPER = "\U0001f5de\ufe0f" +BOOKMARK_TABS = "\U0001f4d1" +BOOKMARK = "\U0001f516" +LABEL = "\U0001f3f7\ufe0f" +MONEY_BAG = "\U0001f4b0" +COIN = "\U0001fa99" +YEN_BANKNOTE = "\U0001f4b4" +DOLLAR_BANKNOTE = "\U0001f4b5" +EURO_BANKNOTE = "\U0001f4b6" +POUND_BANKNOTE = "\U0001f4b7" +MONEY_WITH_WINGS = "\U0001f4b8" +CREDIT_CARD = "\U0001f4b3" +RECEIPT = "\U0001f9fe" +CHART_INCREASING_WITH_YEN = "\U0001f4b9" +ENVELOPE = "\u2709\ufe0f" +E_MAIL = "\U0001f4e7" +INCOMING_ENVELOPE = "\U0001f4e8" +ENVELOPE_WITH_ARROW = "\U0001f4e9" +OUTBOX_TRAY = "\U0001f4e4" +INBOX_TRAY = "\U0001f4e5" +PACKAGE = "\U0001f4e6" +CLOSED_MAILBOX_WITH_RAISED_FLAG = "\U0001f4eb" +CLOSED_MAILBOX_WITH_LOWERED_FLAG = "\U0001f4ea" +OPEN_MAILBOX_WITH_RAISED_FLAG = "\U0001f4ec" +OPEN_MAILBOX_WITH_LOWERED_FLAG = "\U0001f4ed" +POSTBOX = "\U0001f4ee" +BALLOT_BOX_WITH_BALLOT = "\U0001f5f3\ufe0f" +PENCIL = "\u270f\ufe0f" +BLACK_NIB = "\u2712\ufe0f" +FOUNTAIN_PEN = "\U0001f58b\ufe0f" +PEN = "\U0001f58a\ufe0f" +PAINTBRUSH = "\U0001f58c\ufe0f" +CRAYON = "\U0001f58d\ufe0f" +MEMO = "\U0001f4dd" +BRIEFCASE = "\U0001f4bc" +FILE_FOLDER = "\U0001f4c1" +OPEN_FILE_FOLDER = "\U0001f4c2" +CARD_INDEX_DIVIDERS = "\U0001f5c2\ufe0f" +CALENDAR = "\U0001f4c5" +TEAR_OFF_CALENDAR = "\U0001f4c6" +SPIRAL_NOTEPAD = "\U0001f5d2\ufe0f" +SPIRAL_CALENDAR = "\U0001f5d3\ufe0f" +CARD_INDEX = "\U0001f4c7" +CHART_INCREASING = "\U0001f4c8" +CHART_DECREASING = "\U0001f4c9" +BAR_CHART = "\U0001f4ca" +CLIPBOARD = "\U0001f4cb" +PUSHPIN = "\U0001f4cc" +ROUND_PUSHPIN = "\U0001f4cd" +PAPERCLIP = "\U0001f4ce" +LINKED_PAPERCLIPS = "\U0001f587\ufe0f" +STRAIGHT_RULER = "\U0001f4cf" +TRIANGULAR_RULER = "\U0001f4d0" +SCISSORS = "\u2702\ufe0f" +CARD_FILE_BOX = "\U0001f5c3\ufe0f" +FILE_CABINET = "\U0001f5c4\ufe0f" +WASTEBASKET = "\U0001f5d1\ufe0f" +LOCKED = "\U0001f512" +UNLOCKED = "\U0001f513" +LOCKED_WITH_PEN = "\U0001f50f" +LOCKED_WITH_KEY = "\U0001f510" +KEY = "\U0001f511" +OLD_KEY = "\U0001f5dd\ufe0f" +HAMMER = "\U0001f528" +AXE = "\U0001fa93" +PICK = "\u26cf\ufe0f" +HAMMER_AND_PICK = "\u2692\ufe0f" +HAMMER_AND_WRENCH = "\U0001f6e0\ufe0f" +DAGGER = "\U0001f5e1\ufe0f" +CROSSED_SWORDS = "\u2694\ufe0f" +PISTOL = "\U0001f52b" +BOOMERANG = "\U0001fa83" +BOW_AND_ARROW = "\U0001f3f9" +SHIELD = "\U0001f6e1\ufe0f" +CARPENTRY_SAW = "\U0001fa9a" +WRENCH = "\U0001f527" +SCREWDRIVER = "\U0001fa9b" +NUT_AND_BOLT = "\U0001f529" +GEAR = "\u2699\ufe0f" +CLAMP = "\U0001f5dc\ufe0f" +BALANCE_SCALE = "\u2696\ufe0f" +WHITE_CANE = "\U0001f9af" +LINK = "\U0001f517" +CHAINS = "\u26d3\ufe0f" +HOOK = "\U0001fa9d" +TOOLBOX = "\U0001f9f0" +MAGNET = "\U0001f9f2" +LADDER = "\U0001fa9c" +ALEMBIC = "\u2697\ufe0f" +TEST_TUBE = "\U0001f9ea" +PETRI_DISH = "\U0001f9eb" +DNA = "\U0001f9ec" +MICROSCOPE = "\U0001f52c" +TELESCOPE = "\U0001f52d" +SATELLITE_ANTENNA = "\U0001f4e1" +SYRINGE = "\U0001f489" +DROP_OF_BLOOD = "\U0001fa78" +PILL = "\U0001f48a" +ADHESIVE_BANDAGE = "\U0001fa79" +STETHOSCOPE = "\U0001fa7a" +DOOR = "\U0001f6aa" +ELEVATOR = "\U0001f6d7" +MIRROR = "\U0001fa9e" +WINDOW = "\U0001fa9f" +BED = "\U0001f6cf\ufe0f" +COUCH_AND_LAMP = "\U0001f6cb\ufe0f" +CHAIR = "\U0001fa91" +TOILET = "\U0001f6bd" +PLUNGER = "\U0001faa0" +SHOWER = "\U0001f6bf" +BATHTUB = "\U0001f6c1" +MOUSE_TRAP = "\U0001faa4" +RAZOR = "\U0001fa92" +LOTION_BOTTLE = "\U0001f9f4" +SAFETY_PIN = "\U0001f9f7" +BROOM = "\U0001f9f9" +BASKET = "\U0001f9fa" +ROLL_OF_PAPER = "\U0001f9fb" +BUCKET = "\U0001faa3" +SOAP = "\U0001f9fc" +TOOTHBRUSH = "\U0001faa5" +SPONGE = "\U0001f9fd" +FIRE_EXTINGUISHER = "\U0001f9ef" +SHOPPING_CART = "\U0001f6d2" +CIGARETTE = "\U0001f6ac" +COFFIN = "\u26b0\ufe0f" +HEADSTONE = "\U0001faa6" +FUNERAL_URN = "\u26b1\ufe0f" +MOAI = "\U0001f5ff" +PLACARD = "\U0001faa7" +ATM_SIGN = "\U0001f3e7" +LITTER_IN_BIN_SIGN = "\U0001f6ae" +POTABLE_WATER = "\U0001f6b0" +WHEELCHAIR_SYMBOL = "\u267f" +MEN_S_ROOM = "\U0001f6b9" +WOMEN_S_ROOM = "\U0001f6ba" +RESTROOM = "\U0001f6bb" +BABY_SYMBOL = "\U0001f6bc" +WATER_CLOSET = "\U0001f6be" +PASSPORT_CONTROL = "\U0001f6c2" +CUSTOMS = "\U0001f6c3" +BAGGAGE_CLAIM = "\U0001f6c4" +LEFT_LUGGAGE = "\U0001f6c5" +WARNING = "\u26a0\ufe0f" +CHILDREN_CROSSING = "\U0001f6b8" +NO_ENTRY = "\u26d4" +PROHIBITED = "\U0001f6ab" +NO_BICYCLES = "\U0001f6b3" +NO_SMOKING = "\U0001f6ad" +NO_LITTERING = "\U0001f6af" +NON_POTABLE_WATER = "\U0001f6b1" +NO_PEDESTRIANS = "\U0001f6b7" +NO_MOBILE_PHONES = "\U0001f4f5" +NO_ONE_UNDER_EIGHTEEN = "\U0001f51e" +RADIOACTIVE = "\u2622\ufe0f" +BIOHAZARD = "\u2623\ufe0f" +UP_ARROW = "\u2b06\ufe0f" +UP_RIGHT_ARROW = "\u2197\ufe0f" +RIGHT_ARROW = "\u27a1\ufe0f" +DOWN_RIGHT_ARROW = "\u2198\ufe0f" +DOWN_ARROW = "\u2b07\ufe0f" +DOWN_LEFT_ARROW = "\u2199\ufe0f" +LEFT_ARROW = "\u2b05\ufe0f" +UP_LEFT_ARROW = "\u2196\ufe0f" +UP_DOWN_ARROW = "\u2195\ufe0f" +LEFT_RIGHT_ARROW = "\u2194\ufe0f" +RIGHT_ARROW_CURVING_LEFT = "\u21a9\ufe0f" +LEFT_ARROW_CURVING_RIGHT = "\u21aa\ufe0f" +RIGHT_ARROW_CURVING_UP = "\u2934\ufe0f" +RIGHT_ARROW_CURVING_DOWN = "\u2935\ufe0f" +CLOCKWISE_VERTICAL_ARROWS = "\U0001f503" +COUNTERCLOCKWISE_ARROWS_BUTTON = "\U0001f504" +BACK_ARROW = "\U0001f519" +END_ARROW = "\U0001f51a" +ON_ARROW = "\U0001f51b" +SOON_ARROW = "\U0001f51c" +TOP_ARROW = "\U0001f51d" +PLACE_OF_WORSHIP = "\U0001f6d0" +ATOM_SYMBOL = "\u269b\ufe0f" +OM = "\U0001f549\ufe0f" +STAR_OF_DAVID = "\u2721\ufe0f" +WHEEL_OF_DHARMA = "\u2638\ufe0f" +YIN_YANG = "\u262f\ufe0f" +LATIN_CROSS = "\u271d\ufe0f" +ORTHODOX_CROSS = "\u2626\ufe0f" +STAR_AND_CRESCENT = "\u262a\ufe0f" +PEACE_SYMBOL = "\u262e\ufe0f" +MENORAH = "\U0001f54e" +DOTTED_SIX_POINTED_STAR = "\U0001f52f" +ARIES = "\u2648" +TAURUS = "\u2649" +GEMINI = "\u264a" +CANCER = "\u264b" +LEO = "\u264c" +VIRGO = "\u264d" +LIBRA = "\u264e" +SCORPIO = "\u264f" +SAGITTARIUS = "\u2650" +CAPRICORN = "\u2651" +AQUARIUS = "\u2652" +PISCES = "\u2653" +OPHIUCHUS = "\u26ce" +SHUFFLE_TRACKS_BUTTON = "\U0001f500" +REPEAT_BUTTON = "\U0001f501" +REPEAT_SINGLE_BUTTON = "\U0001f502" +PLAY_BUTTON = "\u25b6\ufe0f" +FAST_FORWARD_BUTTON = "\u23e9" +NEXT_TRACK_BUTTON = "\u23ed\ufe0f" +PLAY_OR_PAUSE_BUTTON = "\u23ef\ufe0f" +REVERSE_BUTTON = "\u25c0\ufe0f" +FAST_REVERSE_BUTTON = "\u23ea" +LAST_TRACK_BUTTON = "\u23ee\ufe0f" +UPWARDS_BUTTON = "\U0001f53c" +FAST_UP_BUTTON = "\u23eb" +DOWNWARDS_BUTTON = "\U0001f53d" +FAST_DOWN_BUTTON = "\u23ec" +PAUSE_BUTTON = "\u23f8\ufe0f" +STOP_BUTTON = "\u23f9\ufe0f" +RECORD_BUTTON = "\u23fa\ufe0f" +EJECT_BUTTON = "\u23cf\ufe0f" +CINEMA = "\U0001f3a6" +DIM_BUTTON = "\U0001f505" +BRIGHT_BUTTON = "\U0001f506" +ANTENNA_BARS = "\U0001f4f6" +VIBRATION_MODE = "\U0001f4f3" +MOBILE_PHONE_OFF = "\U0001f4f4" +FEMALE_SIGN = "\u2640\ufe0f" +MALE_SIGN = "\u2642\ufe0f" +TRANSGENDER_SYMBOL = "\u26a7\ufe0f" +MULTIPLY = "\u2716\ufe0f" +PLUS = "\u2795" +MINUS = "\u2796" +DIVIDE = "\u2797" +INFINITY = "\u267e\ufe0f" +DOUBLE_EXCLAMATION_MARK = "\u203c\ufe0f" +EXCLAMATION_QUESTION_MARK = "\u2049\ufe0f" +QUESTION_MARK = "\u2753" +WHITE_QUESTION_MARK = "\u2754" +WHITE_EXCLAMATION_MARK = "\u2755" +EXCLAMATION_MARK = "\u2757" +WAVY_DASH = "\u3030\ufe0f" +CURRENCY_EXCHANGE = "\U0001f4b1" +HEAVY_DOLLAR_SIGN = "\U0001f4b2" +MEDICAL_SYMBOL = "\u2695\ufe0f" +RECYCLING_SYMBOL = "\u267b\ufe0f" +FLEUR_DE_LIS = "\u269c\ufe0f" +TRIDENT_EMBLEM = "\U0001f531" +NAME_BADGE = "\U0001f4db" +JAPANESE_SYMBOL_FOR_BEGINNER = "\U0001f530" +HOLLOW_RED_CIRCLE = "\u2b55" +CHECK_MARK_BUTTON = "\u2705" +CHECK_BOX_WITH_CHECK = "\u2611\ufe0f" +CHECK_MARK = "\u2714\ufe0f" +CROSS_MARK = "\u274c" +CROSS_MARK_BUTTON = "\u274e" +CURLY_LOOP = "\u27b0" +DOUBLE_CURLY_LOOP = "\u27bf" +PART_ALTERNATION_MARK = "\u303d\ufe0f" +EIGHT_SPOKED_ASTERISK = "\u2733\ufe0f" +EIGHT_POINTED_STAR = "\u2734\ufe0f" +SPARKLE = "\u2747\ufe0f" +COPYRIGHT = "\xa9\ufe0f" +REGISTERED = "\xae\ufe0f" +TRADE_MARK = "\u2122\ufe0f" +KEYCAP_NUMBER_SIGN = "#\ufe0f\u20e3" +KEYCAP_ASTERISK = "*\ufe0f\u20e3" +KEYCAP_DIGIT_ZERO = "0\ufe0f\u20e3" +KEYCAP_DIGIT_ONE = "1\ufe0f\u20e3" +KEYCAP_DIGIT_TWO = "2\ufe0f\u20e3" +KEYCAP_DIGIT_THREE = "3\ufe0f\u20e3" +KEYCAP_DIGIT_FOUR = "4\ufe0f\u20e3" +KEYCAP_DIGIT_FIVE = "5\ufe0f\u20e3" +KEYCAP_DIGIT_SIX = "6\ufe0f\u20e3" +KEYCAP_DIGIT_SEVEN = "7\ufe0f\u20e3" +KEYCAP_DIGIT_EIGHT = "8\ufe0f\u20e3" +KEYCAP_DIGIT_NINE = "9\ufe0f\u20e3" +KEYCAP_10 = "\U0001f51f" +INPUT_LATIN_UPPERCASE = "\U0001f520" +INPUT_LATIN_LOWERCASE = "\U0001f521" +INPUT_NUMBERS = "\U0001f522" +INPUT_SYMBOLS = "\U0001f523" +INPUT_LATIN_LETTERS = "\U0001f524" +A_BUTTON_BLOOD_TYPE = "\U0001f170\ufe0f" +AB_BUTTON_BLOOD_TYPE = "\U0001f18e" +B_BUTTON_BLOOD_TYPE = "\U0001f171\ufe0f" +CL_BUTTON = "\U0001f191" +COOL_BUTTON = "\U0001f192" +FREE_BUTTON = "\U0001f193" +INFORMATION = "\u2139\ufe0f" +ID_BUTTON = "\U0001f194" +CIRCLED_M = "\u24c2\ufe0f" +NEW_BUTTON = "\U0001f195" +NG_BUTTON = "\U0001f196" +O_BUTTON_BLOOD_TYPE = "\U0001f17e\ufe0f" +OK_BUTTON = "\U0001f197" +P_BUTTON = "\U0001f17f\ufe0f" +SOS_BUTTON = "\U0001f198" +UP_BUTTON = "\U0001f199" +VS_BUTTON = "\U0001f19a" +JAPANESE_HERE_BUTTON = "\U0001f201" +JAPANESE_SERVICE_CHARGE_BUTTON = "\U0001f202\ufe0f" +JAPANESE_MONTHLY_AMOUNT_BUTTON = "\U0001f237\ufe0f" +JAPANESE_NOT_FREE_OF_CHARGE_BUTTON = "\U0001f236" +JAPANESE_RESERVED_BUTTON = "\U0001f22f" +JAPANESE_BARGAIN_BUTTON = "\U0001f250" +JAPANESE_DISCOUNT_BUTTON = "\U0001f239" +JAPANESE_FREE_OF_CHARGE_BUTTON = "\U0001f21a" +JAPANESE_PROHIBITED_BUTTON = "\U0001f232" +JAPANESE_ACCEPTABLE_BUTTON = "\U0001f251" +JAPANESE_APPLICATION_BUTTON = "\U0001f238" +JAPANESE_PASSING_GRADE_BUTTON = "\U0001f234" +JAPANESE_VACANCY_BUTTON = "\U0001f233" +JAPANESE_CONGRATULATIONS_BUTTON = "\u3297\ufe0f" +JAPANESE_SECRET_BUTTON = "\u3299\ufe0f" +JAPANESE_OPEN_FOR_BUSINESS_BUTTON = "\U0001f23a" +JAPANESE_NO_VACANCY_BUTTON = "\U0001f235" +RED_CIRCLE = "\U0001f534" +ORANGE_CIRCLE = "\U0001f7e0" +YELLOW_CIRCLE = "\U0001f7e1" +GREEN_CIRCLE = "\U0001f7e2" +BLUE_CIRCLE = "\U0001f535" +PURPLE_CIRCLE = "\U0001f7e3" +BROWN_CIRCLE = "\U0001f7e4" +BLACK_CIRCLE = "\u26ab" +WHITE_CIRCLE = "\u26aa" +RED_SQUARE = "\U0001f7e5" +ORANGE_SQUARE = "\U0001f7e7" +YELLOW_SQUARE = "\U0001f7e8" +GREEN_SQUARE = "\U0001f7e9" +BLUE_SQUARE = "\U0001f7e6" +PURPLE_SQUARE = "\U0001f7ea" +BROWN_SQUARE = "\U0001f7eb" +BLACK_LARGE_SQUARE = "\u2b1b" +WHITE_LARGE_SQUARE = "\u2b1c" +BLACK_MEDIUM_SQUARE = "\u25fc\ufe0f" +WHITE_MEDIUM_SQUARE = "\u25fb\ufe0f" +BLACK_MEDIUM_SMALL_SQUARE = "\u25fe" +WHITE_MEDIUM_SMALL_SQUARE = "\u25fd" +BLACK_SMALL_SQUARE = "\u25aa\ufe0f" +WHITE_SMALL_SQUARE = "\u25ab\ufe0f" +LARGE_ORANGE_DIAMOND = "\U0001f536" +LARGE_BLUE_DIAMOND = "\U0001f537" +SMALL_ORANGE_DIAMOND = "\U0001f538" +SMALL_BLUE_DIAMOND = "\U0001f539" +RED_TRIANGLE_POINTED_UP = "\U0001f53a" +RED_TRIANGLE_POINTED_DOWN = "\U0001f53b" +DIAMOND_WITH_A_DOT = "\U0001f4a0" +RADIO_BUTTON = "\U0001f518" +WHITE_SQUARE_BUTTON = "\U0001f533" +BLACK_SQUARE_BUTTON = "\U0001f532" +CHEQUERED_FLAG = "\U0001f3c1" +TRIANGULAR_FLAG = "\U0001f6a9" +CROSSED_FLAGS = "\U0001f38c" +BLACK_FLAG = "\U0001f3f4" +WHITE_FLAG = "\U0001f3f3\ufe0f" +RAINBOW_FLAG = "\U0001f3f3\ufe0f\u200d\U0001f308" +TRANSGENDER_FLAG = "\U0001f3f3\ufe0f\u200d\u26a7\ufe0f" +PIRATE_FLAG = "\U0001f3f4\u200d\u2620\ufe0f" +FLAG_ASCENSION_ISLAND = "\U0001f1e6\U0001f1e8" +FLAG_ANDORRA = "\U0001f1e6\U0001f1e9" +FLAG_UNITED_ARAB_EMIRATES = "\U0001f1e6\U0001f1ea" +FLAG_AFGHANISTAN = "\U0001f1e6\U0001f1eb" +FLAG_ANTIGUA_ANDAMP_BARBUDA = "\U0001f1e6\U0001f1ec" +FLAG_ANGUILLA = "\U0001f1e6\U0001f1ee" +FLAG_ALBANIA = "\U0001f1e6\U0001f1f1" +FLAG_ARMENIA = "\U0001f1e6\U0001f1f2" +FLAG_ANGOLA = "\U0001f1e6\U0001f1f4" +FLAG_ANTARCTICA = "\U0001f1e6\U0001f1f6" +FLAG_ARGENTINA = "\U0001f1e6\U0001f1f7" +FLAG_AMERICAN_SAMOA = "\U0001f1e6\U0001f1f8" +FLAG_AUSTRIA = "\U0001f1e6\U0001f1f9" +FLAG_AUSTRALIA = "\U0001f1e6\U0001f1fa" +FLAG_ARUBA = "\U0001f1e6\U0001f1fc" +FLAG_ALAND_ISLANDS = "\U0001f1e6\U0001f1fd" +FLAG_AZERBAIJAN = "\U0001f1e6\U0001f1ff" +FLAG_BOSNIA_ANDAMP_HERZEGOVINA = "\U0001f1e7\U0001f1e6" +FLAG_BARBADOS = "\U0001f1e7\U0001f1e7" +FLAG_BANGLADESH = "\U0001f1e7\U0001f1e9" +FLAG_BELGIUM = "\U0001f1e7\U0001f1ea" +FLAG_BURKINA_FASO = "\U0001f1e7\U0001f1eb" +FLAG_BULGARIA = "\U0001f1e7\U0001f1ec" +FLAG_BAHRAIN = "\U0001f1e7\U0001f1ed" +FLAG_BURUNDI = "\U0001f1e7\U0001f1ee" +FLAG_BENIN = "\U0001f1e7\U0001f1ef" +FLAG_ST_BARTHELEMY = "\U0001f1e7\U0001f1f1" +FLAG_BERMUDA = "\U0001f1e7\U0001f1f2" +FLAG_BRUNEI = "\U0001f1e7\U0001f1f3" +FLAG_BOLIVIA = "\U0001f1e7\U0001f1f4" +FLAG_CARIBBEAN_NETHERLANDS = "\U0001f1e7\U0001f1f6" +FLAG_BRAZIL = "\U0001f1e7\U0001f1f7" +FLAG_BAHAMAS = "\U0001f1e7\U0001f1f8" +FLAG_BHUTAN = "\U0001f1e7\U0001f1f9" +FLAG_BOUVET_ISLAND = "\U0001f1e7\U0001f1fb" +FLAG_BOTSWANA = "\U0001f1e7\U0001f1fc" +FLAG_BELARUS = "\U0001f1e7\U0001f1fe" +FLAG_BELIZE = "\U0001f1e7\U0001f1ff" +FLAG_CANADA = "\U0001f1e8\U0001f1e6" +FLAG_COCOS_KEELING_ISLANDS = "\U0001f1e8\U0001f1e8" +FLAG_CONGO_KINSHASA = "\U0001f1e8\U0001f1e9" +FLAG_CENTRAL_AFRICAN_REPUBLIC = "\U0001f1e8\U0001f1eb" +FLAG_CONGO_BRAZZAVILLE = "\U0001f1e8\U0001f1ec" +FLAG_SWITZERLAND = "\U0001f1e8\U0001f1ed" +FLAG_COTE_D_IVOIRE = "\U0001f1e8\U0001f1ee" +FLAG_COOK_ISLANDS = "\U0001f1e8\U0001f1f0" +FLAG_CHILE = "\U0001f1e8\U0001f1f1" +FLAG_CAMEROON = "\U0001f1e8\U0001f1f2" +FLAG_CHINA = "\U0001f1e8\U0001f1f3" +FLAG_COLOMBIA = "\U0001f1e8\U0001f1f4" +FLAG_CLIPPERTON_ISLAND = "\U0001f1e8\U0001f1f5" +FLAG_COSTA_RICA = "\U0001f1e8\U0001f1f7" +FLAG_CUBA = "\U0001f1e8\U0001f1fa" +FLAG_CAPE_VERDE = "\U0001f1e8\U0001f1fb" +FLAG_CURACAO = "\U0001f1e8\U0001f1fc" +FLAG_CHRISTMAS_ISLAND = "\U0001f1e8\U0001f1fd" +FLAG_CYPRUS = "\U0001f1e8\U0001f1fe" +FLAG_CZECHIA = "\U0001f1e8\U0001f1ff" +FLAG_GERMANY = "\U0001f1e9\U0001f1ea" +FLAG_DIEGO_GARCIA = "\U0001f1e9\U0001f1ec" +FLAG_DJIBOUTI = "\U0001f1e9\U0001f1ef" +FLAG_DENMARK = "\U0001f1e9\U0001f1f0" +FLAG_DOMINICA = "\U0001f1e9\U0001f1f2" +FLAG_DOMINICAN_REPUBLIC = "\U0001f1e9\U0001f1f4" +FLAG_ALGERIA = "\U0001f1e9\U0001f1ff" +FLAG_CEUTA_ANDAMP_MELILLA = "\U0001f1ea\U0001f1e6" +FLAG_ECUADOR = "\U0001f1ea\U0001f1e8" +FLAG_ESTONIA = "\U0001f1ea\U0001f1ea" +FLAG_EGYPT = "\U0001f1ea\U0001f1ec" +FLAG_WESTERN_SAHARA = "\U0001f1ea\U0001f1ed" +FLAG_ERITREA = "\U0001f1ea\U0001f1f7" +FLAG_SPAIN = "\U0001f1ea\U0001f1f8" +FLAG_ETHIOPIA = "\U0001f1ea\U0001f1f9" +FLAG_EUROPEAN_UNION = "\U0001f1ea\U0001f1fa" +FLAG_FINLAND = "\U0001f1eb\U0001f1ee" +FLAG_FIJI = "\U0001f1eb\U0001f1ef" +FLAG_FALKLAND_ISLANDS = "\U0001f1eb\U0001f1f0" +FLAG_MICRONESIA = "\U0001f1eb\U0001f1f2" +FLAG_FAROE_ISLANDS = "\U0001f1eb\U0001f1f4" +FLAG_FRANCE = "\U0001f1eb\U0001f1f7" +FLAG_GABON = "\U0001f1ec\U0001f1e6" +FLAG_UNITED_KINGDOM = "\U0001f1ec\U0001f1e7" +FLAG_GRENADA = "\U0001f1ec\U0001f1e9" +FLAG_GEORGIA = "\U0001f1ec\U0001f1ea" +FLAG_FRENCH_GUIANA = "\U0001f1ec\U0001f1eb" +FLAG_GUERNSEY = "\U0001f1ec\U0001f1ec" +FLAG_GHANA = "\U0001f1ec\U0001f1ed" +FLAG_GIBRALTAR = "\U0001f1ec\U0001f1ee" +FLAG_GREENLAND = "\U0001f1ec\U0001f1f1" +FLAG_GAMBIA = "\U0001f1ec\U0001f1f2" +FLAG_GUINEA = "\U0001f1ec\U0001f1f3" +FLAG_GUADELOUPE = "\U0001f1ec\U0001f1f5" +FLAG_EQUATORIAL_GUINEA = "\U0001f1ec\U0001f1f6" +FLAG_GREECE = "\U0001f1ec\U0001f1f7" +FLAG_SOUTH_GEORGIA_ANDAMP_SOUTH_SANDWICH_ISLANDS = "\U0001f1ec\U0001f1f8" +FLAG_GUATEMALA = "\U0001f1ec\U0001f1f9" +FLAG_GUAM = "\U0001f1ec\U0001f1fa" +FLAG_GUINEA_BISSAU = "\U0001f1ec\U0001f1fc" +FLAG_GUYANA = "\U0001f1ec\U0001f1fe" +FLAG_HONG_KONG_SAR_CHINA = "\U0001f1ed\U0001f1f0" +FLAG_HEARD_ANDAMP_MCDONALD_ISLANDS = "\U0001f1ed\U0001f1f2" +FLAG_HONDURAS = "\U0001f1ed\U0001f1f3" +FLAG_CROATIA = "\U0001f1ed\U0001f1f7" +FLAG_HAITI = "\U0001f1ed\U0001f1f9" +FLAG_HUNGARY = "\U0001f1ed\U0001f1fa" +FLAG_CANARY_ISLANDS = "\U0001f1ee\U0001f1e8" +FLAG_INDONESIA = "\U0001f1ee\U0001f1e9" +FLAG_IRELAND = "\U0001f1ee\U0001f1ea" +FLAG_ISRAEL = "\U0001f1ee\U0001f1f1" +FLAG_ISLE_OF_MAN = "\U0001f1ee\U0001f1f2" +FLAG_INDIA = "\U0001f1ee\U0001f1f3" +FLAG_BRITISH_INDIAN_OCEAN_TERRITORY = "\U0001f1ee\U0001f1f4" +FLAG_IRAQ = "\U0001f1ee\U0001f1f6" +FLAG_IRAN = "\U0001f1ee\U0001f1f7" +FLAG_ICELAND = "\U0001f1ee\U0001f1f8" +FLAG_ITALY = "\U0001f1ee\U0001f1f9" +FLAG_JERSEY = "\U0001f1ef\U0001f1ea" +FLAG_JAMAICA = "\U0001f1ef\U0001f1f2" +FLAG_JORDAN = "\U0001f1ef\U0001f1f4" +FLAG_JAPAN = "\U0001f1ef\U0001f1f5" +FLAG_KENYA = "\U0001f1f0\U0001f1ea" +FLAG_KYRGYZSTAN = "\U0001f1f0\U0001f1ec" +FLAG_CAMBODIA = "\U0001f1f0\U0001f1ed" +FLAG_KIRIBATI = "\U0001f1f0\U0001f1ee" +FLAG_COMOROS = "\U0001f1f0\U0001f1f2" +FLAG_ST_KITTS_ANDAMP_NEVIS = "\U0001f1f0\U0001f1f3" +FLAG_NORTH_KOREA = "\U0001f1f0\U0001f1f5" +FLAG_SOUTH_KOREA = "\U0001f1f0\U0001f1f7" +FLAG_KUWAIT = "\U0001f1f0\U0001f1fc" +FLAG_CAYMAN_ISLANDS = "\U0001f1f0\U0001f1fe" +FLAG_KAZAKHSTAN = "\U0001f1f0\U0001f1ff" +FLAG_LAOS = "\U0001f1f1\U0001f1e6" +FLAG_LEBANON = "\U0001f1f1\U0001f1e7" +FLAG_ST_LUCIA = "\U0001f1f1\U0001f1e8" +FLAG_LIECHTENSTEIN = "\U0001f1f1\U0001f1ee" +FLAG_SRI_LANKA = "\U0001f1f1\U0001f1f0" +FLAG_LIBERIA = "\U0001f1f1\U0001f1f7" +FLAG_LESOTHO = "\U0001f1f1\U0001f1f8" +FLAG_LITHUANIA = "\U0001f1f1\U0001f1f9" +FLAG_LUXEMBOURG = "\U0001f1f1\U0001f1fa" +FLAG_LATVIA = "\U0001f1f1\U0001f1fb" +FLAG_LIBYA = "\U0001f1f1\U0001f1fe" +FLAG_MOROCCO = "\U0001f1f2\U0001f1e6" +FLAG_MONACO = "\U0001f1f2\U0001f1e8" +FLAG_MOLDOVA = "\U0001f1f2\U0001f1e9" +FLAG_MONTENEGRO = "\U0001f1f2\U0001f1ea" +FLAG_ST_MARTIN = "\U0001f1f2\U0001f1eb" +FLAG_MADAGASCAR = "\U0001f1f2\U0001f1ec" +FLAG_MARSHALL_ISLANDS = "\U0001f1f2\U0001f1ed" +FLAG_NORTH_MACEDONIA = "\U0001f1f2\U0001f1f0" +FLAG_MALI = "\U0001f1f2\U0001f1f1" +FLAG_MYANMAR_BURMA = "\U0001f1f2\U0001f1f2" +FLAG_MONGOLIA = "\U0001f1f2\U0001f1f3" +FLAG_MACAO_SAR_CHINA = "\U0001f1f2\U0001f1f4" +FLAG_NORTHERN_MARIANA_ISLANDS = "\U0001f1f2\U0001f1f5" +FLAG_MARTINIQUE = "\U0001f1f2\U0001f1f6" +FLAG_MAURITANIA = "\U0001f1f2\U0001f1f7" +FLAG_MONTSERRAT = "\U0001f1f2\U0001f1f8" +FLAG_MALTA = "\U0001f1f2\U0001f1f9" +FLAG_MAURITIUS = "\U0001f1f2\U0001f1fa" +FLAG_MALDIVES = "\U0001f1f2\U0001f1fb" +FLAG_MALAWI = "\U0001f1f2\U0001f1fc" +FLAG_MEXICO = "\U0001f1f2\U0001f1fd" +FLAG_MALAYSIA = "\U0001f1f2\U0001f1fe" +FLAG_MOZAMBIQUE = "\U0001f1f2\U0001f1ff" +FLAG_NAMIBIA = "\U0001f1f3\U0001f1e6" +FLAG_NEW_CALEDONIA = "\U0001f1f3\U0001f1e8" +FLAG_NIGER = "\U0001f1f3\U0001f1ea" +FLAG_NORFOLK_ISLAND = "\U0001f1f3\U0001f1eb" +FLAG_NIGERIA = "\U0001f1f3\U0001f1ec" +FLAG_NICARAGUA = "\U0001f1f3\U0001f1ee" +FLAG_NETHERLANDS = "\U0001f1f3\U0001f1f1" +FLAG_NORWAY = "\U0001f1f3\U0001f1f4" +FLAG_NEPAL = "\U0001f1f3\U0001f1f5" +FLAG_NAURU = "\U0001f1f3\U0001f1f7" +FLAG_NIUE = "\U0001f1f3\U0001f1fa" +FLAG_NEW_ZEALAND = "\U0001f1f3\U0001f1ff" +FLAG_OMAN = "\U0001f1f4\U0001f1f2" +FLAG_PANAMA = "\U0001f1f5\U0001f1e6" +FLAG_PERU = "\U0001f1f5\U0001f1ea" +FLAG_FRENCH_POLYNESIA = "\U0001f1f5\U0001f1eb" +FLAG_PAPUA_NEW_GUINEA = "\U0001f1f5\U0001f1ec" +FLAG_PHILIPPINES = "\U0001f1f5\U0001f1ed" +FLAG_PAKISTAN = "\U0001f1f5\U0001f1f0" +FLAG_POLAND = "\U0001f1f5\U0001f1f1" +FLAG_ST_PIERRE_ANDAMP_MIQUELON = "\U0001f1f5\U0001f1f2" +FLAG_PITCAIRN_ISLANDS = "\U0001f1f5\U0001f1f3" +FLAG_PUERTO_RICO = "\U0001f1f5\U0001f1f7" +FLAG_PALESTINIAN_TERRITORIES = "\U0001f1f5\U0001f1f8" +FLAG_PORTUGAL = "\U0001f1f5\U0001f1f9" +FLAG_PALAU = "\U0001f1f5\U0001f1fc" +FLAG_PARAGUAY = "\U0001f1f5\U0001f1fe" +FLAG_QATAR = "\U0001f1f6\U0001f1e6" +FLAG_REUNION = "\U0001f1f7\U0001f1ea" +FLAG_ROMANIA = "\U0001f1f7\U0001f1f4" +FLAG_SERBIA = "\U0001f1f7\U0001f1f8" +FLAG_RUSSIA = "\U0001f1f7\U0001f1fa" +FLAG_RWANDA = "\U0001f1f7\U0001f1fc" +FLAG_SAUDI_ARABIA = "\U0001f1f8\U0001f1e6" +FLAG_SOLOMON_ISLANDS = "\U0001f1f8\U0001f1e7" +FLAG_SEYCHELLES = "\U0001f1f8\U0001f1e8" +FLAG_SUDAN = "\U0001f1f8\U0001f1e9" +FLAG_SWEDEN = "\U0001f1f8\U0001f1ea" +FLAG_SINGAPORE = "\U0001f1f8\U0001f1ec" +FLAG_ST_HELENA = "\U0001f1f8\U0001f1ed" +FLAG_SLOVENIA = "\U0001f1f8\U0001f1ee" +FLAG_SVALBARD_ANDAMP_JAN_MAYEN = "\U0001f1f8\U0001f1ef" +FLAG_SLOVAKIA = "\U0001f1f8\U0001f1f0" +FLAG_SIERRA_LEONE = "\U0001f1f8\U0001f1f1" +FLAG_SAN_MARINO = "\U0001f1f8\U0001f1f2" +FLAG_SENEGAL = "\U0001f1f8\U0001f1f3" +FLAG_SOMALIA = "\U0001f1f8\U0001f1f4" +FLAG_SURINAME = "\U0001f1f8\U0001f1f7" +FLAG_SOUTH_SUDAN = "\U0001f1f8\U0001f1f8" +FLAG_SAO_TOME_ANDAMP_PRINCIPE = "\U0001f1f8\U0001f1f9" +FLAG_EL_SALVADOR = "\U0001f1f8\U0001f1fb" +FLAG_SINT_MAARTEN = "\U0001f1f8\U0001f1fd" +FLAG_SYRIA = "\U0001f1f8\U0001f1fe" +FLAG_ESWATINI = "\U0001f1f8\U0001f1ff" +FLAG_TRISTAN_DA_CUNHA = "\U0001f1f9\U0001f1e6" +FLAG_TURKS_ANDAMP_CAICOS_ISLANDS = "\U0001f1f9\U0001f1e8" +FLAG_CHAD = "\U0001f1f9\U0001f1e9" +FLAG_FRENCH_SOUTHERN_TERRITORIES = "\U0001f1f9\U0001f1eb" +FLAG_TOGO = "\U0001f1f9\U0001f1ec" +FLAG_THAILAND = "\U0001f1f9\U0001f1ed" +FLAG_TAJIKISTAN = "\U0001f1f9\U0001f1ef" +FLAG_TOKELAU = "\U0001f1f9\U0001f1f0" +FLAG_TIMOR_LESTE = "\U0001f1f9\U0001f1f1" +FLAG_TURKMENISTAN = "\U0001f1f9\U0001f1f2" +FLAG_TUNISIA = "\U0001f1f9\U0001f1f3" +FLAG_TONGA = "\U0001f1f9\U0001f1f4" +FLAG_TURKEY = "\U0001f1f9\U0001f1f7" +FLAG_TRINIDAD_ANDAMP_TOBAGO = "\U0001f1f9\U0001f1f9" +FLAG_TUVALU = "\U0001f1f9\U0001f1fb" +FLAG_TAIWAN = "\U0001f1f9\U0001f1fc" +FLAG_TANZANIA = "\U0001f1f9\U0001f1ff" +FLAG_UKRAINE = "\U0001f1fa\U0001f1e6" +FLAG_UGANDA = "\U0001f1fa\U0001f1ec" +FLAG_U_S_OUTLYING_ISLANDS = "\U0001f1fa\U0001f1f2" +FLAG_UNITED_NATIONS = "\U0001f1fa\U0001f1f3" +FLAG_UNITED_STATES = "\U0001f1fa\U0001f1f8" +FLAG_URUGUAY = "\U0001f1fa\U0001f1fe" +FLAG_UZBEKISTAN = "\U0001f1fa\U0001f1ff" +FLAG_VATICAN_CITY = "\U0001f1fb\U0001f1e6" +FLAG_ST_VINCENT_ANDAMP_GRENADINES = "\U0001f1fb\U0001f1e8" +FLAG_VENEZUELA = "\U0001f1fb\U0001f1ea" +FLAG_BRITISH_VIRGIN_ISLANDS = "\U0001f1fb\U0001f1ec" +FLAG_U_S_VIRGIN_ISLANDS = "\U0001f1fb\U0001f1ee" +FLAG_VIETNAM = "\U0001f1fb\U0001f1f3" +FLAG_VANUATU = "\U0001f1fb\U0001f1fa" +FLAG_WALLIS_ANDAMP_FUTUNA = "\U0001f1fc\U0001f1eb" +FLAG_SAMOA = "\U0001f1fc\U0001f1f8" +FLAG_KOSOVO = "\U0001f1fd\U0001f1f0" +FLAG_YEMEN = "\U0001f1fe\U0001f1ea" +FLAG_MAYOTTE = "\U0001f1fe\U0001f1f9" +FLAG_SOUTH_AFRICA = "\U0001f1ff\U0001f1e6" +FLAG_ZAMBIA = "\U0001f1ff\U0001f1f2" +FLAG_ZIMBABWE = "\U0001f1ff\U0001f1fc" +FLAG_ENGLAND = "\U0001f3f4\U000e0067\U000e0062\U000e0065\U000e006e\U000e0067\U000e007f" +FLAG_SCOTLAND = "\U0001f3f4\U000e0067\U000e0062\U000e0073\U000e0063\U000e0074\U000e007f" +FLAG_WALES = "\U0001f3f4\U000e0067\U000e0062\U000e0077\U000e006c\U000e0073\U000e007f" +REGIONAL_INDICATOR_SYMBOL_LETTER_A = "\U0001f1e6" +REGIONAL_INDICATOR_SYMBOL_LETTER_B = "\U0001f1e7" +REGIONAL_INDICATOR_SYMBOL_LETTER_C = "\U0001f1e8" +REGIONAL_INDICATOR_SYMBOL_LETTER_D = "\U0001f1e9" +REGIONAL_INDICATOR_SYMBOL_LETTER_E = "\U0001f1ea" +REGIONAL_INDICATOR_SYMBOL_LETTER_F = "\U0001f1eb" +REGIONAL_INDICATOR_SYMBOL_LETTER_G = "\U0001f1ec" +REGIONAL_INDICATOR_SYMBOL_LETTER_H = "\U0001f1ed" +REGIONAL_INDICATOR_SYMBOL_LETTER_I = "\U0001f1ee" +REGIONAL_INDICATOR_SYMBOL_LETTER_J = "\U0001f1ef" +REGIONAL_INDICATOR_SYMBOL_LETTER_K = "\U0001f1f0" +REGIONAL_INDICATOR_SYMBOL_LETTER_L = "\U0001f1f1" +REGIONAL_INDICATOR_SYMBOL_LETTER_M = "\U0001f1f2" +REGIONAL_INDICATOR_SYMBOL_LETTER_N = "\U0001f1f3" +REGIONAL_INDICATOR_SYMBOL_LETTER_O = "\U0001f1f4" +REGIONAL_INDICATOR_SYMBOL_LETTER_P = "\U0001f1f5" +REGIONAL_INDICATOR_SYMBOL_LETTER_Q = "\U0001f1f6" +REGIONAL_INDICATOR_SYMBOL_LETTER_R = "\U0001f1f7" +REGIONAL_INDICATOR_SYMBOL_LETTER_S = "\U0001f1f8" +REGIONAL_INDICATOR_SYMBOL_LETTER_T = "\U0001f1f9" +REGIONAL_INDICATOR_SYMBOL_LETTER_U = "\U0001f1fa" +REGIONAL_INDICATOR_SYMBOL_LETTER_V = "\U0001f1fb" +REGIONAL_INDICATOR_SYMBOL_LETTER_W = "\U0001f1fc" +REGIONAL_INDICATOR_SYMBOL_LETTER_X = "\U0001f1fd" +REGIONAL_INDICATOR_SYMBOL_LETTER_Y = "\U0001f1fe" +REGIONAL_INDICATOR_SYMBOL_LETTER_Z = "\U0001f1ff" +TAG_VERTICAL_LINE = "\U000e007c" +TAG_LATIN_CAPITAL_LETTER_O = "\U000e004f" +TAG_LOW_LINE = "\U000e005f" +TAG_LATIN_SMALL_LETTER_E = "\U000e0065" +TAG_PERCENT_SIGN = "\U000e0025" +TAG_DIGIT_SEVEN = "\U000e0037" +TAG_LATIN_SMALL_LETTER_W = "\U000e0077" +TAG_DIGIT_ONE = "\U000e0031" +TAG_LATIN_CAPITAL_LETTER_A = "\U000e0041" +TAG_LATIN_CAPITAL_LETTER_Z = "\U000e005a" +TAG_LATIN_CAPITAL_LETTER_M = "\U000e004d" +TAG_QUESTION_MARK = "\U000e003f" +TAG_LATIN_CAPITAL_LETTER_W = "\U000e0057" +TAG_LATIN_SMALL_LETTER_D = "\U000e0064" +TAG_LATIN_SMALL_LETTER_K = "\U000e006b" +TAG_LATIN_SMALL_LETTER_V = "\U000e0076" +DIGIT_ZERO = "0\ufe0f" +TAG_DIGIT_SIX = "\U000e0036" +TAG_LATIN_SMALL_LETTER_C = "\U000e0063" +TAG_LATIN_SMALL_LETTER_X = "\U000e0078" +TAG_LATIN_SMALL_LETTER_U = "\U000e0075" +TAG_DIGIT_NINE = "\U000e0039" +DIGIT_FOUR = "4\ufe0f" +DIGIT_EIGHT = "8\ufe0f" +TAG_RIGHT_CURLY_BRACKET = "\U000e007d" +TAG_LATIN_CAPITAL_LETTER_S = "\U000e0053" +TAG_DIGIT_FIVE = "\U000e0035" +TAG_LATIN_CAPITAL_LETTER_V = "\U000e0056" +TAG_LATIN_SMALL_LETTER_L = "\U000e006c" +TAG_SOLIDUS = "\U000e002f" +TAG_LATIN_SMALL_LETTER_B = "\U000e0062" +TAG_SEMICOLON = "\U000e003b" +TAG_LESS_THAN_SIGN = "\U000e003c" +TAG_DIGIT_TWO = "\U000e0032" +TAG_LATIN_SMALL_LETTER_M = "\U000e006d" +TAG_LATIN_CAPITAL_LETTER_N = "\U000e004e" +TAG_LATIN_SMALL_LETTER_P = "\U000e0070" +TAG_LATIN_SMALL_LETTER_I = "\U000e0069" +TAG_LATIN_SMALL_LETTER_A = "\U000e0061" +TAG_NUMBER_SIGN = "\U000e0023" +TAG_LATIN_CAPITAL_LETTER_K = "\U000e004b" +TAG_LATIN_CAPITAL_LETTER_J = "\U000e004a" +TAG_LATIN_CAPITAL_LETTER_T = "\U000e0054" +TAG_LATIN_SMALL_LETTER_Y = "\U000e0079" +TAG_QUOTATION_MARK = "\U000e0022" +TAG_TILDE = "\U000e007e" +TAG_FULL_STOP = "\U000e002e" +TAG_LATIN_SMALL_LETTER_S = "\U000e0073" +TAG_LATIN_SMALL_LETTER_F = "\U000e0066" +TAG_LATIN_SMALL_LETTER_O = "\U000e006f" +TAG_DIGIT_ZERO = "\U000e0030" +DIGIT_FIVE = "5\ufe0f" +DIGIT_ONE = "1\ufe0f" +TAG_LATIN_CAPITAL_LETTER_B = "\U000e0042" +DIGIT_NINE = "9\ufe0f" +TAG_LATIN_CAPITAL_LETTER_F = "\U000e0046" +TAG_LATIN_CAPITAL_LETTER_Y = "\U000e0059" +TAG_GREATER_THAN_SIGN = "\U000e003e" +TAG_HYPHEN_MINUS = "\U000e002d" +TAG_LEFT_CURLY_BRACKET = "\U000e007b" +TAG_DIGIT_THREE = "\U000e0033" +TAG_EQUALS_SIGN = "\U000e003d" +TAG_DIGIT_FOUR = "\U000e0034" +TAG_LATIN_SMALL_LETTER_N = "\U000e006e" +TAG_DOLLAR_SIGN = "\U000e0024" +TAG_RIGHT_PARENTHESIS = "\U000e0029" +TAG_LATIN_CAPITAL_LETTER_Q = "\U000e0051" +TAG_CIRCUMFLEX_ACCENT = "\U000e005e" +DIGIT_SEVEN = "7\ufe0f" +VARIATION_SELECTOR_16 = "\ufe0f" +NUMBER_SIGN = "#\ufe0f" +DIGIT_TWO = "2\ufe0f" +TAG_GRAVE_ACCENT = "\U000e0060" +TAG_COLON = "\U000e003a" +TAG_REVERSE_SOLIDUS = "\U000e005c" +TAG_COMMERCIAL_AT = "\U000e0040" +TAG_EXCLAMATION_MARK = "\U000e0021" +TAG_APOSTROPHE = "\U000e0027" +TAG_LATIN_SMALL_LETTER_Z = "\U000e007a" +TAG_LATIN_CAPITAL_LETTER_X = "\U000e0058" +TAG_LATIN_CAPITAL_LETTER_R = "\U000e0052" +COMBINING_ENCLOSING_KEYCAP = "\u20e3" +ZERO_WIDTH_JOINER = "\u200d" +TAG_LATIN_SMALL_LETTER_G = "\U000e0067" +TAG_LATIN_CAPITAL_LETTER_U = "\U000e0055" +TAG_LATIN_CAPITAL_LETTER_D = "\U000e0044" +TAG_LATIN_SMALL_LETTER_R = "\U000e0072" +TAG_LATIN_CAPITAL_LETTER_P = "\U000e0050" +TAG_LATIN_SMALL_LETTER_H = "\U000e0068" +CANCEL_TAG = "\U000e007f" +TAG_AMPERSAND = "\U000e0026" +TAG_LEFT_PARENTHESIS = "\U000e0028" +TAG_LATIN_SMALL_LETTER_Q = "\U000e0071" +TAG_LATIN_CAPITAL_LETTER_C = "\U000e0043" +TAG_ASTERISK = "\U000e002a" +TAG_LATIN_SMALL_LETTER_T = "\U000e0074" +TAG_LATIN_CAPITAL_LETTER_L = "\U000e004c" +TAG_COMMA = "\U000e002c" +TAG_RIGHT_SQUARE_BRACKET = "\U000e005d" +DIGIT_THREE = "3\ufe0f" +TAG_LATIN_CAPITAL_LETTER_H = "\U000e0048" +TAG_LATIN_SMALL_LETTER_J = "\U000e006a" +TAG_SPACE = "\U000e0020" +TAG_PLUS_SIGN = "\U000e002b" +TAG_LEFT_SQUARE_BRACKET = "\U000e005b" +ASTERISK = "*\ufe0f" +DIGIT_SIX = "6\ufe0f" +TAG_DIGIT_EIGHT = "\U000e0038" +TAG_LATIN_CAPITAL_LETTER_E = "\U000e0045" +TAG_LATIN_CAPITAL_LETTER_G = "\U000e0047" +TAG_LATIN_CAPITAL_LETTER_I = "\U000e0049" diff --git a/pyrogram/errors/rpc_error.py b/pyrogram/errors/rpc_error.py index 2586d8737e..d11467945b 100644 --- a/pyrogram/errors/rpc_error.py +++ b/pyrogram/errors/rpc_error.py @@ -21,9 +21,8 @@ from importlib import import_module from typing import Type -from pyrogram.api.types import RpcError as RawRPCError - -from pyrogram.api.core import TLObject +from pyrogram import raw +from pyrogram.raw.core import TLObject from .exceptions.all import exceptions @@ -33,12 +32,12 @@ class RPCError(Exception): NAME = None MESSAGE = "{x}" - def __init__(self, x: int or RawRPCError = None, rpc_name: str = None, is_unknown: bool = False): + def __init__(self, x: int or raw.types.RpcError = None, rpc_name: str = None, is_unknown: bool = False): super().__init__("[{} {}]: {} {}".format( self.CODE, self.ID or self.NAME, self.MESSAGE.format(x=x), - '(caused by "{}")'.format(rpc_name) if rpc_name else "" + f'(caused by "{rpc_name}")' if rpc_name else "" )) try: @@ -48,17 +47,17 @@ def __init__(self, x: int or RawRPCError = None, rpc_name: str = None, is_unknow if is_unknown: with open("unknown_errors.txt", "a", encoding="utf-8") as f: - f.write("{}\t{}\t{}\n".format(datetime.now(), x, rpc_name)) + f.write(f"{datetime.now()}\t{x}\t{rpc_name}\n") @staticmethod - def raise_it(rpc_error: RawRPCError, rpc_type: Type[TLObject]): + def raise_it(rpc_error: "raw.types.RpcError", rpc_type: Type[TLObject]): error_code = rpc_error.error_code error_message = rpc_error.error_message rpc_name = ".".join(rpc_type.QUALNAME.split(".")[1:]) if error_code not in exceptions: raise UnknownError( - x="[{} {}]".format(error_code, error_message), + x=f"[{error_code} {error_message}]", rpc_name=rpc_name, is_unknown=True ) @@ -69,7 +68,7 @@ def raise_it(rpc_error: RawRPCError, rpc_type: Type[TLObject]): raise getattr( import_module("pyrogram.errors"), exceptions[error_code]["_"] - )(x="[{} {}]".format(error_code, error_message), + )(x=f"[{error_code} {error_message}]", rpc_name=rpc_name, is_unknown=True) diff --git a/pyrogram/filters.py b/pyrogram/filters.py new file mode 100644 index 0000000000..45762aee7d --- /dev/null +++ b/pyrogram/filters.py @@ -0,0 +1,877 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan +# +# 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 inspect +import re +from typing import Callable, Union, List, Pattern + +import pyrogram +from pyrogram.types import Message, CallbackQuery, InlineQuery, InlineKeyboardMarkup, ReplyKeyboardMarkup, Update + + +class Filter: + async def __call__(self, client: "pyrogram.Client", update: Update): + raise NotImplementedError + + def __invert__(self): + return InvertFilter(self) + + def __and__(self, other): + return AndFilter(self, other) + + def __or__(self, other): + return OrFilter(self, other) + + +class InvertFilter(Filter): + def __init__(self, base): + self.base = base + + async def __call__(self, client: "pyrogram.Client", update: Update): + if inspect.iscoroutinefunction(self.base.__call__): + x = await self.base(client, update) + else: + x = await client.loop.run_in_executor( + client.executor, + self.base, + client, update + ) + + return not x + + +class AndFilter(Filter): + def __init__(self, base, other): + self.base = base + self.other = other + + async def __call__(self, client: "pyrogram.Client", update: Update): + if inspect.iscoroutinefunction(self.base.__call__): + x = await self.base(client, update) + else: + x = await client.loop.run_in_executor( + client.executor, + self.base, + client, update + ) + + if inspect.iscoroutinefunction(self.other.__call__): + y = await self.other(client, update) + else: + y = await client.loop.run_in_executor( + client.executor, + self.other, + client, update + ) + + return x and y + + +class OrFilter(Filter): + def __init__(self, base, other): + self.base = base + self.other = other + + async def __call__(self, client: "pyrogram.Client", update: Update): + if inspect.iscoroutinefunction(self.base.__call__): + x = await self.base(client, update) + else: + x = await client.loop.run_in_executor( + client.executor, + self.base, + client, update + ) + + if inspect.iscoroutinefunction(self.other.__call__): + y = await self.other(client, update) + else: + y = await client.loop.run_in_executor( + client.executor, + self.other, + client, update + ) + + return x or y + + +CUSTOM_FILTER_NAME = "CustomFilter" + + +def create(func: Callable, name: str = None, **kwargs) -> Filter: + """Easily create a custom filter. + + Custom filters give you extra control over which updates are allowed or not to be processed by your handlers. + + Parameters: + func (``callable``): + A function that accepts two positional arguments *(filter, update)* and returns a boolean: True if the + update should be handled, False otherwise. The *filter* argument refers to the filter itself and can be used + to access keyword arguments (read below). The *update* argument type will vary depending on which + `Handler `_ is coming from. For example, in a :obj:`~pyrogram.handlers.MessageHandler` the + *update* argument will be a :obj:`~pyrogram.types.Message`; in a + :obj:`~pyrogram.handlers.CallbackQueryHandler` the *update* will be a :obj:`~pyrogram.types.CallbackQuery`. + Your function body can then access the incoming update attributes and decide whether to allow it or not. + + name (``str``, *optional*): + Your filter's name. Can be anything you like. + Defaults to "CustomFilter". + + **kwargs (``any``, *optional*): + Any keyword argument you would like to pass. Useful when creating parameterized custom filters, such as + :meth:`~Filters.command` or :meth:`~Filters.regex`. + """ + return type( + name or func.__name__ or CUSTOM_FILTER_NAME, + (Filter,), + {"__call__": func, **kwargs} + )() + + +# region all_filter +async def all_filter(_, __, ___): + return True + + +all = create(all_filter) +"""Filter all messages.""" + + +# endregion + +# region me_filter +async def me_filter(_, __, m: Message): + return bool(m.from_user and m.from_user.is_self) + + +me = create(me_filter) +"""Filter messages generated by you yourself.""" + + +# endregion + +# region bot_filter +async def bot_filter(_, __, m: Message): + return bool(m.from_user and m.from_user.is_bot) + + +bot = create(bot_filter) +"""Filter messages coming from bots.""" + + +# endregion + +# region incoming_filter +async def incoming_filter(_, __, m: Message): + return not m.outgoing + + +incoming = create(incoming_filter) +"""Filter incoming messages. Messages sent to your own chat (Saved Messages) are also recognised as incoming.""" + + +# endregion + +# region outgoing_filter +async def outgoing_filter(_, __, m: Message): + return m.outgoing + + +outgoing = create(outgoing_filter) +"""Filter outgoing messages. Messages sent to your own chat (Saved Messages) are not recognized as outgoing.""" + + +# endregion + +# region text_filter +async def text_filter(_, __, m: Message): + return bool(m.text) + + +text = create(text_filter) +"""Filter text messages.""" + + +# endregion + +# region reply_filter +async def reply_filter(_, __, m: Message): + return bool(m.reply_to_message) + + +reply = create(reply_filter) +"""Filter messages that are replies to other messages.""" + + +# endregion + +# region forwarded_filter +async def forwarded_filter(_, __, m: Message): + return bool(m.forward_date) + + +forwarded = create(forwarded_filter) +"""Filter messages that are forwarded.""" + + +# endregion + +# region caption_filter +async def caption_filter(_, __, m: Message): + return bool(m.caption) + + +caption = create(caption_filter) +"""Filter media messages that contain captions.""" + + +# endregion + +# region edited_filter +async def edited_filter(_, __, m: Message): + return bool(m.edit_date) + + +edited = create(edited_filter) +"""Filter edited messages.""" + + +# endregion + +# region audio_filter +async def audio_filter(_, __, m: Message): + return bool(m.audio) + + +audio = create(audio_filter) +"""Filter messages that contain :obj:`~pyrogram.types.Audio` objects.""" + + +# endregion + +# region document_filter +async def document_filter(_, __, m: Message): + return bool(m.document) + + +document = create(document_filter) +"""Filter messages that contain :obj:`~pyrogram.types.Document` objects.""" + + +# endregion + +# region photo_filter +async def photo_filter(_, __, m: Message): + return bool(m.photo) + + +photo = create(photo_filter) +"""Filter messages that contain :obj:`~pyrogram.types.Photo` objects.""" + + +# endregion + +# region sticker_filter +async def sticker_filter(_, __, m: Message): + return bool(m.sticker) + + +sticker = create(sticker_filter) +"""Filter messages that contain :obj:`~pyrogram.types.Sticker` objects.""" + + +# endregion + +# region animation_filter +async def animation_filter(_, __, m: Message): + return bool(m.animation) + + +animation = create(animation_filter) +"""Filter messages that contain :obj:`~pyrogram.types.Animation` objects.""" + + +# endregion + +# region game_filter +async def game_filter(_, __, m: Message): + return bool(m.game) + + +game = create(game_filter) +"""Filter messages that contain :obj:`~pyrogram.types.Game` objects.""" + + +# endregion + +# region video_filter +async def video_filter(_, __, m: Message): + return bool(m.video) + + +video = create(video_filter) +"""Filter messages that contain :obj:`~pyrogram.types.Video` objects.""" + + +# endregion + +# region media_group_filter +async def media_group_filter(_, __, m: Message): + return bool(m.media_group_id) + + +media_group = create(media_group_filter) +"""Filter messages containing photos or videos being part of an album.""" + + +# endregion + +# region voice_filter +async def voice_filter(_, __, m: Message): + return bool(m.voice) + + +voice = create(voice_filter) +"""Filter messages that contain :obj:`~pyrogram.types.Voice` note objects.""" + + +# endregion + +# region video_note_filter +async def video_note_filter(_, __, m: Message): + return bool(m.video_note) + + +video_note = create(video_note_filter) +"""Filter messages that contain :obj:`~pyrogram.types.VideoNote` objects.""" + + +# endregion + +# region contact_filter +async def contact_filter(_, __, m: Message): + return bool(m.contact) + + +contact = create(contact_filter) +"""Filter messages that contain :obj:`~pyrogram.types.Contact` objects.""" + + +# endregion + +# region location_filter +async def location_filter(_, __, m: Message): + return bool(m.location) + + +location = create(location_filter) +"""Filter messages that contain :obj:`~pyrogram.types.Location` objects.""" + + +# endregion + +# region venue_filter +async def venue_filter(_, __, m: Message): + return bool(m.venue) + + +venue = create(venue_filter) +"""Filter messages that contain :obj:`~pyrogram.types.Venue` objects.""" + + +# endregion + +# region web_page_filter +async def web_page_filter(_, __, m: Message): + return m.web_page + + +web_page = create(web_page_filter) +"""Filter messages sent with a webpage preview.""" + + +# endregion + +# region poll_filter +async def poll_filter(_, __, m: Message): + return m.poll + + +poll = create(poll_filter) +"""Filter messages that contain :obj:`~pyrogram.types.Poll` objects.""" + + +# endregion + +# region private_filter +async def private_filter(_, __, m: Message): + return bool(m.chat and m.chat.type in {"private", "bot"}) + + +private = create(private_filter) +"""Filter messages sent in private chats.""" + + +# endregion + +# region group_filter +async def group_filter(_, __, m: Message): + return bool(m.chat and m.chat.type in {"group", "supergroup"}) + + +group = create(group_filter) +"""Filter messages sent in group or supergroup chats.""" + + +# endregion + +# region channel_filter +async def channel_filter(_, __, m: Message): + return bool(m.chat and m.chat.type == "channel") + + +channel = create(channel_filter) +"""Filter messages sent in channels.""" + + +# endregion + +# region new_chat_members_filter +async def new_chat_members_filter(_, __, m: Message): + return bool(m.new_chat_members) + + +new_chat_members = create(new_chat_members_filter) +"""Filter service messages for new chat members.""" + + +# endregion + +# region left_chat_member_filter +async def left_chat_member_filter(_, __, m: Message): + return bool(m.left_chat_member) + + +left_chat_member = create(left_chat_member_filter) +"""Filter service messages for members that left the chat.""" + + +# endregion + +# region new_chat_title_filter +async def new_chat_title_filter(_, __, m: Message): + return bool(m.new_chat_title) + + +new_chat_title = create(new_chat_title_filter) +"""Filter service messages for new chat titles.""" + + +# endregion + +# region new_chat_photo_filter +async def new_chat_photo_filter(_, __, m: Message): + return bool(m.new_chat_photo) + + +new_chat_photo = create(new_chat_photo_filter) +"""Filter service messages for new chat photos.""" + + +# endregion + +# region delete_chat_photo_filter +async def delete_chat_photo_filter(_, __, m: Message): + return bool(m.delete_chat_photo) + + +delete_chat_photo = create(delete_chat_photo_filter) +"""Filter service messages for deleted photos.""" + + +# endregion + +# region group_chat_created_filter +async def group_chat_created_filter(_, __, m: Message): + return bool(m.group_chat_created) + + +group_chat_created = create(group_chat_created_filter) +"""Filter service messages for group chat creations.""" + + +# endregion + +# region supergroup_chat_created_filter +async def supergroup_chat_created_filter(_, __, m: Message): + return bool(m.supergroup_chat_created) + + +supergroup_chat_created = create(supergroup_chat_created_filter) +"""Filter service messages for supergroup chat creations.""" + + +# endregion + +# region channel_chat_created_filter +async def channel_chat_created_filter(_, __, m: Message): + return bool(m.channel_chat_created) + + +channel_chat_created = create(channel_chat_created_filter) +"""Filter service messages for channel chat creations.""" + + +# endregion + +# region migrate_to_chat_id_filter +async def migrate_to_chat_id_filter(_, __, m: Message): + return bool(m.migrate_to_chat_id) + + +migrate_to_chat_id = create(migrate_to_chat_id_filter) +"""Filter service messages that contain migrate_to_chat_id.""" + + +# endregion + +# region migrate_from_chat_id_filter +async def migrate_from_chat_id_filter(_, __, m: Message): + return bool(m.migrate_from_chat_id) + + +migrate_from_chat_id = create(migrate_from_chat_id_filter) +"""Filter service messages that contain migrate_from_chat_id.""" + + +# endregion + +# region pinned_message_filter +async def pinned_message_filter(_, __, m: Message): + return bool(m.pinned_message) + + +pinned_message = create(pinned_message_filter) +"""Filter service messages for pinned messages.""" + + +# endregion + +# region game_high_score_filter +async def game_high_score_filter(_, __, m: Message): + return bool(m.game_high_score) + + +game_high_score = create(game_high_score_filter) +"""Filter service messages for game high scores.""" + + +# endregion + +# region reply_keyboard_filter +async def reply_keyboard_filter(_, __, m: Message): + return isinstance(m.reply_markup, ReplyKeyboardMarkup) + + +reply_keyboard = create(reply_keyboard_filter) +"""Filter messages containing reply keyboard markups""" + + +# endregion + +# region inline_keyboard_filter +async def inline_keyboard_filter(_, __, m: Message): + return isinstance(m.reply_markup, InlineKeyboardMarkup) + + +inline_keyboard = create(inline_keyboard_filter) +"""Filter messages containing inline keyboard markups""" + + +# endregion + +# region mentioned_filter +async def mentioned_filter(_, __, m: Message): + return bool(m.mentioned) + + +mentioned = create(mentioned_filter) +"""Filter messages containing mentions""" + + +# endregion + +# region via_bot_filter +async def via_bot_filter(_, __, m: Message): + return bool(m.via_bot) + + +via_bot = create(via_bot_filter) +"""Filter messages sent via inline bots""" + + +# endregion + +# region service_filter +async def service_filter(_, __, m: Message): + return bool(m.service) + + +service = create(service_filter) +"""Filter service messages. + +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*. +""" + + +# endregion + +# region media_filter +async def media_filter(_, __, m: Message): + return bool(m.media) + + +media = create(media_filter) +"""Filter media messages. + +A media message contains any of the following fields set: *audio*, *document*, *photo*, *sticker*, *video*, +*animation*, *voice*, *video_note*, *contact*, *location*, *venue*, *poll*. +""" + + +# endregion + +# region scheduled_filter +async def scheduled_filter(_, __, m: Message): + return bool(m.scheduled) + + +scheduled = create(scheduled_filter) +"""Filter messages that have been scheduled (not yet sent).""" + + +# endregion + +# region from_scheduled_filter +async def from_scheduled_filter(_, __, m: Message): + return bool(m.from_scheduled) + + +from_scheduled = create(from_scheduled_filter) +"""Filter new automatically sent messages that were previously scheduled.""" + + +# endregion + +# region linked_channel_filter +async def linked_channel_filter(_, __, m: Message): + return bool(m.forward_from_chat and not m.from_user) + + +linked_channel = create(linked_channel_filter) +"""Filter messages that are automatically forwarded from the linked channel to the group chat.""" + + +# endregion + + +def command(commands: str or List[str], prefixes: str or List[str] = "/", case_sensitive: bool = False): + """Filter commands, i.e.: text messages starting with "/" or any other custom prefix. + + Parameters: + commands (``str`` | ``list``): + The command or list of commands as string the filter should look for. + Examples: "start", ["start", "help", "settings"]. When a message text containing + a command arrives, the command itself and its arguments will be stored in the *command* + field of the :obj:`~pyrogram.types.Message`. + + prefixes (``str`` | ``list``, *optional*): + A prefix or a list of prefixes as string the filter should look for. + Defaults to "/" (slash). Examples: ".", "!", ["/", "!", "."], list(".:!"). + Pass None or "" (empty string) to allow commands with no prefix at all. + + case_sensitive (``bool``, *optional*): + Pass True if you want your command(s) to be case sensitive. Defaults to False. + Examples: when True, command="Start" would trigger /Start but not /start. + """ + command_re = re.compile(r"([\"'])(.*?)(?`_ are + stored in the ``matches`` field of the update object itself. + + Parameters: + pattern (``str`` | ``Pattern``): + The regex pattern as string or as pre-compiled pattern. + + flags (``int``, *optional*): + Regex flags. + """ + + async def func(flt, _, update: Update): + if isinstance(update, Message): + value = update.text or update.caption + elif isinstance(update, CallbackQuery): + value = update.data + elif isinstance(update, InlineQuery): + value = update.query + else: + raise ValueError(f"Regex filter doesn't work with {type(update)}") + + if value: + update.matches = list(flt.p.finditer(value)) or None + + return bool(update.matches) + + return create( + func, + "RegexFilter", + p=pattern if isinstance(pattern, Pattern) else re.compile(pattern, flags) + ) + + +# noinspection PyPep8Naming +class user(Filter, set): + """Filter messages coming from one or more users. + + You can use `set bound methods `_ to manipulate the + users container. + + Parameters: + users (``int`` | ``str`` | ``list``): + Pass one or more user ids/usernames to filter users. + For you yourself, "me" or "self" can be used as well. + Defaults to None (no users). + """ + + def __init__(self, users: int or str or list = None): + users = [] if users is None else users if isinstance(users, list) else [users] + + super().__init__( + "me" if u in ["me", "self"] + else u.lower().strip("@") if isinstance(u, str) + else u for u in users + ) + + async def __call__(self, _, message: Message): + return (message.from_user + and (message.from_user.id in self + or (message.from_user.username + and message.from_user.username.lower() in self) + or ("me" in self + and message.from_user.is_self))) + + +# noinspection PyPep8Naming +class chat(Filter, set): + """Filter messages coming from one or more chats. + + You can use `set bound methods `_ to manipulate the + chats container. + + Parameters: + chats (``int`` | ``str`` | ``list``): + Pass one or more chat ids/usernames to filter chats. + For your personal cloud (Saved Messages) you can simply use "me" or "self". + Defaults to None (no chats). + """ + + def __init__(self, chats: int or str or list = None): + chats = [] if chats is None else chats if isinstance(chats, list) else [chats] + + super().__init__( + "me" if c in ["me", "self"] + else c.lower().strip("@") if isinstance(c, str) + else c for c in chats + ) + + async def __call__(self, _, message: Message): + return (message.chat + and (message.chat.id in self + or (message.chat.username + and message.chat.username.lower() in self) + or ("me" in self + and message.from_user + and message.from_user.is_self + and not message.outgoing))) + + +# region dan_filter +async def dan_filter(_, __, m: Message): + return bool(m.from_user and m.from_user.id == 23122162) + + +dan = create(dan_filter) +# endregion diff --git a/pyrogram/client/handlers/__init__.py b/pyrogram/handlers/__init__.py similarity index 85% rename from pyrogram/client/handlers/__init__.py rename to pyrogram/handlers/__init__.py index 87ef10c5bd..e0c37e87c3 100644 --- a/pyrogram/client/handlers/__init__.py +++ b/pyrogram/handlers/__init__.py @@ -17,6 +17,7 @@ # along with Pyrogram. If not, see . from .callback_query_handler import CallbackQueryHandler +from .chosen_inline_result_handler import ChosenInlineResultHandler from .deleted_messages_handler import DeletedMessagesHandler from .disconnect_handler import DisconnectHandler from .inline_query_handler import InlineQueryHandler @@ -24,9 +25,3 @@ from .poll_handler import PollHandler from .raw_update_handler import RawUpdateHandler from .user_status_handler import UserStatusHandler -from .chosen_inline_result_handler import ChosenInlineResultHandler - -__all__ = [ - "MessageHandler", "DeletedMessagesHandler", "CallbackQueryHandler", "RawUpdateHandler", "DisconnectHandler", - "UserStatusHandler", "InlineQueryHandler", "PollHandler", "ChosenInlineResultHandler" -] diff --git a/pyrogram/client/handlers/callback_query_handler.py b/pyrogram/handlers/callback_query_handler.py similarity index 88% rename from pyrogram/client/handlers/callback_query_handler.py rename to pyrogram/handlers/callback_query_handler.py index 99aa2e70b7..84e7ebede3 100644 --- a/pyrogram/client/handlers/callback_query_handler.py +++ b/pyrogram/handlers/callback_query_handler.py @@ -21,10 +21,10 @@ class CallbackQueryHandler(Handler): """The CallbackQuery handler class. Used to handle callback queries coming from inline buttons. - It is intended to be used with :meth:`~Client.add_handler` + It is intended to be used with :meth:`~pyrogram.Client.add_handler` For a nicer way to register this handler, have a look at the - :meth:`~Client.on_callback_query` decorator. + :meth:`~pyrogram.Client.on_callback_query` decorator. Parameters: callback (``callable``): @@ -36,10 +36,10 @@ class CallbackQueryHandler(Handler): in your callback function. Other parameters: - client (:obj:`Client`): + client (:obj:`~pyrogram.Client`): The Client itself, useful when you want to call other API methods inside the message handler. - callback_query (:obj:`CallbackQuery `): + callback_query (:obj:`~pyrogram.types.CallbackQuery`): The received callback query. """ diff --git a/pyrogram/client/handlers/chosen_inline_result_handler.py b/pyrogram/handlers/chosen_inline_result_handler.py similarity index 87% rename from pyrogram/client/handlers/chosen_inline_result_handler.py rename to pyrogram/handlers/chosen_inline_result_handler.py index f5014ab08e..428b153dd6 100644 --- a/pyrogram/client/handlers/chosen_inline_result_handler.py +++ b/pyrogram/handlers/chosen_inline_result_handler.py @@ -21,10 +21,10 @@ class ChosenInlineResultHandler(Handler): """The ChosenInlineResultHandler handler class. Used to handle chosen inline results coming from inline queries. - It is intended to be used with :meth:`~Client.add_handler` + It is intended to be used with :meth:`~pyrogram.Client.add_handler` For a nicer way to register this handler, have a look at the - :meth:`~Client.on_chosen_inline_query` decorator. + :meth:`~pyrogram.Client.on_chosen_inline_query` decorator. Parameters: callback (``callable``): @@ -37,10 +37,10 @@ class ChosenInlineResultHandler(Handler): in your callback function. Other parameters: - client (:obj:`Client`): + client (:obj:`~pyrogram.Client`): The Client itself, useful when you want to call other API methods inside the message handler. - chosen_inline_result (:obj:`ChosenInlineResult`): + chosen_inline_result (:obj:`~pyrogram.types.ChosenInlineResult`): The received chosen inline result. """ diff --git a/pyrogram/client/handlers/deleted_messages_handler.py b/pyrogram/handlers/deleted_messages_handler.py similarity index 77% rename from pyrogram/client/handlers/deleted_messages_handler.py rename to pyrogram/handlers/deleted_messages_handler.py index 0386354163..2073092ce0 100644 --- a/pyrogram/client/handlers/deleted_messages_handler.py +++ b/pyrogram/handlers/deleted_messages_handler.py @@ -16,15 +16,20 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import List, Callable + +import pyrogram +from pyrogram.filters import Filter +from pyrogram.types import Message from .handler import Handler class DeletedMessagesHandler(Handler): """The deleted messages handler class. Used to handle deleted messages coming from any chat - (private, group, channel). It is intended to be used with :meth:`~Client.add_handler` + (private, group, channel). It is intended to be used with :meth:`~pyrogram.Client.add_handler` For a nicer way to register this handler, have a look at the - :meth:`~Client.on_deleted_messages` decorator. + :meth:`~pyrogram.Client.on_deleted_messages` decorator. Parameters: callback (``callable``): @@ -36,15 +41,15 @@ class DeletedMessagesHandler(Handler): in your callback function. Other parameters: - client (:obj:`Client`): + client (:obj:`~pyrogram.Client`): The Client itself, useful when you want to call other API methods inside the message handler. - messages (List of :obj:`Message`): + messages (List of :obj:`~pyrogram.types.Message`): The deleted messages, as list. """ - def __init__(self, callback: callable, filters=None): + def __init__(self, callback: Callable, filters: Filter = None): super().__init__(callback, filters) - async def check(self, messages): - return (await super().check(messages[0])) + async def check(self, client: "pyrogram.Client", messages: List[Message]): + return await super().check(client, messages[0]) diff --git a/pyrogram/client/handlers/disconnect_handler.py b/pyrogram/handlers/disconnect_handler.py similarity index 91% rename from pyrogram/client/handlers/disconnect_handler.py rename to pyrogram/handlers/disconnect_handler.py index f4aec6b22d..59ab401571 100644 --- a/pyrogram/client/handlers/disconnect_handler.py +++ b/pyrogram/handlers/disconnect_handler.py @@ -21,10 +21,10 @@ class DisconnectHandler(Handler): """The Disconnect handler class. Used to handle disconnections. It is intended to be used with - :meth:`~Client.add_handler` + :meth:`~pyrogram.Client.add_handler` For a nicer way to register this handler, have a look at the - :meth:`~Client.on_disconnect` decorator. + :meth:`~pyrogram.Client.on_disconnect` decorator. Parameters: callback (``callable``): @@ -32,7 +32,7 @@ class DisconnectHandler(Handler): as positional argument (look at the section below for a detailed description). Other parameters: - client (:obj:`Client`): + client (:obj:`~pyrogram.Client`): The Client itself. Useful, for example, when you want to change the proxy before a new connection is established. """ diff --git a/pyrogram/client/handlers/handler.py b/pyrogram/handlers/handler.py similarity index 59% rename from pyrogram/client/handlers/handler.py rename to pyrogram/handlers/handler.py index d50b069b33..d4d41ea56c 100644 --- a/pyrogram/client/handlers/handler.py +++ b/pyrogram/handlers/handler.py @@ -16,22 +16,28 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -import asyncio +import inspect +from typing import Callable + +import pyrogram +from pyrogram.filters import Filter +from pyrogram.types import Update class Handler: - def __init__(self, callback: callable, filters=None): + def __init__(self, callback: Callable, filters: Filter = None): self.callback = callback self.filters = filters - async def check(self, update): - - if callable(self.filters): - if asyncio.iscoroutinefunction(self.filters.__call__): - return (await self.filters(update)) - + async def check(self, client: "pyrogram.Client", update: Update): + if self.filters: + if inspect.iscoroutinefunction(self.filters.__call__): + return await self.filters(client, update) else: - return self.filters(update) + return await client.loop.run_in_executor( + client.executor, + self.filters, + client, update + ) - else: - return True + return True diff --git a/pyrogram/client/handlers/inline_query_handler.py b/pyrogram/handlers/inline_query_handler.py similarity index 88% rename from pyrogram/client/handlers/inline_query_handler.py rename to pyrogram/handlers/inline_query_handler.py index aaa63c351c..af7f9e3bad 100644 --- a/pyrogram/client/handlers/inline_query_handler.py +++ b/pyrogram/handlers/inline_query_handler.py @@ -21,10 +21,10 @@ class InlineQueryHandler(Handler): """The InlineQuery handler class. Used to handle inline queries. - It is intended to be used with :meth:`~Client.add_handler` + It is intended to be used with :meth:`~pyrogram.Client.add_handler` For a nicer way to register this handler, have a look at the - :meth:`~Client.on_inline_query` decorator. + :meth:`~pyrogram.Client.on_inline_query` decorator. Parameters: callback (``callable``): @@ -36,10 +36,10 @@ class InlineQueryHandler(Handler): in your callback function. Other parameters: - client (:obj:`Client`): + client (:obj:`~pyrogram.Client`): The Client itself, useful when you want to call other API methods inside the inline query handler. - inline_query (:obj:`InlineQuery`): + inline_query (:obj:`~pyrogram.types.InlineQuery`): The received inline query. """ diff --git a/pyrogram/client/handlers/message_handler.py b/pyrogram/handlers/message_handler.py similarity index 90% rename from pyrogram/client/handlers/message_handler.py rename to pyrogram/handlers/message_handler.py index f5a3b6e935..e9ef2aa53c 100644 --- a/pyrogram/client/handlers/message_handler.py +++ b/pyrogram/handlers/message_handler.py @@ -21,10 +21,10 @@ class MessageHandler(Handler): """The Message handler class. Used to handle text, media and service messages coming from - any chat (private, group, channel). It is intended to be used with :meth:`~Client.add_handler` + any chat (private, group, channel). It is intended to be used with :meth:`~pyrogram.Client.add_handler` For a nicer way to register this handler, have a look at the - :meth:`~Client.on_message` decorator. + :meth:`~pyrogram.Client.on_message` decorator. Parameters: callback (``callable``): @@ -36,10 +36,10 @@ class MessageHandler(Handler): in your callback function. Other parameters: - client (:obj:`Client`): + client (:obj:`~pyrogram.Client`): The Client itself, useful when you want to call other API methods inside the message handler. - message (:obj:`Message`): + message (:obj:`~pyrogram.types.Message`): The received message. """ diff --git a/pyrogram/client/handlers/poll_handler.py b/pyrogram/handlers/poll_handler.py similarity index 89% rename from pyrogram/client/handlers/poll_handler.py rename to pyrogram/handlers/poll_handler.py index 9dc90c4f56..3fe7c382af 100644 --- a/pyrogram/client/handlers/poll_handler.py +++ b/pyrogram/handlers/poll_handler.py @@ -22,10 +22,10 @@ class PollHandler(Handler): """The Poll handler class. Used to handle polls updates. - It is intended to be used with :meth:`~Client.add_handler` + It is intended to be used with :meth:`~pyrogram.Client.add_handler` For a nicer way to register this handler, have a look at the - :meth:`~Client.on_poll` decorator. + :meth:`~pyrogram.Client.on_poll` decorator. Parameters: callback (``callable``): @@ -37,10 +37,10 @@ class PollHandler(Handler): in your callback function. Other parameters: - client (:obj:`Client`): + client (:obj:`~pyrogram.Client`): The Client itself, useful when you want to call other API methods inside the poll handler. - poll (:obj:`Poll`): + poll (:obj:`~pyrogram.types.Poll`): The received poll. """ diff --git a/pyrogram/client/handlers/raw_update_handler.py b/pyrogram/handlers/raw_update_handler.py similarity index 88% rename from pyrogram/client/handlers/raw_update_handler.py rename to pyrogram/handlers/raw_update_handler.py index fa01ced53c..9376409b3e 100644 --- a/pyrogram/client/handlers/raw_update_handler.py +++ b/pyrogram/handlers/raw_update_handler.py @@ -21,10 +21,10 @@ class RawUpdateHandler(Handler): """The Raw Update handler class. Used to handle raw updates. It is intended to be used with - :meth:`~Client.add_handler` + :meth:`~pyrogram.Client.add_handler` For a nicer way to register this handler, have a look at the - :meth:`~Client.on_raw_update` decorator. + :meth:`~pyrogram.Client.on_raw_update` decorator. Parameters: callback (``callable``): @@ -33,20 +33,20 @@ class RawUpdateHandler(Handler): a detailed description). Other Parameters: - client (:obj:`Client`): + client (:obj:`~pyrogram.Client`): The Client itself, useful when you want to call other API methods inside the update handler. update (``Update``): The received update, which can be one of the many single Updates listed in the *updates* - field you see in the :obj:`Update ` type. + field you see in the :obj:`~pyrogram.types.Update` type. users (``dict``): - Dictionary of all :obj:`User ` mentioned in the update. + Dictionary of all :obj:`~pyrogram.types.User` mentioned in the update. You can access extra info about the user (such as *first_name*, *last_name*, etc...) by using the IDs you find in the *update* argument (e.g.: *users[1768841572]*). chats (``dict``): - Dictionary of all :obj:`Chat ` and + Dictionary of all :obj:`~pyrogram.types.Chat` and :obj:`Channel ` mentioned in the update. You can access extra info about the chat (such as *title*, *participants_count*, etc...) by using the IDs you find in the *update* argument (e.g.: *chats[1701277281]*). diff --git a/pyrogram/client/handlers/user_status_handler.py b/pyrogram/handlers/user_status_handler.py similarity index 89% rename from pyrogram/client/handlers/user_status_handler.py rename to pyrogram/handlers/user_status_handler.py index 94404d69f2..50c5a89e5a 100644 --- a/pyrogram/client/handlers/user_status_handler.py +++ b/pyrogram/handlers/user_status_handler.py @@ -21,9 +21,9 @@ class UserStatusHandler(Handler): """The UserStatus handler class. Used to handle user status updates (user going online or offline). - It is intended to be used with :meth:`~Client.add_handler`. + It is intended to be used with :meth:`~pyrogram.Client.add_handler`. - For a nicer way to register this handler, have a look at the :meth:`~Client.on_user_status` decorator. + For a nicer way to register this handler, have a look at the :meth:`~pyrogram.Client.on_user_status` decorator. Parameters: callback (``callable``): @@ -34,10 +34,10 @@ class UserStatusHandler(Handler): Pass one or more filters to allow only a subset of users to be passed in your callback function. Other parameters: - client (:obj:`Client`): + client (:obj:`~pyrogram.Client`): The Client itself, useful when you want to call other API methods inside the user status handler. - user (:obj:`User`): + user (:obj:`~pyrogram.types.User`): The user containing the updated status. """ diff --git a/pyrogram/client/methods/__init__.py b/pyrogram/methods/__init__.py similarity index 88% rename from pyrogram/client/methods/__init__.py rename to pyrogram/methods/__init__.py index 54516e98fd..30fe28a388 100644 --- a/pyrogram/client/methods/__init__.py +++ b/pyrogram/methods/__init__.py @@ -16,6 +16,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from .advanced import Advanced +from .auth import Auth from .bots import Bots from .chats import Chats from .contacts import Contacts @@ -23,15 +25,19 @@ from .messages import Messages from .password import Password from .users import Users +from .utilities import Utilities class Methods( + Advanced, + Auth, Bots, Contacts, Password, Chats, Users, Messages, - Decorators + Decorators, + Utilities ): pass diff --git a/pyrogram/methods/advanced/__init__.py b/pyrogram/methods/advanced/__init__.py new file mode 100644 index 0000000000..318f40eef2 --- /dev/null +++ b/pyrogram/methods/advanced/__init__.py @@ -0,0 +1,29 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan +# +# 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 .resolve_peer import ResolvePeer +from .save_file import SaveFile +from .send import Send + + +class Advanced( + ResolvePeer, + SaveFile, + Send +): + pass diff --git a/pyrogram/methods/advanced/resolve_peer.py b/pyrogram/methods/advanced/resolve_peer.py new file mode 100644 index 0000000000..babc35d3a5 --- /dev/null +++ b/pyrogram/methods/advanced/resolve_peer.py @@ -0,0 +1,123 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan +# +# 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 re +from typing import Union + +from pyrogram import raw +from pyrogram import utils +from pyrogram.errors import PeerIdInvalid +from pyrogram.scaffold import Scaffold + +log = logging.getLogger(__name__) + + +class ResolvePeer(Scaffold): + async def resolve_peer( + self, + peer_id: Union[int, str] + ) -> Union[raw.base.InputPeer, raw.base.InputUser, raw.base.InputChannel]: + """Get the InputPeer of a known peer id. + Useful whenever an InputPeer type is required. + + .. note:: + + This is a utility method intended to be used **only** when working with raw + :obj:`functions ` (i.e: a Telegram API method you wish to use which is not + available yet in the Client class as an easy-to-use method). + + Parameters: + peer_id (``int`` | ``str``): + The peer id you want to extract the InputPeer from. + Can be a direct id (int), a username (str) or a phone number (str). + + Returns: + ``InputPeer``: On success, the resolved peer id is returned in form of an InputPeer object. + + Raises: + KeyError: In case the peer doesn't exist in the internal database. + """ + if not self.is_connected: + raise ConnectionError("Client has not been started yet") + + try: + return await self.storage.get_peer_by_id(peer_id) + except KeyError: + if isinstance(peer_id, str): + if peer_id in ("self", "me"): + return raw.types.InputPeerSelf() + + peer_id = re.sub(r"[@+\s]", "", peer_id.lower()) + + try: + int(peer_id) + except ValueError: + try: + return await self.storage.get_peer_by_username(peer_id) + except KeyError: + await self.send( + raw.functions.contacts.ResolveUsername( + username=peer_id + ) + ) + + return await self.storage.get_peer_by_username(peer_id) + else: + try: + return await self.storage.get_peer_by_phone_number(peer_id) + except KeyError: + raise PeerIdInvalid + + peer_type = utils.get_peer_type(peer_id) + + if peer_type == "user": + await self.fetch_peers( + await self.send( + raw.functions.users.GetUsers( + id=[ + raw.types.InputUser( + user_id=peer_id, + access_hash=0 + ) + ] + ) + ) + ) + elif peer_type == "chat": + await self.send( + raw.functions.messages.GetChats( + id=[-peer_id] + ) + ) + else: + await self.send( + raw.functions.channels.GetChannels( + id=[ + raw.types.InputChannel( + channel_id=utils.get_channel_id(peer_id), + access_hash=0 + ) + ] + ) + ) + + try: + return await self.storage.get_peer_by_id(peer_id) + except KeyError: + raise PeerIdInvalid diff --git a/pyrogram/methods/advanced/save_file.py b/pyrogram/methods/advanced/save_file.py new file mode 100644 index 0000000000..4e594b6485 --- /dev/null +++ b/pyrogram/methods/advanced/save_file.py @@ -0,0 +1,222 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan +# +# 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 asyncio +import functools +import io +import logging +import math +import os +from hashlib import md5 +from pathlib import PurePath +from typing import Union, BinaryIO + +from pyrogram import StopTransmission +from pyrogram import raw +from pyrogram.scaffold import Scaffold +from pyrogram.session import Session + +log = logging.getLogger(__name__) + + +class SaveFile(Scaffold): + async def save_file( + self, + path: Union[str, BinaryIO], + file_id: int = None, + file_part: int = 0, + progress: callable = None, + progress_args: tuple = () + ): + """Upload a file onto Telegram servers, without actually sending the message to anyone. + Useful whenever an InputFile type is required. + + .. note:: + + This is a utility method intended to be used **only** when working with raw + :obj:`functions ` (i.e: a Telegram API method you wish to use which is not + available yet in the Client class as an easy-to-use method). + + Parameters: + path (``str``): + The path of the file you want to upload that exists on your local machine. + + file_id (``int``, *optional*): + In case a file part expired, pass the file_id and the file_part to retry uploading that specific chunk. + + file_part (``int``, *optional*): + In case a file part expired, pass the file_id and the file_part to retry uploading that specific chunk. + + progress (``callable``, *optional*): + Pass a callback function to view the file transmission progress. + The function must take *(current, total)* as positional arguments (look at Other Parameters below for a + detailed description) and will be called back each time a new file chunk has been successfully + transmitted. + + progress_args (``tuple``, *optional*): + Extra custom arguments for the progress callback function. + You can pass anything you need to be available in the progress callback scope; for example, a Message + object or a Client instance in order to edit the message with the updated progress status. + + Other Parameters: + current (``int``): + The amount of bytes transmitted so far. + + total (``int``): + The total size of the file. + + *args (``tuple``, *optional*): + Extra custom arguments as defined in the *progress_args* parameter. + You can either keep *\*args* or add every single extra argument in your function signature. + + Returns: + ``InputFile``: On success, the uploaded file is returned in form of an InputFile object. + + Raises: + RPCError: In case of a Telegram RPC error. + """ + if path is None: + return None + + async def worker(session): + while True: + data = await queue.get() + + if data is None: + return + + try: + await asyncio.ensure_future(session.send(data)) + except Exception as e: + log.error(e) + + part_size = 512 * 1024 + + if isinstance(path, (str, PurePath)): + fp = open(path, "rb") + elif isinstance(path, io.IOBase): + fp = path + else: + raise ValueError("Invalid file. Expected a file path as string or a binary (not text) file pointer") + + file_name = fp.name + + fp.seek(0, os.SEEK_END) + file_size = fp.tell() + fp.seek(0) + + if file_size == 0: + raise ValueError("File size equals to 0 B") + + if file_size > 2000 * 1024 * 1024: + raise ValueError("Telegram doesn't support uploading files bigger than 2000 MiB") + + 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, await self.storage.dc_id(), await self.storage.auth_key(), + await self.storage.test_mode(), 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: + for session in pool: + await session.start() + + with fp: + fp.seek(part_size * file_part) + + while True: + chunk = fp.read(part_size) + + if not chunk: + if not is_big: + md5_sum = "".join([hex(i)[2:].zfill(2) for i in md5_sum.digest()]) + break + + if is_big: + rpc = raw.functions.upload.SaveBigFilePart( + file_id=file_id, + file_part=file_part, + file_total_parts=file_total_parts, + bytes=chunk + ) + else: + rpc = raw.functions.upload.SaveFilePart( + file_id=file_id, + file_part=file_part, + bytes=chunk + ) + + await queue.put(rpc) + + if is_missing_part: + return + + if not is_big: + md5_sum.update(chunk) + + file_part += 1 + + if progress: + if asyncio.iscoroutinefunction(progress): + await progress(min(file_part * part_size, file_size), file_size, *progress_args) + else: + func = functools.partial( + progress, + min(file_part * part_size, file_size), + file_size, + *progress_args + ) + + await self.loop.run_in_executor(self.executor, func) + except StopTransmission: + raise + except Exception as e: + log.error(e, exc_info=True) + else: + if is_big: + return raw.types.InputFileBig( + id=file_id, + parts=file_total_parts, + name=file_name, + + ) + else: + return raw.types.InputFile( + id=file_id, + parts=file_total_parts, + name=file_name, + md5_checksum=md5_sum + ) + finally: + for _ in workers: + await queue.put(None) + + await asyncio.gather(*workers) + + for session in pool: + await session.stop() diff --git a/pyrogram/methods/advanced/send.py b/pyrogram/methods/advanced/send.py new file mode 100644 index 0000000000..fa6b9f44ce --- /dev/null +++ b/pyrogram/methods/advanced/send.py @@ -0,0 +1,73 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan +# +# 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 + +from pyrogram import raw +from pyrogram.raw.core import TLObject +from pyrogram.scaffold import Scaffold +from pyrogram.session import Session + +log = logging.getLogger(__name__) + + +class Send(Scaffold): + async def send(self, data: TLObject, retries: int = Session.MAX_RETRIES, timeout: float = Session.WAIT_TIMEOUT): + """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 + data types from :obj:`types ` as well as bare types such as ``int``, ``str``, etc... + + .. note:: + + This is a utility method intended to be used **only** when working with raw + :obj:`functions ` (i.e: a Telegram API method you wish to use which is not + available yet in the Client class as an easy-to-use method). + + Parameters: + data (``RawFunction``): + The API Schema function filled with proper arguments. + + retries (``int``): + Number of retries. + + timeout (``float``): + Timeout in seconds. + + Returns: + ``RawType``: The raw type response generated by the query. + + Raises: + RPCError: In case of a Telegram RPC error. + """ + if not self.is_connected: + raise ConnectionError("Client has not been started yet") + + if self.no_updates: + data = raw.functions.InvokeWithoutUpdates(query=data) + + if self.takeout_id: + data = raw.functions.InvokeWithTakeout(takeout_id=self.takeout_id, query=data) + + r = await self.session.send(data, retries, timeout, self.sleep_threshold) + + await self.fetch_peers(getattr(r, "users", [])) + await self.fetch_peers(getattr(r, "chats", [])) + + return r diff --git a/pyrogram/methods/auth/__init__.py b/pyrogram/methods/auth/__init__.py new file mode 100644 index 0000000000..dca53a0854 --- /dev/null +++ b/pyrogram/methods/auth/__init__.py @@ -0,0 +1,53 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan +# +# 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 .accept_terms_of_service import AcceptTermsOfService +from .check_password import CheckPassword +from .connect import Connect +from .disconnect import Disconnect +from .get_password_hint import GetPasswordHint +from .initialize import Initialize +from .log_out import LogOut +from .recover_password import RecoverPassword +from .resend_code import ResendCode +from .send_code import SendCode +from .send_recovery_code import SendRecoveryCode +from .sign_in import SignIn +from .sign_in_bot import SignInBot +from .sign_up import SignUp +from .terminate import Terminate + + +class Auth( + AcceptTermsOfService, + CheckPassword, + Connect, + Disconnect, + GetPasswordHint, + Initialize, + LogOut, + RecoverPassword, + ResendCode, + SendCode, + SendRecoveryCode, + SignIn, + SignInBot, + SignUp, + Terminate +): + pass diff --git a/pyrogram/methods/auth/accept_terms_of_service.py b/pyrogram/methods/auth/accept_terms_of_service.py new file mode 100644 index 0000000000..33da88af12 --- /dev/null +++ b/pyrogram/methods/auth/accept_terms_of_service.py @@ -0,0 +1,41 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan +# +# 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 pyrogram import raw +from pyrogram.scaffold import Scaffold + + +class AcceptTermsOfService(Scaffold): + async def accept_terms_of_service(self, terms_of_service_id: str) -> bool: + """Accept the given terms of service. + + Parameters: + terms_of_service_id (``str``): + The terms of service identifier. + """ + r = await self.send( + raw.functions.help.AcceptTermsOfService( + id=raw.types.DataJSON( + data=terms_of_service_id + ) + ) + ) + + assert r + + return True diff --git a/pyrogram/methods/auth/check_password.py b/pyrogram/methods/auth/check_password.py new file mode 100644 index 0000000000..8942780a65 --- /dev/null +++ b/pyrogram/methods/auth/check_password.py @@ -0,0 +1,55 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan +# +# 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 + +from pyrogram import raw +from pyrogram import types +from pyrogram.scaffold import Scaffold +from pyrogram.utils import compute_password_check + +log = logging.getLogger(__name__) + + +class CheckPassword(Scaffold): + async def check_password(self, password: str) -> "types.User": + """Check your Two-Step Verification password and log in. + + Parameters: + password (``str``): + Your Two-Step Verification password. + + Returns: + :obj:`~pyrogram.types.User`: On success, the authorized user is returned. + + Raises: + BadRequest: In case the password is invalid. + """ + r = await self.send( + raw.functions.auth.CheckPassword( + password=compute_password_check( + await self.send(raw.functions.account.GetPassword()), + password + ) + ) + ) + + await self.storage.user_id(r.user.id) + await self.storage.is_bot(False) + + return types.User._parse(self, r.user) diff --git a/pyrogram/methods/auth/connect.py b/pyrogram/methods/auth/connect.py new file mode 100644 index 0000000000..cbb5bec898 --- /dev/null +++ b/pyrogram/methods/auth/connect.py @@ -0,0 +1,50 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan +# +# 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 pyrogram.scaffold import Scaffold +from pyrogram.session import Session + + +class Connect(Scaffold): + async def connect(self) -> bool: + """ + Connect the client to Telegram servers. + + Returns: + ``bool``: On success, in case the passed-in session is authorized, True is returned. Otherwise, in case + the session needs to be authorized, False is returned. + + Raises: + ConnectionError: In case you try to connect an already connected client. + """ + if self.is_connected: + raise ConnectionError("Client is already connected") + + self.load_config() + await self.load_session() + + self.session = Session( + self, await self.storage.dc_id(), + await self.storage.auth_key(), await self.storage.test_mode() + ) + + await self.session.start() + + self.is_connected = True + + return bool(await self.storage.user_id()) diff --git a/pyrogram/methods/auth/disconnect.py b/pyrogram/methods/auth/disconnect.py new file mode 100644 index 0000000000..08b88e6239 --- /dev/null +++ b/pyrogram/methods/auth/disconnect.py @@ -0,0 +1,38 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan +# +# 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 pyrogram.scaffold import Scaffold + + +class Disconnect(Scaffold): + async def disconnect(self): + """Disconnect the client from Telegram servers. + + Raises: + ConnectionError: In case you try to disconnect an already disconnected client or in case you try to + disconnect a client that needs to be terminated first. + """ + if not self.is_connected: + raise ConnectionError("Client is already disconnected") + + if self.is_initialized: + raise ConnectionError("Can't disconnect an initialized client") + + await self.session.stop() + await self.storage.close() + self.is_connected = False diff --git a/pyrogram/methods/auth/get_password_hint.py b/pyrogram/methods/auth/get_password_hint.py new file mode 100644 index 0000000000..ca1ad66244 --- /dev/null +++ b/pyrogram/methods/auth/get_password_hint.py @@ -0,0 +1,34 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan +# +# 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 + +from pyrogram import raw +from pyrogram.scaffold import Scaffold + +log = logging.getLogger(__name__) + + +class GetPasswordHint(Scaffold): + async def get_password_hint(self) -> str: + """Get your Two-Step Verification password hint. + + Returns: + ``str``: On success, the password hint as string is returned. + """ + return (await self.send(raw.functions.account.GetPassword())).hint diff --git a/pyrogram/methods/auth/initialize.py b/pyrogram/methods/auth/initialize.py new file mode 100644 index 0000000000..1b50069274 --- /dev/null +++ b/pyrogram/methods/auth/initialize.py @@ -0,0 +1,49 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan +# +# 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 + +from pyrogram.scaffold import Scaffold +from pyrogram.syncer import Syncer + +log = logging.getLogger(__name__) + + +class Initialize(Scaffold): + async def initialize(self): + """Initialize the client by starting up workers. + + This method will start updates and download workers. + It will also load plugins and start the internal dispatcher. + + Raises: + ConnectionError: In case you try to initialize a disconnected client or in case you try to initialize an + already initialized client. + """ + if not self.is_connected: + raise ConnectionError("Can't initialize a disconnected client") + + if self.is_initialized: + raise ConnectionError("Client is already initialized") + + self.load_plugins() + + await self.dispatcher.start() + await Syncer.add(self) + + self.is_initialized = True diff --git a/pyrogram/methods/auth/log_out.py b/pyrogram/methods/auth/log_out.py new file mode 100644 index 0000000000..e554c37763 --- /dev/null +++ b/pyrogram/methods/auth/log_out.py @@ -0,0 +1,47 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan +# +# 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 + +from pyrogram import raw +from pyrogram.scaffold import Scaffold + +log = logging.getLogger(__name__) + + +class LogOut(Scaffold): + async def log_out(self): + """Log out from Telegram and delete the *\\*.session* file. + + When you log out, the current client is stopped and the storage session deleted. + No more API calls can be made until you start the client and re-authorize again. + + Returns: + ``bool``: On success, True is returned. + + Example: + .. code-block:: python + + # Log out. + app.log_out() + """ + await self.send(raw.functions.auth.LogOut()) + await self.stop() + await self.storage.delete() + + return True diff --git a/pyrogram/methods/auth/recover_password.py b/pyrogram/methods/auth/recover_password.py new file mode 100644 index 0000000000..f49e4a28a7 --- /dev/null +++ b/pyrogram/methods/auth/recover_password.py @@ -0,0 +1,52 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan +# +# 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 + +from pyrogram import raw +from pyrogram import types +from pyrogram.scaffold import Scaffold + +log = logging.getLogger(__name__) + + +class RecoverPassword(Scaffold): + async def recover_password(self, recovery_code: str) -> "types.User": + """Recover your password with a recovery code and log in. + + Parameters: + recovery_code (``str``): + The recovery code sent via email. + + Returns: + :obj:`~pyrogram.types.User`: On success, the authorized user is returned and the Two-Step Verification + password reset. + + Raises: + BadRequest: In case the recovery code is invalid. + """ + r = await self.send( + raw.functions.auth.RecoverPassword( + code=recovery_code + ) + ) + + await self.storage.user_id(r.user.id) + await self.storage.is_bot(False) + + return types.User._parse(self, r.user) diff --git a/pyrogram/methods/auth/resend_code.py b/pyrogram/methods/auth/resend_code.py new file mode 100644 index 0000000000..870876a876 --- /dev/null +++ b/pyrogram/methods/auth/resend_code.py @@ -0,0 +1,58 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan +# +# 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 + +from pyrogram import raw +from pyrogram import types +from pyrogram.scaffold import Scaffold + +log = logging.getLogger(__name__) + + +class ResendCode(Scaffold): + async def resend_code(self, phone_number: str, phone_code_hash: str) -> "types.SentCode": + """Re-send the confirmation code using a different type. + + The type of the code to be re-sent is specified in the *next_type* attribute of the + :obj:`~pyrogram.types.SentCode` object returned by :meth:`send_code`. + + Parameters: + phone_number (``str``): + Phone number in international format (includes the country prefix). + + phone_code_hash (``str``): + Confirmation code identifier. + + Returns: + :obj:`~pyrogram.types.SentCode`: On success, an object containing information on the re-sent confirmation + code is returned. + + Raises: + BadRequest: In case the arguments are invalid. + """ + phone_number = phone_number.strip(" +") + + r = await self.send( + raw.functions.auth.ResendCode( + phone_number=phone_number, + phone_code_hash=phone_code_hash + ) + ) + + return types.SentCode._parse(r) diff --git a/pyrogram/methods/auth/send_code.py b/pyrogram/methods/auth/send_code.py new file mode 100644 index 0000000000..5f0232f252 --- /dev/null +++ b/pyrogram/methods/auth/send_code.py @@ -0,0 +1,74 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan +# +# 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 + +from pyrogram import raw +from pyrogram import types +from pyrogram.errors import PhoneMigrate, NetworkMigrate +from pyrogram.scaffold import Scaffold +from pyrogram.session import Session, Auth + +log = logging.getLogger(__name__) + + +class SendCode(Scaffold): + async def send_code(self, phone_number: str) -> "types.SentCode": + """Send the confirmation code to the given phone number. + + Parameters: + phone_number (``str``): + Phone number in international format (includes the country prefix). + + Returns: + :obj:`~pyrogram.types.SentCode`: On success, an object containing information on the sent confirmation code + is returned. + + Raises: + BadRequest: In case the phone number is invalid. + """ + phone_number = phone_number.strip(" +") + + while True: + try: + r = await self.send( + raw.functions.auth.SendCode( + phone_number=phone_number, + api_id=self.api_id, + api_hash=self.api_hash, + settings=raw.types.CodeSettings() + ) + ) + except (PhoneMigrate, NetworkMigrate) as e: + await self.session.stop() + + await self.storage.dc_id(e.x) + await self.storage.auth_key( + await Auth( + self, await self.storage.dc_id(), + await self.storage.test_mode() + ).create() + ) + self.session = Session( + self, await self.storage.dc_id(), + await self.storage.auth_key(), await self.storage.test_mode() + ) + + await self.session.start() + else: + return types.SentCode._parse(r) diff --git a/pyrogram/crypto/kdf.py b/pyrogram/methods/auth/send_recovery_code.py similarity index 58% rename from pyrogram/crypto/kdf.py rename to pyrogram/methods/auth/send_recovery_code.py index cb4ee96388..1a128b467d 100644 --- a/pyrogram/crypto/kdf.py +++ b/pyrogram/methods/auth/send_recovery_code.py @@ -16,18 +16,24 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from hashlib import sha256 +import logging +from pyrogram import raw +from pyrogram.scaffold import Scaffold -class KDF: - def __new__(cls, auth_key: bytes, msg_key: bytes, outgoing: bool) -> tuple: - # https://core.telegram.org/mtproto/description#defining-aes-key-and-initialization-vector - x = 0 if outgoing else 8 +log = logging.getLogger(__name__) - sha256_a = sha256(msg_key + auth_key[x: x + 36]).digest() - sha256_b = sha256(auth_key[x + 40:x + 76] + msg_key).digest() # 76 = 40 + 36 - aes_key = sha256_a[:8] + sha256_b[8:24] + sha256_a[24:32] - aes_iv = sha256_b[:8] + sha256_a[8:24] + sha256_b[24:32] +class SendRecoveryCode(Scaffold): + async def send_recovery_code(self) -> str: + """Send a code to your email to recover your password. - return aes_key, aes_iv + Returns: + ``str``: On success, the hidden email pattern is returned and a recovery code is sent to that email. + + Raises: + BadRequest: In case no recovery email was set up. + """ + return (await self.send( + raw.functions.auth.RequestPasswordRecovery() + )).email_pattern diff --git a/pyrogram/methods/auth/sign_in.py b/pyrogram/methods/auth/sign_in.py new file mode 100644 index 0000000000..ccd2dabe94 --- /dev/null +++ b/pyrogram/methods/auth/sign_in.py @@ -0,0 +1,78 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan +# +# 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 +from typing import Union + +from pyrogram import raw +from pyrogram import types +from pyrogram.scaffold import Scaffold + +log = logging.getLogger(__name__) + + +class SignIn(Scaffold): + async def sign_in( + self, + phone_number: str, + phone_code_hash: str, + phone_code: str + ) -> Union["types.User", "types.TermsOfService", bool]: + """Authorize a user in Telegram with a valid confirmation code. + + Parameters: + phone_number (``str``): + Phone number in international format (includes the country prefix). + + phone_code_hash (``str``): + Code identifier taken from the result of :meth:`~pyrogram.Client.send_code`. + + phone_code (``str``): + The valid confirmation code you received (either as Telegram message or as SMS in your phone number). + + Returns: + :obj:`~pyrogram.types.User` | :obj:`~pyrogram.types.TermsOfService` | bool: On success, in case the + authorization completed, the user is returned. In case the phone number needs to be registered first AND the + terms of services accepted (with :meth:`~pyrogram.Client.accept_terms_of_service`), an object containing + them is returned. In case the phone number needs to be registered, but the terms of services don't need to + be accepted, False is returned instead. + + Raises: + BadRequest: In case the arguments are invalid. + SessionPasswordNeeded: In case a password is needed to sign in. + """ + phone_number = phone_number.strip(" +") + + r = await self.send( + raw.functions.auth.SignIn( + phone_number=phone_number, + phone_code_hash=phone_code_hash, + phone_code=phone_code + ) + ) + + if isinstance(r, raw.types.auth.AuthorizationSignUpRequired): + if r.terms_of_service: + return types.TermsOfService._parse(terms_of_service=r.terms_of_service) + + return False + else: + await self.storage.user_id(r.user.id) + await self.storage.is_bot(False) + + return types.User._parse(self, r.user) diff --git a/pyrogram/methods/auth/sign_in_bot.py b/pyrogram/methods/auth/sign_in_bot.py new file mode 100644 index 0000000000..c5ada72cdd --- /dev/null +++ b/pyrogram/methods/auth/sign_in_bot.py @@ -0,0 +1,74 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan +# +# 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 + +from pyrogram import raw +from pyrogram import types +from pyrogram.errors import UserMigrate +from pyrogram.scaffold import Scaffold +from pyrogram.session import Session, Auth + +log = logging.getLogger(__name__) + + +class SignInBot(Scaffold): + async def sign_in_bot(self, bot_token: str) -> "types.User": + """Authorize a bot using its bot token generated by BotFather. + + Parameters: + bot_token (``str``): + The bot token generated by BotFather + + Returns: + :obj:`~pyrogram.types.User`: On success, the bot identity is return in form of a user object. + + Raises: + BadRequest: In case the bot token is invalid. + """ + while True: + try: + r = await self.send( + raw.functions.auth.ImportBotAuthorization( + flags=0, + api_id=self.api_id, + api_hash=self.api_hash, + bot_auth_token=bot_token + ) + ) + except UserMigrate as e: + await self.session.stop() + + await self.storage.dc_id(e.x) + await self.storage.auth_key( + await Auth( + self, await self.storage.dc_id(), + await self.storage.test_mode() + ).create() + ) + self.session = Session( + self, await self.storage.dc_id(), + await self.storage.auth_key(), await self.storage.test_mode() + ) + + await self.session.start() + else: + await self.storage.user_id(r.user.id) + await self.storage.is_bot(True) + + return types.User._parse(self, r.user) diff --git a/pyrogram/methods/auth/sign_up.py b/pyrogram/methods/auth/sign_up.py new file mode 100644 index 0000000000..b8d42916c3 --- /dev/null +++ b/pyrogram/methods/auth/sign_up.py @@ -0,0 +1,71 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan +# +# 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 + +from pyrogram import raw +from pyrogram import types +from pyrogram.scaffold import Scaffold + +log = logging.getLogger(__name__) + + +class SignUp(Scaffold): + async def sign_up( + self, + phone_number: str, + phone_code_hash: str, + first_name: str, + last_name: str = "" + ) -> "types.User": + """Register a new user in Telegram. + + Parameters: + phone_number (``str``): + Phone number in international format (includes the country prefix). + + phone_code_hash (``str``): + Code identifier taken from the result of :meth:`~pyrogram.Client.send_code`. + + first_name (``str``): + New user first name. + + last_name (``str``, *optional*): + New user last name. Defaults to "" (empty string, no last name). + + Returns: + :obj:`~pyrogram.types.User`: On success, the new registered user is returned. + + Raises: + BadRequest: In case the arguments are invalid. + """ + phone_number = phone_number.strip(" +") + + r = await self.send( + raw.functions.auth.SignUp( + phone_number=phone_number, + first_name=first_name, + last_name=last_name, + phone_code_hash=phone_code_hash + ) + ) + + await self.storage.user_id(r.user.id) + await self.storage.is_bot(False) + + return types.User._parse(self, r.user) diff --git a/pyrogram/methods/auth/terminate.py b/pyrogram/methods/auth/terminate.py new file mode 100644 index 0000000000..72d8038042 --- /dev/null +++ b/pyrogram/methods/auth/terminate.py @@ -0,0 +1,53 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan +# +# 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 + +from pyrogram import raw +from pyrogram.scaffold import Scaffold +from pyrogram.syncer import Syncer + +log = logging.getLogger(__name__) + + +class Terminate(Scaffold): + async def terminate(self): + """Terminate the client by shutting down workers. + + This method does the opposite of :meth:`~pyrogram.Client.initialize`. + It will stop the dispatcher and shut down updates and download workers. + + Raises: + ConnectionError: In case you try to terminate a client that is already terminated. + """ + if not self.is_initialized: + raise ConnectionError("Client is already terminated") + + if self.takeout_id: + await self.send(raw.functions.account.FinishTakeoutSession()) + log.warning(f"Takeout session {self.takeout_id} finished") + + await Syncer.remove(self) + await self.dispatcher.stop() + + for media_session in self.media_sessions.values(): + await media_session.stop() + + self.media_sessions.clear() + + self.is_initialized = False diff --git a/pyrogram/client/methods/bots/__init__.py b/pyrogram/methods/bots/__init__.py similarity index 100% rename from pyrogram/client/methods/bots/__init__.py rename to pyrogram/methods/bots/__init__.py diff --git a/pyrogram/client/methods/bots/answer_callback_query.py b/pyrogram/methods/bots/answer_callback_query.py similarity index 94% rename from pyrogram/client/methods/bots/answer_callback_query.py rename to pyrogram/methods/bots/answer_callback_query.py index ff9a5b51fa..bbc7c72488 100644 --- a/pyrogram/client/methods/bots/answer_callback_query.py +++ b/pyrogram/methods/bots/answer_callback_query.py @@ -16,11 +16,11 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from pyrogram.api import functions -from pyrogram.client.ext import BaseClient +from pyrogram import raw +from pyrogram.scaffold import Scaffold -class AnswerCallbackQuery(BaseClient): +class AnswerCallbackQuery(Scaffold): async def answer_callback_query( self, callback_query_id: str, @@ -69,7 +69,7 @@ async def answer_callback_query( app.answer_callback_query(query_id, text=text, show_alert=True) """ return await self.send( - functions.messages.SetBotCallbackAnswer( + raw.functions.messages.SetBotCallbackAnswer( query_id=int(callback_query_id), cache_time=cache_time, alert=show_alert or None, diff --git a/pyrogram/client/methods/bots/answer_inline_query.py b/pyrogram/methods/bots/answer_inline_query.py similarity index 88% rename from pyrogram/client/methods/bots/answer_inline_query.py rename to pyrogram/methods/bots/answer_inline_query.py index 69b9184de6..e84491ee3a 100644 --- a/pyrogram/client/methods/bots/answer_inline_query.py +++ b/pyrogram/methods/bots/answer_inline_query.py @@ -18,16 +18,16 @@ from typing import List -from pyrogram.api import functions, types -from pyrogram.client.ext import BaseClient -from ...types.inline_mode import InlineQueryResult +from pyrogram import raw +from pyrogram import types +from pyrogram.scaffold import Scaffold -class AnswerInlineQuery(BaseClient): +class AnswerInlineQuery(Scaffold): async def answer_inline_query( self, inline_query_id: str, - results: List[InlineQueryResult], + results: List["types.InlineQueryResult"], cache_time: int = 300, is_gallery: bool = False, is_personal: bool = False, @@ -43,7 +43,7 @@ async def answer_inline_query( inline_query_id (``str``): Unique identifier for the answered query. - results (List of :obj:`InlineQueryResult`): + results (List of :obj:`~pyrogram.types.InlineQueryResult`): A list of results for the inline query. cache_time (``int``, *optional*): @@ -93,20 +93,16 @@ async def answer_inline_query( "Title", InputTextMessageContent("Message content"))]) """ - 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( + raw.functions.messages.SetInlineBotResults( query_id=int(inline_query_id), - results=written_results, + results=[await r.write() for r in results], cache_time=cache_time, gallery=is_gallery or None, private=is_personal or None, next_offset=next_offset or None, - switch_pm=types.InlineBotSwitchPM( + switch_pm=raw.types.InlineBotSwitchPM( text=switch_pm_text, start_param=switch_pm_parameter ) if switch_pm_text else None diff --git a/pyrogram/client/methods/bots/get_game_high_scores.py b/pyrogram/methods/bots/get_game_high_scores.py similarity index 85% rename from pyrogram/client/methods/bots/get_game_high_scores.py rename to pyrogram/methods/bots/get_game_high_scores.py index c40350ad58..af2c6d7cbc 100644 --- a/pyrogram/client/methods/bots/get_game_high_scores.py +++ b/pyrogram/methods/bots/get_game_high_scores.py @@ -18,18 +18,18 @@ from typing import Union, List -import pyrogram -from pyrogram.api import functions -from pyrogram.client.ext import BaseClient +from pyrogram import raw +from pyrogram import types +from pyrogram.scaffold import Scaffold -class GetGameHighScores(BaseClient): +class GetGameHighScores(Scaffold): async def get_game_high_scores( self, user_id: Union[int, str], chat_id: Union[int, str], message_id: int = None - ) -> List["pyrogram.GameHighScore"]: + ) -> List["types.GameHighScore"]: """Get data for high score tables. Parameters: @@ -49,7 +49,7 @@ async def get_game_high_scores( Required if inline_message_id is not specified. Returns: - List of :obj:`GameHighScore`: On success. + List of :obj:`~pyrogram.types.GameHighScore`: On success. Example: .. code-block:: python @@ -60,11 +60,11 @@ async def get_game_high_scores( # TODO: inline_message_id r = await self.send( - functions.messages.GetGameHighScores( + raw.functions.messages.GetGameHighScores( peer=await self.resolve_peer(chat_id), id=message_id, user_id=await self.resolve_peer(user_id) ) ) - return pyrogram.List(pyrogram.GameHighScore._parse(self, score, r.users) for score in r.scores) + return types.List(types.GameHighScore._parse(self, score, r.users) for score in r.scores) diff --git a/pyrogram/client/methods/bots/get_inline_bot_results.py b/pyrogram/methods/bots/get_inline_bot_results.py similarity index 88% rename from pyrogram/client/methods/bots/get_inline_bot_results.py rename to pyrogram/methods/bots/get_inline_bot_results.py index 366594fcb0..e507e313ac 100644 --- a/pyrogram/client/methods/bots/get_inline_bot_results.py +++ b/pyrogram/methods/bots/get_inline_bot_results.py @@ -18,12 +18,12 @@ from typing import Union -from pyrogram.api import functions, types -from pyrogram.client.ext import BaseClient +from pyrogram import raw from pyrogram.errors import UnknownError +from pyrogram.scaffold import Scaffold -class GetInlineBotResults(BaseClient): +class GetInlineBotResults(Scaffold): async def get_inline_bot_results( self, bot: Union[int, str], @@ -33,7 +33,7 @@ async def get_inline_bot_results( longitude: float = None ): """Get bot results via inline queries. - You can then send a result using :obj:`send_inline_bot_result ` + You can then send a result using :meth:`~pyrogram.Client.send_inline_bot_result` Parameters: bot (``int`` | ``str``): @@ -71,12 +71,12 @@ async def get_inline_bot_results( try: return await self.send( - functions.messages.GetInlineBotResults( + raw.functions.messages.GetInlineBotResults( bot=await self.resolve_peer(bot), - peer=types.InputPeerSelf(), + peer=raw.types.InputPeerSelf(), query=query, offset=offset, - geo_point=types.InputGeoPoint( + geo_point=raw.types.InputGeoPoint( lat=latitude, long=longitude ) if (latitude is not None and longitude is not None) else None diff --git a/pyrogram/client/methods/bots/request_callback_answer.py b/pyrogram/methods/bots/request_callback_answer.py similarity index 93% rename from pyrogram/client/methods/bots/request_callback_answer.py rename to pyrogram/methods/bots/request_callback_answer.py index 97eacf0dbb..c42306ee1e 100644 --- a/pyrogram/client/methods/bots/request_callback_answer.py +++ b/pyrogram/methods/bots/request_callback_answer.py @@ -18,11 +18,11 @@ from typing import Union -from pyrogram.api import functions -from pyrogram.client.ext import BaseClient +from pyrogram import raw +from pyrogram.scaffold import Scaffold -class RequestCallbackAnswer(BaseClient): +class RequestCallbackAnswer(Scaffold): async def request_callback_answer( self, chat_id: Union[int, str], @@ -65,7 +65,7 @@ async def request_callback_answer( data = bytes(callback_data, "utf-8") if isinstance(callback_data, str) else callback_data return await self.send( - functions.messages.GetBotCallbackAnswer( + raw.functions.messages.GetBotCallbackAnswer( peer=await self.resolve_peer(chat_id), msg_id=message_id, data=data diff --git a/pyrogram/client/methods/bots/send_game.py b/pyrogram/methods/bots/send_game.py similarity index 77% rename from pyrogram/client/methods/bots/send_game.py rename to pyrogram/methods/bots/send_game.py index 4b4d2c02d2..3913946ae8 100644 --- a/pyrogram/client/methods/bots/send_game.py +++ b/pyrogram/methods/bots/send_game.py @@ -18,12 +18,12 @@ from typing import Union -import pyrogram -from pyrogram.api import functions, types -from pyrogram.client.ext import BaseClient +from pyrogram import raw +from pyrogram import types +from pyrogram.scaffold import Scaffold -class SendGame(BaseClient): +class SendGame(Scaffold): async def send_game( self, chat_id: Union[int, str], @@ -31,12 +31,12 @@ async def send_game( disable_notification: bool = None, reply_to_message_id: int = None, reply_markup: Union[ - "pyrogram.InlineKeyboardMarkup", - "pyrogram.ReplyKeyboardMarkup", - "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply" + "types.InlineKeyboardMarkup", + "types.ReplyKeyboardMarkup", + "types.ReplyKeyboardRemove", + "types.ForceReply" ] = None - ) -> "pyrogram.Message": + ) -> "types.Message": """Send a game. Parameters: @@ -55,12 +55,12 @@ async def send_game( reply_to_message_id (``int``, *optional*): If the message is a reply, ID of the original message. - reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*): An object for an inline keyboard. If empty, one ‘Play game_title’ button will be shown automatically. If not empty, the first button must launch the game. Returns: - :obj:`Message`: On success, the sent game message is returned. + :obj:`~pyrogram.types.Message`: On success, the sent game message is returned. Example: .. code-block:: python @@ -68,11 +68,11 @@ async def send_game( app.send_game(chat_id, "gamename") """ r = await self.send( - functions.messages.SendMedia( + raw.functions.messages.SendMedia( peer=await self.resolve_peer(chat_id), - media=types.InputMediaGame( - id=types.InputGameShortName( - bot_id=types.InputUserSelf(), + media=raw.types.InputMediaGame( + id=raw.types.InputGameShortName( + bot_id=raw.types.InputUserSelf(), short_name=game_short_name ), ), @@ -85,8 +85,8 @@ async def send_game( ) for i in r.updates: - if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - return await pyrogram.Message._parse( + if isinstance(i, (raw.types.UpdateNewMessage, raw.types.UpdateNewChannelMessage)): + return await types.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/send_inline_bot_result.py b/pyrogram/methods/bots/send_inline_bot_result.py similarity index 87% rename from pyrogram/client/methods/bots/send_inline_bot_result.py rename to pyrogram/methods/bots/send_inline_bot_result.py index 8cc5bf115a..7d396a609f 100644 --- a/pyrogram/client/methods/bots/send_inline_bot_result.py +++ b/pyrogram/methods/bots/send_inline_bot_result.py @@ -18,11 +18,11 @@ from typing import Union -from pyrogram.api import functions -from pyrogram.client.ext import BaseClient +from pyrogram import raw +from pyrogram.scaffold import Scaffold -class SendInlineBotResult(BaseClient): +class SendInlineBotResult(Scaffold): async def send_inline_bot_result( self, chat_id: Union[int, str], @@ -33,7 +33,7 @@ async def send_inline_bot_result( hide_via: bool = None ): """Send an inline bot result. - Bot results can be retrieved using :obj:`get_inline_bot_results ` + Bot results can be retrieved using :meth:`~pyrogram.Client.get_inline_bot_results` Parameters: chat_id (``int`` | ``str``): @@ -58,7 +58,7 @@ async def send_inline_bot_result( Sends the message with *via @bot* hidden. Returns: - :obj:`Message`: On success, the sent inline result message is returned. + :obj:`~pyrogram.types.Message`: On success, the sent inline result message is returned. Example: .. code-block:: python @@ -66,7 +66,7 @@ async def send_inline_bot_result( app.send_inline_bot_result(chat_id, query_id, result_id) """ return await self.send( - functions.messages.SendInlineBotResult( + raw.functions.messages.SendInlineBotResult( peer=await self.resolve_peer(chat_id), query_id=query_id, id=result_id, diff --git a/pyrogram/client/methods/bots/set_game_score.py b/pyrogram/methods/bots/set_game_score.py similarity index 85% rename from pyrogram/client/methods/bots/set_game_score.py rename to pyrogram/methods/bots/set_game_score.py index 3912d29492..b9c2fe0ebb 100644 --- a/pyrogram/client/methods/bots/set_game_score.py +++ b/pyrogram/methods/bots/set_game_score.py @@ -18,12 +18,12 @@ from typing import Union -import pyrogram -from pyrogram.api import functions, types -from pyrogram.client.ext import BaseClient +from pyrogram import raw +from pyrogram import types +from pyrogram.scaffold import Scaffold -class SetGameScore(BaseClient): +class SetGameScore(Scaffold): async def set_game_score( self, user_id: Union[int, str], @@ -32,7 +32,7 @@ async def set_game_score( disable_edit_message: bool = None, chat_id: Union[int, str] = None, message_id: int = None - ) -> Union["pyrogram.Message", bool]: + ) -> Union["types.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 @@ async def set_game_score( Required if inline_message_id is not specified. Returns: - :obj:`Message` | ``bool``: On success, if the message was sent by the bot, the edited message is returned, - True otherwise. + :obj:`~pyrogram.types.Message` | ``bool``: On success, if the message was sent by the bot, the edited + message is returned, True otherwise. Example: .. code-block:: python @@ -76,7 +76,7 @@ async def set_game_score( app.set_game_score(user_id, 25, force=True) """ r = await self.send( - functions.messages.SetGameScore( + raw.functions.messages.SetGameScore( peer=await self.resolve_peer(chat_id), score=score, id=message_id, @@ -87,8 +87,9 @@ async def set_game_score( ) for i in r.updates: - if isinstance(i, (types.UpdateEditMessage, types.UpdateEditChannelMessage)): - return await pyrogram.Message._parse( + if isinstance(i, (raw.types.UpdateEditMessage, + raw.types.UpdateEditChannelMessage)): + return await types.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/chats/__init__.py b/pyrogram/methods/chats/__init__.py similarity index 100% rename from pyrogram/client/methods/chats/__init__.py rename to pyrogram/methods/chats/__init__.py diff --git a/pyrogram/client/methods/chats/add_chat_members.py b/pyrogram/methods/chats/add_chat_members.py similarity index 92% rename from pyrogram/client/methods/chats/add_chat_members.py rename to pyrogram/methods/chats/add_chat_members.py index 9a5f18eac8..6164f411bb 100644 --- a/pyrogram/client/methods/chats/add_chat_members.py +++ b/pyrogram/methods/chats/add_chat_members.py @@ -18,11 +18,11 @@ from typing import Union, List -from pyrogram.api import functions, types -from ...ext import BaseClient +from pyrogram import raw +from pyrogram.scaffold import Scaffold -class AddChatMembers(BaseClient): +class AddChatMembers(Scaffold): async def add_chat_members( self, chat_id: Union[int, str], @@ -65,10 +65,10 @@ async def add_chat_members( if not isinstance(user_ids, list): user_ids = [user_ids] - if isinstance(peer, types.InputPeerChat): + if isinstance(peer, raw.types.InputPeerChat): for user_id in user_ids: await self.send( - functions.messages.AddChatUser( + raw.functions.messages.AddChatUser( chat_id=peer.chat_id, user_id=await self.resolve_peer(user_id), fwd_limit=forward_limit @@ -76,7 +76,7 @@ async def add_chat_members( ) else: await self.send( - functions.channels.InviteToChannel( + raw.functions.channels.InviteToChannel( channel=peer, users=[ await self.resolve_peer(user_id) diff --git a/pyrogram/client/methods/chats/archive_chats.py b/pyrogram/methods/chats/archive_chats.py similarity index 91% rename from pyrogram/client/methods/chats/archive_chats.py rename to pyrogram/methods/chats/archive_chats.py index 54c452a2df..144a22718c 100644 --- a/pyrogram/client/methods/chats/archive_chats.py +++ b/pyrogram/methods/chats/archive_chats.py @@ -18,11 +18,11 @@ from typing import Union, List -from pyrogram.api import functions, types -from ...ext import BaseClient +from pyrogram import raw +from pyrogram.scaffold import Scaffold -class ArchiveChats(BaseClient): +class ArchiveChats(Scaffold): async def archive_chats( self, chat_ids: Union[int, str, List[Union[int, str]]], @@ -54,14 +54,14 @@ async def archive_chats( for chat in chat_ids: folder_peers.append( - types.InputFolderPeer( + raw.types.InputFolderPeer( peer=await self.resolve_peer(chat), folder_id=1 ) ) await self.send( - functions.folders.EditPeerFolders( + raw.functions.folders.EditPeerFolders( folder_peers=folder_peers ) ) diff --git a/pyrogram/client/methods/chats/create_channel.py b/pyrogram/methods/chats/create_channel.py similarity index 81% rename from pyrogram/client/methods/chats/create_channel.py rename to pyrogram/methods/chats/create_channel.py index 7885ed3ea9..6c2a597218 100644 --- a/pyrogram/client/methods/chats/create_channel.py +++ b/pyrogram/methods/chats/create_channel.py @@ -16,17 +16,17 @@ # 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 -from ...ext import BaseClient +from pyrogram import raw +from pyrogram import types +from pyrogram.scaffold import Scaffold -class CreateChannel(BaseClient): +class CreateChannel(Scaffold): async def create_channel( self, title: str, description: str = "" - ) -> "pyrogram.Chat": + ) -> "types.Chat": """Create a new broadcast channel. Parameters: @@ -37,7 +37,7 @@ async def create_channel( The channel description. Returns: - :obj:`Chat`: On success, a chat object is returned. + :obj:`~pyrogram.types.Chat`: On success, a chat object is returned. Example: .. code-block:: python @@ -45,11 +45,11 @@ async def create_channel( app.create_channel("Channel Title", "Channel Description") """ r = await self.send( - functions.channels.CreateChannel( + raw.functions.channels.CreateChannel( title=title, about=description, broadcast=True ) ) - return pyrogram.Chat._parse_chat(self, r.chats[0]) + return types.Chat._parse_chat(self, r.chats[0]) diff --git a/pyrogram/client/methods/chats/create_group.py b/pyrogram/methods/chats/create_group.py similarity index 85% rename from pyrogram/client/methods/chats/create_group.py rename to pyrogram/methods/chats/create_group.py index 631aa75a71..7c68f90c05 100644 --- a/pyrogram/client/methods/chats/create_group.py +++ b/pyrogram/methods/chats/create_group.py @@ -18,17 +18,17 @@ from typing import Union, List -import pyrogram -from pyrogram.api import functions -from ...ext import BaseClient +from pyrogram import raw +from pyrogram import types +from pyrogram.scaffold import Scaffold -class CreateGroup(BaseClient): +class CreateGroup(Scaffold): async def create_group( self, title: str, users: Union[Union[int, str], List[Union[int, str]]] - ) -> "pyrogram.Chat": + ) -> "types.Chat": """Create a new basic group. .. note:: @@ -45,7 +45,7 @@ async def create_group( Multiple users can be invited by passing a list of IDs, usernames or phone numbers. Returns: - :obj:`Chat`: On success, a chat object is returned. + :obj:`~pyrogram.types.Chat`: On success, a chat object is returned. Example: .. code-block:: python @@ -56,10 +56,10 @@ async def create_group( users = [users] r = await self.send( - functions.messages.CreateChat( + raw.functions.messages.CreateChat( title=title, users=[await self.resolve_peer(u) for u in users] ) ) - return pyrogram.Chat._parse_chat(self, r.chats[0]) + return types.Chat._parse_chat(self, r.chats[0]) diff --git a/pyrogram/client/methods/chats/create_supergroup.py b/pyrogram/methods/chats/create_supergroup.py similarity index 82% rename from pyrogram/client/methods/chats/create_supergroup.py rename to pyrogram/methods/chats/create_supergroup.py index 1310d65ec2..07b5adca21 100644 --- a/pyrogram/client/methods/chats/create_supergroup.py +++ b/pyrogram/methods/chats/create_supergroup.py @@ -16,17 +16,17 @@ # 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 -from ...ext import BaseClient +from pyrogram import raw +from pyrogram import types +from pyrogram.scaffold import Scaffold -class CreateSupergroup(BaseClient): +class CreateSupergroup(Scaffold): async def create_supergroup( self, title: str, description: str = "" - ) -> "pyrogram.Chat": + ) -> "types.Chat": """Create a new supergroup. .. note:: @@ -41,7 +41,7 @@ async def create_supergroup( The supergroup description. Returns: - :obj:`Chat`: On success, a chat object is returned. + :obj:`~pyrogram.types.Chat`: On success, a chat object is returned. Example: .. code-block:: python @@ -49,11 +49,11 @@ async def create_supergroup( app.create_supergroup("Supergroup Title", "Supergroup Description") """ r = await self.send( - functions.channels.CreateChannel( + raw.functions.channels.CreateChannel( title=title, about=description, megagroup=True ) ) - return pyrogram.Chat._parse_chat(self, r.chats[0]) + return types.Chat._parse_chat(self, r.chats[0]) diff --git a/pyrogram/client/methods/chats/delete_channel.py b/pyrogram/methods/chats/delete_channel.py similarity index 90% rename from pyrogram/client/methods/chats/delete_channel.py rename to pyrogram/methods/chats/delete_channel.py index ff62e8d62f..7d884bb726 100644 --- a/pyrogram/client/methods/chats/delete_channel.py +++ b/pyrogram/methods/chats/delete_channel.py @@ -18,11 +18,11 @@ from typing import Union -from pyrogram.api import functions -from ...ext import BaseClient +from pyrogram import raw +from pyrogram.scaffold import Scaffold -class DeleteChannel(BaseClient): +class DeleteChannel(Scaffold): async def delete_channel(self, chat_id: Union[int, str]) -> bool: """Delete a channel. @@ -39,7 +39,7 @@ async def delete_channel(self, chat_id: Union[int, str]) -> bool: app.delete_channel(channel_id) """ await self.send( - functions.channels.DeleteChannel( + raw.functions.channels.DeleteChannel( channel=await self.resolve_peer(chat_id) ) ) diff --git a/pyrogram/client/methods/chats/delete_chat_photo.py b/pyrogram/methods/chats/delete_chat_photo.py similarity index 77% rename from pyrogram/client/methods/chats/delete_chat_photo.py rename to pyrogram/methods/chats/delete_chat_photo.py index cc2e06dd37..de9f707c01 100644 --- a/pyrogram/client/methods/chats/delete_chat_photo.py +++ b/pyrogram/methods/chats/delete_chat_photo.py @@ -18,11 +18,11 @@ from typing import Union -from pyrogram.api import functions, types -from ...ext import BaseClient +from pyrogram import raw +from pyrogram.scaffold import Scaffold -class DeleteChatPhoto(BaseClient): +class DeleteChatPhoto(Scaffold): async def delete_chat_photo( self, chat_id: Union[int, str] @@ -48,21 +48,21 @@ async def delete_chat_photo( """ peer = await self.resolve_peer(chat_id) - if isinstance(peer, types.InputPeerChat): + if isinstance(peer, raw.types.InputPeerChat): await self.send( - functions.messages.EditChatPhoto( + raw.functions.messages.EditChatPhoto( chat_id=peer.chat_id, - photo=types.InputChatPhotoEmpty() + photo=raw.types.InputChatPhotoEmpty() ) ) - elif isinstance(peer, types.InputPeerChannel): + elif isinstance(peer, raw.types.InputPeerChannel): await self.send( - functions.channels.EditPhoto( + raw.functions.channels.EditPhoto( channel=peer, - photo=types.InputChatPhotoEmpty() + photo=raw.types.InputChatPhotoEmpty() ) ) else: - raise ValueError("The chat_id \"{}\" belongs to a user".format(chat_id)) + raise ValueError(f'The chat_id "{chat_id}" belongs to a user') return True diff --git a/pyrogram/client/methods/chats/delete_supergroup.py b/pyrogram/methods/chats/delete_supergroup.py similarity index 90% rename from pyrogram/client/methods/chats/delete_supergroup.py rename to pyrogram/methods/chats/delete_supergroup.py index f24c55a131..d4abba8c43 100644 --- a/pyrogram/client/methods/chats/delete_supergroup.py +++ b/pyrogram/methods/chats/delete_supergroup.py @@ -18,11 +18,11 @@ from typing import Union -from pyrogram.api import functions -from ...ext import BaseClient +from pyrogram import raw +from pyrogram.scaffold import Scaffold -class DeleteSupergroup(BaseClient): +class DeleteSupergroup(Scaffold): async def delete_supergroup(self, chat_id: Union[int, str]) -> bool: """Delete a supergroup. @@ -39,7 +39,7 @@ async def delete_supergroup(self, chat_id: Union[int, str]) -> bool: app.delete_supergroup(supergroup_id) """ await self.send( - functions.channels.DeleteChannel( + raw.functions.channels.DeleteChannel( channel=await self.resolve_peer(chat_id) ) ) diff --git a/pyrogram/client/methods/chats/delete_user_history.py b/pyrogram/methods/chats/delete_user_history.py similarity index 82% rename from pyrogram/client/methods/chats/delete_user_history.py rename to pyrogram/methods/chats/delete_user_history.py index a35bf10c93..d2c1797905 100644 --- a/pyrogram/client/methods/chats/delete_user_history.py +++ b/pyrogram/methods/chats/delete_user_history.py @@ -18,12 +18,12 @@ from typing import Union -from pyrogram.api import functions -from pyrogram.client.ext import BaseClient +from pyrogram import raw +from pyrogram.scaffold import Scaffold -class DeleteUserHistory(BaseClient): - def delete_user_history( +class DeleteUserHistory(Scaffold): + async def delete_user_history( self, chat_id: Union[int, str], user_id: Union[int, str], @@ -41,12 +41,12 @@ def delete_user_history( ``bool``: True on success, False otherwise. """ - r = self.send( - functions.channels.DeleteUserHistory( - channel=self.resolve_peer(chat_id), - user_id=self.resolve_peer(user_id) - ) + r = await self.send( + raw.functions.channels.DeleteUserHistory( + channel=await self.resolve_peer(chat_id), + user_id=await self.resolve_peer(user_id) ) + ) # Deleting messages you don't have right onto won't raise any error. # Check for pts_count, which is 0 in case deletes fail. diff --git a/pyrogram/client/methods/chats/export_chat_invite_link.py b/pyrogram/methods/chats/export_chat_invite_link.py similarity index 80% rename from pyrogram/client/methods/chats/export_chat_invite_link.py rename to pyrogram/methods/chats/export_chat_invite_link.py index ac0d3b9102..7b3121cae3 100644 --- a/pyrogram/client/methods/chats/export_chat_invite_link.py +++ b/pyrogram/methods/chats/export_chat_invite_link.py @@ -18,11 +18,11 @@ from typing import Union -from pyrogram.api import functions, types -from ...ext import BaseClient +from pyrogram import raw +from pyrogram.scaffold import Scaffold -class ExportChatInviteLink(BaseClient): +class ExportChatInviteLink(Scaffold): async def export_chat_invite_link( self, chat_id: Union[int, str] @@ -35,8 +35,9 @@ async def export_chat_invite_link( Each administrator in a chat generates their own invite links. Bots can't use invite links generated by other administrators. If you want your bot to work with invite links, it will need to generate its own link - using this method – after this the link will become available to the bot via the :meth:`~Client.get_chat` - method. If your bot needs to generate a new invite link replacing its previous one, use this method again. + using this method – after this the link will become available to the bot via the + :meth:`~pyrogram.Client.get_chat` method. If your bot needs to generate a new invite link replacing its + previous one, use this method again. Parameters: chat_id (``int`` | ``str``): @@ -57,11 +58,11 @@ async def export_chat_invite_link( """ peer = await self.resolve_peer(chat_id) - if isinstance(peer, (types.InputPeerChat, types.InputPeerChannel)): + if isinstance(peer, (raw.types.InputPeerChat, raw.types.InputPeerChannel)): return (await self.send( - functions.messages.ExportChatInvite( + raw.functions.messages.ExportChatInvite( peer=peer ) )).link else: - raise ValueError('The chat_id "{}" belongs to a user'.format(chat_id)) + raise ValueError(f'The chat_id "{chat_id}" belongs to a user') diff --git a/pyrogram/client/methods/chats/get_chat.py b/pyrogram/methods/chats/get_chat.py similarity index 67% rename from pyrogram/client/methods/chats/get_chat.py rename to pyrogram/methods/chats/get_chat.py index 3c94507073..4e9a5428fa 100644 --- a/pyrogram/client/methods/chats/get_chat.py +++ b/pyrogram/methods/chats/get_chat.py @@ -18,16 +18,17 @@ from typing import Union -import pyrogram -from pyrogram.api import functions, types -from ...ext import BaseClient, utils +from pyrogram import raw +from pyrogram import types +from pyrogram import utils +from pyrogram.scaffold import Scaffold -class GetChat(BaseClient): +class GetChat(Scaffold): async def get_chat( self, chat_id: Union[int, str] - ) -> Union["pyrogram.Chat", "pyrogram.ChatPreview"]: + ) -> Union["types.Chat", "types.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 @@ -40,7 +41,7 @@ async def get_chat( of the target channel/supergroup (in the format @username). Returns: - :obj:`Chat` | :obj:`ChatPreview`: On success, if you've already joined the chat, a chat object is returned, + :obj:`~pyrogram.types.Chat` | :obj:`~pyrogram.types.ChatPreview`: On success, if you've already joined the chat, a chat object is returned, otherwise, a chat preview object is returned. Raises: @@ -56,29 +57,29 @@ async def get_chat( if match: r = await self.send( - functions.messages.CheckChatInvite( + raw.functions.messages.CheckChatInvite( hash=match.group(1) ) ) - if isinstance(r, types.ChatInvite): - return pyrogram.ChatPreview._parse(self, r) + if isinstance(r, raw.types.ChatInvite): + return types.ChatPreview._parse(self, r) - self.fetch_peers([r.chat]) + await self.fetch_peers([r.chat]) if isinstance(r.chat, types.Chat): chat_id = -r.chat.id - if isinstance(r.chat, types.Channel): + if isinstance(r.chat, raw.types.Channel): chat_id = utils.get_channel_id(r.chat.id) peer = await self.resolve_peer(chat_id) - if isinstance(peer, types.InputPeerChannel): - r = await self.send(functions.channels.GetFullChannel(channel=peer)) - elif isinstance(peer, (types.InputPeerUser, types.InputPeerSelf)): - r = await self.send(functions.users.GetFullUser(id=peer)) + if isinstance(peer, raw.types.InputPeerChannel): + r = await self.send(raw.functions.channels.GetFullChannel(channel=peer)) + elif isinstance(peer, (raw.types.InputPeerUser, raw.types.InputPeerSelf)): + r = await self.send(raw.functions.users.GetFullUser(id=peer)) else: - r = await self.send(functions.messages.GetFullChat(chat_id=peer.chat_id)) + r = await self.send(raw.functions.messages.GetFullChat(chat_id=peer.chat_id)) - return await pyrogram.Chat._parse_full(self, r) + return await types.Chat._parse_full(self, r) diff --git a/pyrogram/client/methods/chats/get_chat_member.py b/pyrogram/methods/chats/get_chat_member.py similarity index 77% rename from pyrogram/client/methods/chats/get_chat_member.py rename to pyrogram/methods/chats/get_chat_member.py index b77bca8553..c4555c2bbd 100644 --- a/pyrogram/client/methods/chats/get_chat_member.py +++ b/pyrogram/methods/chats/get_chat_member.py @@ -18,19 +18,18 @@ from typing import Union -import pyrogram -from pyrogram.api import functions, types +from pyrogram import raw +from pyrogram import types from pyrogram.errors import UserNotParticipant +from pyrogram.scaffold import Scaffold -from ...ext import BaseClient - -class GetChatMember(BaseClient): +class GetChatMember(Scaffold): async def get_chat_member( self, chat_id: Union[int, str], user_id: Union[int, str] - ) -> "pyrogram.ChatMember": + ) -> "types.ChatMember": """Get information about one member of a chat. Parameters: @@ -43,7 +42,7 @@ async def get_chat_member( For a contact that exists in your Telegram address book you can use his phone number (str). Returns: - :obj:`ChatMember`: On success, a chat member is returned. + :obj:`~pyrogram.types.ChatMember`: On success, a chat member is returned. Example: .. code-block:: python @@ -54,9 +53,9 @@ async def get_chat_member( chat = await self.resolve_peer(chat_id) user = await self.resolve_peer(user_id) - if isinstance(chat, types.InputPeerChat): + if isinstance(chat, raw.types.InputPeerChat): r = await self.send( - functions.messages.GetFullChat( + raw.functions.messages.GetFullChat( chat_id=chat.chat_id ) ) @@ -65,9 +64,9 @@ async def get_chat_member( users = {i.id: i for i in r.users} for member in members: - member = pyrogram.ChatMember._parse(self, member, users) + member = types.ChatMember._parse(self, member, users) - if isinstance(user, types.InputPeerSelf): + if isinstance(user, raw.types.InputPeerSelf): if member.user.is_self: return member else: @@ -75,9 +74,9 @@ async def get_chat_member( return member else: raise UserNotParticipant - elif isinstance(chat, types.InputPeerChannel): + elif isinstance(chat, raw.types.InputPeerChannel): r = await self.send( - functions.channels.GetParticipant( + raw.functions.channels.GetParticipant( channel=chat, user_id=user ) @@ -85,6 +84,6 @@ async def get_chat_member( users = {i.id: i for i in r.users} - return pyrogram.ChatMember._parse(self, r.participant, users) + return types.ChatMember._parse(self, r.participant, users) else: - raise ValueError("The chat_id \"{}\" belongs to a user".format(chat_id)) + raise ValueError(f'The chat_id "{chat_id}" belongs to a user') diff --git a/pyrogram/client/methods/chats/get_chat_members.py b/pyrogram/methods/chats/get_chat_members.py similarity index 79% rename from pyrogram/client/methods/chats/get_chat_members.py rename to pyrogram/methods/chats/get_chat_members.py index 4f7613ce35..e5a25f4191 100644 --- a/pyrogram/client/methods/chats/get_chat_members.py +++ b/pyrogram/methods/chats/get_chat_members.py @@ -19,9 +19,9 @@ import logging from typing import Union, List -import pyrogram -from pyrogram.api import functions, types -from ...ext import BaseClient +from pyrogram import raw +from pyrogram import types +from pyrogram.scaffold import Scaffold log = logging.getLogger(__name__) @@ -35,7 +35,7 @@ class Filters: ADMINISTRATORS = "administrators" -class GetChatMembers(BaseClient): +class GetChatMembers(Scaffold): async def get_chat_members( self, chat_id: Union[int, str], @@ -43,13 +43,13 @@ async def get_chat_members( limit: int = 200, query: str = "", filter: str = Filters.ALL - ) -> List["pyrogram.ChatMember"]: + ) -> List["types.ChatMember"]: """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. You must be admin to retrieve the members list of a channel (also known as "subscribers"). - For a more convenient way of getting chat members see :meth:`~Client.iter_chat_members`. + For a more convenient way of getting chat members see :meth:`~pyrogram.Client.iter_chat_members`. Parameters: chat_id (``int`` | ``str``): @@ -86,7 +86,7 @@ async def get_chat_members( .. [2] A query string is applicable only for *"all"*, *"kicked"* and *"restricted"* filters only. Returns: - List of :obj:`ChatMember`: On success, a list of chat members is returned. + List of :obj:`~pyrogram.types.ChatMember`: On success, a list of chat members is returned. Raises: ValueError: In case you used an invalid filter or a chat id that belongs to a user. @@ -105,9 +105,9 @@ async def get_chat_members( """ peer = await self.resolve_peer(chat_id) - if isinstance(peer, types.InputPeerChat): + if isinstance(peer, raw.types.InputPeerChat): r = await self.send( - functions.messages.GetFullChat( + raw.functions.messages.GetFullChat( chat_id=peer.chat_id ) ) @@ -115,27 +115,27 @@ async def get_chat_members( members = r.full_chat.participants.participants users = {i.id: i for i in r.users} - return pyrogram.List(pyrogram.ChatMember._parse(self, member, users) for member in members) - elif isinstance(peer, types.InputPeerChannel): + return types.List(types.ChatMember._parse(self, member, users) for member in members) + elif isinstance(peer, raw.types.InputPeerChannel): filter = filter.lower() if filter == Filters.ALL: - filter = types.ChannelParticipantsSearch(q=query) + filter = raw.types.ChannelParticipantsSearch(q=query) elif filter == Filters.KICKED: - filter = types.ChannelParticipantsKicked(q=query) + filter = raw.types.ChannelParticipantsKicked(q=query) elif filter == Filters.RESTRICTED: - filter = types.ChannelParticipantsBanned(q=query) + filter = raw.types.ChannelParticipantsBanned(q=query) elif filter == Filters.BOTS: - filter = types.ChannelParticipantsBots() + filter = raw.types.ChannelParticipantsBots() elif filter == Filters.RECENT: - filter = types.ChannelParticipantsRecent() + filter = raw.types.ChannelParticipantsRecent() elif filter == Filters.ADMINISTRATORS: - filter = types.ChannelParticipantsAdmins() + filter = raw.types.ChannelParticipantsAdmins() else: - raise ValueError("Invalid filter \"{}\"".format(filter)) + raise ValueError(f'Invalid filter "{filter}"') r = await self.send( - functions.channels.GetParticipants( + raw.functions.channels.GetParticipants( channel=peer, filter=filter, offset=offset, @@ -147,6 +147,6 @@ async def get_chat_members( members = r.participants users = {i.id: i for i in r.users} - return pyrogram.List(pyrogram.ChatMember._parse(self, member, users) for member in members) + return types.List(types.ChatMember._parse(self, member, users) for member in members) else: - raise ValueError("The chat_id \"{}\" belongs to a user".format(chat_id)) + raise ValueError(f'The chat_id "{chat_id}" belongs to a user') diff --git a/pyrogram/client/methods/chats/get_chat_members_count.py b/pyrogram/methods/chats/get_chat_members_count.py similarity index 82% rename from pyrogram/client/methods/chats/get_chat_members_count.py rename to pyrogram/methods/chats/get_chat_members_count.py index 88c126690f..8bb1b7bec7 100644 --- a/pyrogram/client/methods/chats/get_chat_members_count.py +++ b/pyrogram/methods/chats/get_chat_members_count.py @@ -18,11 +18,11 @@ from typing import Union -from pyrogram.api import functions, types -from ...ext import BaseClient +from pyrogram import raw +from pyrogram.scaffold import Scaffold -class GetChatMembersCount(BaseClient): +class GetChatMembersCount(Scaffold): async def get_chat_members_count( self, chat_id: Union[int, str] @@ -47,21 +47,21 @@ async def get_chat_members_count( """ peer = await self.resolve_peer(chat_id) - if isinstance(peer, types.InputPeerChat): + if isinstance(peer, raw.types.InputPeerChat): r = await self.send( - functions.messages.GetChats( + raw.functions.messages.GetChats( id=[peer.chat_id] ) ) return r.chats[0].participants_count - elif isinstance(peer, types.InputPeerChannel): + elif isinstance(peer, raw.types.InputPeerChannel): r = await self.send( - functions.channels.GetFullChannel( + raw.functions.channels.GetFullChannel( channel=peer ) ) return r.full_chat.participants_count else: - raise ValueError("The chat_id \"{}\" belongs to a user".format(chat_id)) + raise ValueError(f'The chat_id "{chat_id}" belongs to a user') diff --git a/pyrogram/client/methods/chats/get_dialogs.py b/pyrogram/methods/chats/get_dialogs.py similarity index 75% rename from pyrogram/client/methods/chats/get_dialogs.py rename to pyrogram/methods/chats/get_dialogs.py index d52bd2cedd..8549fb139f 100644 --- a/pyrogram/client/methods/chats/get_dialogs.py +++ b/pyrogram/methods/chats/get_dialogs.py @@ -19,28 +19,29 @@ import logging from typing import List -import pyrogram -from pyrogram.api import functions, types -from ...ext import BaseClient, utils +from pyrogram import raw +from pyrogram import types +from pyrogram import utils +from pyrogram.scaffold import Scaffold log = logging.getLogger(__name__) -class GetDialogs(BaseClient): +class GetDialogs(Scaffold): async def get_dialogs( self, offset_date: int = 0, limit: int = 100, pinned_only: bool = False - ) -> List["pyrogram.Dialog"]: + ) -> List["types.Dialog"]: """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:`~Client.iter_dialogs`. + For a more convenient way of getting a user's dialogs see :meth:`~pyrogram.Client.iter_dialogs`. Parameters: offset_date (``int``): - The offset date in Unix time taken from the top message of a :obj:`Dialog`. + The offset date in Unix time taken from the top message of a :obj:`~pyrogram.types.Dialog`. Defaults to 0. Valid for non-pinned dialogs only. limit (``str``, *optional*): @@ -52,7 +53,7 @@ async def get_dialogs( Defaults to False. Returns: - List of :obj:`Dialog`: On success, a list of dialogs is returned. + List of :obj:`~pyrogram.types.Dialog`: On success, a list of dialogs is returned. Example: .. code-block:: python @@ -65,13 +66,13 @@ async def get_dialogs( """ if pinned_only: - r = await self.send(functions.messages.GetPinnedDialogs(folder_id=0)) + r = await self.send(raw.functions.messages.GetPinnedDialogs(folder_id=0)) else: r = await self.send( - functions.messages.GetDialogs( + raw.functions.messages.GetDialogs( offset_date=offset_date, offset_id=0, - offset_peer=types.InputPeerEmpty(), + offset_peer=raw.types.InputPeerEmpty(), limit=limit, hash=0, exclude_pinned=True @@ -86,7 +87,7 @@ async def get_dialogs( for message in r.messages: to_id = message.to_id - if isinstance(to_id, types.PeerUser): + if isinstance(to_id, raw.types.PeerUser): if message.out: chat_id = to_id.user_id else: @@ -94,14 +95,14 @@ async def get_dialogs( else: chat_id = utils.get_peer_id(to_id) - messages[chat_id] = await pyrogram.Message._parse(self, message, users, chats) + messages[chat_id] = await types.Message._parse(self, message, users, chats) parsed_dialogs = [] for dialog in r.dialogs: - if not isinstance(dialog, types.Dialog): + if not isinstance(dialog, raw.types.Dialog): continue - parsed_dialogs.append(pyrogram.Dialog._parse(self, dialog, messages, users, chats)) + parsed_dialogs.append(types.Dialog._parse(self, dialog, messages, users, chats)) - return pyrogram.List(parsed_dialogs) + return types.List(parsed_dialogs) diff --git a/pyrogram/client/methods/chats/get_dialogs_count.py b/pyrogram/methods/chats/get_dialogs_count.py similarity index 81% rename from pyrogram/client/methods/chats/get_dialogs_count.py rename to pyrogram/methods/chats/get_dialogs_count.py index da4c60cee7..4eef96ad7b 100644 --- a/pyrogram/client/methods/chats/get_dialogs_count.py +++ b/pyrogram/methods/chats/get_dialogs_count.py @@ -16,11 +16,11 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from pyrogram.api import functions, types -from ...ext import BaseClient +from pyrogram import raw +from pyrogram.scaffold import Scaffold -class GetDialogsCount(BaseClient): +class GetDialogsCount(Scaffold): async def get_dialogs_count(self, pinned_only: bool = False) -> int: """Get the total count of your dialogs. @@ -39,19 +39,19 @@ async def get_dialogs_count(self, pinned_only: bool = False) -> int: """ if pinned_only: - return len((await self.send(functions.messages.GetPinnedDialogs(folder_id=0))).dialogs) + return len((await self.send(raw.functions.messages.GetPinnedDialogs(folder_id=0))).dialogs) else: r = await self.send( - functions.messages.GetDialogs( + raw.functions.messages.GetDialogs( offset_date=0, offset_id=0, - offset_peer=types.InputPeerEmpty(), + offset_peer=raw.types.InputPeerEmpty(), limit=1, hash=0 ) ) - if isinstance(r, types.messages.Dialogs): + if isinstance(r, raw.types.messages.Dialogs): return len(r.dialogs) else: return r.count diff --git a/pyrogram/client/methods/chats/get_nearby_chats.py b/pyrogram/methods/chats/get_nearby_chats.py similarity index 77% rename from pyrogram/client/methods/chats/get_nearby_chats.py rename to pyrogram/methods/chats/get_nearby_chats.py index 4e76b7e6f7..697465035e 100644 --- a/pyrogram/client/methods/chats/get_nearby_chats.py +++ b/pyrogram/methods/chats/get_nearby_chats.py @@ -18,17 +18,18 @@ from typing import List -import pyrogram -from pyrogram.api import functions, types -from ...ext import BaseClient, utils +from pyrogram import raw +from pyrogram import types +from pyrogram import utils +from pyrogram.scaffold import Scaffold -class GetNearbyChats(BaseClient): +class GetNearbyChats(Scaffold): async def get_nearby_chats( self, latitude: float, longitude: float - ) -> List["pyrogram.Chat"]: + ) -> List["types.Chat"]: """Get nearby chats. Parameters: @@ -39,7 +40,7 @@ async def get_nearby_chats( Longitude of the location. Returns: - List of :obj:`Chat`: On success, a list of nearby chats is returned. + List of :obj:`~pyrogram.types.Chat`: On success, a list of nearby chats is returned. Example: .. code-block:: python @@ -49,8 +50,8 @@ async def get_nearby_chats( """ r = await self.send( - functions.contacts.GetLocated( - geo_point=types.InputGeoPoint( + raw.functions.contacts.GetLocated( + geo_point=raw.types.InputGeoPoint( lat=latitude, long=longitude ) @@ -60,11 +61,11 @@ async def get_nearby_chats( if not r.updates: return [] - chats = pyrogram.List([pyrogram.Chat._parse_chat(self, chat) for chat in r.chats]) + chats = types.List([types.Chat._parse_chat(self, chat) for chat in r.chats]) peers = r.updates[0].peers for peer in peers: - if isinstance(peer.peer, types.PeerChannel): + if isinstance(peer.peer, raw.types.PeerChannel): chat_id = utils.get_channel_id(peer.peer.channel_id) for chat in chats: diff --git a/pyrogram/client/methods/chats/iter_chat_members.py b/pyrogram/methods/chats/iter_chat_members.py similarity index 85% rename from pyrogram/client/methods/chats/iter_chat_members.py rename to pyrogram/methods/chats/iter_chat_members.py index b5ded4dce4..2954bcdba4 100644 --- a/pyrogram/client/methods/chats/iter_chat_members.py +++ b/pyrogram/methods/chats/iter_chat_members.py @@ -17,13 +17,11 @@ # along with Pyrogram. If not, see . from string import ascii_lowercase -from typing import Union, Generator, Optional +from typing import Union, AsyncGenerator, Optional -import pyrogram -from async_generator import async_generator, yield_ -from pyrogram.api import types - -from ...ext import BaseClient +from pyrogram import raw +from pyrogram import types +from pyrogram.scaffold import Scaffold class Filters: @@ -39,20 +37,19 @@ class Filters: QUERYABLE_FILTERS = (Filters.ALL, Filters.KICKED, Filters.RESTRICTED) -class IterChatMembers(BaseClient): - @async_generator +class IterChatMembers(Scaffold): async def iter_chat_members( self, chat_id: Union[int, str], limit: int = 0, query: str = "", filter: str = Filters.ALL - ) -> Optional[Generator["pyrogram.ChatMember", None, None]]: + ) -> Optional[AsyncGenerator["types.ChatMember", None]]: """Iterate through the members of a chat sequentially. - This convenience method does the same as repeatedly calling :meth:`~Client.get_chat_members` in a loop, thus saving you - from the hassle of setting up boilerplate code. It is useful for getting the whole members list of a chat with - a single call. + This convenience method does the same as repeatedly calling :meth:`~pyrogram.Client.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 a single call. Parameters: chat_id (``int`` | ``str``): @@ -78,7 +75,7 @@ async def iter_chat_members( Defaults to *"all"*. Returns: - ``Generator``: A generator yielding :obj:`ChatMember` objects. + ``Generator``: A generator yielding :obj:`~pyrogram.types.ChatMember` objects. Example: .. code-block:: python @@ -120,7 +117,7 @@ async def iter_chat_members( if not chat_members: break - if isinstance(resolved_chat_id, types.InputPeerChat): + if isinstance(resolved_chat_id, raw.types.InputPeerChat): total = len(chat_members) offset += len(chat_members) @@ -131,7 +128,7 @@ async def iter_chat_members( if user_id in yielded: continue - await yield_(chat_member) + yield chat_member yielded.add(chat_member.user.id) diff --git a/pyrogram/client/methods/chats/iter_dialogs.py b/pyrogram/methods/chats/iter_dialogs.py similarity index 79% rename from pyrogram/client/methods/chats/iter_dialogs.py rename to pyrogram/methods/chats/iter_dialogs.py index a1933a7e20..0c878f0ef4 100644 --- a/pyrogram/client/methods/chats/iter_dialogs.py +++ b/pyrogram/methods/chats/iter_dialogs.py @@ -16,26 +16,23 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Generator, Optional +from typing import AsyncGenerator, Optional -from async_generator import async_generator, yield_ +from pyrogram import types +from pyrogram.scaffold import Scaffold -import pyrogram -from ...ext import BaseClient - -class IterDialogs(BaseClient): - @async_generator +class IterDialogs(Scaffold): async def iter_dialogs( self, limit: int = 0, offset_date: int = 0 - ) -> Optional[Generator["pyrogram.Dialog", None, None]]: + ) -> Optional[AsyncGenerator["types.Dialog", None]]: """Iterate through a user's dialogs sequentially. - This convenience method does the same as repeatedly calling :meth:`~Client.get_dialogs` in a loop, thus saving - you from the hassle of setting up boilerplate code. It is useful for getting the whole dialogs list with a - single call. + This convenience method does the same as repeatedly calling :meth:`~pyrogram.Client.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. Parameters: limit (``int``, *optional*): @@ -43,11 +40,11 @@ async def iter_dialogs( 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`. + The offset date in Unix time taken from the top message of a :obj:`~pyrogram.types.Dialog`. Defaults to 0 (most recent dialog). Returns: - ``Generator``: A generator yielding :obj:`Dialog` objects. + ``Generator``: A generator yielding :obj:`~pyrogram.types.Dialog` objects. Example: .. code-block:: python @@ -65,7 +62,7 @@ async def iter_dialogs( ) for dialog in pinned_dialogs: - await yield_(dialog) + yield dialog current += 1 @@ -84,7 +81,7 @@ async def iter_dialogs( offset_date = dialogs[-1].top_message.date for dialog in dialogs: - await yield_(dialog) + yield dialog current += 1 diff --git a/pyrogram/client/methods/chats/join_chat.py b/pyrogram/methods/chats/join_chat.py similarity index 75% rename from pyrogram/client/methods/chats/join_chat.py rename to pyrogram/methods/chats/join_chat.py index 05bde18643..88adc4ed58 100644 --- a/pyrogram/client/methods/chats/join_chat.py +++ b/pyrogram/methods/chats/join_chat.py @@ -18,12 +18,12 @@ from typing import Union -import pyrogram -from pyrogram.api import functions, types -from ...ext import BaseClient +from pyrogram import raw +from pyrogram import types +from pyrogram.scaffold import Scaffold -class JoinChat(BaseClient): +class JoinChat(Scaffold): async def join_chat( self, chat_id: Union[int, str] @@ -36,7 +36,7 @@ async def join_chat( channel/supergroup (in the format @username) or a chat id of a linked chat (channel or supergroup). Returns: - :obj:`Chat`: On success, a chat object is returned. + :obj:`~pyrogram.types.Chat`: On success, a chat object is returned. Example: .. code-block:: python @@ -54,19 +54,19 @@ async def join_chat( if match: chat = await self.send( - functions.messages.ImportChatInvite( + raw.functions.messages.ImportChatInvite( hash=match.group(1) ) ) - if isinstance(chat.chats[0], types.Chat): - return pyrogram.Chat._parse_chat_chat(self, chat.chats[0]) - elif isinstance(chat.chats[0], types.Channel): - return pyrogram.Chat._parse_channel_chat(self, chat.chats[0]) + if isinstance(chat.chats[0], raw.types.Chat): + return types.Chat._parse_chat_chat(self, chat.chats[0]) + elif isinstance(chat.chats[0], raw.types.Channel): + return types.Chat._parse_channel_chat(self, chat.chats[0]) else: chat = await self.send( - functions.channels.JoinChannel( + raw.functions.channels.JoinChannel( channel=await self.resolve_peer(chat_id) ) ) - return pyrogram.Chat._parse_channel_chat(self, chat.chats[0]) + return types.Chat._parse_channel_chat(self, chat.chats[0]) diff --git a/pyrogram/client/methods/chats/kick_chat_member.py b/pyrogram/methods/chats/kick_chat_member.py similarity index 83% rename from pyrogram/client/methods/chats/kick_chat_member.py rename to pyrogram/methods/chats/kick_chat_member.py index d72da5ab22..ce814f25c1 100644 --- a/pyrogram/client/methods/chats/kick_chat_member.py +++ b/pyrogram/methods/chats/kick_chat_member.py @@ -18,18 +18,18 @@ from typing import Union -import pyrogram -from pyrogram.api import functions, types -from ...ext import BaseClient +from pyrogram import raw +from pyrogram import types +from pyrogram.scaffold import Scaffold -class KickChatMember(BaseClient): +class KickChatMember(Scaffold): async def kick_chat_member( self, chat_id: Union[int, str], user_id: Union[int, str], until_date: int = 0 - ) -> Union["pyrogram.Message", bool]: + ) -> Union["types.Message", bool]: """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 @@ -54,8 +54,8 @@ async def kick_chat_member( considered to be banned forever. Defaults to 0 (ban forever). Returns: - :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. + :obj:`~pyrogram.types.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. Example: .. code-block:: python @@ -71,12 +71,12 @@ async def kick_chat_member( chat_peer = await self.resolve_peer(chat_id) user_peer = await self.resolve_peer(user_id) - if isinstance(chat_peer, types.InputPeerChannel): + if isinstance(chat_peer, raw.types.InputPeerChannel): r = await self.send( - functions.channels.EditBanned( + raw.functions.channels.EditBanned( channel=chat_peer, user_id=user_peer, - banned_rights=types.ChatBannedRights( + banned_rights=raw.types.ChatBannedRights( until_date=until_date, view_messages=True, send_messages=True, @@ -91,15 +91,15 @@ async def kick_chat_member( ) else: r = await self.send( - functions.messages.DeleteChatUser( + raw.functions.messages.DeleteChatUser( chat_id=abs(chat_id), user_id=user_peer ) ) for i in r.updates: - if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage)): - return await pyrogram.Message._parse( + if isinstance(i, (raw.types.UpdateNewMessage, raw.types.UpdateNewChannelMessage)): + return await types.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/chats/leave_chat.py b/pyrogram/methods/chats/leave_chat.py similarity index 83% rename from pyrogram/client/methods/chats/leave_chat.py rename to pyrogram/methods/chats/leave_chat.py index 31b7cc78cd..5c60e6581c 100644 --- a/pyrogram/client/methods/chats/leave_chat.py +++ b/pyrogram/methods/chats/leave_chat.py @@ -18,11 +18,11 @@ from typing import Union -from pyrogram.api import functions, types -from ...ext import BaseClient +from pyrogram import raw +from pyrogram.scaffold import Scaffold -class LeaveChat(BaseClient): +class LeaveChat(Scaffold): async def leave_chat( self, chat_id: Union[int, str], @@ -50,23 +50,23 @@ async def leave_chat( """ peer = await self.resolve_peer(chat_id) - if isinstance(peer, types.InputPeerChannel): + if isinstance(peer, raw.types.InputPeerChannel): return await self.send( - functions.channels.LeaveChannel( + raw.functions.channels.LeaveChannel( channel=await self.resolve_peer(chat_id) ) ) - elif isinstance(peer, types.InputPeerChat): + elif isinstance(peer, raw.types.InputPeerChat): r = await self.send( - functions.messages.DeleteChatUser( + raw.functions.messages.DeleteChatUser( chat_id=peer.chat_id, - user_id=types.InputPeerSelf() + user_id=raw.types.InputUserSelf() ) ) if delete: await self.send( - functions.messages.DeleteHistory( + raw.functions.messages.DeleteHistory( peer=peer, max_id=0 ) diff --git a/pyrogram/client/methods/chats/pin_chat_message.py b/pyrogram/methods/chats/pin_chat_message.py similarity index 93% rename from pyrogram/client/methods/chats/pin_chat_message.py rename to pyrogram/methods/chats/pin_chat_message.py index 6adaa2e84c..a357aab9ec 100644 --- a/pyrogram/client/methods/chats/pin_chat_message.py +++ b/pyrogram/methods/chats/pin_chat_message.py @@ -18,11 +18,11 @@ from typing import Union -from pyrogram.api import functions -from ...ext import BaseClient +from pyrogram import raw +from pyrogram.scaffold import Scaffold -class PinChatMessage(BaseClient): +class PinChatMessage(Scaffold): async def pin_chat_message( self, chat_id: Union[int, str], @@ -57,7 +57,7 @@ async def pin_chat_message( app.pin_chat_message(chat_id, message_id, disable_notification=True) """ await self.send( - functions.messages.UpdatePinnedMessage( + raw.functions.messages.UpdatePinnedMessage( peer=await self.resolve_peer(chat_id), id=message_id, silent=disable_notification or None diff --git a/pyrogram/client/methods/chats/promote_chat_member.py b/pyrogram/methods/chats/promote_chat_member.py similarity index 95% rename from pyrogram/client/methods/chats/promote_chat_member.py rename to pyrogram/methods/chats/promote_chat_member.py index c6912031f7..eb49bce05b 100644 --- a/pyrogram/client/methods/chats/promote_chat_member.py +++ b/pyrogram/methods/chats/promote_chat_member.py @@ -18,11 +18,11 @@ from typing import Union -from pyrogram.api import functions, types -from ...ext import BaseClient +from pyrogram import raw +from pyrogram.scaffold import Scaffold -class PromoteChatMember(BaseClient): +class PromoteChatMember(Scaffold): async def promote_chat_member( self, chat_id: Union[int, str], @@ -85,10 +85,10 @@ async def promote_chat_member( app.promote_chat_member(chat_id, user_id) """ await self.send( - functions.channels.EditAdmin( + raw.functions.channels.EditAdmin( channel=await self.resolve_peer(chat_id), user_id=await self.resolve_peer(user_id), - admin_rights=types.ChatAdminRights( + admin_rights=raw.types.ChatAdminRights( change_info=can_change_info or None, post_messages=can_post_messages or None, edit_messages=can_edit_messages or None, diff --git a/pyrogram/client/methods/chats/restrict_chat_member.py b/pyrogram/methods/chats/restrict_chat_member.py similarity index 88% rename from pyrogram/client/methods/chats/restrict_chat_member.py rename to pyrogram/methods/chats/restrict_chat_member.py index 500482a1fd..af760feb74 100644 --- a/pyrogram/client/methods/chats/restrict_chat_member.py +++ b/pyrogram/methods/chats/restrict_chat_member.py @@ -18,19 +18,19 @@ from typing import Union -from pyrogram.api import functions, types -from ...ext import BaseClient -from ...types.user_and_chats import Chat, ChatPermissions +from pyrogram import raw +from pyrogram import types +from pyrogram.scaffold import Scaffold -class RestrictChatMember(BaseClient): +class RestrictChatMember(Scaffold): async def restrict_chat_member( self, chat_id: Union[int, str], user_id: Union[int, str], - permissions: ChatPermissions, + permissions: "types.ChatPermissions", until_date: int = 0 - ) -> Chat: + ) -> "types.Chat": """Restrict a user in a supergroup. You must be an administrator in the supergroup for this to work and must have the appropriate admin rights. @@ -44,7 +44,7 @@ async def restrict_chat_member( Unique identifier (int) or username (str) of the target user. For a contact that exists in your Telegram address book you can use his phone number (str). - permissions (:obj:`ChatPermissions`): + permissions (:obj:`~pyrogram.types.ChatPermissions`): New user permissions. until_date (``int``, *optional*): @@ -53,7 +53,7 @@ async def restrict_chat_member( considered to be banned forever. Defaults to 0 (ban forever). Returns: - :obj:`Chat`: On success, a chat object is returned. + :obj:`~pyrogram.types.Chat`: On success, a chat object is returned. Example: .. code-block:: python @@ -72,10 +72,10 @@ async def restrict_chat_member( app.restrict_chat_member(chat_id, user_id, ChatPermissions(can_send_messages=True)) """ r = await self.send( - functions.channels.EditBanned( + raw.functions.channels.EditBanned( channel=await self.resolve_peer(chat_id), user_id=await self.resolve_peer(user_id), - banned_rights=types.ChatBannedRights( + banned_rights=raw.types.ChatBannedRights( until_date=until_date, send_messages=True if not permissions.can_send_messages else None, send_media=True if not permissions.can_send_media_messages else None, @@ -92,4 +92,4 @@ async def restrict_chat_member( ) ) - return Chat._parse_chat(self, r.chats[0]) + return types.Chat._parse_chat(self, r.chats[0]) diff --git a/pyrogram/client/methods/chats/set_administrator_title.py b/pyrogram/methods/chats/set_administrator_title.py similarity index 90% rename from pyrogram/client/methods/chats/set_administrator_title.py rename to pyrogram/methods/chats/set_administrator_title.py index 0cbc937a33..e354fd5b72 100644 --- a/pyrogram/client/methods/chats/set_administrator_title.py +++ b/pyrogram/methods/chats/set_administrator_title.py @@ -18,11 +18,11 @@ from typing import Union -from pyrogram.api import functions, types -from ...ext import BaseClient +from pyrogram import raw +from pyrogram.scaffold import Scaffold -class SetAdministratorTitle(BaseClient): +class SetAdministratorTitle(Scaffold): async def set_administrator_title( self, chat_id: Union[int, str], @@ -58,14 +58,14 @@ async def set_administrator_title( user_id = await self.resolve_peer(user_id) r = (await self.send( - functions.channels.GetParticipant( + raw.functions.channels.GetParticipant( channel=chat_id, user_id=user_id ) )).participant - if isinstance(r, types.ChannelParticipantCreator): - admin_rights = types.ChatAdminRights( + if isinstance(r, raw.types.ChannelParticipantCreator): + admin_rights = raw.types.ChatAdminRights( change_info=True, post_messages=True, edit_messages=True, @@ -75,7 +75,7 @@ async def set_administrator_title( pin_messages=True, add_admins=True, ) - elif isinstance(r, types.ChannelParticipantAdmin): + elif isinstance(r, raw.types.ChannelParticipantAdmin): admin_rights = r.admin_rights else: raise ValueError("Custom titles can only be applied to owners or administrators of supergroups") @@ -105,7 +105,7 @@ async def set_administrator_title( admin_rights.add_admins = None await self.send( - functions.channels.EditAdmin( + raw.functions.channels.EditAdmin( channel=chat_id, user_id=user_id, admin_rights=admin_rights, diff --git a/pyrogram/client/methods/chats/set_chat_description.py b/pyrogram/methods/chats/set_chat_description.py similarity index 85% rename from pyrogram/client/methods/chats/set_chat_description.py rename to pyrogram/methods/chats/set_chat_description.py index e296040894..0dc87c4837 100644 --- a/pyrogram/client/methods/chats/set_chat_description.py +++ b/pyrogram/methods/chats/set_chat_description.py @@ -18,11 +18,11 @@ from typing import Union -from pyrogram.api import functions, types -from ...ext import BaseClient +from pyrogram import raw +from pyrogram.scaffold import Scaffold -class SetChatDescription(BaseClient): +class SetChatDescription(Scaffold): async def set_chat_description( self, chat_id: Union[int, str], @@ -51,14 +51,14 @@ async def set_chat_description( """ peer = await self.resolve_peer(chat_id) - if isinstance(peer, (types.InputPeerChannel, types.InputPeerChat)): + if isinstance(peer, (raw.types.InputPeerChannel, raw.types.InputPeerChat)): await self.send( - functions.messages.EditChatAbout( + raw.functions.messages.EditChatAbout( peer=peer, about=description ) ) else: - raise ValueError("The chat_id \"{}\" belongs to a user".format(chat_id)) + raise ValueError(f'The chat_id "{chat_id}" belongs to a user') return True diff --git a/pyrogram/client/methods/chats/set_chat_permissions.py b/pyrogram/methods/chats/set_chat_permissions.py similarity index 85% rename from pyrogram/client/methods/chats/set_chat_permissions.py rename to pyrogram/methods/chats/set_chat_permissions.py index 3509baf4ac..028f0eb167 100644 --- a/pyrogram/client/methods/chats/set_chat_permissions.py +++ b/pyrogram/methods/chats/set_chat_permissions.py @@ -18,17 +18,17 @@ from typing import Union -from pyrogram.api import functions, types -from ...ext import BaseClient -from ...types.user_and_chats import Chat, ChatPermissions +from pyrogram import raw +from pyrogram import types +from pyrogram.scaffold import Scaffold -class SetChatPermissions(BaseClient): +class SetChatPermissions(Scaffold): async def set_chat_permissions( self, chat_id: Union[int, str], - permissions: ChatPermissions, - ) -> Chat: + permissions: "types.ChatPermissions", + ) -> "types.Chat": """Set default chat permissions for all members. You must be an administrator in the group or a supergroup for this to work and must have the @@ -38,11 +38,11 @@ async def set_chat_permissions( chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. - permissions (:obj:`ChatPermissions`): + permissions (:obj:`~pyrogram.types.ChatPermissions`): New default chat permissions. Returns: - :obj:`Chat`: On success, a chat object is returned. + :obj:`~pyrogram.types.Chat`: On success, a chat object is returned. Example: .. code-block:: python @@ -64,9 +64,9 @@ async def set_chat_permissions( ) """ r = await self.send( - functions.messages.EditChatDefaultBannedRights( + raw.functions.messages.EditChatDefaultBannedRights( peer=await self.resolve_peer(chat_id), - banned_rights=types.ChatBannedRights( + banned_rights=raw.types.ChatBannedRights( until_date=0, send_messages=True if not permissions.can_send_messages else None, send_media=True if not permissions.can_send_media_messages else None, @@ -83,4 +83,4 @@ async def set_chat_permissions( ) ) - return Chat._parse_chat(self, r.chats[0]) + return types.Chat._parse_chat(self, r.chats[0]) diff --git a/pyrogram/client/methods/chats/set_chat_photo.py b/pyrogram/methods/chats/set_chat_photo.py similarity index 74% rename from pyrogram/client/methods/chats/set_chat_photo.py rename to pyrogram/methods/chats/set_chat_photo.py index e6d3544871..8ccb123cd3 100644 --- a/pyrogram/client/methods/chats/set_chat_photo.py +++ b/pyrogram/methods/chats/set_chat_photo.py @@ -19,11 +19,12 @@ import os from typing import Union, BinaryIO -from pyrogram.api import functions, types -from ...ext import BaseClient, utils +from pyrogram import raw +from pyrogram import utils +from pyrogram.scaffold import Scaffold -class SetChatPhoto(BaseClient): +class SetChatPhoto(Scaffold): async def set_chat_photo( self, chat_id: Union[int, str], @@ -44,14 +45,14 @@ async def set_chat_photo( Unique identifier (int) or username (str) of the target chat. photo (``str`` | ``BinaryIO``, *optional*): - New chat photo. You can pass a :obj:`Photo` file_id (in pair with a valid file_ref), a file path to - upload a new photo from your local machine or a binary file-like object with its attribute ".name" - set for in-memory uploads. + New chat photo. You can pass a :obj:`~pyrogram.types.Photo` file_id (in pair with a valid file_ref), a + file path to upload a new photo from your local machine or a binary file-like object with its attribute + ".name" set for in-memory uploads. video (``str`` | ``BinaryIO``, *optional*): - New chat video. You can pass a :obj:`Video` file_id (in pair with a valid file_ref), a file path to - upload a new video from your local machine or a binary file-like object with its attribute ".name" - set for in-memory uploads. + New chat video. You can pass a :obj:`~pyrogram.types.Video` file_id (in pair with a valid file_ref), a + file path to upload a new video from your local machine or a binary file-like object with its attribute + ".name" set for in-memory uploads. file_ref (``str``, *optional*): A valid file reference obtained by a recently fetched media message. @@ -83,34 +84,34 @@ async def set_chat_photo( if isinstance(photo, str): if os.path.isfile(photo): - photo = types.InputChatUploadedPhoto( + photo = raw.types.InputChatUploadedPhoto( file=await self.save_file(photo), video=await self.save_file(video) ) else: photo = utils.get_input_media_from_file_id(photo, file_ref, 2) - photo = types.InputChatPhoto(id=photo.id) + photo = raw.types.InputChatPhoto(id=photo.id) else: - photo = types.InputChatUploadedPhoto( + photo = raw.types.InputChatUploadedPhoto( file=await self.save_file(photo), video=await self.save_file(video) ) - if isinstance(peer, types.InputPeerChat): + if isinstance(peer, raw.types.InputPeerChat): await self.send( - functions.messages.EditChatPhoto( + raw.functions.messages.EditChatPhoto( chat_id=peer.chat_id, photo=photo ) ) - elif isinstance(peer, types.InputPeerChannel): + elif isinstance(peer, raw.types.InputPeerChannel): await self.send( - functions.channels.EditPhoto( + raw.functions.channels.EditPhoto( channel=peer, photo=photo ) ) else: - raise ValueError("The chat_id \"{}\" belongs to a user".format(chat_id)) + raise ValueError(f'The chat_id "{chat_id}" belongs to a user') return True diff --git a/pyrogram/client/methods/chats/set_chat_title.py b/pyrogram/methods/chats/set_chat_title.py similarity index 84% rename from pyrogram/client/methods/chats/set_chat_title.py rename to pyrogram/methods/chats/set_chat_title.py index a6b9bc71e3..2ed7d2e836 100644 --- a/pyrogram/client/methods/chats/set_chat_title.py +++ b/pyrogram/methods/chats/set_chat_title.py @@ -18,11 +18,11 @@ from typing import Union -from pyrogram.api import functions, types -from ...ext import BaseClient +from pyrogram import raw +from pyrogram.scaffold import Scaffold -class SetChatTitle(BaseClient): +class SetChatTitle(Scaffold): async def set_chat_title( self, chat_id: Union[int, str], @@ -56,21 +56,21 @@ async def set_chat_title( """ peer = await self.resolve_peer(chat_id) - if isinstance(peer, types.InputPeerChat): + if isinstance(peer, raw.types.InputPeerChat): await self.send( - functions.messages.EditChatTitle( + raw.functions.messages.EditChatTitle( chat_id=peer.chat_id, title=title ) ) - elif isinstance(peer, types.InputPeerChannel): + elif isinstance(peer, raw.types.InputPeerChannel): await self.send( - functions.channels.EditTitle( + raw.functions.channels.EditTitle( channel=peer, title=title ) ) else: - raise ValueError("The chat_id \"{}\" belongs to a user".format(chat_id)) + raise ValueError(f'The chat_id "{chat_id}" belongs to a user') return True diff --git a/pyrogram/client/methods/chats/set_slow_mode.py b/pyrogram/methods/chats/set_slow_mode.py similarity index 92% rename from pyrogram/client/methods/chats/set_slow_mode.py rename to pyrogram/methods/chats/set_slow_mode.py index 185a3824eb..e2db5a85dd 100644 --- a/pyrogram/client/methods/chats/set_slow_mode.py +++ b/pyrogram/methods/chats/set_slow_mode.py @@ -18,11 +18,11 @@ from typing import Union -from pyrogram.api import functions -from ...ext import BaseClient +from pyrogram import raw +from pyrogram.scaffold import Scaffold -class SetSlowMode(BaseClient): +class SetSlowMode(Scaffold): async def set_slow_mode( self, chat_id: Union[int, str], @@ -52,7 +52,7 @@ async def set_slow_mode( """ await self.send( - functions.channels.ToggleSlowMode( + raw.functions.channels.ToggleSlowMode( channel=await self.resolve_peer(chat_id), seconds=0 if seconds is None else seconds ) diff --git a/pyrogram/client/methods/chats/unarchive_chats.py b/pyrogram/methods/chats/unarchive_chats.py similarity index 91% rename from pyrogram/client/methods/chats/unarchive_chats.py rename to pyrogram/methods/chats/unarchive_chats.py index dfba70a7fa..af62d9a730 100644 --- a/pyrogram/client/methods/chats/unarchive_chats.py +++ b/pyrogram/methods/chats/unarchive_chats.py @@ -18,11 +18,11 @@ from typing import Union, List -from pyrogram.api import functions, types -from ...ext import BaseClient +from pyrogram import raw +from pyrogram.scaffold import Scaffold -class UnarchiveChats(BaseClient): +class UnarchiveChats(Scaffold): async def unarchive_chats( self, chat_ids: Union[int, str, List[Union[int, str]]], @@ -54,14 +54,14 @@ async def unarchive_chats( for chat in chat_ids: folder_peers.append( - types.InputFolderPeer( + raw.types.InputFolderPeer( peer=await self.resolve_peer(chat), folder_id=0 ) ) await self.send( - functions.folders.EditPeerFolders( + raw.functions.folders.EditPeerFolders( folder_peers=folder_peers ) ) diff --git a/pyrogram/client/methods/chats/unban_chat_member.py b/pyrogram/methods/chats/unban_chat_member.py similarity index 90% rename from pyrogram/client/methods/chats/unban_chat_member.py rename to pyrogram/methods/chats/unban_chat_member.py index 4a7b3940cf..e6f0b7b0b0 100644 --- a/pyrogram/client/methods/chats/unban_chat_member.py +++ b/pyrogram/methods/chats/unban_chat_member.py @@ -18,11 +18,11 @@ from typing import Union -from pyrogram.api import functions, types -from ...ext import BaseClient +from pyrogram import raw +from pyrogram.scaffold import Scaffold -class UnbanChatMember(BaseClient): +class UnbanChatMember(Scaffold): async def unban_chat_member( self, chat_id: Union[int, str], @@ -50,10 +50,10 @@ async def unban_chat_member( app.unban_chat_member(chat_id, user_id) """ await self.send( - functions.channels.EditBanned( + raw.functions.channels.EditBanned( channel=await self.resolve_peer(chat_id), user_id=await self.resolve_peer(user_id), - banned_rights=types.ChatBannedRights( + banned_rights=raw.types.ChatBannedRights( until_date=0 ) ) diff --git a/pyrogram/client/methods/chats/unpin_chat_message.py b/pyrogram/methods/chats/unpin_chat_message.py similarity index 91% rename from pyrogram/client/methods/chats/unpin_chat_message.py rename to pyrogram/methods/chats/unpin_chat_message.py index 0ca8254aec..3ee7814dbd 100644 --- a/pyrogram/client/methods/chats/unpin_chat_message.py +++ b/pyrogram/methods/chats/unpin_chat_message.py @@ -18,11 +18,11 @@ from typing import Union -from pyrogram.api import functions -from ...ext import BaseClient +from pyrogram import raw +from pyrogram.scaffold import Scaffold -class UnpinChatMessage(BaseClient): +class UnpinChatMessage(Scaffold): async def unpin_chat_message( self, chat_id: Union[int, str] @@ -44,7 +44,7 @@ async def unpin_chat_message( app.unpin_chat_message(chat_id) """ await self.send( - functions.messages.UpdatePinnedMessage( + raw.functions.messages.UpdatePinnedMessage( peer=await self.resolve_peer(chat_id), id=0 ) diff --git a/pyrogram/client/methods/chats/update_chat_username.py b/pyrogram/methods/chats/update_chat_username.py similarity index 84% rename from pyrogram/client/methods/chats/update_chat_username.py rename to pyrogram/methods/chats/update_chat_username.py index b1c57f1ed3..876115a4bc 100644 --- a/pyrogram/client/methods/chats/update_chat_username.py +++ b/pyrogram/methods/chats/update_chat_username.py @@ -18,11 +18,11 @@ from typing import Union -from pyrogram.api import functions, types -from ...ext import BaseClient +from pyrogram import raw +from pyrogram.scaffold import Scaffold -class UpdateChatUsername(BaseClient): +class UpdateChatUsername(Scaffold): async def update_chat_username( self, chat_id: Union[int, str], @@ -30,7 +30,7 @@ async def update_chat_username( ) -> bool: """Update a channel or a supergroup username. - To update your own username (for users only, not bots) you can use :meth:`~Client.update_username`. + To update your own username (for users only, not bots) you can use :meth:`~pyrogram.Client.update_username`. Parameters: chat_id (``int`` | ``str``) @@ -52,14 +52,14 @@ async def update_chat_username( peer = await self.resolve_peer(chat_id) - if isinstance(peer, types.InputPeerChannel): + if isinstance(peer, raw.types.InputPeerChannel): return bool( await self.send( - functions.channels.UpdateUsername( + raw.functions.channels.UpdateUsername( channel=peer, username=username or "" ) ) ) else: - raise ValueError("The chat_id \"{}\" belongs to a user or chat".format(chat_id)) + raise ValueError(f'The chat_id "{chat_id}" belongs to a user or chat') diff --git a/pyrogram/client/methods/contacts/__init__.py b/pyrogram/methods/contacts/__init__.py similarity index 100% rename from pyrogram/client/methods/contacts/__init__.py rename to pyrogram/methods/contacts/__init__.py diff --git a/pyrogram/client/methods/contacts/add_contacts.py b/pyrogram/methods/contacts/add_contacts.py similarity index 84% rename from pyrogram/client/methods/contacts/add_contacts.py rename to pyrogram/methods/contacts/add_contacts.py index 7226d60b19..b786991cc7 100644 --- a/pyrogram/client/methods/contacts/add_contacts.py +++ b/pyrogram/methods/contacts/add_contacts.py @@ -18,20 +18,20 @@ from typing import List -import pyrogram -from pyrogram.api import functions -from ...ext import BaseClient +from pyrogram import raw +from pyrogram import types +from pyrogram.scaffold import Scaffold -class AddContacts(BaseClient): +class AddContacts(Scaffold): async def add_contacts( self, - contacts: List["pyrogram.InputPhoneContact"] + contacts: List["types.InputPhoneContact"] ): """Add contacts to your Telegram address book. Parameters: - contacts (List of :obj:`InputPhoneContact`): + contacts (List of :obj:`~pyrogram.types.InputPhoneContact`): The contact list to be added Returns: @@ -48,7 +48,7 @@ async def add_contacts( InputPhoneContact("01234567891", "Baz")]) """ imported_contacts = await self.send( - functions.contacts.ImportContacts( + raw.functions.contacts.ImportContacts( contacts=contacts ) ) diff --git a/pyrogram/client/methods/contacts/delete_contacts.py b/pyrogram/methods/contacts/delete_contacts.py similarity index 88% rename from pyrogram/client/methods/contacts/delete_contacts.py rename to pyrogram/methods/contacts/delete_contacts.py index 777d8b39d0..2f576a5e2f 100644 --- a/pyrogram/client/methods/contacts/delete_contacts.py +++ b/pyrogram/methods/contacts/delete_contacts.py @@ -18,12 +18,12 @@ from typing import List -from pyrogram.api import functions, types +from pyrogram import raw from pyrogram.errors import PeerIdInvalid -from ...ext import BaseClient +from pyrogram.scaffold import Scaffold -class DeleteContacts(BaseClient): +class DeleteContacts(Scaffold): async def delete_contacts( self, ids: List[int] @@ -51,11 +51,11 @@ async def delete_contacts( except PeerIdInvalid: continue else: - if isinstance(input_user, types.InputPeerUser): + if isinstance(input_user, raw.types.InputPeerUser): contacts.append(input_user) return await self.send( - functions.contacts.DeleteContacts( + raw.functions.contacts.DeleteContacts( id=contacts ) ) diff --git a/pyrogram/client/methods/contacts/get_contacts.py b/pyrogram/methods/contacts/get_contacts.py similarity index 72% rename from pyrogram/client/methods/contacts/get_contacts.py rename to pyrogram/methods/contacts/get_contacts.py index 8f8392d68a..d4f08168e8 100644 --- a/pyrogram/client/methods/contacts/get_contacts.py +++ b/pyrogram/methods/contacts/get_contacts.py @@ -16,23 +16,22 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -import asyncio import logging from typing import List -import pyrogram -from pyrogram.api import functions -from ...ext import BaseClient +from pyrogram import raw +from pyrogram import types +from pyrogram.scaffold import Scaffold log = logging.getLogger(__name__) -class GetContacts(BaseClient): - async def get_contacts(self) -> List["pyrogram.User"]: +class GetContacts(Scaffold): + async def get_contacts(self) -> List["types.User"]: """Get contacts from your Telegram address book. Returns: - List of :obj:`User`: On success, a list of users is returned. + List of :obj:`~pyrogram.types.User`: On success, a list of users is returned. Example: .. code-block:: python @@ -40,5 +39,5 @@ async def get_contacts(self) -> List["pyrogram.User"]: contacts = app.get_contacts() print(contacts) """ - contacts = await self.send(functions.contacts.GetContacts(hash=0)) - return pyrogram.List(pyrogram.User._parse(self, user) for user in contacts.users) + contacts = await self.send(raw.functions.contacts.GetContacts(hash=0)) + return types.List(types.User._parse(self, user) for user in contacts.users) diff --git a/pyrogram/client/methods/contacts/get_contacts_count.py b/pyrogram/methods/contacts/get_contacts_count.py similarity index 86% rename from pyrogram/client/methods/contacts/get_contacts_count.py rename to pyrogram/methods/contacts/get_contacts_count.py index 8435557a9e..3a16c9050e 100644 --- a/pyrogram/client/methods/contacts/get_contacts_count.py +++ b/pyrogram/methods/contacts/get_contacts_count.py @@ -16,11 +16,11 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from pyrogram.api import functions -from ...ext import BaseClient +from pyrogram import raw +from pyrogram.scaffold import Scaffold -class GetContactsCount(BaseClient): +class GetContactsCount(Scaffold): async def get_contacts_count(self) -> int: """Get the total count of contacts from your Telegram address book. @@ -34,4 +34,4 @@ async def get_contacts_count(self) -> int: print(count) """ - return len((await self.send(functions.contacts.GetContacts(hash=0))).contacts) + return len((await self.send(raw.functions.contacts.GetContacts(hash=0))).contacts) diff --git a/pyrogram/client/methods/decorators/__init__.py b/pyrogram/methods/decorators/__init__.py similarity index 100% rename from pyrogram/client/methods/decorators/__init__.py rename to pyrogram/methods/decorators/__init__.py index 5a60ff9b20..fbc4fac690 100644 --- a/pyrogram/client/methods/decorators/__init__.py +++ b/pyrogram/methods/decorators/__init__.py @@ -17,6 +17,7 @@ # along with Pyrogram. If not, see . from .on_callback_query import OnCallbackQuery +from .on_chosen_inline_result import OnChosenInlineResult from .on_deleted_messages import OnDeletedMessages from .on_disconnect import OnDisconnect from .on_inline_query import OnInlineQuery @@ -24,7 +25,6 @@ from .on_poll import OnPoll from .on_raw_update import OnRawUpdate from .on_user_status import OnUserStatus -from .on_chosen_inline_result import OnChosenInlineResult class Decorators( diff --git a/pyrogram/client/methods/decorators/on_callback_query.py b/pyrogram/methods/decorators/on_callback_query.py similarity index 84% rename from pyrogram/client/methods/decorators/on_callback_query.py rename to pyrogram/methods/decorators/on_callback_query.py index c7bcae2676..f681eee70b 100644 --- a/pyrogram/client/methods/decorators/on_callback_query.py +++ b/pyrogram/methods/decorators/on_callback_query.py @@ -19,11 +19,11 @@ from typing import Callable import pyrogram -from pyrogram.client.filters.filter import Filter -from ...ext import BaseClient +from pyrogram.filters import Filter +from pyrogram.scaffold import Scaffold -class OnCallbackQuery(BaseClient): +class OnCallbackQuery(Scaffold): def on_callback_query( self=None, filters=None, @@ -32,7 +32,7 @@ def on_callback_query( """Decorator for handling callback queries. This does the same thing as :meth:`~pyrogram.Client.add_handler` using the - :obj:`~pyrogram.CallbackQueryHandler`. + :obj:`~pyrogram.handlers.CallbackQueryHandler`. Parameters: filters (:obj:`~pyrogram.Filters`, *optional*): @@ -45,10 +45,10 @@ def on_callback_query( def decorator(func: Callable) -> Callable: if isinstance(self, pyrogram.Client): - self.add_handler(pyrogram.CallbackQueryHandler(func, filters), group) + self.add_handler(pyrogram.handlers.CallbackQueryHandler(func, filters), group) elif isinstance(self, Filter) or self is None: func.handler = ( - pyrogram.CallbackQueryHandler(func, self), + pyrogram.handlers.CallbackQueryHandler(func, self), group if filters is None else filters ) diff --git a/pyrogram/client/methods/decorators/on_chosen_inline_result.py b/pyrogram/methods/decorators/on_chosen_inline_result.py similarity index 82% rename from pyrogram/client/methods/decorators/on_chosen_inline_result.py rename to pyrogram/methods/decorators/on_chosen_inline_result.py index 40446eb2ff..3163ccb388 100644 --- a/pyrogram/client/methods/decorators/on_chosen_inline_result.py +++ b/pyrogram/methods/decorators/on_chosen_inline_result.py @@ -19,11 +19,11 @@ from typing import Callable import pyrogram -from pyrogram.client.filters.filter import Filter -from ...ext import BaseClient +from pyrogram.filters import Filter +from pyrogram.scaffold import Scaffold -class OnChosenInlineResult(BaseClient): +class OnChosenInlineResult(Scaffold): def on_chosen_inline_result( self=None, filters=None, @@ -31,7 +31,8 @@ def on_chosen_inline_result( ) -> callable: """Decorator for handling chosen inline results. - This does the same thing as :meth:`~pyrogram.Client.add_handler` using the :obj:`~pyrogram.ChosenInlineResult`. + This does the same thing as :meth:`~pyrogram.Client.add_handler` using the + :obj:`~pyrogram.handlers.ChosenInlineResult`. Parameters: filters (:obj:`~pyrogram.Filters`, *optional*): @@ -44,10 +45,10 @@ def on_chosen_inline_result( def decorator(func: Callable) -> Callable: if isinstance(self, pyrogram.Client): - self.add_handler(pyrogram.ChosenInlineResultHandler(func, filters), group) + self.add_handler(pyrogram.handlers.ChosenInlineResultHandler(func, filters), group) elif isinstance(self, Filter) or self is None: func.handler = ( - pyrogram.ChosenInlineResultHandler(func, self), + pyrogram.handlers.ChosenInlineResultHandler(func, self), group if filters is None else filters ) diff --git a/pyrogram/client/methods/decorators/on_deleted_messages.py b/pyrogram/methods/decorators/on_deleted_messages.py similarity index 83% rename from pyrogram/client/methods/decorators/on_deleted_messages.py rename to pyrogram/methods/decorators/on_deleted_messages.py index 8a7e1e55ec..adb4d87914 100644 --- a/pyrogram/client/methods/decorators/on_deleted_messages.py +++ b/pyrogram/methods/decorators/on_deleted_messages.py @@ -19,11 +19,11 @@ from typing import Callable import pyrogram -from pyrogram.client.filters.filter import Filter -from ...ext import BaseClient +from pyrogram.filters import Filter +from pyrogram.scaffold import Scaffold -class OnDeletedMessages(BaseClient): +class OnDeletedMessages(Scaffold): def on_deleted_messages( self=None, filters=None, @@ -32,7 +32,7 @@ def on_deleted_messages( """Decorator for handling deleted messages. This does the same thing as :meth:`~pyrogram.Client.add_handler` using the - :obj:`~pyrogram.DeletedMessagesHandler`. + :obj:`~pyrogram.handlers.DeletedMessagesHandler`. Parameters: filters (:obj:`~pyrogram.Filters`, *optional*): @@ -45,10 +45,10 @@ def on_deleted_messages( def decorator(func: Callable) -> Callable: if isinstance(self, pyrogram.Client): - self.add_handler(pyrogram.DeletedMessagesHandler(func, filters), group) + self.add_handler(pyrogram.handlers.DeletedMessagesHandler(func, filters), group) elif isinstance(self, Filter) or self is None: func.handler = ( - pyrogram.DeletedMessagesHandler(func, self), + pyrogram.handlers.DeletedMessagesHandler(func, self), group if filters is None else filters ) diff --git a/pyrogram/client/methods/decorators/on_disconnect.py b/pyrogram/methods/decorators/on_disconnect.py similarity index 84% rename from pyrogram/client/methods/decorators/on_disconnect.py rename to pyrogram/methods/decorators/on_disconnect.py index 0df6584837..6e7b1522ff 100644 --- a/pyrogram/client/methods/decorators/on_disconnect.py +++ b/pyrogram/methods/decorators/on_disconnect.py @@ -19,19 +19,20 @@ from typing import Callable import pyrogram -from ...ext import BaseClient +from pyrogram.scaffold import Scaffold -class OnDisconnect(BaseClient): +class OnDisconnect(Scaffold): def on_disconnect(self=None) -> callable: """Decorator for handling disconnections. - This does the same thing as :meth:`~pyrogram.Client.add_handler` using the :obj:`~pyrogram.DisconnectHandler`. + This does the same thing as :meth:`~pyrogram.Client.add_handler` using the + :obj:`~pyrogram.handlers.DisconnectHandler`. """ def decorator(func: Callable) -> Callable: if isinstance(self, pyrogram.Client): - self.add_handler(pyrogram.DisconnectHandler(func)) + self.add_handler(pyrogram.handlers.DisconnectHandler(func)) return func diff --git a/pyrogram/client/methods/decorators/on_inline_query.py b/pyrogram/methods/decorators/on_inline_query.py similarity index 83% rename from pyrogram/client/methods/decorators/on_inline_query.py rename to pyrogram/methods/decorators/on_inline_query.py index 4a9b1128ea..fdc287ecfe 100644 --- a/pyrogram/client/methods/decorators/on_inline_query.py +++ b/pyrogram/methods/decorators/on_inline_query.py @@ -19,11 +19,11 @@ from typing import Callable import pyrogram -from pyrogram.client.filters.filter import Filter -from ...ext import BaseClient +from pyrogram.filters import Filter +from pyrogram.scaffold import Scaffold -class OnInlineQuery(BaseClient): +class OnInlineQuery(Scaffold): def on_inline_query( self=None, filters=None, @@ -31,7 +31,8 @@ def on_inline_query( ) -> callable: """Decorator for handling inline queries. - This does the same thing as :meth:`~pyrogram.Client.add_handler` using the :obj:`~pyrogram.InlineQueryHandler`. + This does the same thing as :meth:`~pyrogram.Client.add_handler` using the + :obj:`~pyrogram.handlers.InlineQueryHandler`. Parameters: filters (:obj:`~pyrogram.Filters`, *optional*): @@ -44,10 +45,10 @@ def on_inline_query( def decorator(func: Callable) -> Callable: if isinstance(self, pyrogram.Client): - self.add_handler(pyrogram.InlineQueryHandler(func, filters), group) + self.add_handler(pyrogram.handlers.InlineQueryHandler(func, filters), group) elif isinstance(self, Filter) or self is None: func.handler = ( - pyrogram.InlineQueryHandler(func, self), + pyrogram.handlers.InlineQueryHandler(func, self), group if filters is None else filters ) diff --git a/pyrogram/client/methods/decorators/on_message.py b/pyrogram/methods/decorators/on_message.py similarity index 83% rename from pyrogram/client/methods/decorators/on_message.py rename to pyrogram/methods/decorators/on_message.py index 9d2791c279..6316def640 100644 --- a/pyrogram/client/methods/decorators/on_message.py +++ b/pyrogram/methods/decorators/on_message.py @@ -19,11 +19,11 @@ from typing import Callable import pyrogram -from pyrogram.client.filters.filter import Filter -from ...ext import BaseClient +from pyrogram.filters import Filter +from pyrogram.scaffold import Scaffold -class OnMessage(BaseClient): +class OnMessage(Scaffold): def on_message( self=None, filters=None, @@ -31,7 +31,8 @@ def on_message( ) -> callable: """Decorator for handling messages. - This does the same thing as :meth:`~pyrogram.Client.add_handler` using the :obj:`~pyrogram.MessageHandler`. + This does the same thing as :meth:`~pyrogram.Client.add_handler` using the + :obj:`~pyrogram.handlers.MessageHandler`. Parameters: filters (:obj:`~pyrogram.Filters`, *optional*): @@ -44,10 +45,10 @@ def on_message( def decorator(func: Callable) -> Callable: if isinstance(self, pyrogram.Client): - self.add_handler(pyrogram.MessageHandler(func, filters), group) + self.add_handler(pyrogram.handlers.MessageHandler(func, filters), group) elif isinstance(self, Filter) or self is None: func.handler = ( - pyrogram.MessageHandler(func, self), + pyrogram.handlers.MessageHandler(func, self), group if filters is None else filters ) diff --git a/pyrogram/client/methods/decorators/on_poll.py b/pyrogram/methods/decorators/on_poll.py similarity index 84% rename from pyrogram/client/methods/decorators/on_poll.py rename to pyrogram/methods/decorators/on_poll.py index 2326b3e558..7506c21828 100644 --- a/pyrogram/client/methods/decorators/on_poll.py +++ b/pyrogram/methods/decorators/on_poll.py @@ -19,11 +19,11 @@ from typing import Callable import pyrogram -from pyrogram.client.filters.filter import Filter -from ...ext import BaseClient +from pyrogram.filters import Filter +from pyrogram.scaffold import Scaffold -class OnPoll(BaseClient): +class OnPoll(Scaffold): def on_poll( self=None, filters=None, @@ -31,7 +31,8 @@ def on_poll( ) -> callable: """Decorator for handling poll updates. - This does the same thing as :meth:`~pyrogram.Client.add_handler` using the :obj:`~pyrogram.PollHandler`. + This does the same thing as :meth:`~pyrogram.Client.add_handler` using the + :obj:`~pyrogram.handlers.PollHandler`. Parameters: filters (:obj:`~pyrogram.Filters`, *optional*): @@ -44,10 +45,10 @@ def on_poll( def decorator(func: Callable) -> Callable: if isinstance(self, pyrogram.Client): - self.add_handler(pyrogram.PollHandler(func, filters), group) + self.add_handler(pyrogram.handlers.PollHandler(func, filters), group) elif isinstance(self, Filter) or self is None: func.handler = ( - pyrogram.PollHandler(func, self), + pyrogram.handlers.PollHandler(func, self), group if filters is None else filters ) diff --git a/pyrogram/client/methods/decorators/on_raw_update.py b/pyrogram/methods/decorators/on_raw_update.py similarity index 83% rename from pyrogram/client/methods/decorators/on_raw_update.py rename to pyrogram/methods/decorators/on_raw_update.py index 749b9b5401..9e3a7cd847 100644 --- a/pyrogram/client/methods/decorators/on_raw_update.py +++ b/pyrogram/methods/decorators/on_raw_update.py @@ -19,17 +19,18 @@ from typing import Callable import pyrogram -from ...ext import BaseClient +from pyrogram.scaffold import Scaffold -class OnRawUpdate(BaseClient): +class OnRawUpdate(Scaffold): def on_raw_update( self=None, group: int = 0 ) -> callable: """Decorator for handling raw updates. - This does the same thing as :meth:`~pyrogram.Client.add_handler` using the :obj:`~pyrogram.RawUpdateHandler`. + This does the same thing as :meth:`~pyrogram.Client.add_handler` using the + :obj:`~pyrogram.handlers.RawUpdateHandler`. Parameters: group (``int``, *optional*): @@ -38,10 +39,10 @@ def on_raw_update( def decorator(func: Callable) -> Callable: if isinstance(self, pyrogram.Client): - self.add_handler(pyrogram.RawUpdateHandler(func), group) + self.add_handler(pyrogram.handlers.RawUpdateHandler(func), group) else: func.handler = ( - pyrogram.RawUpdateHandler(func), + pyrogram.handlers.RawUpdateHandler(func), group if self is None else group ) diff --git a/pyrogram/client/methods/decorators/on_user_status.py b/pyrogram/methods/decorators/on_user_status.py similarity index 83% rename from pyrogram/client/methods/decorators/on_user_status.py rename to pyrogram/methods/decorators/on_user_status.py index ea09c8a495..3063ad3d48 100644 --- a/pyrogram/client/methods/decorators/on_user_status.py +++ b/pyrogram/methods/decorators/on_user_status.py @@ -19,18 +19,19 @@ from typing import Callable import pyrogram -from pyrogram.client.filters.filter import Filter -from ...ext import BaseClient +from pyrogram.filters import Filter +from pyrogram.scaffold import Scaffold -class OnUserStatus(BaseClient): +class OnUserStatus(Scaffold): def on_user_status( self=None, filters=None, group: int = 0 ) -> callable: """Decorator for handling user status updates. - This does the same thing as :meth:`~pyrogram.Client.add_handler` using the :obj:`~pyrogram.UserStatusHandler`. + This does the same thing as :meth:`~pyrogram.Client.add_handler` using the + :obj:`~pyrogram.handlers.UserStatusHandler`. Parameters: filters (:obj:`~pyrogram.Filters`, *optional*): @@ -42,10 +43,10 @@ def on_user_status( def decorator(func: Callable) -> Callable: if isinstance(self, pyrogram.Client): - self.add_handler(pyrogram.UserStatusHandler(func, filters), group) + self.add_handler(pyrogram.handlers.UserStatusHandler(func, filters), group) elif isinstance(self, Filter) or self is None: func.handler = ( - pyrogram.UserStatusHandler(func, self), + pyrogram.handlers.UserStatusHandler(func, self), group if filters is None else filters ) diff --git a/pyrogram/client/methods/messages/__init__.py b/pyrogram/methods/messages/__init__.py similarity index 100% rename from pyrogram/client/methods/messages/__init__.py rename to pyrogram/methods/messages/__init__.py diff --git a/pyrogram/client/methods/messages/delete_messages.py b/pyrogram/methods/messages/delete_messages.py similarity index 86% rename from pyrogram/client/methods/messages/delete_messages.py rename to pyrogram/methods/messages/delete_messages.py index 5deb6d5aa8..1855ce9bc7 100644 --- a/pyrogram/client/methods/messages/delete_messages.py +++ b/pyrogram/methods/messages/delete_messages.py @@ -18,15 +18,15 @@ from typing import Union, Iterable -from pyrogram.api import functions, types -from pyrogram.client.ext import BaseClient +from pyrogram import raw +from pyrogram.scaffold import Scaffold -class DeleteMessages(BaseClient): +class DeleteMessages(Scaffold): async def delete_messages( self, chat_id: Union[int, str], - message_ids: Iterable[int], + message_ids: Union[int, Iterable[int]], revoke: bool = True ) -> bool: """Delete messages, including service messages. @@ -37,8 +37,8 @@ async def delete_messages( For your personal cloud (Saved Messages) you can simply use "me" or "self". For a contact that exists in your Telegram address book you can use his phone number (str). - message_ids (``iterable``): - A list of Message identifiers to delete or a single message id. + message_ids (``int`` | ``Iterable[int]``): + A list of Message identifiers to delete (integers) or a single message id. Iterators and Generators are also accepted. revoke (``bool``, *optional*): @@ -65,16 +65,16 @@ async def delete_messages( 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): + if isinstance(peer, raw.types.InputPeerChannel): r = await self.send( - functions.channels.DeleteMessages( + raw.functions.channels.DeleteMessages( channel=peer, id=message_ids ) ) else: r = await self.send( - functions.messages.DeleteMessages( + raw.functions.messages.DeleteMessages( id=message_ids, revoke=revoke or None ) diff --git a/pyrogram/client/methods/messages/download_media.py b/pyrogram/methods/messages/download_media.py similarity index 84% rename from pyrogram/client/methods/messages/download_media.py rename to pyrogram/methods/messages/download_media.py index 3c1a8cbeb3..24092caca7 100644 --- a/pyrogram/client/methods/messages/download_media.py +++ b/pyrogram/methods/messages/download_media.py @@ -22,20 +22,45 @@ import struct import time from datetime import datetime -from threading import Event from typing import Union -import pyrogram -from pyrogram.client.ext import BaseClient, FileData, utils +from pyrogram import types +from pyrogram import utils from pyrogram.errors import FileIdInvalid +from pyrogram.scaffold import Scaffold DEFAULT_DOWNLOAD_DIR = "downloads/" -class DownloadMedia(BaseClient): +class FileData: + def __init__( + self, *, media_type: int = None, dc_id: int = None, document_id: int = None, access_hash: int = None, + thumb_size: str = None, peer_id: int = None, peer_type: str = None, peer_access_hash: int = None, + volume_id: int = None, local_id: int = None, is_big: bool = None, file_size: int = None, mime_type: str = None, + file_name: str = None, date: int = None, file_ref: str = None + ): + self.media_type = media_type + self.dc_id = dc_id + self.document_id = document_id + self.access_hash = access_hash + self.thumb_size = thumb_size + self.peer_id = peer_id + self.peer_type = peer_type + self.peer_access_hash = peer_access_hash + self.volume_id = volume_id + self.local_id = local_id + self.is_big = is_big + self.file_size = file_size + self.mime_type = mime_type + self.file_name = file_name + self.date = date + self.file_ref = file_ref + + +class DownloadMedia(Scaffold): async def download_media( self, - message: Union["pyrogram.Message", str], + message: Union["types.Message", str], file_ref: str = None, file_name: str = DEFAULT_DOWNLOAD_DIR, block: bool = True, @@ -45,7 +70,7 @@ async def download_media( """Download the media from a message. Parameters: - message (:obj:`Message` | ``str``): + message (:obj:`~pyrogram.types.Message` | ``str``): Pass a Message containing the media, the media itself (message.audio, message.video, ...) or the file id as string. @@ -87,7 +112,8 @@ async def download_media( Returns: ``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:`~Client.stop_transmission`, None is returned. + the download failed or was deliberately stopped with :meth:`~pyrogram.Client.stop_transmission`, None is + returned. Raises: ValueError: if the message doesn't contain any downloadable media @@ -103,7 +129,7 @@ async def download_media( # Keep track of the progress while downloading def progress(current, total): - print("{:.1f}%".format(current * 100 / total)) + print(f"{current * 100 / total:.1f}%") app.download_media(message, progress=progress) """ @@ -115,7 +141,7 @@ def progress(current, total): mime_type = None date = None - if isinstance(message, pyrogram.Message): + if isinstance(message, types.Message): for kind in available_media: media = getattr(message, kind, None) @@ -199,13 +225,10 @@ def get_existing_attributes() -> dict: access_hash=access_hash ) else: - raise ValueError("Unknown media type: {}".format(file_id_str)) + raise ValueError(f"Unknown media type: {file_id_str}") except (AssertionError, binascii.Error, struct.error): raise FileIdInvalid from None - done = asyncio.Event() - path = [None] - directory, file_name = os.path.split(file_name) file_name = file_name or data.file_name or "" @@ -239,10 +262,9 @@ def get_existing_attributes() -> dict: extension ) - # Cast to string because Path objects aren't supported by Python 3.5 - self.download_queue.put_nowait((data, str(directory), str(file_name), done, progress, progress_args, path)) + downloader = self.handle_download((data, directory, file_name, progress, progress_args)) if block: - await done.wait() - - return path[0] + return await downloader + else: + asyncio.get_event_loop().create_task(downloader) diff --git a/pyrogram/client/methods/messages/edit_inline_caption.py b/pyrogram/methods/messages/edit_inline_caption.py similarity index 89% rename from pyrogram/client/methods/messages/edit_inline_caption.py rename to pyrogram/methods/messages/edit_inline_caption.py index 335f878eb7..055303b41e 100644 --- a/pyrogram/client/methods/messages/edit_inline_caption.py +++ b/pyrogram/methods/messages/edit_inline_caption.py @@ -18,17 +18,17 @@ from typing import Union -import pyrogram -from pyrogram.client.ext import BaseClient +from pyrogram import types +from pyrogram.scaffold import Scaffold -class EditInlineCaption(BaseClient): +class EditInlineCaption(Scaffold): async def edit_inline_caption( self, inline_message_id: str, caption: str, parse_mode: Union[str, None] = object, - reply_markup: "pyrogram.InlineKeyboardMarkup" = None + reply_markup: "types.InlineKeyboardMarkup" = None ) -> bool: """Edit the caption of inline media messages. @@ -46,7 +46,7 @@ async def edit_inline_caption( Pass "html" to enable HTML-style parsing only. Pass None to completely disable style parsing. - reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*): An InlineKeyboardMarkup object. Returns: diff --git a/pyrogram/client/methods/messages/edit_inline_media.py b/pyrogram/methods/messages/edit_inline_media.py similarity index 76% rename from pyrogram/client/methods/messages/edit_inline_media.py rename to pyrogram/methods/messages/edit_inline_media.py index 74cb29108b..76b716f1ab 100644 --- a/pyrogram/client/methods/messages/edit_inline_media.py +++ b/pyrogram/methods/messages/edit_inline_media.py @@ -18,22 +18,18 @@ import re -import pyrogram -from pyrogram.api import functions, types -from pyrogram.client.ext import BaseClient, utils -from pyrogram.client.types import ( - InputMediaPhoto, InputMediaVideo, InputMediaAudio, - InputMediaAnimation, InputMediaDocument -) -from pyrogram.client.types.input_media import InputMedia +from pyrogram import raw +from pyrogram import types +from pyrogram import utils +from pyrogram.scaffold import Scaffold -class EditInlineMedia(BaseClient): +class EditInlineMedia(Scaffold): async def edit_inline_media( self, inline_message_id: str, - media: InputMedia, - reply_markup: "pyrogram.InlineKeyboardMarkup" = None + media: "types.InputMedia", + reply_markup: "types.InlineKeyboardMarkup" = None ) -> bool: """Edit inline animation, audio, document, photo or video messages. @@ -45,10 +41,10 @@ async def edit_inline_media( Required if *chat_id* and *message_id* are not specified. Identifier of the inline message. - media (:obj:`InputMedia`): + media (:obj:`~pyrogram.types.InputMedia`): One of the InputMedia objects describing an animation, audio, document, photo or video. - reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*): An InlineKeyboardMarkup object. Returns: @@ -73,44 +69,44 @@ async def edit_inline_media( caption = media.caption parse_mode = media.parse_mode - if isinstance(media, InputMediaPhoto): + if isinstance(media, types.InputMediaPhoto): if re.match("^https?://", media.media): - media = types.InputMediaPhotoExternal( + media = raw.types.InputMediaPhotoExternal( url=media.media ) else: media = utils.get_input_media_from_file_id(media.media, media.file_ref, 2) - elif isinstance(media, InputMediaVideo): + elif isinstance(media, types.InputMediaVideo): if re.match("^https?://", media.media): - media = types.InputMediaDocumentExternal( + media = raw.types.InputMediaDocumentExternal( url=media.media ) else: media = utils.get_input_media_from_file_id(media.media, media.file_ref, 4) - elif isinstance(media, InputMediaAudio): + elif isinstance(media, types.InputMediaAudio): if re.match("^https?://", media.media): - media = types.InputMediaDocumentExternal( + media = raw.types.InputMediaDocumentExternal( url=media.media ) else: media = utils.get_input_media_from_file_id(media.media, media.file_ref, 9) - elif isinstance(media, InputMediaAnimation): + elif isinstance(media, types.InputMediaAnimation): if re.match("^https?://", media.media): - media = types.InputMediaDocumentExternal( + media = raw.types.InputMediaDocumentExternal( url=media.media ) else: media = utils.get_input_media_from_file_id(media.media, media.file_ref, 10) - elif isinstance(media, InputMediaDocument): + elif isinstance(media, types.InputMediaDocument): if re.match("^https?://", media.media): - media = types.InputMediaDocumentExternal( + media = raw.types.InputMediaDocumentExternal( url=media.media ) else: media = utils.get_input_media_from_file_id(media.media, media.file_ref, 5) return await self.send( - functions.messages.EditInlineBotMessage( + raw.functions.messages.EditInlineBotMessage( id=utils.unpack_inline_message_id(inline_message_id), media=media, reply_markup=reply_markup.write() if reply_markup else None, diff --git a/pyrogram/client/methods/messages/edit_inline_reply_markup.py b/pyrogram/methods/messages/edit_inline_reply_markup.py similarity index 83% rename from pyrogram/client/methods/messages/edit_inline_reply_markup.py rename to pyrogram/methods/messages/edit_inline_reply_markup.py index f538180641..5d1b24635b 100644 --- a/pyrogram/client/methods/messages/edit_inline_reply_markup.py +++ b/pyrogram/methods/messages/edit_inline_reply_markup.py @@ -16,16 +16,17 @@ # 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 -from pyrogram.client.ext import BaseClient, utils +from pyrogram import raw +from pyrogram import types +from pyrogram import utils +from pyrogram.scaffold import Scaffold -class EditInlineReplyMarkup(BaseClient): +class EditInlineReplyMarkup(Scaffold): async def edit_inline_reply_markup( self, inline_message_id: str, - reply_markup: "pyrogram.InlineKeyboardMarkup" = None + reply_markup: "types.InlineKeyboardMarkup" = None ) -> bool: """Edit only the reply markup of inline messages sent via the bot (for inline bots). @@ -33,7 +34,7 @@ async def edit_inline_reply_markup( inline_message_id (``str``): Identifier of the inline message. - reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*): An InlineKeyboardMarkup object. Returns: @@ -51,7 +52,7 @@ async def edit_inline_reply_markup( InlineKeyboardButton("New button", callback_data="new_data")]])) """ return await self.send( - functions.messages.EditInlineBotMessage( + raw.functions.messages.EditInlineBotMessage( id=utils.unpack_inline_message_id(inline_message_id), reply_markup=reply_markup.write() if reply_markup else None, ) diff --git a/pyrogram/client/methods/messages/edit_inline_text.py b/pyrogram/methods/messages/edit_inline_text.py similarity index 88% rename from pyrogram/client/methods/messages/edit_inline_text.py rename to pyrogram/methods/messages/edit_inline_text.py index cfdd232ba9..ebb2f91a81 100644 --- a/pyrogram/client/methods/messages/edit_inline_text.py +++ b/pyrogram/methods/messages/edit_inline_text.py @@ -18,19 +18,20 @@ from typing import Union -import pyrogram -from pyrogram.api import functions -from pyrogram.client.ext import BaseClient, utils +from pyrogram import raw +from pyrogram import types +from pyrogram import utils +from pyrogram.scaffold import Scaffold -class EditInlineText(BaseClient): +class EditInlineText(Scaffold): async def edit_inline_text( self, inline_message_id: str, text: str, parse_mode: Union[str, None] = object, disable_web_page_preview: bool = None, - reply_markup: "pyrogram.InlineKeyboardMarkup" = None + reply_markup: "types.InlineKeyboardMarkup" = None ) -> bool: """Edit the text of inline messages. @@ -51,7 +52,7 @@ async def edit_inline_text( disable_web_page_preview (``bool``, *optional*): Disables link previews for links in this message. - reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*): An InlineKeyboardMarkup object. Returns: @@ -72,7 +73,7 @@ async def edit_inline_text( """ return await self.send( - functions.messages.EditInlineBotMessage( + raw.functions.messages.EditInlineBotMessage( id=utils.unpack_inline_message_id(inline_message_id), no_webpage=disable_web_page_preview or None, reply_markup=reply_markup.write() if reply_markup else None, diff --git a/pyrogram/client/methods/messages/edit_message_caption.py b/pyrogram/methods/messages/edit_message_caption.py similarity index 87% rename from pyrogram/client/methods/messages/edit_message_caption.py rename to pyrogram/methods/messages/edit_message_caption.py index 01bd81474a..76b6cc483d 100644 --- a/pyrogram/client/methods/messages/edit_message_caption.py +++ b/pyrogram/methods/messages/edit_message_caption.py @@ -18,19 +18,19 @@ from typing import Union -import pyrogram -from pyrogram.client.ext import BaseClient +from pyrogram import types +from pyrogram.scaffold import Scaffold -class EditMessageCaption(BaseClient): +class EditMessageCaption(Scaffold): async def edit_message_caption( self, chat_id: Union[int, str], message_id: int, caption: str, parse_mode: Union[str, None] = object, - reply_markup: "pyrogram.InlineKeyboardMarkup" = None - ) -> "pyrogram.Message": + reply_markup: "types.InlineKeyboardMarkup" = None + ) -> "types.Message": """Edit the caption of media messages. Parameters: @@ -52,11 +52,11 @@ async def edit_message_caption( Pass "html" to enable HTML-style parsing only. Pass None to completely disable style parsing. - reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*): An InlineKeyboardMarkup object. Returns: - :obj:`Message`: On success, the edited message is returned. + :obj:`~pyrogram.types.Message`: On success, the edited message is returned. Example: .. code-block:: python diff --git a/pyrogram/client/methods/messages/edit_message_media.py b/pyrogram/methods/messages/edit_message_media.py similarity index 74% rename from pyrogram/client/methods/messages/edit_message_media.py rename to pyrogram/methods/messages/edit_message_media.py index 765c159842..5733c5bda6 100644 --- a/pyrogram/client/methods/messages/edit_message_media.py +++ b/pyrogram/methods/messages/edit_message_media.py @@ -20,25 +20,21 @@ import re from typing import Union -import pyrogram -from pyrogram.api import functions, types -from pyrogram.client.ext import BaseClient, utils -from pyrogram.client.types import ( - InputMediaPhoto, InputMediaVideo, InputMediaAudio, - InputMediaAnimation, InputMediaDocument -) -from pyrogram.client.types.input_media import InputMedia +from pyrogram import raw +from pyrogram import types +from pyrogram import utils +from pyrogram.scaffold import Scaffold -class EditMessageMedia(BaseClient): +class EditMessageMedia(Scaffold): async def edit_message_media( self, chat_id: Union[int, str], message_id: int, - media: InputMedia, - reply_markup: "pyrogram.InlineKeyboardMarkup" = None, + media: "types.InputMedia", + reply_markup: "types.InlineKeyboardMarkup" = None, file_name: str = None - ) -> "pyrogram.Message": + ) -> "types.Message": """Edit animation, 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, the @@ -53,10 +49,10 @@ async def edit_message_media( message_id (``int``): Message identifier in the chat specified in chat_id. - media (:obj:`InputMedia`): + media (:obj:`~pyrogram.types.InputMedia`): One of the InputMedia objects describing an animation, audio, document, photo or video. - reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*): An InlineKeyboardMarkup object. file_name (``str``, *optional*): @@ -64,7 +60,7 @@ async def edit_message_media( Defaults to file's path basename. Returns: - :obj:`Message`: On success, the edited message is returned. + :obj:`~pyrogram.types.Message`: On success, the edited message is returned. Example: .. code-block:: python @@ -83,47 +79,47 @@ async def edit_message_media( caption = media.caption parse_mode = media.parse_mode - if isinstance(media, InputMediaPhoto): + if isinstance(media, types.InputMediaPhoto): if os.path.isfile(media.media): media = await self.send( - functions.messages.UploadMedia( + raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), - media=types.InputMediaUploadedPhoto( + media=raw.types.InputMediaUploadedPhoto( file=await self.save_file(media.media) ) ) ) - media = types.InputMediaPhoto( - id=types.InputPhoto( + media = raw.types.InputMediaPhoto( + id=raw.types.InputPhoto( id=media.photo.id, access_hash=media.photo.access_hash, file_reference=media.photo.file_reference ) ) elif re.match("^https?://", media.media): - media = types.InputMediaPhotoExternal( + media = raw.types.InputMediaPhotoExternal( url=media.media ) else: media = utils.get_input_media_from_file_id(media.media, media.file_ref, 2) - elif isinstance(media, InputMediaVideo): + elif isinstance(media, types.InputMediaVideo): if os.path.isfile(media.media): media = await self.send( - functions.messages.UploadMedia( + raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), - media=types.InputMediaUploadedDocument( + media=raw.types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(media.media) or "video/mp4", thumb=await self.save_file(media.thumb), file=await self.save_file(media.media), attributes=[ - types.DocumentAttributeVideo( + raw.types.DocumentAttributeVideo( supports_streaming=media.supports_streaming or None, duration=media.duration, w=media.width, h=media.height ), - types.DocumentAttributeFilename( + raw.types.DocumentAttributeFilename( file_name=file_name or os.path.basename(media.media) ) ] @@ -131,35 +127,35 @@ async def edit_message_media( ) ) - media = types.InputMediaDocument( - id=types.InputDocument( + media = raw.types.InputMediaDocument( + id=raw.types.InputDocument( id=media.document.id, access_hash=media.document.access_hash, file_reference=media.document.file_reference ) ) elif re.match("^https?://", media.media): - media = types.InputMediaDocumentExternal( + media = raw.types.InputMediaDocumentExternal( url=media.media ) else: media = utils.get_input_media_from_file_id(media.media, media.file_ref, 4) - elif isinstance(media, InputMediaAudio): + elif isinstance(media, types.InputMediaAudio): if os.path.isfile(media.media): media = await self.send( - functions.messages.UploadMedia( + raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), - media=types.InputMediaUploadedDocument( + media=raw.types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(media.media) or "audio/mpeg", thumb=await self.save_file(media.thumb), file=await self.save_file(media.media), attributes=[ - types.DocumentAttributeAudio( + raw.types.DocumentAttributeAudio( duration=media.duration, performer=media.performer, title=media.title ), - types.DocumentAttributeFilename( + raw.types.DocumentAttributeFilename( file_name=file_name or os.path.basename(media.media) ) ] @@ -167,68 +163,68 @@ async def edit_message_media( ) ) - media = types.InputMediaDocument( - id=types.InputDocument( + media = raw.types.InputMediaDocument( + id=raw.types.InputDocument( id=media.document.id, access_hash=media.document.access_hash, file_reference=media.document.file_reference ) ) elif re.match("^https?://", media.media): - media = types.InputMediaDocumentExternal( + media = raw.types.InputMediaDocumentExternal( url=media.media ) else: media = utils.get_input_media_from_file_id(media.media, media.file_ref, 9) - elif isinstance(media, InputMediaAnimation): + elif isinstance(media, types.InputMediaAnimation): if os.path.isfile(media.media): media = await self.send( - functions.messages.UploadMedia( + raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), - media=types.InputMediaUploadedDocument( + media=raw.types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(media.media) or "video/mp4", - thumb=self.save_file(media.thumb), + thumb=await self.save_file(media.thumb), file=await self.save_file(media.media), attributes=[ - types.DocumentAttributeVideo( + raw.types.DocumentAttributeVideo( supports_streaming=True, duration=media.duration, w=media.width, h=media.height ), - types.DocumentAttributeFilename( + raw.types.DocumentAttributeFilename( file_name=file_name or os.path.basename(media.media) ), - types.DocumentAttributeAnimated() + raw.types.DocumentAttributeAnimated() ] ) ) ) - media = types.InputMediaDocument( - id=types.InputDocument( + media = raw.types.InputMediaDocument( + id=raw.types.InputDocument( id=media.document.id, access_hash=media.document.access_hash, file_reference=media.document.file_reference ) ) elif re.match("^https?://", media.media): - media = types.InputMediaDocumentExternal( + media = raw.types.InputMediaDocumentExternal( url=media.media ) else: media = utils.get_input_media_from_file_id(media.media, media.file_ref, 10) - elif isinstance(media, InputMediaDocument): + elif isinstance(media, types.InputMediaDocument): if os.path.isfile(media.media): media = await self.send( - functions.messages.UploadMedia( + raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), - media=types.InputMediaUploadedDocument( + media=raw.types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(media.media) or "application/zip", thumb=await self.save_file(media.thumb), file=await self.save_file(media.media), attributes=[ - types.DocumentAttributeFilename( + raw.types.DocumentAttributeFilename( file_name=file_name or os.path.basename(media.media) ) ] @@ -236,22 +232,22 @@ async def edit_message_media( ) ) - media = types.InputMediaDocument( - id=types.InputDocument( + media = raw.types.InputMediaDocument( + id=raw.types.InputDocument( id=media.document.id, access_hash=media.document.access_hash, file_reference=media.document.file_reference ) ) elif re.match("^https?://", media.media): - media = types.InputMediaDocumentExternal( + media = raw.types.InputMediaDocumentExternal( url=media.media ) else: media = utils.get_input_media_from_file_id(media.media, media.file_ref, 5) r = await self.send( - functions.messages.EditMessage( + raw.functions.messages.EditMessage( peer=await self.resolve_peer(chat_id), id=message_id, media=media, @@ -261,8 +257,8 @@ async def edit_message_media( ) for i in r.updates: - if isinstance(i, (types.UpdateEditMessage, types.UpdateEditChannelMessage)): - return await pyrogram.Message._parse( + if isinstance(i, (raw.types.UpdateEditMessage, raw.types.UpdateEditChannelMessage)): + return await types.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/messages/edit_message_reply_markup.py b/pyrogram/methods/messages/edit_message_reply_markup.py similarity index 79% rename from pyrogram/client/methods/messages/edit_message_reply_markup.py rename to pyrogram/methods/messages/edit_message_reply_markup.py index 65fa26e221..43d870adcc 100644 --- a/pyrogram/client/methods/messages/edit_message_reply_markup.py +++ b/pyrogram/methods/messages/edit_message_reply_markup.py @@ -18,18 +18,18 @@ from typing import Union -import pyrogram -from pyrogram.api import functions, types -from pyrogram.client.ext import BaseClient +from pyrogram import raw +from pyrogram import types +from pyrogram.scaffold import Scaffold -class EditMessageReplyMarkup(BaseClient): +class EditMessageReplyMarkup(Scaffold): async def edit_message_reply_markup( self, chat_id: Union[int, str], message_id: int, - reply_markup: "pyrogram.InlineKeyboardMarkup" = None, - ) -> "pyrogram.Message": + reply_markup: "types.InlineKeyboardMarkup" = None, + ) -> "types.Message": """Edit only the reply markup of messages sent by the bot. Parameters: @@ -41,11 +41,11 @@ async def edit_message_reply_markup( message_id (``int``): Message identifier in the chat specified in chat_id. - reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*): An InlineKeyboardMarkup object. Returns: - :obj:`Message`: On success, the edited message is returned. + :obj:`~pyrogram.types.Message`: On success, the edited message is returned. Example: .. code-block:: python @@ -59,7 +59,7 @@ async def edit_message_reply_markup( InlineKeyboardButton("New button", callback_data="new_data")]])) """ r = await self.send( - functions.messages.EditMessage( + raw.functions.messages.EditMessage( peer=await self.resolve_peer(chat_id), id=message_id, reply_markup=reply_markup.write() if reply_markup else None, @@ -67,8 +67,8 @@ async def edit_message_reply_markup( ) for i in r.updates: - if isinstance(i, (types.UpdateEditMessage, types.UpdateEditChannelMessage)): - return await pyrogram.Message._parse( + if isinstance(i, (raw.types.UpdateEditMessage, raw.types.UpdateEditChannelMessage)): + return await types.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/messages/edit_message_text.py b/pyrogram/methods/messages/edit_message_text.py similarity index 84% rename from pyrogram/client/methods/messages/edit_message_text.py rename to pyrogram/methods/messages/edit_message_text.py index a2b43f1681..b7d8483054 100644 --- a/pyrogram/client/methods/messages/edit_message_text.py +++ b/pyrogram/methods/messages/edit_message_text.py @@ -18,12 +18,12 @@ from typing import Union -import pyrogram -from pyrogram.api import functions, types -from pyrogram.client.ext import BaseClient +from pyrogram import raw +from pyrogram import types +from pyrogram.scaffold import Scaffold -class EditMessageText(BaseClient): +class EditMessageText(Scaffold): async def edit_message_text( self, chat_id: Union[int, str], @@ -31,8 +31,8 @@ async def edit_message_text( text: str, parse_mode: Union[str, None] = object, disable_web_page_preview: bool = None, - reply_markup: "pyrogram.InlineKeyboardMarkup" = None - ) -> "pyrogram.Message": + reply_markup: "types.InlineKeyboardMarkup" = None + ) -> "types.Message": """Edit the text of messages. Parameters: @@ -57,11 +57,11 @@ async def edit_message_text( disable_web_page_preview (``bool``, *optional*): Disables link previews for links in this message. - reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*): An InlineKeyboardMarkup object. Returns: - :obj:`Message`: On success, the edited message is returned. + :obj:`~pyrogram.types.Message`: On success, the edited message is returned. Example: .. code-block:: python @@ -76,7 +76,7 @@ async def edit_message_text( """ r = await self.send( - functions.messages.EditMessage( + raw.functions.messages.EditMessage( peer=await self.resolve_peer(chat_id), id=message_id, no_webpage=disable_web_page_preview or None, @@ -86,8 +86,8 @@ async def edit_message_text( ) for i in r.updates: - if isinstance(i, (types.UpdateEditMessage, types.UpdateEditChannelMessage)): - return await pyrogram.Message._parse( + if isinstance(i, (raw.types.UpdateEditMessage, raw.types.UpdateEditChannelMessage)): + return await types.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/messages/forward_messages.py b/pyrogram/methods/messages/forward_messages.py similarity index 83% rename from pyrogram/client/methods/messages/forward_messages.py rename to pyrogram/methods/messages/forward_messages.py index c7f47a5248..5287a4ced9 100644 --- a/pyrogram/client/methods/messages/forward_messages.py +++ b/pyrogram/methods/messages/forward_messages.py @@ -18,13 +18,12 @@ from typing import Union, Iterable, List -import pyrogram -from pyrogram.api import functions, types +from pyrogram import raw +from pyrogram import types +from pyrogram.scaffold import Scaffold -from ...ext import BaseClient - -class ForwardMessages(BaseClient): +class ForwardMessages(Scaffold): async def forward_messages( self, chat_id: Union[int, str], @@ -34,7 +33,7 @@ async def forward_messages( as_copy: bool = False, remove_caption: bool = False, schedule_date: int = None - ) -> List["pyrogram.Message"]: + ) -> List["types.Message"]: """Forward messages of any kind. Parameters: @@ -70,9 +69,9 @@ async def forward_messages( Date when the message will be automatically sent. Unix time. Returns: - :obj:`Message` | List of :obj:`Message`: 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 a list of - messages, even if such iterable contained just a single element. + :obj:`~pyrogram.types.Message` | List of :obj:`~pyrogram.types.Message`: 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 a list of messages, even if such iterable contained just a single element. Example: .. code-block:: python @@ -108,10 +107,10 @@ async def forward_messages( ) ) - return pyrogram.List(forwarded_messages) if is_iterable else forwarded_messages[0] + return types.List(forwarded_messages) if is_iterable else forwarded_messages[0] else: r = await self.send( - functions.messages.ForwardMessages( + raw.functions.messages.ForwardMessages( to_peer=await self.resolve_peer(chat_id), from_peer=await self.resolve_peer(from_chat_id), id=message_ids, @@ -127,12 +126,14 @@ async def forward_messages( chats = {i.id: i for i in r.chats} for i in r.updates: - if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage, types.UpdateNewScheduledMessage)): + if isinstance(i, (raw.types.UpdateNewMessage, + raw.types.UpdateNewChannelMessage, + raw.types.UpdateNewScheduledMessage)): forwarded_messages.append( - await pyrogram.Message._parse( + await types.Message._parse( self, i.message, users, chats ) ) - return pyrogram.List(forwarded_messages) if is_iterable else forwarded_messages[0] + return types.List(forwarded_messages) if is_iterable else forwarded_messages[0] diff --git a/pyrogram/client/methods/messages/get_history.py b/pyrogram/methods/messages/get_history.py similarity index 90% rename from pyrogram/client/methods/messages/get_history.py rename to pyrogram/methods/messages/get_history.py index 92a84d9bf5..00c9f435df 100644 --- a/pyrogram/client/methods/messages/get_history.py +++ b/pyrogram/methods/messages/get_history.py @@ -16,19 +16,18 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -import asyncio import logging from typing import Union, List -import pyrogram -from pyrogram.api import functions -from pyrogram.client.ext import utils -from ...ext import BaseClient +from pyrogram import raw +# from pyrogram import types +from pyrogram import utils +from pyrogram.scaffold import Scaffold log = logging.getLogger(__name__) -class GetHistory(BaseClient): +class GetHistory(Scaffold): async def get_history( self, chat_id: Union[int, str], @@ -37,11 +36,11 @@ async def get_history( offset_id: int = 0, offset_date: int = 0, reverse: bool = False - ) -> List["pyrogram.Message"]: + ) -> List["types.Message"]: """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:`~Client.iter_history`. + For a more convenient way of getting a chat history see :meth:`~pyrogram.Client.iter_history`. Parameters: chat_id (``int`` | ``str``): @@ -67,7 +66,7 @@ async def get_history( Pass True to retrieve the messages in reversed order (from older to most recent). Returns: - List of :obj:`Message` - On success, a list of the retrieved messages is returned. + List of :obj:`~pyrogram.types.Message` - On success, a list of the retrieved messages is returned. Example: .. code-block:: python @@ -87,7 +86,7 @@ async def get_history( messages = await utils.parse_messages( self, await self.send( - functions.messages.GetHistory( + raw.functions.messages.GetHistory( peer=await self.resolve_peer(chat_id), offset_id=offset_id, offset_date=offset_date, diff --git a/pyrogram/client/methods/messages/get_history_count.py b/pyrogram/methods/messages/get_history_count.py similarity index 90% rename from pyrogram/client/methods/messages/get_history_count.py rename to pyrogram/methods/messages/get_history_count.py index d7476f95c2..e08bf027fc 100644 --- a/pyrogram/client/methods/messages/get_history_count.py +++ b/pyrogram/methods/messages/get_history_count.py @@ -19,13 +19,13 @@ import logging from typing import Union -from pyrogram.api import types, functions -from pyrogram.client.ext import BaseClient +from pyrogram import raw +from pyrogram.scaffold import Scaffold log = logging.getLogger(__name__) -class GetHistoryCount(BaseClient): +class GetHistoryCount(Scaffold): async def get_history_count( self, chat_id: Union[int, str] @@ -52,7 +52,7 @@ async def get_history_count( """ r = await self.send( - functions.messages.GetHistory( + raw.functions.messages.GetHistory( peer=await self.resolve_peer(chat_id), offset_id=0, offset_date=0, @@ -64,7 +64,7 @@ async def get_history_count( ) ) - if isinstance(r, types.messages.Messages): + if isinstance(r, raw.types.messages.Messages): return len(r.messages) else: return r.count diff --git a/pyrogram/client/methods/messages/get_messages.py b/pyrogram/methods/messages/get_messages.py similarity index 81% rename from pyrogram/client/methods/messages/get_messages.py rename to pyrogram/methods/messages/get_messages.py index b4199b3070..d99bcb2f95 100644 --- a/pyrogram/client/methods/messages/get_messages.py +++ b/pyrogram/methods/messages/get_messages.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 logging from typing import Union, Iterable, List -import pyrogram -from pyrogram.api import functions, types -from ...ext import BaseClient, utils +from pyrogram import raw +from pyrogram import types +from pyrogram import utils +from pyrogram.scaffold import Scaffold log = logging.getLogger(__name__) @@ -30,14 +30,14 @@ # TODO: Rewrite using a flag for replied messages and have message_ids non-optional -class GetMessages(BaseClient): +class GetMessages(Scaffold): async def get_messages( self, chat_id: Union[int, str], message_ids: Union[int, Iterable[int]] = None, reply_to_message_ids: Union[int, Iterable[int]] = None, replies: int = 1 - ) -> Union["pyrogram.Message", List["pyrogram.Message"]]: + ) -> Union["types.Message", List["types.Message"]]: """Get one or more messages from a chat by using message identifiers. You can retrieve up to 200 messages at once. @@ -63,9 +63,9 @@ async def get_messages( Defaults to 1. Returns: - :obj:`Message` | List of :obj:`Message`: 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 a list of messages, - even if such iterable contained just a single element. + :obj:`~pyrogram.types.Message` | List of :obj:`~pyrogram.types.Message`: 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 a list of messages, even if such iterable contained just a single element. Example: .. code-block:: python @@ -89,8 +89,8 @@ async def get_messages( ValueError: In case of invalid arguments. """ ids, ids_type = ( - (message_ids, types.InputMessageID) if message_ids - else (reply_to_message_ids, types.InputMessageReplyTo) if reply_to_message_ids + (message_ids, raw.types.InputMessageID) if message_ids + else (reply_to_message_ids, raw.types.InputMessageReplyTo) if reply_to_message_ids else (None, None) ) @@ -106,10 +106,10 @@ async def get_messages( if replies < 0: replies = (1 << 31) - 1 - if isinstance(peer, types.InputPeerChannel): - rpc = functions.channels.GetMessages(channel=peer, id=ids) + if isinstance(peer, raw.types.InputPeerChannel): + rpc = raw.functions.channels.GetMessages(channel=peer, id=ids) else: - rpc = functions.messages.GetMessages(id=ids) + rpc = raw.functions.messages.GetMessages(id=ids) r = await self.send(rpc) diff --git a/pyrogram/client/methods/messages/iter_history.py b/pyrogram/methods/messages/iter_history.py similarity index 88% rename from pyrogram/client/methods/messages/iter_history.py rename to pyrogram/methods/messages/iter_history.py index 04c7dc1a77..6bd3d5815e 100644 --- a/pyrogram/client/methods/messages/iter_history.py +++ b/pyrogram/methods/messages/iter_history.py @@ -16,16 +16,13 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Union, Optional, Generator +from typing import Union, Optional, AsyncGenerator -import pyrogram -from async_generator import async_generator, yield_ +from pyrogram import types +from pyrogram.scaffold import Scaffold -from ...ext import BaseClient - -class IterHistory(BaseClient): - @async_generator +class IterHistory(Scaffold): async def iter_history( self, chat_id: Union[int, str], @@ -34,10 +31,10 @@ async def iter_history( offset_id: int = 0, offset_date: int = 0, reverse: bool = False - ) -> Optional[Generator["pyrogram.Message", None, None]]: + ) -> Optional[AsyncGenerator["types.Message", None]]: """Iterate through a chat history sequentially. - This convenience method does the same as repeatedly calling :meth:`~Client.get_history` in a loop, thus saving + This convenience method does the same as repeatedly calling :meth:`~pyrogram.Client.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. @@ -65,7 +62,7 @@ async def iter_history( Pass True to retrieve the messages in reversed order (from older to most recent). Returns: - ``Generator``: A generator yielding :obj:`Message` objects. + ``Generator``: A generator yielding :obj:`~pyrogram.types.Message` objects. Example: .. code-block:: python @@ -94,7 +91,7 @@ async def iter_history( offset_id = messages[-1].message_id + (1 if reverse else 0) for message in messages: - await yield_(message) + yield message current += 1 diff --git a/pyrogram/client/methods/messages/read_history.py b/pyrogram/methods/messages/read_history.py similarity index 89% rename from pyrogram/client/methods/messages/read_history.py rename to pyrogram/methods/messages/read_history.py index 5e1e265bb3..15f0912870 100644 --- a/pyrogram/client/methods/messages/read_history.py +++ b/pyrogram/methods/messages/read_history.py @@ -18,11 +18,11 @@ from typing import Union -from pyrogram.api import functions, types -from ...ext import BaseClient +from pyrogram import raw +from pyrogram.scaffold import Scaffold -class ReadHistory(BaseClient): +class ReadHistory(Scaffold): async def read_history( self, chat_id: Union[int, str], @@ -55,13 +55,13 @@ async def read_history( peer = await self.resolve_peer(chat_id) - if isinstance(peer, types.InputPeerChannel): - q = functions.channels.ReadHistory( + if isinstance(peer, raw.types.InputPeerChannel): + q = raw.functions.channels.ReadHistory( channel=peer, max_id=max_id ) else: - q = functions.messages.ReadHistory( + q = raw.functions.messages.ReadHistory( peer=peer, max_id=max_id ) diff --git a/pyrogram/client/methods/messages/retract_vote.py b/pyrogram/methods/messages/retract_vote.py similarity index 83% rename from pyrogram/client/methods/messages/retract_vote.py rename to pyrogram/methods/messages/retract_vote.py index 191c8c7539..add319c72b 100644 --- a/pyrogram/client/methods/messages/retract_vote.py +++ b/pyrogram/methods/messages/retract_vote.py @@ -18,17 +18,17 @@ from typing import Union -import pyrogram -from pyrogram.api import functions -from pyrogram.client.ext import BaseClient +from pyrogram import raw +from pyrogram import types +from pyrogram.scaffold import Scaffold -class RetractVote(BaseClient): +class RetractVote(Scaffold): async def retract_vote( self, chat_id: Union[int, str], message_id: int - ) -> "pyrogram.Poll": + ) -> "types.Poll": """Retract your vote in a poll. Parameters: @@ -41,7 +41,7 @@ async def retract_vote( Identifier of the original message with the poll. Returns: - :obj:`Poll`: On success, the poll with the retracted vote is returned. + :obj:`~pyrogram.types.Poll`: On success, the poll with the retracted vote is returned. Example: .. code-block:: python @@ -49,11 +49,11 @@ async def retract_vote( app.retract_vote(chat_id, message_id) """ r = await self.send( - functions.messages.SendVote( + raw.functions.messages.SendVote( peer=await self.resolve_peer(chat_id), msg_id=message_id, options=[] ) ) - return pyrogram.Poll._parse(self, r.updates[0]) + return types.Poll._parse(self, r.updates[0]) diff --git a/pyrogram/client/methods/messages/search_global.py b/pyrogram/methods/messages/search_global.py similarity index 83% rename from pyrogram/client/methods/messages/search_global.py rename to pyrogram/methods/messages/search_global.py index 2a889e312e..60c0c752ab 100644 --- a/pyrogram/client/methods/messages/search_global.py +++ b/pyrogram/methods/messages/search_global.py @@ -16,22 +16,20 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Generator, Optional +from typing import AsyncGenerator, Optional -from async_generator import async_generator, yield_ +from pyrogram import raw +from pyrogram import types +from pyrogram import utils +from pyrogram.scaffold import Scaffold -import pyrogram -from pyrogram.api import functions, types -from pyrogram.client.ext import BaseClient, utils - -class SearchGlobal(BaseClient): - @async_generator +class SearchGlobal(Scaffold): async def search_global( self, query: str, limit: int = 0, - ) -> Optional[Generator["pyrogram.Message", None, None]]: + ) -> Optional[AsyncGenerator["types.Message", None]]: """Search messages globally from all of your chats. .. note:: @@ -48,7 +46,7 @@ async def search_global( By default, no limit is applied and all messages are returned. Returns: - ``Generator``: A generator yielding :obj:`Message` objects. + ``Generator``: A generator yielding :obj:`~pyrogram.types.Message` objects. Example: .. code-block:: python @@ -63,14 +61,14 @@ async def search_global( limit = min(100, total) offset_date = 0 - offset_peer = types.InputPeerEmpty() + offset_peer = raw.types.InputPeerEmpty() offset_id = 0 while True: messages = await utils.parse_messages( self, await self.send( - functions.messages.SearchGlobal( + raw.functions.messages.SearchGlobal( q=query, offset_rate=offset_date, offset_peer=offset_peer, @@ -91,7 +89,7 @@ async def search_global( offset_id = last.message_id for message in messages: - await yield_(message) + yield message current += 1 diff --git a/pyrogram/client/methods/messages/search_messages.py b/pyrogram/methods/messages/search_messages.py similarity index 80% rename from pyrogram/client/methods/messages/search_messages.py rename to pyrogram/methods/messages/search_messages.py index bfd56663db..44132dcef0 100644 --- a/pyrogram/client/methods/messages/search_messages.py +++ b/pyrogram/methods/messages/search_messages.py @@ -16,31 +16,31 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Union, List, Generator, Optional +from typing import Union, List, AsyncGenerator, Optional -import pyrogram -from pyrogram.client.ext import BaseClient, utils -from pyrogram.api import functions, types -from async_generator import async_generator, yield_ +from pyrogram import raw +from pyrogram import types +from pyrogram import utils +from pyrogram.scaffold import Scaffold class Filters: - EMPTY = types.InputMessagesFilterEmpty() - PHOTO = types.InputMessagesFilterPhotos() - VIDEO = types.InputMessagesFilterVideo() - PHOTO_VIDEO = types.InputMessagesFilterPhotoVideo() - DOCUMENT = types.InputMessagesFilterDocument() - URL = types.InputMessagesFilterUrl() - ANIMATION = types.InputMessagesFilterGif() - VOICE_NOTE = types.InputMessagesFilterVoice() - AUDIO = types.InputMessagesFilterMusic() - CHAT_PHOTO = types.InputMessagesFilterChatPhotos() - PHONE_CALL = types.InputMessagesFilterPhoneCalls() - AUDIO_VIDEO_NOTE = types.InputMessagesFilterRoundVideo() - VIDEO_NOTE = types.InputMessagesFilterRoundVideo() - MENTION = types.InputMessagesFilterMyMentions() - LOCATION = types.InputMessagesFilterGeo() - CONTACT = types.InputMessagesFilterContacts() + EMPTY = raw.types.InputMessagesFilterEmpty() + PHOTO = raw.types.InputMessagesFilterPhotos() + VIDEO = raw.types.InputMessagesFilterVideo() + PHOTO_VIDEO = raw.types.InputMessagesFilterPhotoVideo() + DOCUMENT = raw.types.InputMessagesFilterDocument() + URL = raw.types.InputMessagesFilterUrl() + ANIMATION = raw.types.InputMessagesFilterGif() + VOICE_NOTE = raw.types.InputMessagesFilterVoice() + AUDIO = raw.types.InputMessagesFilterMusic() + CHAT_PHOTO = raw.types.InputMessagesFilterChatPhotos() + PHONE_CALL = raw.types.InputMessagesFilterPhoneCalls() + AUDIO_VIDEO_NOTE = raw.types.InputMessagesFilterRoundVideo() + VIDEO_NOTE = raw.types.InputMessagesFilterRoundVideo() + MENTION = raw.types.InputMessagesFilterMyMentions() + LOCATION = raw.types.InputMessagesFilterGeo() + CONTACT = raw.types.InputMessagesFilterContacts() POSSIBLE_VALUES = list(map(lambda x: x.lower(), filter(lambda x: not x.startswith("__"), Filters.__dict__.keys()))) @@ -48,22 +48,22 @@ class Filters: # noinspection PyShadowingBuiltins async def get_chunk( - client: BaseClient, + client: Scaffold, chat_id: Union[int, str], query: str = "", filter: str = "empty", offset: int = 0, limit: int = 100, from_user: Union[int, str] = None -) -> List["pyrogram.Message"]: +) -> List["types.Message"]: try: filter = Filters.__dict__[filter.upper()] except KeyError: raise ValueError('Invalid filter "{}". Possible values are: {}'.format( - filter, ", ".join('"{}"'.format(v) for v in POSSIBLE_VALUES))) from None + filter, ", ".join(f'"{v}"' for v in POSSIBLE_VALUES))) from None r = await client.send( - functions.messages.Search( + raw.functions.messages.Search( peer=await client.resolve_peer(chat_id), q=query, filter=filter, @@ -86,9 +86,8 @@ async def get_chunk( return await utils.parse_messages(client, r) -class SearchMessages(BaseClient): +class SearchMessages(Scaffold): # noinspection PyShadowingBuiltins - @async_generator async def search_messages( self, chat_id: Union[int, str], @@ -97,7 +96,7 @@ async def search_messages( filter: str = "empty", limit: int = 0, from_user: Union[int, str] = None - ) -> Optional[Generator["pyrogram.Message", None, None]]: + ) -> Optional[AsyncGenerator["types.Message", None]]: """Search for text and media messages inside a specific chat. Parameters: @@ -144,7 +143,7 @@ async def search_messages( Unique identifier (int) or username (str) of the target user you want to search for messages from. Returns: - ``Generator``: A generator yielding :obj:`Message` objects. + ``Generator``: A generator yielding :obj:`~pyrogram.types.Message` objects. Example: .. code-block:: python @@ -178,7 +177,7 @@ async def search_messages( offset += 100 for message in messages: - await yield_(message) + yield message current += 1 diff --git a/pyrogram/client/methods/messages/send_animation.py b/pyrogram/methods/messages/send_animation.py similarity index 82% rename from pyrogram/client/methods/messages/send_animation.py rename to pyrogram/methods/messages/send_animation.py index 465622140d..9851c11bef 100644 --- a/pyrogram/client/methods/messages/send_animation.py +++ b/pyrogram/methods/messages/send_animation.py @@ -20,13 +20,15 @@ import re from typing import Union, BinaryIO -import pyrogram -from pyrogram.api import functions, types -from pyrogram.client.ext import BaseClient, utils +from pyrogram import StopTransmission +from pyrogram import raw +from pyrogram import types +from pyrogram import utils from pyrogram.errors import FilePartMissing +from pyrogram.scaffold import Scaffold -class SendAnimation(BaseClient): +class SendAnimation(Scaffold): async def send_animation( self, chat_id: Union[int, str], @@ -44,14 +46,14 @@ async def send_animation( reply_to_message_id: int = None, schedule_date: int = None, reply_markup: Union[ - "pyrogram.InlineKeyboardMarkup", - "pyrogram.ReplyKeyboardMarkup", - "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply" + "types.InlineKeyboardMarkup", + "types.ReplyKeyboardMarkup", + "types.ReplyKeyboardRemove", + "types.ForceReply" ] = None, progress: callable = None, progress_args: tuple = () - ) -> Union["pyrogram.Message", None]: + ) -> Union["types.Message", None]: """Send animation files (animation or H.264/MPEG-4 AVC video without sound). Parameters: @@ -114,7 +116,7 @@ async def send_animation( schedule_date (``int``, *optional*): Date when the message will be automatically sent. Unix time. - reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. @@ -141,8 +143,9 @@ async def send_animation( You can either keep *\*args* or add every single extra argument in your function signature. Returns: - :obj:`Message` | ``None``: On success, the sent animation message is returned, otherwise, in case the upload - is deliberately stopped with :meth:`~Client.stop_transmission`, None is returned. + :obj:`~pyrogram.types.Message` | ``None``: On success, the sent animation message is returned, otherwise, + in case the upload is deliberately stopped with :meth:`~pyrogram.Client.stop_transmission`, None is + returned. Example: .. code-block:: python @@ -158,7 +161,7 @@ async def send_animation( # Keep track of the progress while uploading def progress(current, total): - print("{:.1f}%".format(current * 100 / total)) + print(f"{current * 100 / total:.1f}%") app.send_animation("me", "animation.gif", progress=progress) """ @@ -169,23 +172,23 @@ def progress(current, total): if os.path.isfile(animation): thumb = await self.save_file(thumb) file = await self.save_file(animation, progress=progress, progress_args=progress_args) - media = types.InputMediaUploadedDocument( + media = raw.types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(animation) or "video/mp4", file=file, thumb=thumb, attributes=[ - types.DocumentAttributeVideo( + raw.types.DocumentAttributeVideo( supports_streaming=True, duration=duration, w=width, h=height ), - types.DocumentAttributeFilename(file_name=file_name or os.path.basename(animation)), - types.DocumentAttributeAnimated() + raw.types.DocumentAttributeFilename(file_name=file_name or os.path.basename(animation)), + raw.types.DocumentAttributeAnimated() ] ) elif re.match("^https?://", animation): - media = types.InputMediaDocumentExternal( + media = raw.types.InputMediaDocumentExternal( url=animation ) else: @@ -193,26 +196,26 @@ def progress(current, total): else: thumb = await self.save_file(thumb) file = await self.save_file(animation, progress=progress, progress_args=progress_args) - media = types.InputMediaUploadedDocument( + media = raw.types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(animation.name) or "video/mp4", file=file, thumb=thumb, attributes=[ - types.DocumentAttributeVideo( + raw.types.DocumentAttributeVideo( supports_streaming=True, duration=duration, w=width, h=height ), - types.DocumentAttributeFilename(file_name=animation.name), - types.DocumentAttributeAnimated() + raw.types.DocumentAttributeFilename(file_name=animation.name), + raw.types.DocumentAttributeAnimated() ] ) while True: try: r = await self.send( - functions.messages.SendMedia( + raw.functions.messages.SendMedia( peer=await self.resolve_peer(chat_id), media=media, silent=disable_notification or None, @@ -227,15 +230,14 @@ def progress(current, total): 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, types.UpdateNewScheduledMessage) - ): - message = await pyrogram.Message._parse( + if isinstance(i, (raw.types.UpdateNewMessage, + raw.types.UpdateNewChannelMessage, + raw.types.UpdateNewScheduledMessage)): + message = await types.Message._parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats}, - is_scheduled=isinstance(i, types.UpdateNewScheduledMessage) + is_scheduled=isinstance(i, raw.types.UpdateNewScheduledMessage) ) if unsave: @@ -243,7 +245,7 @@ def progress(current, total): document_id = utils.get_input_media_from_file_id(document.file_id, document.file_ref).id await self.send( - functions.messages.SaveGif( + raw.functions.messages.SaveGif( id=document_id, unsave=True ) @@ -251,5 +253,5 @@ def progress(current, total): return message - except BaseClient.StopTransmission: + except StopTransmission: return None diff --git a/pyrogram/client/methods/messages/send_audio.py b/pyrogram/methods/messages/send_audio.py similarity index 82% rename from pyrogram/client/methods/messages/send_audio.py rename to pyrogram/methods/messages/send_audio.py index dc460e9744..fc5213cc9a 100644 --- a/pyrogram/client/methods/messages/send_audio.py +++ b/pyrogram/methods/messages/send_audio.py @@ -20,13 +20,15 @@ import re from typing import Union, BinaryIO -import pyrogram -from pyrogram.api import functions, types -from pyrogram.client.ext import BaseClient, utils +from pyrogram import StopTransmission +from pyrogram import raw +from pyrogram import types +from pyrogram import utils from pyrogram.errors import FilePartMissing +from pyrogram.scaffold import Scaffold -class SendAudio(BaseClient): +class SendAudio(Scaffold): async def send_audio( self, chat_id: Union[int, str], @@ -42,17 +44,17 @@ async def send_audio( reply_to_message_id: int = None, schedule_date: int = None, reply_markup: Union[ - "pyrogram.InlineKeyboardMarkup", - "pyrogram.ReplyKeyboardMarkup", - "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply" + "types.InlineKeyboardMarkup", + "types.ReplyKeyboardMarkup", + "types.ReplyKeyboardRemove", + "types.ForceReply" ] = None, progress: callable = None, progress_args: tuple = () - ) -> Union["pyrogram.Message", None]: + ) -> Union["types.Message", None]: """Send audio files. - For sending voice messages, use the :obj:`send_voice()` method instead. + For sending voice messages, use the :meth:`~pyrogram.Client.send_voice` method instead. Parameters: chat_id (``int`` | ``str``): @@ -110,7 +112,7 @@ async def send_audio( schedule_date (``int``, *optional*): Date when the message will be automatically sent. Unix time. - reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. @@ -137,8 +139,8 @@ async def send_audio( You can either keep *\*args* or add every single extra argument in your function signature. Returns: - :obj:`Message` | ``None``: On success, the sent audio message is returned, otherwise, in case the upload - is deliberately stopped with :meth:`~Client.stop_transmission`, None is returned. + :obj:`~pyrogram.types.Message` | ``None``: On success, the sent audio message is returned, otherwise, in + case the upload is deliberately stopped with :meth:`~pyrogram.Client.stop_transmission`, None is returned. Example: .. code-block:: python @@ -157,7 +159,7 @@ async def send_audio( # Keep track of the progress while uploading def progress(current, total): - print("{:.1f}%".format(current * 100 / total)) + print(f"{current * 100 / total:.1f}%") app.send_audio("me", "audio.mp3", progress=progress) """ @@ -168,21 +170,21 @@ def progress(current, total): if os.path.isfile(audio): thumb = await self.save_file(thumb) file = await self.save_file(audio, progress=progress, progress_args=progress_args) - media = types.InputMediaUploadedDocument( + media = raw.types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(audio) or "audio/mpeg", file=file, thumb=thumb, attributes=[ - types.DocumentAttributeAudio( + raw.types.DocumentAttributeAudio( duration=duration, performer=performer, title=title ), - types.DocumentAttributeFilename(file_name=file_name or os.path.basename(audio)) + raw.types.DocumentAttributeFilename(file_name=file_name or os.path.basename(audio)) ] ) elif re.match("^https?://", audio): - media = types.InputMediaDocumentExternal( + media = raw.types.InputMediaDocumentExternal( url=audio ) else: @@ -190,24 +192,24 @@ def progress(current, total): else: thumb = await self.save_file(thumb) file = await self.save_file(audio, progress=progress, progress_args=progress_args) - media = types.InputMediaUploadedDocument( + media = raw.types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(audio.name) or "audio/mpeg", file=file, thumb=thumb, attributes=[ - types.DocumentAttributeAudio( + raw.types.DocumentAttributeAudio( duration=duration, performer=performer, title=title ), - types.DocumentAttributeFilename(file_name=audio.name) + raw.types.DocumentAttributeFilename(file_name=audio.name) ] ) while True: try: r = await self.send( - functions.messages.SendMedia( + raw.functions.messages.SendMedia( peer=await self.resolve_peer(chat_id), media=media, silent=disable_notification or None, @@ -222,15 +224,14 @@ def progress(current, total): 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, types.UpdateNewScheduledMessage) - ): - return await pyrogram.Message._parse( + if isinstance(i, (raw.types.UpdateNewMessage, + raw.types.UpdateNewChannelMessage, + raw.types.UpdateNewScheduledMessage)): + return await types.Message._parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats}, - is_scheduled=isinstance(i, types.UpdateNewScheduledMessage) + is_scheduled=isinstance(i, raw.types.UpdateNewScheduledMessage) ) - except BaseClient.StopTransmission: + except StopTransmission: return None diff --git a/pyrogram/client/methods/messages/send_cached_media.py b/pyrogram/methods/messages/send_cached_media.py similarity index 80% rename from pyrogram/client/methods/messages/send_cached_media.py rename to pyrogram/methods/messages/send_cached_media.py index d550cc279f..3d805c4dc4 100644 --- a/pyrogram/client/methods/messages/send_cached_media.py +++ b/pyrogram/methods/messages/send_cached_media.py @@ -18,12 +18,13 @@ from typing import Union -import pyrogram -from pyrogram.api import functions, types -from pyrogram.client.ext import BaseClient, utils +from pyrogram import raw +from pyrogram import types +from pyrogram import utils +from pyrogram.scaffold import Scaffold -class SendCachedMedia(BaseClient): +class SendCachedMedia(Scaffold): async def send_cached_media( self, chat_id: Union[int, str], @@ -35,12 +36,12 @@ async def send_cached_media( reply_to_message_id: int = None, schedule_date: int = None, reply_markup: Union[ - "pyrogram.InlineKeyboardMarkup", - "pyrogram.ReplyKeyboardMarkup", - "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply" + "types.InlineKeyboardMarkup", + "types.ReplyKeyboardMarkup", + "types.ReplyKeyboardRemove", + "types.ForceReply" ] = None - ) -> Union["pyrogram.Message", None]: + ) -> Union["types.Message", None]: """Send any media stored on the Telegram servers using a file_id. This convenience method works with any valid file_id only. @@ -81,12 +82,12 @@ async def send_cached_media( schedule_date (``int``, *optional*): Date when the message will be automatically sent. Unix time. - reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. Returns: - :obj:`Message`: On success, the sent media message is returned. + :obj:`~pyrogram.types.Message`: On success, the sent media message is returned. Example: .. code-block:: python @@ -95,7 +96,7 @@ async def send_cached_media( """ r = await self.send( - functions.messages.SendMedia( + raw.functions.messages.SendMedia( peer=await self.resolve_peer(chat_id), media=utils.get_input_media_from_file_id(file_id, file_ref), silent=disable_notification or None, @@ -108,10 +109,12 @@ async def send_cached_media( ) for i in r.updates: - if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage, types.UpdateNewScheduledMessage)): - return await pyrogram.Message._parse( + if isinstance(i, (raw.types.UpdateNewMessage, + raw.types.UpdateNewChannelMessage, + raw.types.UpdateNewScheduledMessage)): + return await types.Message._parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats}, - is_scheduled=isinstance(i, types.UpdateNewScheduledMessage) + is_scheduled=isinstance(i, raw.types.UpdateNewScheduledMessage) ) diff --git a/pyrogram/client/methods/messages/send_chat_action.py b/pyrogram/methods/messages/send_chat_action.py similarity index 78% rename from pyrogram/client/methods/messages/send_chat_action.py rename to pyrogram/methods/messages/send_chat_action.py index 35a3d722af..c9c5d5692d 100644 --- a/pyrogram/client/methods/messages/send_chat_action.py +++ b/pyrogram/methods/messages/send_chat_action.py @@ -19,30 +19,30 @@ import json from typing import Union -from pyrogram.api import functions, types -from pyrogram.client.ext import BaseClient +from pyrogram import raw +from pyrogram.scaffold import Scaffold class ChatAction: - TYPING = types.SendMessageTypingAction - UPLOAD_PHOTO = types.SendMessageUploadPhotoAction - RECORD_VIDEO = types.SendMessageRecordVideoAction - UPLOAD_VIDEO = types.SendMessageUploadVideoAction - RECORD_AUDIO = types.SendMessageRecordAudioAction - UPLOAD_AUDIO = types.SendMessageUploadAudioAction - UPLOAD_DOCUMENT = types.SendMessageUploadDocumentAction - FIND_LOCATION = types.SendMessageGeoLocationAction - RECORD_VIDEO_NOTE = types.SendMessageRecordRoundAction - UPLOAD_VIDEO_NOTE = types.SendMessageUploadRoundAction - PLAYING = types.SendMessageGamePlayAction - CHOOSE_CONTACT = types.SendMessageChooseContactAction - CANCEL = types.SendMessageCancelAction + TYPING = raw.types.SendMessageTypingAction + UPLOAD_PHOTO = raw.types.SendMessageUploadPhotoAction + RECORD_VIDEO = raw.types.SendMessageRecordVideoAction + UPLOAD_VIDEO = raw.types.SendMessageUploadVideoAction + RECORD_AUDIO = raw.types.SendMessageRecordAudioAction + UPLOAD_AUDIO = raw.types.SendMessageUploadAudioAction + UPLOAD_DOCUMENT = raw.types.SendMessageUploadDocumentAction + FIND_LOCATION = raw.types.SendMessageGeoLocationAction + RECORD_VIDEO_NOTE = raw.types.SendMessageRecordRoundAction + UPLOAD_VIDEO_NOTE = raw.types.SendMessageUploadRoundAction + PLAYING = raw.types.SendMessageGamePlayAction + CHOOSE_CONTACT = raw.types.SendMessageChooseContactAction + CANCEL = raw.types.SendMessageCancelAction POSSIBLE_VALUES = list(map(lambda x: x.lower(), filter(lambda x: not x.startswith("__"), ChatAction.__dict__.keys()))) -class SendChatAction(BaseClient): +class SendChatAction(Scaffold): async def send_chat_action(self, chat_id: Union[int, str], action: str) -> bool: """Tell the other party that something is happening on your side. @@ -94,7 +94,7 @@ async def send_chat_action(self, chat_id: Union[int, str], action: str) -> bool: action = action() return await self.send( - functions.messages.SetTyping( + raw.functions.messages.SetTyping( peer=await self.resolve_peer(chat_id), action=action ) diff --git a/pyrogram/client/methods/messages/send_contact.py b/pyrogram/methods/messages/send_contact.py similarity index 77% rename from pyrogram/client/methods/messages/send_contact.py rename to pyrogram/methods/messages/send_contact.py index 0eacd8fd4c..50909e7c95 100644 --- a/pyrogram/client/methods/messages/send_contact.py +++ b/pyrogram/methods/messages/send_contact.py @@ -18,12 +18,12 @@ from typing import Union -import pyrogram -from pyrogram.api import functions, types -from pyrogram.client.ext import BaseClient +from pyrogram import raw +from pyrogram import types +from pyrogram.scaffold import Scaffold -class SendContact(BaseClient): +class SendContact(Scaffold): async def send_contact( self, chat_id: Union[int, str], @@ -35,12 +35,12 @@ async def send_contact( reply_to_message_id: int = None, schedule_date: int = None, reply_markup: Union[ - "pyrogram.InlineKeyboardMarkup", - "pyrogram.ReplyKeyboardMarkup", - "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply" + "types.InlineKeyboardMarkup", + "types.ReplyKeyboardMarkup", + "types.ReplyKeyboardRemove", + "types.ForceReply" ] = None - ) -> "pyrogram.Message": + ) -> "types.Message": """Send phone contacts. Parameters: @@ -71,12 +71,12 @@ async def send_contact( schedule_date (``int``, *optional*): Date when the message will be automatically sent. Unix time. - reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. Returns: - :obj:`Message`: On success, the sent contact message is returned. + :obj:`~pyrogram.types.Message`: On success, the sent contact message is returned. Example: .. code-block:: python @@ -84,9 +84,9 @@ async def send_contact( app.send_contact("me", "+39 123 456 7890", "Dan") """ r = await self.send( - functions.messages.SendMedia( + raw.functions.messages.SendMedia( peer=await self.resolve_peer(chat_id), - media=types.InputMediaContact( + media=raw.types.InputMediaContact( phone_number=phone_number, first_name=first_name, last_name=last_name or "", @@ -102,10 +102,12 @@ async def send_contact( ) for i in r.updates: - if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage, types.UpdateNewScheduledMessage)): - return await pyrogram.Message._parse( + if isinstance(i, (raw.types.UpdateNewMessage, + raw.types.UpdateNewChannelMessage, + raw.types.UpdateNewScheduledMessage)): + return await types.Message._parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats}, - is_scheduled=isinstance(i, types.UpdateNewScheduledMessage) + is_scheduled=isinstance(i, raw.types.UpdateNewScheduledMessage) ) diff --git a/pyrogram/client/methods/messages/send_dice.py b/pyrogram/methods/messages/send_dice.py similarity index 76% rename from pyrogram/client/methods/messages/send_dice.py rename to pyrogram/methods/messages/send_dice.py index 155185cd3e..f7734a4c59 100644 --- a/pyrogram/client/methods/messages/send_dice.py +++ b/pyrogram/methods/messages/send_dice.py @@ -18,12 +18,12 @@ from typing import Union -import pyrogram -from pyrogram.api import functions, types -from pyrogram.client.ext import BaseClient +from pyrogram import raw +from pyrogram import types +from pyrogram.scaffold import Scaffold -class SendDice(BaseClient): +class SendDice(Scaffold): async def send_dice( self, chat_id: Union[int, str], @@ -32,12 +32,12 @@ async def send_dice( reply_to_message_id: int = None, schedule_date: int = None, reply_markup: Union[ - "pyrogram.InlineKeyboardMarkup", - "pyrogram.ReplyKeyboardMarkup", - "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply" + "types.InlineKeyboardMarkup", + "types.ReplyKeyboardMarkup", + "types.ReplyKeyboardRemove", + "types.ForceReply" ] = None - ) -> Union["pyrogram.Message", None]: + ) -> Union["types.Message", None]: """Send a dice with a random value from 1 to 6. Parameters: @@ -60,12 +60,12 @@ async def send_dice( schedule_date (``int``, *optional*): Date when the message will be automatically sent. Unix time. - reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. Returns: - :obj:`Message`: On success, the sent dice message is returned. + :obj:`~pyrogram.types.Message`: On success, the sent dice message is returned. Example: .. code-block:: python @@ -81,9 +81,9 @@ async def send_dice( """ r = await self.send( - functions.messages.SendMedia( + raw.functions.messages.SendMedia( peer=await self.resolve_peer(chat_id), - media=types.InputMediaDice(emoticon=emoji), + media=raw.types.InputMediaDice(emoticon=emoji), silent=disable_notification or None, reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), @@ -94,10 +94,12 @@ async def send_dice( ) for i in r.updates: - if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage, types.UpdateNewScheduledMessage)): - return await pyrogram.Message._parse( + if isinstance(i, (raw.types.UpdateNewMessage, + raw.types.UpdateNewChannelMessage, + raw.types.UpdateNewScheduledMessage)): + return await types.Message._parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats}, - is_scheduled=isinstance(i, types.UpdateNewScheduledMessage) + is_scheduled=isinstance(i, raw.types.UpdateNewScheduledMessage) ) diff --git a/pyrogram/client/methods/messages/send_document.py b/pyrogram/methods/messages/send_document.py similarity index 81% rename from pyrogram/client/methods/messages/send_document.py rename to pyrogram/methods/messages/send_document.py index 7f93d6cba0..0c77a94086 100644 --- a/pyrogram/client/methods/messages/send_document.py +++ b/pyrogram/methods/messages/send_document.py @@ -20,13 +20,15 @@ import re from typing import Union, BinaryIO -import pyrogram -from pyrogram.api import functions, types -from pyrogram.client.ext import BaseClient, utils +from pyrogram import StopTransmission +from pyrogram import raw +from pyrogram import types +from pyrogram import utils from pyrogram.errors import FilePartMissing +from pyrogram.scaffold import Scaffold -class SendDocument(BaseClient): +class SendDocument(Scaffold): async def send_document( self, chat_id: Union[int, str], @@ -40,14 +42,14 @@ async def send_document( reply_to_message_id: int = None, schedule_date: int = None, reply_markup: Union[ - "pyrogram.InlineKeyboardMarkup", - "pyrogram.ReplyKeyboardMarkup", - "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply" + "types.InlineKeyboardMarkup", + "types.ReplyKeyboardMarkup", + "types.ReplyKeyboardRemove", + "types.ForceReply" ] = None, progress: callable = None, progress_args: tuple = () - ) -> Union["pyrogram.Message", None]: + ) -> Union["types.Message", None]: """Send generic files. Parameters: @@ -97,7 +99,7 @@ async def send_document( schedule_date (``int``, *optional*): Date when the message will be automatically sent. Unix time. - reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. @@ -124,8 +126,8 @@ async def send_document( You can either keep *\*args* or add every single extra argument in your function signature. Returns: - :obj:`Message` | ``None``: On success, the sent document message is returned, otherwise, in case the upload - is deliberately stopped with :meth:`~Client.stop_transmission`, None is returned. + :obj:`~pyrogram.types.Message` | ``None``: On success, the sent document message is returned, otherwise, in + case the upload is deliberately stopped with :meth:`~pyrogram.Client.stop_transmission`, None is returned. Example: .. code-block:: python @@ -138,28 +140,31 @@ async def send_document( # Keep track of the progress while uploading def progress(current, total): - print("{:.1f}%".format(current * 100 / total)) + print(f"{current * 100 / total:.1f}%") app.send_document("me", "document.zip", progress=progress) """ file = None + # if isinstance(document, PurePath): + # document = str(document) + try: if isinstance(document, str): if os.path.isfile(document): thumb = await self.save_file(thumb) file = await self.save_file(document, progress=progress, progress_args=progress_args) - media = types.InputMediaUploadedDocument( + media = raw.types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(document) or "application/zip", file=file, force_file=True, thumb=thumb, attributes=[ - types.DocumentAttributeFilename(file_name=file_name or os.path.basename(document)) + raw.types.DocumentAttributeFilename(file_name=file_name or os.path.basename(document)) ] ) elif re.match("^https?://", document): - media = types.InputMediaDocumentExternal( + media = raw.types.InputMediaDocumentExternal( url=document ) else: @@ -167,19 +172,19 @@ def progress(current, total): else: thumb = await self.save_file(thumb) file = await self.save_file(document, progress=progress, progress_args=progress_args) - media = types.InputMediaUploadedDocument( + media = raw.types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(document.name) or "application/zip", file=file, thumb=thumb, attributes=[ - types.DocumentAttributeFilename(file_name=document.name) + raw.types.DocumentAttributeFilename(file_name=document.name) ] ) while True: try: r = await self.send( - functions.messages.SendMedia( + raw.functions.messages.SendMedia( peer=await self.resolve_peer(chat_id), media=media, silent=disable_notification or None, @@ -194,15 +199,14 @@ def progress(current, total): 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, types.UpdateNewScheduledMessage) - ): - return await pyrogram.Message._parse( + if isinstance(i, (raw.types.UpdateNewMessage, + raw.types.UpdateNewChannelMessage, + raw.types.UpdateNewScheduledMessage)): + return await types.Message._parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats}, - is_scheduled=isinstance(i, types.UpdateNewScheduledMessage) + is_scheduled=isinstance(i, raw.types.UpdateNewScheduledMessage) ) - except BaseClient.StopTransmission: + except StopTransmission: return None diff --git a/pyrogram/client/methods/messages/send_location.py b/pyrogram/methods/messages/send_location.py similarity index 74% rename from pyrogram/client/methods/messages/send_location.py rename to pyrogram/methods/messages/send_location.py index b23d9b10ef..d144855027 100644 --- a/pyrogram/client/methods/messages/send_location.py +++ b/pyrogram/methods/messages/send_location.py @@ -18,12 +18,12 @@ from typing import Union -import pyrogram -from pyrogram.api import functions, types -from pyrogram.client.ext import BaseClient +from pyrogram import raw +from pyrogram import types +from pyrogram.scaffold import Scaffold -class SendLocation(BaseClient): +class SendLocation(Scaffold): async def send_location( self, chat_id: Union[int, str], @@ -33,12 +33,12 @@ async def send_location( reply_to_message_id: int = None, schedule_date: int = None, reply_markup: Union[ - "pyrogram.InlineKeyboardMarkup", - "pyrogram.ReplyKeyboardMarkup", - "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply" + "types.InlineKeyboardMarkup", + "types.ReplyKeyboardMarkup", + "types.ReplyKeyboardRemove", + "types.ForceReply" ] = None - ) -> "pyrogram.Message": + ) -> "types.Message": """Send points on the map. Parameters: @@ -63,12 +63,12 @@ async def send_location( schedule_date (``int``, *optional*): Date when the message will be automatically sent. Unix time. - reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. Returns: - :obj:`Message`: On success, the sent location message is returned. + :obj:`~pyrogram.types.Message`: On success, the sent location message is returned. Example: .. code-block:: python @@ -76,10 +76,10 @@ async def send_location( app.send_location("me", 51.500729, -0.124583) """ r = await self.send( - functions.messages.SendMedia( + raw.functions.messages.SendMedia( peer=await self.resolve_peer(chat_id), - media=types.InputMediaGeoPoint( - geo_point=types.InputGeoPoint( + media=raw.types.InputMediaGeoPoint( + geo_point=raw.types.InputGeoPoint( lat=latitude, long=longitude ) @@ -94,10 +94,12 @@ async def send_location( ) for i in r.updates: - if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage, types.UpdateNewScheduledMessage)): - return await pyrogram.Message._parse( + if isinstance(i, (raw.types.UpdateNewMessage, + raw.types.UpdateNewChannelMessage, + raw.types.UpdateNewScheduledMessage)): + return await types.Message._parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats}, - is_scheduled=isinstance(i, types.UpdateNewScheduledMessage) + is_scheduled=isinstance(i, raw.types.UpdateNewScheduledMessage) ) diff --git a/pyrogram/client/methods/messages/send_media_group.py b/pyrogram/methods/messages/send_media_group.py similarity index 74% rename from pyrogram/client/methods/messages/send_media_group.py rename to pyrogram/methods/messages/send_media_group.py index 0fad6e91e5..6c9bd04d4f 100644 --- a/pyrogram/client/methods/messages/send_media_group.py +++ b/pyrogram/methods/messages/send_media_group.py @@ -21,22 +21,23 @@ import re from typing import Union, List -import pyrogram -from pyrogram.api import functions, types -from pyrogram.client.ext import BaseClient, utils +from pyrogram import raw +from pyrogram import types +from pyrogram import utils +from pyrogram.scaffold import Scaffold log = logging.getLogger(__name__) -class SendMediaGroup(BaseClient): +class SendMediaGroup(Scaffold): # TODO: Add progress parameter async def send_media_group( self, chat_id: Union[int, str], - media: List[Union["pyrogram.InputMediaPhoto", "pyrogram.InputMediaVideo"]], + media: List[Union["types.InputMediaPhoto", "types.InputMediaVideo"]], disable_notification: bool = None, reply_to_message_id: int = None - ) -> List["pyrogram.Message"]: + ) -> List["types.Message"]: """Send a group of photos or videos as an album. Parameters: @@ -45,7 +46,7 @@ async def send_media_group( For your personal cloud (Saved Messages) you can simply use "me" or "self". For a contact that exists in your Telegram address book you can use his phone number (str). - media (List of :obj:`InputMediaPhoto` and :obj:`InputMediaVideo`): + media (List of :obj:`~pyrogram.types.InputMediaPhoto` and :obj:`~pyrogram.types.InputMediaVideo`): A list describing photos and videos to be sent, must include 2–10 items. disable_notification (``bool``, *optional*): @@ -56,7 +57,7 @@ async def send_media_group( If the message is a reply, ID of the original message. Returns: - List of :obj:`Message`: On success, a list of the sent messages is returned. + List of :obj:`~pyrogram.types.Message`: On success, a list of the sent messages is returned. Example: .. code-block:: python @@ -75,19 +76,19 @@ async def send_media_group( multi_media = [] for i in media: - if isinstance(i, pyrogram.InputMediaPhoto): + if isinstance(i, types.InputMediaPhoto): if os.path.isfile(i.media): media = await self.send( - functions.messages.UploadMedia( + raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), - media=types.InputMediaUploadedPhoto( + media=raw.types.InputMediaUploadedPhoto( file=await self.save_file(i.media) ) ) ) - media = types.InputMediaPhoto( - id=types.InputPhoto( + media = raw.types.InputMediaPhoto( + id=raw.types.InputPhoto( id=media.photo.id, access_hash=media.photo.access_hash, file_reference=media.photo.file_reference @@ -95,16 +96,16 @@ async def send_media_group( ) elif re.match("^https?://", i.media): media = await self.send( - functions.messages.UploadMedia( + raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), - media=types.InputMediaPhotoExternal( + media=raw.types.InputMediaPhotoExternal( url=i.media ) ) ) - media = types.InputMediaPhoto( - id=types.InputPhoto( + media = raw.types.InputMediaPhoto( + id=raw.types.InputPhoto( id=media.photo.id, access_hash=media.photo.access_hash, file_reference=media.photo.file_reference @@ -112,30 +113,30 @@ async def send_media_group( ) else: media = utils.get_input_media_from_file_id(i.media, i.file_ref, 2) - elif isinstance(i, pyrogram.InputMediaVideo): + elif isinstance(i, types.InputMediaVideo): if os.path.isfile(i.media): media = await self.send( - functions.messages.UploadMedia( + raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), - media=types.InputMediaUploadedDocument( + media=raw.types.InputMediaUploadedDocument( file=await self.save_file(i.media), - thumb=self.save_file(i.thumb), + thumb=await self.save_file(i.thumb), mime_type=self.guess_mime_type(i.media) or "video/mp4", attributes=[ - types.DocumentAttributeVideo( + raw.types.DocumentAttributeVideo( supports_streaming=i.supports_streaming or None, duration=i.duration, w=i.width, h=i.height ), - types.DocumentAttributeFilename(file_name=os.path.basename(i.media)) + raw.types.DocumentAttributeFilename(file_name=os.path.basename(i.media)) ] ) ) ) - media = types.InputMediaDocument( - id=types.InputDocument( + media = raw.types.InputMediaDocument( + id=raw.types.InputDocument( id=media.document.id, access_hash=media.document.access_hash, file_reference=media.document.file_reference @@ -143,16 +144,16 @@ async def send_media_group( ) elif re.match("^https?://", i.media): media = await self.send( - functions.messages.UploadMedia( + raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), - media=types.InputMediaDocumentExternal( + media=raw.types.InputMediaDocumentExternal( url=i.media ) ) ) - media = types.InputMediaDocument( - id=types.InputDocument( + media = raw.types.InputMediaDocument( + id=raw.types.InputDocument( id=media.document.id, access_hash=media.document.access_hash, file_reference=media.document.file_reference @@ -162,7 +163,7 @@ async def send_media_group( media = utils.get_input_media_from_file_id(i.media, i.file_ref, 4) multi_media.append( - types.InputSingleMedia( + raw.types.InputSingleMedia( media=media, random_id=self.rnd_id(), **await self.parser.parse(i.caption, i.parse_mode) @@ -170,7 +171,7 @@ async def send_media_group( ) r = await self.send( - functions.messages.SendMultiMedia( + raw.functions.messages.SendMultiMedia( peer=await self.resolve_peer(chat_id), multi_media=multi_media, silent=disable_notification or None, @@ -180,9 +181,9 @@ async def send_media_group( return await utils.parse_messages( self, - types.messages.Messages( + raw.types.messages.Messages( messages=[m.message for m in filter( - lambda u: isinstance(u, (types.UpdateNewMessage, types.UpdateNewChannelMessage)), + lambda u: isinstance(u, (raw.types.UpdateNewMessage, raw.types.UpdateNewChannelMessage)), r.updates )], users=r.users, diff --git a/pyrogram/client/methods/messages/send_message.py b/pyrogram/methods/messages/send_message.py similarity index 82% rename from pyrogram/client/methods/messages/send_message.py rename to pyrogram/methods/messages/send_message.py index 58385bcf7e..e2ccc853cf 100644 --- a/pyrogram/client/methods/messages/send_message.py +++ b/pyrogram/methods/messages/send_message.py @@ -18,12 +18,12 @@ from typing import Union -import pyrogram -from pyrogram.api import functions, types -from ...ext import BaseClient +from pyrogram import raw +from pyrogram import types +from pyrogram.scaffold import Scaffold -class SendMessage(BaseClient): +class SendMessage(Scaffold): async def send_message( self, chat_id: Union[int, str], @@ -34,12 +34,12 @@ async def send_message( reply_to_message_id: int = None, schedule_date: int = None, reply_markup: Union[ - "pyrogram.InlineKeyboardMarkup", - "pyrogram.ReplyKeyboardMarkup", - "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply" + "types.InlineKeyboardMarkup", + "types.ReplyKeyboardMarkup", + "types.ReplyKeyboardRemove", + "types.ForceReply" ] = None - ) -> "pyrogram.Message": + ) -> "types.Message": """Send text messages. Parameters: @@ -71,12 +71,12 @@ async def send_message( schedule_date (``int``, *optional*): Date when the message will be automatically sent. Unix time. - reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. Returns: - :obj:`Message`: On success, the sent text message is returned. + :obj:`~pyrogram.types.Message`: On success, the sent text message is returned. Example: .. code-block:: python @@ -119,7 +119,7 @@ async def send_message( message, entities = (await self.parser.parse(text, parse_mode)).values() r = await self.send( - functions.messages.SendMessage( + raw.functions.messages.SendMessage( peer=await self.resolve_peer(chat_id), no_webpage=disable_web_page_preview or None, silent=disable_notification or None, @@ -132,18 +132,18 @@ async def send_message( ) ) - if isinstance(r, types.UpdateShortSentMessage): + if isinstance(r, raw.types.UpdateShortSentMessage): peer = await self.resolve_peer(chat_id) peer_id = ( peer.user_id - if isinstance(peer, types.InputPeerUser) + if isinstance(peer, raw.types.InputPeerUser) else -peer.chat_id ) - return pyrogram.Message( + return types.Message( message_id=r.id, - chat=pyrogram.Chat( + chat=types.Chat( id=peer_id, type="private", client=self @@ -152,17 +152,19 @@ async def send_message( date=r.date, outgoing=r.out, entities=[ - pyrogram.MessageEntity._parse(None, entity, {}) + types.MessageEntity._parse(None, entity, {}) for entity in entities ], client=self ) for i in r.updates: - if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage, types.UpdateNewScheduledMessage)): - return await pyrogram.Message._parse( + if isinstance(i, (raw.types.UpdateNewMessage, + raw.types.UpdateNewChannelMessage, + raw.types.UpdateNewScheduledMessage)): + return await types.Message._parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats}, - is_scheduled=isinstance(i, types.UpdateNewScheduledMessage) + is_scheduled=isinstance(i, raw.types.UpdateNewScheduledMessage) ) diff --git a/pyrogram/client/methods/messages/send_photo.py b/pyrogram/methods/messages/send_photo.py similarity index 83% rename from pyrogram/client/methods/messages/send_photo.py rename to pyrogram/methods/messages/send_photo.py index 7c2c194c36..06a6688be1 100644 --- a/pyrogram/client/methods/messages/send_photo.py +++ b/pyrogram/methods/messages/send_photo.py @@ -21,12 +21,14 @@ from typing import Union, BinaryIO import pyrogram -from pyrogram.api import functions, types -from pyrogram.client.ext import BaseClient, utils +from pyrogram import raw +from pyrogram import types +from pyrogram import utils from pyrogram.errors import FilePartMissing +from pyrogram.scaffold import Scaffold -class SendPhoto(BaseClient): +class SendPhoto(Scaffold): async def send_photo( self, chat_id: Union[int, str], @@ -39,14 +41,14 @@ async def send_photo( reply_to_message_id: int = None, schedule_date: int = None, reply_markup: Union[ - "pyrogram.InlineKeyboardMarkup", - "pyrogram.ReplyKeyboardMarkup", - "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply" + "types.InlineKeyboardMarkup", + "types.ReplyKeyboardMarkup", + "types.ReplyKeyboardRemove", + "types.ForceReply" ] = None, progress: callable = None, progress_args: tuple = () - ) -> Union["pyrogram.Message", None]: + ) -> Union["types.Message", None]: """Send photos. Parameters: @@ -91,7 +93,7 @@ async def send_photo( schedule_date (``int``, *optional*): Date when the message will be automatically sent. Unix time. - reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. @@ -118,8 +120,8 @@ async def send_photo( You can either keep *\*args* or add every single extra argument in your function signature. Returns: - :obj:`Message` | ``None``: On success, the sent photo message is returned, otherwise, in case the upload - is deliberately stopped with :meth:`~Client.stop_transmission`, None is returned. + :obj:`~pyrogram.types.Message` | ``None``: On success, the sent photo message is returned, otherwise, in + case the upload is deliberately stopped with :meth:`~pyrogram.Client.stop_transmission`, None is returned. Example: .. code-block:: python @@ -142,12 +144,12 @@ async def send_photo( if isinstance(photo, str): if os.path.isfile(photo): file = await self.save_file(photo, progress=progress, progress_args=progress_args) - media = types.InputMediaUploadedPhoto( + media = raw.types.InputMediaUploadedPhoto( file=file, ttl_seconds=ttl_seconds ) elif re.match("^https?://", photo): - media = types.InputMediaPhotoExternal( + media = raw.types.InputMediaPhotoExternal( url=photo, ttl_seconds=ttl_seconds ) @@ -155,7 +157,7 @@ async def send_photo( media = utils.get_input_media_from_file_id(photo, file_ref, 2) else: file = await self.save_file(photo, progress=progress, progress_args=progress_args) - media = types.InputMediaUploadedPhoto( + media = raw.types.InputMediaUploadedPhoto( file=file, ttl_seconds=ttl_seconds ) @@ -163,7 +165,7 @@ async def send_photo( while True: try: r = await self.send( - functions.messages.SendMedia( + raw.functions.messages.SendMedia( peer=await self.resolve_peer(chat_id), media=media, silent=disable_notification or None, @@ -178,15 +180,14 @@ async def send_photo( 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, types.UpdateNewScheduledMessage) - ): - return await pyrogram.Message._parse( + if isinstance(i, (raw.types.UpdateNewMessage, + raw.types.UpdateNewChannelMessage, + raw.types.UpdateNewScheduledMessage)): + return await types.Message._parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats}, - is_scheduled=isinstance(i, types.UpdateNewScheduledMessage) + is_scheduled=isinstance(i, raw.types.UpdateNewScheduledMessage) ) - except BaseClient.StopTransmission: + except pyrogram.StopTransmission: return None diff --git a/pyrogram/client/methods/messages/send_poll.py b/pyrogram/methods/messages/send_poll.py similarity index 79% rename from pyrogram/client/methods/messages/send_poll.py rename to pyrogram/methods/messages/send_poll.py index 7607a54609..4758e83ed8 100644 --- a/pyrogram/client/methods/messages/send_poll.py +++ b/pyrogram/methods/messages/send_poll.py @@ -18,12 +18,12 @@ from typing import Union, List -import pyrogram -from pyrogram.api import functions, types -from pyrogram.client.ext import BaseClient +from pyrogram import raw +from pyrogram import types +from pyrogram.scaffold import Scaffold -class SendPoll(BaseClient): +class SendPoll(Scaffold): async def send_poll( self, chat_id: Union[int, str], @@ -37,12 +37,12 @@ async def send_poll( reply_to_message_id: int = None, schedule_date: int = None, reply_markup: Union[ - "pyrogram.InlineKeyboardMarkup", - "pyrogram.ReplyKeyboardMarkup", - "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply" + "types.InlineKeyboardMarkup", + "types.ReplyKeyboardMarkup", + "types.ReplyKeyboardRemove", + "types.ForceReply" ] = None - ) -> "pyrogram.Message": + ) -> "types.Message": """Send a new poll. Parameters: @@ -83,12 +83,12 @@ async def send_poll( schedule_date (``int``, *optional*): Date when the message will be automatically sent. Unix time. - reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. Returns: - :obj:`Message`: On success, the sent poll message is returned. + :obj:`~pyrogram.types.Message`: On success, the sent poll message is returned. Example: .. code-block:: python @@ -96,14 +96,14 @@ async def send_poll( app.send_poll(chat_id, "Is this a poll question?", ["Yes", "No", "Maybe"]) """ r = await self.send( - functions.messages.SendMedia( + raw.functions.messages.SendMedia( peer=await self.resolve_peer(chat_id), - media=types.InputMediaPoll( - poll=types.Poll( + media=raw.types.InputMediaPoll( + poll=raw.types.Poll( id=0, question=question, answers=[ - types.PollAnswer(text=o, option=bytes([i])) + raw.types.PollAnswer(text=o, option=bytes([i])) for i, o in enumerate(options) ], multiple_choice=allows_multiple_answers or None, @@ -122,10 +122,12 @@ async def send_poll( ) for i in r.updates: - if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage, types.UpdateNewScheduledMessage)): - return await pyrogram.Message._parse( + if isinstance(i, (raw.types.UpdateNewMessage, + raw.types.UpdateNewChannelMessage, + raw.types.UpdateNewScheduledMessage)): + return await types.Message._parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats}, - is_scheduled=isinstance(i, types.UpdateNewScheduledMessage) + is_scheduled=isinstance(i, raw.types.UpdateNewScheduledMessage) ) diff --git a/pyrogram/client/methods/messages/send_sticker.py b/pyrogram/methods/messages/send_sticker.py similarity index 79% rename from pyrogram/client/methods/messages/send_sticker.py rename to pyrogram/methods/messages/send_sticker.py index 8025a0dd73..6ab211f894 100644 --- a/pyrogram/client/methods/messages/send_sticker.py +++ b/pyrogram/methods/messages/send_sticker.py @@ -20,13 +20,15 @@ import re from typing import Union, BinaryIO -import pyrogram -from pyrogram.api import functions, types -from pyrogram.client.ext import BaseClient, utils +from pyrogram import StopTransmission +from pyrogram import raw +from pyrogram import types +from pyrogram import utils from pyrogram.errors import FilePartMissing +from pyrogram.scaffold import Scaffold -class SendSticker(BaseClient): +class SendSticker(Scaffold): async def send_sticker( self, chat_id: Union[int, str], @@ -36,14 +38,14 @@ async def send_sticker( reply_to_message_id: int = None, schedule_date: int = None, reply_markup: Union[ - "pyrogram.InlineKeyboardMarkup", - "pyrogram.ReplyKeyboardMarkup", - "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply" + "types.InlineKeyboardMarkup", + "types.ReplyKeyboardMarkup", + "types.ReplyKeyboardRemove", + "types.ForceReply" ] = None, progress: callable = None, progress_args: tuple = () - ) -> Union["pyrogram.Message", None]: + ) -> Union["types.Message", None]: """Send static .webp or animated .tgs stickers. Parameters: @@ -73,7 +75,7 @@ async def send_sticker( schedule_date (``int``, *optional*): Date when the message will be automatically sent. Unix time. - reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. @@ -100,8 +102,9 @@ async def send_sticker( You can either keep *\*args* or add every single extra argument in your function signature. Returns: - :obj:`Message` | ``None``: On success, the sent sticker message is returned, otherwise, in case the upload - is deliberately stopped with :meth:`~Client.stop_transmission`, None is returned. + :obj:`~pyrogram.types.Message` | ``None``: On success, the sent sticker message is returned, otherwise, + in case the upload is deliberately stopped with :meth:`~pyrogram.Client.stop_transmission`, None is + returned. Example: .. code-block:: python @@ -118,33 +121,33 @@ async def send_sticker( if isinstance(sticker, str): if os.path.isfile(sticker): file = await self.save_file(sticker, progress=progress, progress_args=progress_args) - media = types.InputMediaUploadedDocument( + media = raw.types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(sticker) or "image/webp", file=file, attributes=[ - types.DocumentAttributeFilename(file_name=os.path.basename(sticker)) + raw.types.DocumentAttributeFilename(file_name=os.path.basename(sticker)) ] ) elif re.match("^https?://", sticker): - media = types.InputMediaDocumentExternal( + media = raw.types.InputMediaDocumentExternal( url=sticker ) else: media = utils.get_input_media_from_file_id(sticker, file_ref, 8) else: file = await self.save_file(sticker, progress=progress, progress_args=progress_args) - media = types.InputMediaUploadedDocument( + media = raw.types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(sticker.name) or "image/webp", file=file, attributes=[ - types.DocumentAttributeFilename(file_name=sticker.name) + raw.types.DocumentAttributeFilename(file_name=sticker.name) ] ) while True: try: r = await self.send( - functions.messages.SendMedia( + raw.functions.messages.SendMedia( peer=await self.resolve_peer(chat_id), media=media, silent=disable_notification or None, @@ -159,15 +162,14 @@ async def send_sticker( 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, types.UpdateNewScheduledMessage) - ): - return await pyrogram.Message._parse( + if isinstance(i, (raw.types.UpdateNewMessage, + raw.types.UpdateNewChannelMessage, + raw.types.UpdateNewScheduledMessage)): + return await types.Message._parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats}, - is_scheduled=isinstance(i, types.UpdateNewScheduledMessage) + is_scheduled=isinstance(i, raw.types.UpdateNewScheduledMessage) ) - except BaseClient.StopTransmission: + except StopTransmission: return None diff --git a/pyrogram/client/methods/messages/send_venue.py b/pyrogram/methods/messages/send_venue.py similarity index 79% rename from pyrogram/client/methods/messages/send_venue.py rename to pyrogram/methods/messages/send_venue.py index f6f09d5eca..e46119109d 100644 --- a/pyrogram/client/methods/messages/send_venue.py +++ b/pyrogram/methods/messages/send_venue.py @@ -18,12 +18,12 @@ from typing import Union -import pyrogram -from pyrogram.api import functions, types -from pyrogram.client.ext import BaseClient +from pyrogram import raw +from pyrogram import types +from pyrogram.scaffold import Scaffold -class SendVenue(BaseClient): +class SendVenue(Scaffold): async def send_venue( self, chat_id: Union[int, str], @@ -37,12 +37,12 @@ async def send_venue( reply_to_message_id: int = None, schedule_date: int = None, reply_markup: Union[ - "pyrogram.InlineKeyboardMarkup", - "pyrogram.ReplyKeyboardMarkup", - "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply" + "types.InlineKeyboardMarkup", + "types.ReplyKeyboardMarkup", + "types.ReplyKeyboardRemove", + "types.ForceReply" ] = None - ) -> "pyrogram.Message": + ) -> "types.Message": """Send information about a venue. Parameters: @@ -80,12 +80,12 @@ async def send_venue( schedule_date (``int``, *optional*): Date when the message will be automatically sent. Unix time. - reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. Returns: - :obj:`Message`: On success, the sent venue message is returned. + :obj:`~pyrogram.types.Message`: On success, the sent venue message is returned. Example: .. code-block:: python @@ -95,10 +95,10 @@ async def send_venue( "Elizabeth Tower", "Westminster, London SW1A 0AA, UK") """ r = await self.send( - functions.messages.SendMedia( + raw.functions.messages.SendMedia( peer=await self.resolve_peer(chat_id), - media=types.InputMediaVenue( - geo_point=types.InputGeoPoint( + media=raw.types.InputMediaVenue( + geo_point=raw.types.InputGeoPoint( lat=latitude, long=longitude ), @@ -118,10 +118,12 @@ async def send_venue( ) for i in r.updates: - if isinstance(i, (types.UpdateNewMessage, types.UpdateNewChannelMessage, types.UpdateNewScheduledMessage)): - return await pyrogram.Message._parse( + if isinstance(i, (raw.types.UpdateNewMessage, + raw.types.UpdateNewChannelMessage, + raw.types.UpdateNewScheduledMessage)): + return await types.Message._parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats}, - is_scheduled=isinstance(i, types.UpdateNewScheduledMessage) + is_scheduled=isinstance(i, raw.types.UpdateNewScheduledMessage) ) diff --git a/pyrogram/client/methods/messages/send_video.py b/pyrogram/methods/messages/send_video.py similarity index 82% rename from pyrogram/client/methods/messages/send_video.py rename to pyrogram/methods/messages/send_video.py index 87cecf5a1f..3b2bc03eb2 100644 --- a/pyrogram/client/methods/messages/send_video.py +++ b/pyrogram/methods/messages/send_video.py @@ -20,13 +20,15 @@ import re from typing import Union, BinaryIO -import pyrogram -from pyrogram.api import functions, types -from pyrogram.client.ext import BaseClient, utils +from pyrogram import StopTransmission +from pyrogram import raw +from pyrogram import types +from pyrogram import utils from pyrogram.errors import FilePartMissing +from pyrogram.scaffold import Scaffold -class SendVideo(BaseClient): +class SendVideo(Scaffold): async def send_video( self, chat_id: Union[int, str], @@ -44,14 +46,14 @@ async def send_video( reply_to_message_id: int = None, schedule_date: int = None, reply_markup: Union[ - "pyrogram.InlineKeyboardMarkup", - "pyrogram.ReplyKeyboardMarkup", - "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply" + "types.InlineKeyboardMarkup", + "types.ReplyKeyboardMarkup", + "types.ReplyKeyboardRemove", + "types.ForceReply" ] = None, progress: callable = None, progress_args: tuple = () - ) -> Union["pyrogram.Message", None]: + ) -> Union["types.Message", None]: """Send video files. Parameters: @@ -114,7 +116,7 @@ async def send_video( schedule_date (``int``, *optional*): Date when the message will be automatically sent. Unix time. - reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. @@ -141,8 +143,8 @@ async def send_video( You can either keep *\*args* or add every single extra argument in your function signature. Returns: - :obj:`Message` | ``None``: On success, the sent video message is returned, otherwise, in case the upload - is deliberately stopped with :meth:`~Client.stop_transmission`, None is returned. + :obj:`~pyrogram.types.Message` | ``None``: On success, the sent video message is returned, otherwise, in + case the upload is deliberately stopped with :meth:`~pyrogram.Client.stop_transmission`, None is returned. Example: .. code-block:: python @@ -155,7 +157,7 @@ async def send_video( # Keep track of the progress while uploading def progress(current, total): - print("{:.1f}%".format(current * 100 / total)) + print(f"{current * 100 / total:.1f}%") app.send_video("me", "video.mp4", progress=progress) """ @@ -166,22 +168,22 @@ def progress(current, total): if os.path.isfile(video): thumb = await self.save_file(thumb) file = await self.save_file(video, progress=progress, progress_args=progress_args) - media = types.InputMediaUploadedDocument( + media = raw.types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(video) or "video/mp4", file=file, thumb=thumb, attributes=[ - types.DocumentAttributeVideo( + raw.types.DocumentAttributeVideo( supports_streaming=supports_streaming or None, duration=duration, w=width, h=height ), - types.DocumentAttributeFilename(file_name=file_name or os.path.basename(video)) + raw.types.DocumentAttributeFilename(file_name=file_name or os.path.basename(video)) ] ) elif re.match("^https?://", video): - media = types.InputMediaDocumentExternal( + media = raw.types.InputMediaDocumentExternal( url=video ) else: @@ -189,25 +191,25 @@ def progress(current, total): else: thumb = await self.save_file(thumb) file = await self.save_file(video, progress=progress, progress_args=progress_args) - media = types.InputMediaUploadedDocument( + media = raw.types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(video.name) or "video/mp4", file=file, thumb=thumb, attributes=[ - types.DocumentAttributeVideo( + raw.types.DocumentAttributeVideo( supports_streaming=supports_streaming or None, duration=duration, w=width, h=height ), - types.DocumentAttributeFilename(file_name=video.name) + raw.types.DocumentAttributeFilename(file_name=video.name) ] ) while True: try: r = await self.send( - functions.messages.SendMedia( + raw.functions.messages.SendMedia( peer=await self.resolve_peer(chat_id), media=media, silent=disable_notification or None, @@ -222,15 +224,14 @@ def progress(current, total): 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, types.UpdateNewScheduledMessage) - ): - return await pyrogram.Message._parse( + if isinstance(i, (raw.types.UpdateNewMessage, + raw.types.UpdateNewChannelMessage, + raw.types.UpdateNewScheduledMessage)): + return await types.Message._parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats}, - is_scheduled=isinstance(i, types.UpdateNewScheduledMessage) + is_scheduled=isinstance(i, raw.types.UpdateNewScheduledMessage) ) - except BaseClient.StopTransmission: + except StopTransmission: return None diff --git a/pyrogram/client/methods/messages/send_video_note.py b/pyrogram/methods/messages/send_video_note.py similarity index 83% rename from pyrogram/client/methods/messages/send_video_note.py rename to pyrogram/methods/messages/send_video_note.py index c204f6ca15..82a3688bd3 100644 --- a/pyrogram/client/methods/messages/send_video_note.py +++ b/pyrogram/methods/messages/send_video_note.py @@ -19,13 +19,15 @@ import os from typing import Union, BinaryIO -import pyrogram -from pyrogram.api import functions, types -from pyrogram.client.ext import BaseClient, utils +from pyrogram import StopTransmission +from pyrogram import raw +from pyrogram import types +from pyrogram import utils from pyrogram.errors import FilePartMissing +from pyrogram.scaffold import Scaffold -class SendVideoNote(BaseClient): +class SendVideoNote(Scaffold): async def send_video_note( self, chat_id: Union[int, str], @@ -38,14 +40,14 @@ async def send_video_note( reply_to_message_id: int = None, schedule_date: int = None, reply_markup: Union[ - "pyrogram.InlineKeyboardMarkup", - "pyrogram.ReplyKeyboardMarkup", - "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply" + "types.InlineKeyboardMarkup", + "types.ReplyKeyboardMarkup", + "types.ReplyKeyboardRemove", + "types.ForceReply" ] = None, progress: callable = None, progress_args: tuple = () - ) -> Union["pyrogram.Message", None]: + ) -> Union["types.Message", None]: """Send video messages. Parameters: @@ -87,7 +89,7 @@ async def send_video_note( schedule_date (``int``, *optional*): Date when the message will be automatically sent. Unix time. - reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. @@ -114,8 +116,9 @@ async def send_video_note( You can either keep *\*args* or add every single extra argument in your function signature. Returns: - :obj:`Message` | ``None``: On success, the sent video note message is returned, otherwise, in case the - upload is deliberately stopped with :meth:`~Client.stop_transmission`, None is returned. + :obj:`~pyrogram.types.Message` | ``None``: On success, the sent video note message is returned, otherwise, + in case the upload is deliberately stopped with :meth:`~pyrogram.Client.stop_transmission`, None is + returned. Example: .. code-block:: python @@ -133,12 +136,12 @@ async def send_video_note( if os.path.isfile(video_note): thumb = await self.save_file(thumb) file = await self.save_file(video_note, progress=progress, progress_args=progress_args) - media = types.InputMediaUploadedDocument( + media = raw.types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(video_note) or "video/mp4", file=file, thumb=thumb, attributes=[ - types.DocumentAttributeVideo( + raw.types.DocumentAttributeVideo( round_message=True, duration=duration, w=length, @@ -151,12 +154,12 @@ async def send_video_note( else: thumb = await self.save_file(thumb) file = await self.save_file(video_note, progress=progress, progress_args=progress_args) - media = types.InputMediaUploadedDocument( + media = raw.types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(video_note.name) or "video/mp4", file=file, thumb=thumb, attributes=[ - types.DocumentAttributeVideo( + raw.types.DocumentAttributeVideo( round_message=True, duration=duration, w=length, @@ -168,7 +171,7 @@ async def send_video_note( while True: try: r = await self.send( - functions.messages.SendMedia( + raw.functions.messages.SendMedia( peer=await self.resolve_peer(chat_id), media=media, silent=disable_notification or None, @@ -183,15 +186,14 @@ async def send_video_note( 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, types.UpdateNewScheduledMessage) - ): - return await pyrogram.Message._parse( + if isinstance(i, (raw.types.UpdateNewMessage, + raw.types.UpdateNewChannelMessage, + raw.types.UpdateNewScheduledMessage)): + return await types.Message._parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats}, - is_scheduled=isinstance(i, types.UpdateNewScheduledMessage) + is_scheduled=isinstance(i, raw.types.UpdateNewScheduledMessage) ) - except BaseClient.StopTransmission: + except StopTransmission: return None diff --git a/pyrogram/client/methods/messages/send_voice.py b/pyrogram/methods/messages/send_voice.py similarity index 82% rename from pyrogram/client/methods/messages/send_voice.py rename to pyrogram/methods/messages/send_voice.py index 98221e8dbd..58d401d79d 100644 --- a/pyrogram/client/methods/messages/send_voice.py +++ b/pyrogram/methods/messages/send_voice.py @@ -20,13 +20,15 @@ import re from typing import Union, BinaryIO -import pyrogram -from pyrogram.api import functions, types -from pyrogram.client.ext import BaseClient, utils +from pyrogram import StopTransmission +from pyrogram import raw +from pyrogram import types +from pyrogram import utils from pyrogram.errors import FilePartMissing +from pyrogram.scaffold import Scaffold -class SendVoice(BaseClient): +class SendVoice(Scaffold): async def send_voice( self, chat_id: Union[int, str], @@ -39,14 +41,14 @@ async def send_voice( reply_to_message_id: int = None, schedule_date: int = None, reply_markup: Union[ - "pyrogram.InlineKeyboardMarkup", - "pyrogram.ReplyKeyboardMarkup", - "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply" + "types.InlineKeyboardMarkup", + "types.ReplyKeyboardMarkup", + "types.ReplyKeyboardRemove", + "types.ForceReply" ] = None, progress: callable = None, progress_args: tuple = () - ) -> Union["pyrogram.Message", None]: + ) -> Union["types.Message", None]: """Send audio files. Parameters: @@ -89,7 +91,7 @@ async def send_voice( schedule_date (``int``, *optional*): Date when the message will be automatically sent. Unix time. - reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. @@ -116,8 +118,8 @@ async def send_voice( You can either keep *\*args* or add every single extra argument in your function signature. Returns: - :obj:`Message` | ``None``: On success, the sent voice message is returned, otherwise, in case the upload - is deliberately stopped with :meth:`~Client.stop_transmission`, None is returned. + :obj:`~pyrogram.types.Message` | ``None``: On success, the sent voice message is returned, otherwise, in + case the upload is deliberately stopped with :meth:`~pyrogram.Client.stop_transmission`, None is returned. Example: .. code-block:: python @@ -137,29 +139,29 @@ async def send_voice( if isinstance(voice, str): if os.path.isfile(voice): file = await self.save_file(voice, progress=progress, progress_args=progress_args) - media = types.InputMediaUploadedDocument( + media = raw.types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(voice) or "audio/mpeg", file=file, attributes=[ - types.DocumentAttributeAudio( + raw.types.DocumentAttributeAudio( voice=True, duration=duration ) ] ) elif re.match("^https?://", voice): - media = types.InputMediaDocumentExternal( + media = raw.types.InputMediaDocumentExternal( url=voice ) else: media = utils.get_input_media_from_file_id(voice, file_ref, 3) else: file = await self.save_file(voice, progress=progress, progress_args=progress_args) - media = types.InputMediaUploadedDocument( + media = raw.types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(voice.name) or "audio/mpeg", file=file, attributes=[ - types.DocumentAttributeAudio( + raw.types.DocumentAttributeAudio( voice=True, duration=duration ) @@ -169,7 +171,7 @@ async def send_voice( while True: try: r = await self.send( - functions.messages.SendMedia( + raw.functions.messages.SendMedia( peer=await self.resolve_peer(chat_id), media=media, silent=disable_notification or None, @@ -184,15 +186,14 @@ async def send_voice( 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, types.UpdateNewScheduledMessage) - ): - return await pyrogram.Message._parse( + if isinstance(i, (raw.types.UpdateNewMessage, + raw.types.UpdateNewChannelMessage, + raw.types.UpdateNewScheduledMessage)): + return await types.Message._parse( self, i.message, {i.id: i for i in r.users}, {i.id: i for i in r.chats}, - is_scheduled=isinstance(i, types.UpdateNewScheduledMessage) + is_scheduled=isinstance(i, raw.types.UpdateNewScheduledMessage) ) - except BaseClient.StopTransmission: + except StopTransmission: return None diff --git a/pyrogram/client/methods/messages/stop_poll.py b/pyrogram/methods/messages/stop_poll.py similarity index 79% rename from pyrogram/client/methods/messages/stop_poll.py rename to pyrogram/methods/messages/stop_poll.py index 79498e45ed..c0b0754244 100644 --- a/pyrogram/client/methods/messages/stop_poll.py +++ b/pyrogram/methods/messages/stop_poll.py @@ -18,18 +18,18 @@ from typing import Union -import pyrogram -from pyrogram.api import functions, types -from pyrogram.client.ext import BaseClient +from pyrogram import raw +from pyrogram import types +from pyrogram.scaffold import Scaffold -class StopPoll(BaseClient): +class StopPoll(Scaffold): async def stop_poll( self, chat_id: Union[int, str], message_id: int, - reply_markup: "pyrogram.InlineKeyboardMarkup" = None - ) -> "pyrogram.Poll": + reply_markup: "types.InlineKeyboardMarkup" = None + ) -> "types.Poll": """Stop a poll which was sent by you. Stopped polls can't be reopened and nobody will be able to vote in it anymore. @@ -43,11 +43,11 @@ async def stop_poll( message_id (``int``): Identifier of the original message with the poll. - reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*): An InlineKeyboardMarkup object. Returns: - :obj:`Poll`: On success, the stopped poll with the final results is returned. + :obj:`~pyrogram.types.Poll`: On success, the stopped poll with the final results is returned. Example: .. code-block:: python @@ -57,11 +57,11 @@ async def stop_poll( poll = (await self.get_messages(chat_id, message_id)).poll r = await self.send( - functions.messages.EditMessage( + raw.functions.messages.EditMessage( peer=await self.resolve_peer(chat_id), id=message_id, - media=types.InputMediaPoll( - poll=types.Poll( + media=raw.types.InputMediaPoll( + poll=raw.types.Poll( id=int(poll.id), closed=True, question="", @@ -72,4 +72,4 @@ async def stop_poll( ) ) - return pyrogram.Poll._parse(self, r.updates[0]) + return types.Poll._parse(self, r.updates[0]) diff --git a/pyrogram/client/methods/messages/vote_poll.py b/pyrogram/methods/messages/vote_poll.py similarity index 86% rename from pyrogram/client/methods/messages/vote_poll.py rename to pyrogram/methods/messages/vote_poll.py index 667df0fb16..e764e0bbba 100644 --- a/pyrogram/client/methods/messages/vote_poll.py +++ b/pyrogram/methods/messages/vote_poll.py @@ -18,18 +18,18 @@ from typing import Union, List -import pyrogram -from pyrogram.api import functions -from pyrogram.client.ext import BaseClient +from pyrogram import raw +from pyrogram import types +from pyrogram.scaffold import Scaffold -class VotePoll(BaseClient): +class VotePoll(Scaffold): async def vote_poll( self, chat_id: Union[int, str], message_id: id, options: Union[int, List[int]] - ) -> "pyrogram.Poll": + ) -> "types.Poll": """Vote a poll. Parameters: @@ -45,7 +45,7 @@ async def vote_poll( Index or list of indexes (for multiple answers) of the poll option(s) you want to vote for (0 to 9). Returns: - :obj:`Poll` - On success, the poll with the chosen option is returned. + :obj:`~pyrogram.types.Poll` - On success, the poll with the chosen option is returned. Example: .. code-block:: python @@ -57,11 +57,11 @@ async def vote_poll( options = [options] if not isinstance(options, list) else options r = await self.send( - functions.messages.SendVote( + raw.functions.messages.SendVote( peer=await self.resolve_peer(chat_id), msg_id=message_id, options=[poll.options[option].data for option in options] ) ) - return pyrogram.Poll._parse(self, r.updates[0]) + return types.Poll._parse(self, r.updates[0]) diff --git a/pyrogram/client/methods/password/__init__.py b/pyrogram/methods/password/__init__.py similarity index 100% rename from pyrogram/client/methods/password/__init__.py rename to pyrogram/methods/password/__init__.py diff --git a/pyrogram/client/methods/password/change_cloud_password.py b/pyrogram/methods/password/change_cloud_password.py similarity index 80% rename from pyrogram/client/methods/password/change_cloud_password.py rename to pyrogram/methods/password/change_cloud_password.py index 54ec289193..4002ef1e36 100644 --- a/pyrogram/client/methods/password/change_cloud_password.py +++ b/pyrogram/methods/password/change_cloud_password.py @@ -18,12 +18,12 @@ import os -from pyrogram.api import functions, types -from .utils import compute_hash, compute_check, btoi, itob -from ...ext import BaseClient +from pyrogram import raw +from pyrogram.scaffold import Scaffold +from pyrogram.utils import compute_password_hash, compute_password_check, btoi, itob -class ChangeCloudPassword(BaseClient): +class ChangeCloudPassword(Scaffold): async def change_cloud_password( self, current_password: str, @@ -57,19 +57,19 @@ async def change_cloud_password( # Change password and hint app.change_cloud_password("current_password", "new_password", new_hint="hint") """ - r = await self.send(functions.account.GetPassword()) + r = await self.send(raw.functions.account.GetPassword()) if not r.has_password: raise ValueError("There is no cloud password to change") r.new_algo.salt1 += os.urandom(32) - new_hash = btoi(compute_hash(r.new_algo, new_password)) + new_hash = btoi(compute_password_hash(r.new_algo, new_password)) new_hash = itob(pow(r.new_algo.g, new_hash, btoi(r.new_algo.p))) await self.send( - functions.account.UpdatePasswordSettings( - password=compute_check(r, current_password), - new_settings=types.account.PasswordInputSettings( + raw.functions.account.UpdatePasswordSettings( + password=compute_password_check(r, current_password), + new_settings=raw.types.account.PasswordInputSettings( new_algo=r.new_algo, new_password_hash=new_hash, hint=new_hint diff --git a/pyrogram/client/methods/password/enable_cloud_password.py b/pyrogram/methods/password/enable_cloud_password.py similarity index 83% rename from pyrogram/client/methods/password/enable_cloud_password.py rename to pyrogram/methods/password/enable_cloud_password.py index b629194345..4fedca154b 100644 --- a/pyrogram/client/methods/password/enable_cloud_password.py +++ b/pyrogram/methods/password/enable_cloud_password.py @@ -18,12 +18,12 @@ import os -from pyrogram.api import functions, types -from .utils import compute_hash, btoi, itob -from ...ext import BaseClient +from pyrogram import raw +from pyrogram.scaffold import Scaffold +from pyrogram.utils import compute_password_hash, btoi, itob -class EnableCloudPassword(BaseClient): +class EnableCloudPassword(Scaffold): async def enable_cloud_password( self, password: str, @@ -62,19 +62,19 @@ async def enable_cloud_password( # Enable password with hint and email app.enable_cloud_password("password", hint="hint", email="user@email.com") """ - r = await self.send(functions.account.GetPassword()) + r = await self.send(raw.functions.account.GetPassword()) if r.has_password: raise ValueError("There is already a cloud password enabled") r.new_algo.salt1 += os.urandom(32) - new_hash = btoi(compute_hash(r.new_algo, password)) + new_hash = btoi(compute_password_hash(r.new_algo, password)) new_hash = itob(pow(r.new_algo.g, new_hash, btoi(r.new_algo.p))) await self.send( - functions.account.UpdatePasswordSettings( - password=types.InputCheckPasswordEmpty(), - new_settings=types.account.PasswordInputSettings( + raw.functions.account.UpdatePasswordSettings( + password=raw.types.InputCheckPasswordEmpty(), + new_settings=raw.types.account.PasswordInputSettings( new_algo=r.new_algo, new_password_hash=new_hash, hint=hint, diff --git a/pyrogram/client/methods/password/remove_cloud_password.py b/pyrogram/methods/password/remove_cloud_password.py similarity index 77% rename from pyrogram/client/methods/password/remove_cloud_password.py rename to pyrogram/methods/password/remove_cloud_password.py index 9d41a9db8b..ecebe226b5 100644 --- a/pyrogram/client/methods/password/remove_cloud_password.py +++ b/pyrogram/methods/password/remove_cloud_password.py @@ -16,12 +16,12 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from pyrogram.api import functions, types -from .utils import compute_check -from ...ext import BaseClient +from pyrogram import raw +from pyrogram.scaffold import Scaffold +from pyrogram.utils import compute_password_check -class RemoveCloudPassword(BaseClient): +class RemoveCloudPassword(Scaffold): async def remove_cloud_password( self, password: str @@ -43,16 +43,16 @@ async def remove_cloud_password( app.remove_cloud_password("password") """ - r = await self.send(functions.account.GetPassword()) + r = await self.send(raw.functions.account.GetPassword()) if not r.has_password: raise ValueError("There is no cloud password to remove") await self.send( - functions.account.UpdatePasswordSettings( - password=compute_check(r, password), - new_settings=types.account.PasswordInputSettings( - new_algo=types.PasswordKdfAlgoUnknown(), + raw.functions.account.UpdatePasswordSettings( + password=compute_password_check(r, password), + new_settings=raw.types.account.PasswordInputSettings( + new_algo=raw.types.PasswordKdfAlgoUnknown(), new_password_hash=b"", hint="" ) diff --git a/pyrogram/client/methods/users/__init__.py b/pyrogram/methods/users/__init__.py similarity index 100% rename from pyrogram/client/methods/users/__init__.py rename to pyrogram/methods/users/__init__.py diff --git a/pyrogram/client/methods/users/block_user.py b/pyrogram/methods/users/block_user.py similarity index 92% rename from pyrogram/client/methods/users/block_user.py rename to pyrogram/methods/users/block_user.py index 8dd6e09dff..6464788901 100644 --- a/pyrogram/client/methods/users/block_user.py +++ b/pyrogram/methods/users/block_user.py @@ -18,11 +18,11 @@ from typing import Union -from pyrogram.api import functions -from ...ext import BaseClient +from pyrogram import raw +from pyrogram.scaffold import Scaffold -class BlockUser(BaseClient): +class BlockUser(Scaffold): async def block_user( self, user_id: Union[int, str] @@ -45,7 +45,7 @@ async def block_user( """ return bool( await self.send( - functions.contacts.Block( + raw.functions.contacts.Block( id=await self.resolve_peer(user_id) ) ) diff --git a/pyrogram/client/methods/users/delete_profile_photos.py b/pyrogram/methods/users/delete_profile_photos.py similarity index 86% rename from pyrogram/client/methods/users/delete_profile_photos.py rename to pyrogram/methods/users/delete_profile_photos.py index ac184da5d9..c56621267b 100644 --- a/pyrogram/client/methods/users/delete_profile_photos.py +++ b/pyrogram/methods/users/delete_profile_photos.py @@ -18,12 +18,12 @@ from typing import List, Union -from pyrogram.api import functions -from pyrogram.client.ext import utils -from ...ext import BaseClient +from pyrogram import raw +from pyrogram import utils +from pyrogram.scaffold import Scaffold -class DeleteProfilePhotos(BaseClient): +class DeleteProfilePhotos(Scaffold): async def delete_profile_photos( self, photo_ids: Union[str, List[str]] @@ -32,7 +32,7 @@ async def delete_profile_photos( Parameters: photo_ids (``str`` | List of ``str``): - A single :obj:`Photo` id as string or multiple ids as list of strings for deleting + A single :obj:`~pyrogram.types.Photo` id as string or multiple ids as list of strings for deleting more than one photos at once. Returns: @@ -54,7 +54,7 @@ async def delete_profile_photos( input_photos = [utils.get_input_media_from_file_id(i).id for i in photo_ids] return bool(await self.send( - functions.photos.DeletePhotos( + raw.functions.photos.DeletePhotos( id=input_photos ) )) diff --git a/pyrogram/client/methods/users/get_common_chats.py b/pyrogram/methods/users/get_common_chats.py similarity index 78% rename from pyrogram/client/methods/users/get_common_chats.py rename to pyrogram/methods/users/get_common_chats.py index fab202fd36..5b788877bc 100644 --- a/pyrogram/client/methods/users/get_common_chats.py +++ b/pyrogram/methods/users/get_common_chats.py @@ -18,12 +18,12 @@ from typing import Union -import pyrogram -from pyrogram.api import functions, types -from ...ext import BaseClient +from pyrogram import raw +from pyrogram import types +from pyrogram.scaffold import Scaffold -class GetCommonChats(BaseClient): +class GetCommonChats(Scaffold): async def get_common_chats(self, user_id: Union[int, str]) -> list: """Get the common chats you have with a user. @@ -34,7 +34,7 @@ async def get_common_chats(self, user_id: Union[int, str]) -> list: For a contact that exists in your Telegram address book you can use his phone number (str). Returns: - List of :obj:`Chat`: On success, a list of the common chats is returned. + List of :obj:`~pyrogram.types.Chat`: On success, a list of the common chats is returned. Raises: ValueError: If the user_id doesn't belong to a user. @@ -48,15 +48,15 @@ async def get_common_chats(self, user_id: Union[int, str]) -> list: peer = await self.resolve_peer(user_id) - if isinstance(peer, types.InputPeerUser): + if isinstance(peer, raw.types.InputPeerUser): r = await self.send( - functions.messages.GetCommonChats( + raw.functions.messages.GetCommonChats( user_id=peer, max_id=0, limit=100, ) ) - return pyrogram.List([pyrogram.Chat._parse_chat(self, x) for x in r.chats]) + return types.List([types.Chat._parse_chat(self, x) for x in r.chats]) - raise ValueError('The user_id "{}" doesn\'t belong to a user'.format(user_id)) + raise ValueError(f'The user_id "{user_id}" doesn\'t belong to a user') diff --git a/pyrogram/client/methods/users/get_me.py b/pyrogram/methods/users/get_me.py similarity index 74% rename from pyrogram/client/methods/users/get_me.py rename to pyrogram/methods/users/get_me.py index 0efbddb2e5..385296717d 100644 --- a/pyrogram/client/methods/users/get_me.py +++ b/pyrogram/methods/users/get_me.py @@ -16,17 +16,17 @@ # 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 +from pyrogram import raw +from pyrogram import types +from pyrogram.scaffold import Scaffold -class GetMe(BaseClient): - async def get_me(self) -> "pyrogram.User": +class GetMe(Scaffold): + async def get_me(self) -> "types.User": """Get your own user identity. Returns: - :obj:`User`: Information about the own logged in user/bot. + :obj:`~pyrogram.types.User`: Information about the own logged in user/bot. Example: .. code-block:: python @@ -34,11 +34,11 @@ async def get_me(self) -> "pyrogram.User": me = app.get_me() print(me) """ - return pyrogram.User._parse( + return types.User._parse( self, (await self.send( - functions.users.GetFullUser( - id=types.InputPeerSelf() + raw.functions.users.GetFullUser( + id=raw.types.InputUserSelf() ) )).user ) diff --git a/pyrogram/client/methods/users/get_profile_photos.py b/pyrogram/methods/users/get_profile_photos.py similarity index 81% rename from pyrogram/client/methods/users/get_profile_photos.py rename to pyrogram/methods/users/get_profile_photos.py index fded8dcb98..b27687a14f 100644 --- a/pyrogram/client/methods/users/get_profile_photos.py +++ b/pyrogram/methods/users/get_profile_photos.py @@ -18,19 +18,19 @@ from typing import Union, List -import pyrogram -from pyrogram.api import functions, types -from pyrogram.client.ext import utils -from ...ext import BaseClient +from pyrogram import raw +from pyrogram import types +from pyrogram import utils +from pyrogram.scaffold import Scaffold -class GetProfilePhotos(BaseClient): +class GetProfilePhotos(Scaffold): async def get_profile_photos( self, chat_id: Union[int, str], offset: int = 0, limit: int = 100 - ) -> List["pyrogram.Photo"]: + ) -> List["types.Photo"]: """Get a list of profile pictures for a user or a chat. Parameters: @@ -48,7 +48,7 @@ async def get_profile_photos( Values between 1—100 are accepted. Defaults to 100. Returns: - List of :obj:`Photo`: On success, a list of profile photos is returned. + List of :obj:`~pyrogram.types.Photo`: On success, a list of profile photos is returned. Example: .. code-block:: python @@ -64,14 +64,14 @@ async def get_profile_photos( """ peer_id = await self.resolve_peer(chat_id) - if isinstance(peer_id, types.InputPeerChannel): + if isinstance(peer_id, raw.types.InputPeerChannel): r = await utils.parse_messages( self, await self.send( - functions.messages.Search( + raw.functions.messages.Search( peer=peer_id, q="", - filter=types.InputMessagesFilterChatPhotos(), + filter=raw.types.InputMessagesFilterChatPhotos(), min_date=0, max_date=0, offset_id=0, @@ -84,10 +84,10 @@ async def get_profile_photos( ) ) - return pyrogram.List([message.new_chat_photo for message in r][:limit]) + return types.List([message.new_chat_photo for message in r][:limit]) else: r = await self.send( - functions.photos.GetUserPhotos( + raw.functions.photos.GetUserPhotos( user_id=peer_id, offset=offset, max_id=0, @@ -95,4 +95,4 @@ async def get_profile_photos( ) ) - return pyrogram.List(pyrogram.Photo._parse(self, photo) for photo in r.photos) + return types.List(types.Photo._parse(self, photo) for photo in r.photos) diff --git a/pyrogram/client/methods/users/get_profile_photos_count.py b/pyrogram/methods/users/get_profile_photos_count.py similarity index 83% rename from pyrogram/client/methods/users/get_profile_photos_count.py rename to pyrogram/methods/users/get_profile_photos_count.py index affc00e110..65b4e16849 100644 --- a/pyrogram/client/methods/users/get_profile_photos_count.py +++ b/pyrogram/methods/users/get_profile_photos_count.py @@ -18,11 +18,11 @@ from typing import Union -from pyrogram.api import functions, types -from ...ext import BaseClient +from pyrogram import raw +from pyrogram.scaffold import Scaffold -class GetProfilePhotosCount(BaseClient): +class GetProfilePhotosCount(Scaffold): async def get_profile_photos_count(self, chat_id: Union[int, str]) -> int: """Get the total count of profile pictures for a user. @@ -44,18 +44,18 @@ async def get_profile_photos_count(self, chat_id: Union[int, str]) -> int: peer_id = await self.resolve_peer(chat_id) - if isinstance(peer_id, types.InputPeerChannel): + if isinstance(peer_id, raw.types.InputPeerChannel): r = await self.send( - functions.messages.GetSearchCounters( + raw.functions.messages.GetSearchCounters( peer=peer_id, - filters=[types.InputMessagesFilterChatPhotos()], + filters=[raw.types.InputMessagesFilterChatPhotos()], ) ) return r[0].count else: r = await self.send( - functions.photos.GetUserPhotos( + raw.functions.photos.GetUserPhotos( user_id=peer_id, offset=0, max_id=0, @@ -63,7 +63,7 @@ async def get_profile_photos_count(self, chat_id: Union[int, str]) -> int: ) ) - if isinstance(r, types.photos.Photos): + if isinstance(r, raw.types.photos.Photos): return len(r.photos) else: return r.count diff --git a/pyrogram/client/methods/users/get_users.py b/pyrogram/methods/users/get_users.py similarity index 77% rename from pyrogram/client/methods/users/get_users.py rename to pyrogram/methods/users/get_users.py index 05476bc453..cf591d2bb8 100644 --- a/pyrogram/client/methods/users/get_users.py +++ b/pyrogram/methods/users/get_users.py @@ -19,16 +19,16 @@ import asyncio from typing import Iterable, Union, List -import pyrogram -from pyrogram.api import functions -from ...ext import BaseClient +from pyrogram import raw +from pyrogram import types +from pyrogram.scaffold import Scaffold -class GetUsers(BaseClient): +class GetUsers(Scaffold): async def get_users( self, user_ids: Union[Iterable[Union[int, str]], int, str] - ) -> Union["pyrogram.User", List["pyrogram.User"]]: + ) -> Union["types.User", List["types.User"]]: """Get information about a user. You can retrieve up to 200 users at once. @@ -39,9 +39,9 @@ async def get_users( Iterators and Generators are also accepted. Returns: - :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. + :obj:`~pyrogram.types.User` | List of :obj:`~pyrogram.types.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. Example: .. code-block:: python @@ -57,14 +57,14 @@ async def get_users( user_ids = await asyncio.gather(*[self.resolve_peer(i) for i in user_ids]) r = await self.send( - functions.users.GetUsers( + raw.functions.users.GetUsers( id=user_ids ) ) - users = pyrogram.List() + users = types.List() for i in r: - users.append(pyrogram.User._parse(self, i)) + users.append(types.User._parse(self, i)) return users if is_iterable else users[0] diff --git a/pyrogram/client/methods/users/iter_profile_photos.py b/pyrogram/methods/users/iter_profile_photos.py similarity index 80% rename from pyrogram/client/methods/users/iter_profile_photos.py rename to pyrogram/methods/users/iter_profile_photos.py index bdd3d8b79e..ee196f6553 100644 --- a/pyrogram/client/methods/users/iter_profile_photos.py +++ b/pyrogram/methods/users/iter_profile_photos.py @@ -16,27 +16,24 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Union, Generator, Optional +from typing import Union, AsyncGenerator, Optional -import pyrogram -from async_generator import async_generator, yield_ +from pyrogram import types +from pyrogram.scaffold import Scaffold -from ...ext import BaseClient - -class IterProfilePhotos(BaseClient): - @async_generator +class IterProfilePhotos(Scaffold): async def iter_profile_photos( self, chat_id: Union[int, str], offset: int = 0, limit: int = 0, - ) -> Optional[Generator["pyrogram.Message", None, None]]: + ) -> Optional[AsyncGenerator["types.Message", None]]: """Iterate through a chat or a user profile photos sequentially. - This convenience method does the same as repeatedly calling :meth:`~Client.get_profile_photos` in a loop, thus - saving you from the hassle of setting up boilerplate code. It is useful for getting all the profile photos with - a single call. + This convenience method does the same as repeatedly calling :meth:`~pyrogram.Client.get_profile_photos` in a + loop, thus saving you from the hassle of setting up boilerplate code. It is useful for getting all the profile + photos with a single call. Parameters: chat_id (``int`` | ``str``): @@ -52,7 +49,7 @@ async def iter_profile_photos( Sequential number of the first profile photo to be returned. Returns: - ``Generator``: A generator yielding :obj:`Photo` objects. + ``Generator``: A generator yielding :obj:`~pyrogram.types.Photo` objects. Example: .. code-block:: python @@ -77,7 +74,7 @@ async def iter_profile_photos( offset += len(photos) for photo in photos: - await yield_(photo) + yield photo current += 1 diff --git a/pyrogram/client/methods/users/set_profile_photo.py b/pyrogram/methods/users/set_profile_photo.py similarity index 94% rename from pyrogram/client/methods/users/set_profile_photo.py rename to pyrogram/methods/users/set_profile_photo.py index b9dbbf1048..9d72c033b1 100644 --- a/pyrogram/client/methods/users/set_profile_photo.py +++ b/pyrogram/methods/users/set_profile_photo.py @@ -18,11 +18,11 @@ from typing import Union, BinaryIO -from pyrogram.api import functions -from ...ext import BaseClient +from pyrogram import raw +from pyrogram.scaffold import Scaffold -class SetProfilePhoto(BaseClient): +class SetProfilePhoto(Scaffold): async def set_profile_photo( self, *, @@ -65,7 +65,7 @@ async def set_profile_photo( return bool( await self.send( - functions.photos.UploadProfilePhoto( + raw.functions.photos.UploadProfilePhoto( file=await self.save_file(photo), video=await self.save_file(video) ) diff --git a/pyrogram/client/methods/users/unblock_user.py b/pyrogram/methods/users/unblock_user.py similarity index 91% rename from pyrogram/client/methods/users/unblock_user.py rename to pyrogram/methods/users/unblock_user.py index fddf9ff655..9f9e76bbc3 100644 --- a/pyrogram/client/methods/users/unblock_user.py +++ b/pyrogram/methods/users/unblock_user.py @@ -18,11 +18,11 @@ from typing import Union -from pyrogram.api import functions -from ...ext import BaseClient +from pyrogram import raw +from pyrogram.scaffold import Scaffold -class UnblockUser(BaseClient): +class UnblockUser(Scaffold): async def unblock_user( self, user_id: Union[int, str] @@ -45,7 +45,7 @@ async def unblock_user( """ return bool( await self.send( - functions.contacts.Unblock( + raw.functions.contacts.Unblock( id=await self.resolve_peer(user_id) ) ) diff --git a/pyrogram/client/methods/users/update_profile.py b/pyrogram/methods/users/update_profile.py similarity index 93% rename from pyrogram/client/methods/users/update_profile.py rename to pyrogram/methods/users/update_profile.py index 145d503509..15c7079343 100644 --- a/pyrogram/client/methods/users/update_profile.py +++ b/pyrogram/methods/users/update_profile.py @@ -16,11 +16,11 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from pyrogram.api import functions -from ...ext import BaseClient +from pyrogram import raw +from pyrogram.scaffold import Scaffold -class UpdateProfile(BaseClient): +class UpdateProfile(Scaffold): async def update_profile( self, first_name: str = None, @@ -61,7 +61,7 @@ async def update_profile( return bool( await self.send( - functions.account.UpdateProfile( + raw.functions.account.UpdateProfile( first_name=first_name, last_name=last_name, about=bio diff --git a/pyrogram/client/methods/users/update_username.py b/pyrogram/methods/users/update_username.py similarity index 88% rename from pyrogram/client/methods/users/update_username.py rename to pyrogram/methods/users/update_username.py index 50e2388c7e..2689c59db6 100644 --- a/pyrogram/client/methods/users/update_username.py +++ b/pyrogram/methods/users/update_username.py @@ -18,11 +18,11 @@ from typing import Union -from pyrogram.api import functions -from ...ext import BaseClient +from pyrogram import raw +from pyrogram.scaffold import Scaffold -class UpdateUsername(BaseClient): +class UpdateUsername(Scaffold): async def update_username( self, username: Union[str, None] @@ -31,7 +31,7 @@ async def update_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 - :meth:`~Client.update_chat_username`. + :meth:`~pyrogram.Client.update_chat_username`. Parameters: username (``str`` | ``None``): @@ -48,7 +48,7 @@ async def update_username( return bool( await self.send( - functions.account.UpdateUsername( + raw.functions.account.UpdateUsername( username=username or "" ) ) diff --git a/pyrogram/methods/utilities/__init__.py b/pyrogram/methods/utilities/__init__.py new file mode 100644 index 0000000000..d6ddcd3c1b --- /dev/null +++ b/pyrogram/methods/utilities/__init__.py @@ -0,0 +1,39 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan +# +# 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 .add_handler import AddHandler +from .export_session_string import ExportSessionString +from .remove_handler import RemoveHandler +from .restart import Restart +from .run import Run +from .start import Start +from .stop import Stop +from .stop_transmission import StopTransmission + + +class Utilities( + AddHandler, + ExportSessionString, + RemoveHandler, + Restart, + Run, + Start, + Stop, + StopTransmission +): + pass diff --git a/pyrogram/methods/utilities/add_handler.py b/pyrogram/methods/utilities/add_handler.py new file mode 100644 index 0000000000..f8d993cddb --- /dev/null +++ b/pyrogram/methods/utilities/add_handler.py @@ -0,0 +1,63 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan +# +# 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 pyrogram.handlers import DisconnectHandler +from pyrogram.handlers.handler import Handler +from pyrogram.scaffold import Scaffold + + +class AddHandler(Scaffold): + def add_handler(self, handler: "Handler", group: int = 0): + """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 your handler using a different group id (lower group id + == higher priority). This mechanism is explained in greater details at + :doc:`More on Updates <../../topics/more-on-updates>`. + + Parameters: + handler (``Handler``): + The handler to be registered. + + group (``int``, *optional*): + The group identifier, defaults to 0. + + Returns: + ``tuple``: A tuple consisting of *(handler, group)*. + + Example: + .. code-block:: python + :emphasize-lines: 8 + + from pyrogram import Client, MessageHandler + + def dump(client, message): + print(message) + + app = Client("my_account") + + app.add_handler(MessageHandler(dump)) + + app.run() + """ + if isinstance(handler, DisconnectHandler): + self.disconnect_handler = handler.callback + else: + self.dispatcher.add_handler(handler, group) + + return handler, group diff --git a/pyrogram/methods/utilities/export_session_string.py b/pyrogram/methods/utilities/export_session_string.py new file mode 100644 index 0000000000..4b9a4fce9b --- /dev/null +++ b/pyrogram/methods/utilities/export_session_string.py @@ -0,0 +1,44 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan +# +# 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 pyrogram.scaffold import Scaffold + + +class ExportSessionString(Scaffold): + async def export_session_string(self): + """Export the current authorized session as a serialized string. + + Session strings are useful for storing in-memory authorized sessions in a portable, serialized string. + More detailed information about session strings can be found at the dedicated page of + :doc:`Storage Engines <../../topics/storage-engines>`. + + Returns: + ``str``: The session serialized into a printable, url-safe string. + + Example: + .. code-block:: python + :emphasize-lines: 6 + + from pyrogram import Client + + app = Client("my_account") + + with app: + print(app.export_session_string()) + """ + return await self.storage.export_session_string() diff --git a/pyrogram/methods/utilities/idle.py b/pyrogram/methods/utilities/idle.py new file mode 100644 index 0000000000..86131ea218 --- /dev/null +++ b/pyrogram/methods/utilities/idle.py @@ -0,0 +1,78 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan +# +# 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 asyncio +import logging +import signal + +log = logging.getLogger(__name__) + +loop = asyncio.get_event_loop() +event = asyncio.Event() + + +async def idle(): + """Block the main script execution until a signal is received. + + This function will run indefinitely in order to block the main script execution and prevent it from + exiting while having client(s) that are still running in the background. + + It is useful for event-driven application only, that are, applications which react upon incoming Telegram + updates through handlers, rather than executing a set of methods sequentially. + + The way Pyrogram works, it will keep your handlers in a pool of worker threads, which are executed concurrently + outside the main thread; calling idle() will ensure the client(s) will be kept alive by not letting the main + script to end, until you decide to quit. + + Once a signal is received (e.g.: from CTRL+C) the function will terminate and your main script will continue. + Don't forget to call :meth:`~pyrogram.Client.stop` for each running client before the script ends. + + Example: + .. code-block:: python + :emphasize-lines: 13 + + from pyrogram import Client, idle + + app1 = Client("account1") + app2 = Client("account2") + app3 = Client("account3") + + ... # Set handlers up + + app1.start() + app2.start() + app3.start() + + idle() + + app1.stop() + app2.stop() + app3.stop() + """ + + def handler(): + log.info("Stop signal received") + event.set() + + asyncio.get_event_loop().add_signal_handler(signal.SIGINT, handler) + + log.info("Idle started") + await event.wait() + + log.info("Idle stopped") + event.clear() diff --git a/pyrogram/methods/utilities/remove_handler.py b/pyrogram/methods/utilities/remove_handler.py new file mode 100644 index 0000000000..5a4de8c14f --- /dev/null +++ b/pyrogram/methods/utilities/remove_handler.py @@ -0,0 +1,59 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan +# +# 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 pyrogram.handlers import DisconnectHandler +from pyrogram.handlers.handler import Handler +from pyrogram.scaffold import Scaffold + + +class RemoveHandler(Scaffold): + def remove_handler(self, handler: "Handler", group: int = 0): + """Remove a previously-registered update handler. + + Make sure to provide the right group where the handler was added in. You can use the return value of the + :meth:`~pyrogram.Client.add_handler` method, a tuple of *(handler, group)*, and pass it directly. + + Parameters: + handler (``Handler``): + The handler to be removed. + + group (``int``, *optional*): + The group identifier, defaults to 0. + + Example: + .. code-block:: python + :emphasize-lines: 11 + + from pyrogram import Client, MessageHandler + + def dump(client, message): + print(message) + + app = Client("my_account") + + handler = app.add_handler(MessageHandler(dump)) + + # Starred expression to unpack (handler, group) + app.remove_handler(*handler) + + app.run() + """ + if isinstance(handler, DisconnectHandler): + self.disconnect_handler = None + else: + self.dispatcher.remove_handler(handler, group) diff --git a/pyrogram/methods/utilities/restart.py b/pyrogram/methods/utilities/restart.py new file mode 100644 index 0000000000..ad842bc4b3 --- /dev/null +++ b/pyrogram/methods/utilities/restart.py @@ -0,0 +1,70 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan +# +# 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 asyncio + +from pyrogram.scaffold import Scaffold + + +class Restart(Scaffold): + async def restart(self, block: bool = True): + """Restart the Client. + + This method will first call :meth:`~pyrogram.Client.stop` and then :meth:`~pyrogram.Client.start` in a row in + order to restart a client using a single method. + + Parameters: + block (``bool``, *optional*): + Blocks the code execution until the client has been restarted. It is useful with ``block=False`` in case + you want to restart the own client *within* an handler in order not to cause a deadlock. + Defaults to True. + + Returns: + :obj:`~pyrogram.Client`: The restarted client itself. + + Raises: + ConnectionError: In case you try to restart a stopped Client. + + Example: + .. code-block:: python + :emphasize-lines: 8 + + from pyrogram import Client + + app = Client("my_account") + app.start() + + ... # Call API methods + + app.restart() + + ... # Call other API methods + + app.stop() + """ + + async def do_it(): + await self.stop() + await self.start() + + if block: + await do_it() + else: + asyncio.ensure_future(do_it()) + + return self diff --git a/pyrogram/methods/utilities/run.py b/pyrogram/methods/utilities/run.py new file mode 100644 index 0000000000..dc49a524a4 --- /dev/null +++ b/pyrogram/methods/utilities/run.py @@ -0,0 +1,57 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan +# +# 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 asyncio + +from pyrogram.methods.utilities.idle import idle +from pyrogram.scaffold import Scaffold + + +class Run(Scaffold): + def run(self, coroutine=None): + """Start the client, idle the main script and finally stop the client. + + This is a convenience method that calls :meth:`~pyrogram.Client.start`, :meth:`~pyrogram.idle` and + :meth:`~pyrogram.Client.stop` in sequence. 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, since :meth:`~pyrogram.idle` will block after + starting the own client. + + Raises: + ConnectionError: In case you try to run an already started client. + + Example: + .. code-block:: python + :emphasize-lines: 7 + + from pyrogram import Client + + app = Client("my_account") + + ... # Set handlers up + + app.run() + """ + loop = asyncio.get_event_loop() + run = loop.run_until_complete + + if coroutine is not None: + run(coroutine) + else: + self.start() + run(idle()) + self.stop() diff --git a/pyrogram/methods/utilities/start.py b/pyrogram/methods/utilities/start.py new file mode 100644 index 0000000000..8a8862575e --- /dev/null +++ b/pyrogram/methods/utilities/start.py @@ -0,0 +1,69 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan +# +# 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 + +from pyrogram import raw +from pyrogram.scaffold import Scaffold + +log = logging.getLogger(__name__) + + +class Start(Scaffold): + async def start(self): + """Start the client. + + This method connects the client to Telegram and, in case of new sessions, automatically manages the full + authorization process using an interactive prompt. + + Returns: + :obj:`~pyrogram.Client`: The started client itself. + + Raises: + ConnectionError: In case you try to start an already started client. + + Example: + .. code-block:: python + :emphasize-lines: 4 + + from pyrogram import Client + + app = Client("my_account") + app.start() + + ... # Call API methods + + app.stop() + """ + is_authorized = await self.connect() + + try: + if not is_authorized: + await self.authorize() + + if not await self.storage.is_bot() and self.takeout: + self.takeout_id = (await self.send(raw.functions.account.InitTakeoutSession())).id + log.warning(f"Takeout session {self.takeout_id} initiated") + + await self.send(raw.functions.updates.GetState()) + except (Exception, KeyboardInterrupt): + await self.disconnect() + raise + else: + await self.initialize() + return self diff --git a/pyrogram/methods/utilities/stop.py b/pyrogram/methods/utilities/stop.py new file mode 100644 index 0000000000..b48a143142 --- /dev/null +++ b/pyrogram/methods/utilities/stop.py @@ -0,0 +1,65 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan +# +# 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 asyncio + +from pyrogram.scaffold import Scaffold + + +class Stop(Scaffold): + async def stop(self, block: bool = True): + """Stop the Client. + + This method disconnects the client from Telegram and stops the underlying tasks. + + Parameters: + block (``bool``, *optional*): + Blocks the code execution until the client has been stopped. It is useful with ``block=False`` in case + you want to stop the own client *within* a handler in order not to cause a deadlock. + Defaults to True. + + Returns: + :obj:`~pyrogram.Client`: The stopped client itself. + + Raises: + ConnectionError: In case you try to stop an already stopped client. + + Example: + .. code-block:: python + :emphasize-lines: 8 + + from pyrogram import Client + + app = Client("my_account") + app.start() + + ... # Call API methods + + app.stop() + """ + + async def do_it(): + await self.terminate() + await self.disconnect() + + if block: + await do_it() + else: + asyncio.ensure_future(do_it()) + + return self diff --git a/pyrogram/methods/utilities/stop_transmission.py b/pyrogram/methods/utilities/stop_transmission.py new file mode 100644 index 0000000000..6eaedf4d56 --- /dev/null +++ b/pyrogram/methods/utilities/stop_transmission.py @@ -0,0 +1,47 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan +# +# 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.scaffold import Scaffold + + +class StopTransmission(Scaffold): + def stop_transmission(self): + """Stop downloading or uploading a file. + + This method must be called inside a progress callback function in order to stop the transmission at the + desired time. The progress callback is called every time a file chunk is uploaded/downloaded. + + Example: + .. code-block:: python + :emphasize-lines: 9 + + from pyrogram import Client + + app = Client("my_account") + + # Example to stop transmission once the upload progress reaches 50% + # Useless in practice, but shows how to stop on command + def progress(current, total, client): + if (current * 100 / total) > 50: + client.stop_transmission() + + with app: + app.send_document("me", "files.zip", progress=progress, progress_args=(app,)) + """ + raise pyrogram.StopTransmission diff --git a/pyrogram/client/ext/mime.types b/pyrogram/mime.types similarity index 100% rename from pyrogram/client/ext/mime.types rename to pyrogram/mime.types diff --git a/pyrogram/client/parser/__init__.py b/pyrogram/parser/__init__.py similarity index 100% rename from pyrogram/client/parser/__init__.py rename to pyrogram/parser/__init__.py diff --git a/pyrogram/client/parser/html.py b/pyrogram/parser/html.py similarity index 74% rename from pyrogram/client/parser/html.py rename to pyrogram/parser/html.py index 3ce70d5101..7fa1d94d2e 100644 --- a/pyrogram/client/parser/html.py +++ b/pyrogram/parser/html.py @@ -19,12 +19,11 @@ import html import logging import re -from collections import OrderedDict from html.parser import HTMLParser from typing import Union import pyrogram -from pyrogram.api import types +from pyrogram import raw from pyrogram.errors import PeerIdInvalid from . import utils @@ -34,7 +33,7 @@ class Parser(HTMLParser): MENTION_RE = re.compile(r"tg://user\?id=(\d+)") - def __init__(self, client: "pyrogram.BaseClient"): + def __init__(self, client: "pyrogram.Client"): super().__init__() self.client = client @@ -48,19 +47,19 @@ def handle_starttag(self, tag, attrs): extra = {} if tag in ["b", "strong"]: - entity = types.MessageEntityBold + entity = raw.types.MessageEntityBold elif tag in ["i", "em"]: - entity = types.MessageEntityItalic + entity = raw.types.MessageEntityItalic elif tag == "u": - entity = types.MessageEntityUnderline + entity = raw.types.MessageEntityUnderline elif tag in ["s", "del", "strike"]: - entity = types.MessageEntityStrike + entity = raw.types.MessageEntityStrike elif tag == "blockquote": - entity = types.MessageEntityBlockquote + entity = raw.types.MessageEntityBlockquote elif tag == "code": - entity = types.MessageEntityCode + entity = raw.types.MessageEntityCode elif tag == "pre": - entity = types.MessageEntityPre + entity = raw.types.MessageEntityPre extra["language"] = "" elif tag == "a": url = attrs.get("href", "") @@ -68,10 +67,10 @@ def handle_starttag(self, tag, attrs): mention = Parser.MENTION_RE.match(url) if mention: - entity = types.InputMessageEntityMentionName + entity = raw.types.InputMessageEntityMentionName extra["user_id"] = int(mention.group(1)) else: - entity = types.MessageEntityTextUrl + entity = raw.types.MessageEntityTextUrl extra["url"] = url else: return @@ -97,7 +96,7 @@ def handle_endtag(self, tag): line, offset = self.getpos() offset += 1 - log.warning("Unmatched closing tag at line {}:{}".format(tag, line, offset)) + log.warning(f"Unmatched closing tag at line {line}:{offset}") else: if not self.tag_entities[tag]: self.tag_entities.pop(tag) @@ -107,7 +106,7 @@ def error(self, message): class HTML: - def __init__(self, client: Union["pyrogram.BaseClient", None]): + def __init__(self, client: Union["pyrogram.Client", None]): self.client = client async def parse(self, text: str): @@ -122,14 +121,14 @@ async def parse(self, text: str): unclosed_tags = [] for tag, entities in parser.tag_entities.items(): - unclosed_tags.append("<{}> (x{})".format(tag, len(entities))) + unclosed_tags.append(f"<{tag}> (x{len(entities)})") - log.warning("Unclosed tags: {}".format(", ".join(unclosed_tags))) + log.warning(f"Unclosed tags: {', '.join(unclosed_tags)}") entities = [] for entity in parser.entities: - if isinstance(entity, types.InputMessageEntityMentionName): + if isinstance(entity, raw.types.InputMessageEntityMentionName): try: if self.client is not None: entity.user_id = await self.client.resolve_peer(entity.user_id) @@ -138,11 +137,10 @@ async def parse(self, text: str): entities.append(entity) - # TODO: OrderedDict to be removed in Python 3.6 - return OrderedDict([ - ("message", utils.remove_surrogates(parser.text)), - ("entities", sorted(entities, key=lambda e: e.offset)) - ]) + return { + "message": utils.remove_surrogates(parser.text), + "entities": sorted(entities, key=lambda e: e.offset) + } @staticmethod def unparse(text: str, entities: list): @@ -156,18 +154,18 @@ def unparse(text: str, entities: list): end = start + entity.length if entity_type in ("bold", "italic", "underline", "strike"): - start_tag = "<{}>".format(entity_type[0]) - end_tag = "".format(entity_type[0]) + start_tag = f"<{entity_type[0]}>" + end_tag = "f" elif entity_type in ("code", "pre", "blockquote"): - start_tag = "<{}>".format(entity_type) - end_tag = "".format(entity_type) + start_tag = f"<{entity_type}>" + end_tag = f"" elif entity_type == "text_link": url = entity.url - start_tag = ''.format(url) + start_tag = f'' end_tag = "" elif entity_type == "text_mention": user = entity.user - start_tag = ''.format(user.id) + start_tag = f'' end_tag = "" else: continue diff --git a/pyrogram/client/parser/markdown.py b/pyrogram/parser/markdown.py similarity index 95% rename from pyrogram/client/parser/markdown.py rename to pyrogram/parser/markdown.py index 4c954efd63..e821dbd378 100644 --- a/pyrogram/client/parser/markdown.py +++ b/pyrogram/parser/markdown.py @@ -34,7 +34,7 @@ MARKDOWN_RE = re.compile(r"({d})|\[(.+?)\]\((.+?)\)".format( d="|".join( ["".join(i) for i in [ - [r"\{}".format(j) for j in i] + [rf"\{j}" for j in i] for i in [ PRE_DELIM, CODE_DELIM, @@ -53,7 +53,7 @@ class Markdown: - def __init__(self, client: Union["pyrogram.BaseClient", None]): + def __init__(self, client: Union["pyrogram.Client", None]): self.html = HTML(client) async def parse(self, text: str, strict: bool = False): @@ -130,11 +130,11 @@ def unparse(text: str, entities: list): elif entity_type == "text_link": url = entity.url start_tag = "[" - end_tag = "]({})".format(url) + end_tag = f"]({url})" elif entity_type == "text_mention": user = entity.user start_tag = "[" - end_tag = "](tg://user?id={})".format(user.id) + end_tag = f"](tg://user?id={user.id})" else: continue diff --git a/pyrogram/client/parser/parser.py b/pyrogram/parser/parser.py similarity index 93% rename from pyrogram/client/parser/parser.py rename to pyrogram/parser/parser.py index eb4f2e19b9..64d172417c 100644 --- a/pyrogram/client/parser/parser.py +++ b/pyrogram/parser/parser.py @@ -25,7 +25,7 @@ class Parser: - def __init__(self, client: Union["pyrogram.BaseClient", None]): + def __init__(self, client: Union["pyrogram.Client", None]): self.client = client self.html = HTML(client) self.markdown = Markdown(client) @@ -57,7 +57,7 @@ async def parse(self, text: str, mode: Union[str, None] = object): return await self.html.parse(text) raise ValueError('parse_mode must be one of {} or None. Not "{}"'.format( - ", ".join('"{}"'.format(m) for m in pyrogram.Client.PARSE_MODES[:-1]), + ", ".join(f'"{m}"' for m in pyrogram.Client.PARSE_MODES[:-1]), mode )) diff --git a/pyrogram/client/parser/utils.py b/pyrogram/parser/utils.py similarity index 100% rename from pyrogram/client/parser/utils.py rename to pyrogram/parser/utils.py diff --git a/pyrogram/api/__init__.py b/pyrogram/raw/__init__.py similarity index 95% rename from pyrogram/api/__init__.py rename to pyrogram/raw/__init__.py index da5b075e7b..2bbf90d772 100644 --- a/pyrogram/api/__init__.py +++ b/pyrogram/raw/__init__.py @@ -18,6 +18,7 @@ from importlib import import_module +from . import types, functions, base, core from .all import objects for k, v in objects.items(): diff --git a/pyrogram/api/core/__init__.py b/pyrogram/raw/core/__init__.py similarity index 80% rename from pyrogram/api/core/__init__.py rename to pyrogram/raw/core/__init__.py index 22484f6fb2..0a4b2a8297 100644 --- a/pyrogram/api/core/__init__.py +++ b/pyrogram/raw/core/__init__.py @@ -22,5 +22,10 @@ from .list import List from .message import Message from .msg_container import MsgContainer -from .primitives import * +from .primitives.bool import Bool, BoolFalse, BoolTrue +from .primitives.bytes import Bytes +from .primitives.double import Double +from .primitives.int import Int, Long, Int128, Int256 +from .primitives.string import String +from .primitives.vector import Vector from .tl_object import TLObject diff --git a/pyrogram/api/core/future_salt.py b/pyrogram/raw/core/future_salt.py similarity index 83% rename from pyrogram/api/core/future_salt.py rename to pyrogram/raw/core/future_salt.py index cc9f7bc0fa..7d163e6b4a 100644 --- a/pyrogram/api/core/future_salt.py +++ b/pyrogram/raw/core/future_salt.py @@ -17,13 +17,14 @@ # along with Pyrogram. If not, see . from io import BytesIO +from typing import Any -from .primitives import Int, Long +from .primitives.int import Int, Long from .tl_object import TLObject class FutureSalt(TLObject): - ID = 0x0949d9dc + ID = 0x0949D9DC __slots__ = ["valid_since", "valid_until", "salt"] @@ -35,9 +36,9 @@ def __init__(self, valid_since: int, valid_until: int, salt: int): self.salt = salt @staticmethod - def read(b: BytesIO, *args) -> "FutureSalt": - valid_since = Int.read(b) - valid_until = Int.read(b) - salt = Long.read(b) + def read(data: BytesIO, *args: Any) -> "FutureSalt": + valid_since = Int.read(data) + valid_until = Int.read(data) + salt = Long.read(data) return FutureSalt(valid_since, valid_until, salt) diff --git a/pyrogram/api/core/future_salts.py b/pyrogram/raw/core/future_salts.py similarity index 73% rename from pyrogram/api/core/future_salts.py rename to pyrogram/raw/core/future_salts.py index f22b464301..f8a4835311 100644 --- a/pyrogram/api/core/future_salts.py +++ b/pyrogram/raw/core/future_salts.py @@ -17,30 +17,31 @@ # along with Pyrogram. If not, see . from io import BytesIO +from typing import Any, List -from . import FutureSalt -from .primitives import Int, Long +from .future_salt import FutureSalt +from .primitives.int import Int, Long from .tl_object import TLObject class FutureSalts(TLObject): - ID = 0xae500895 + ID = 0xAE500895 __slots__ = ["req_msg_id", "now", "salts"] QUALNAME = "FutureSalts" - def __init__(self, req_msg_id: int, now: int, salts: list): + def __init__(self, req_msg_id: int, now: int, salts: List[FutureSalt]): self.req_msg_id = req_msg_id self.now = now self.salts = salts @staticmethod - def read(b: BytesIO, *args) -> "FutureSalts": - req_msg_id = Long.read(b) - now = Int.read(b) + def read(data: BytesIO, *args: Any) -> "FutureSalts": + req_msg_id = Long.read(data) + now = Int.read(data) - count = Int.read(b) - salts = [FutureSalt.read(b) for _ in range(count)] + count = Int.read(data) + salts = [FutureSalt.read(data) for _ in range(count)] return FutureSalts(req_msg_id, now, salts) diff --git a/pyrogram/api/core/gzip_packed.py b/pyrogram/raw/core/gzip_packed.py similarity index 82% rename from pyrogram/api/core/gzip_packed.py rename to pyrogram/raw/core/gzip_packed.py index 1920c67de7..094162d59d 100644 --- a/pyrogram/api/core/gzip_packed.py +++ b/pyrogram/raw/core/gzip_packed.py @@ -18,13 +18,15 @@ from gzip import compress, decompress from io import BytesIO +from typing import cast, Any -from .primitives import Int, Bytes +from .primitives.bytes import Bytes +from .primitives.int import Int from .tl_object import TLObject class GzipPacked(TLObject): - ID = 0x3072cfa1 + ID = 0x3072CFA1 __slots__ = ["packed_data"] @@ -34,17 +36,17 @@ def __init__(self, packed_data: TLObject): self.packed_data = packed_data @staticmethod - def read(b: BytesIO, *args) -> "GzipPacked": + def read(data: BytesIO, *args: Any) -> "GzipPacked": # Return the Object itself instead of a GzipPacked wrapping it - return TLObject.read( + return cast(GzipPacked, TLObject.read( BytesIO( decompress( - Bytes.read(b) + Bytes.read(data) ) ) - ) + )) - def write(self) -> bytes: + def write(self, *args: Any) -> bytes: b = BytesIO() b.write(Int(self.ID, False)) diff --git a/pyrogram/api/core/list.py b/pyrogram/raw/core/list.py similarity index 81% rename from pyrogram/api/core/list.py rename to pyrogram/raw/core/list.py index 0e083f17a6..c0014b6841 100644 --- a/pyrogram/api/core/list.py +++ b/pyrogram/raw/core/list.py @@ -16,13 +16,11 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from .tl_object import TLObject +from typing import List as TList, Any +from .tl_object import TLObject -class List(list, TLObject): - __slots__ = [] - def __repr__(self): - return "pyrogram.api.core.List([{}])".format( - ",".join(TLObject.__repr__(i) for i in self) - ) +class List(TList[Any], TLObject): + def __repr__(self) -> str: + return f"pyrogram.api.core.List([{','.join(TLObject.__repr__(i) for i in self)}])" diff --git a/pyrogram/api/core/message.py b/pyrogram/raw/core/message.py similarity index 81% rename from pyrogram/api/core/message.py rename to pyrogram/raw/core/message.py index 787aa3785d..e1e595a042 100644 --- a/pyrogram/api/core/message.py +++ b/pyrogram/raw/core/message.py @@ -17,13 +17,14 @@ # along with Pyrogram. If not, see . from io import BytesIO +from typing import Any -from .primitives import Int, Long +from .primitives.int import Int, Long from .tl_object import TLObject class Message(TLObject): - ID = 0x5bb8e511 # hex(crc32(b"message msg_id:long seqno:int bytes:int body:Object = Message")) + ID = 0x5BB8E511 # hex(crc32(b"message msg_id:long seqno:int bytes:int body:Object = Message")) __slots__ = ["msg_id", "seq_no", "length", "body"] @@ -36,15 +37,15 @@ def __init__(self, body: TLObject, msg_id: int, seq_no: int, length: int): self.body = body @staticmethod - def read(b: BytesIO, *args) -> "Message": - msg_id = Long.read(b) - seq_no = Int.read(b) - length = Int.read(b) - body = b.read(length) + def read(data: BytesIO, *args: Any) -> "Message": + msg_id = Long.read(data) + seq_no = Int.read(data) + length = Int.read(data) + body = data.read(length) return Message(TLObject.read(BytesIO(body)), msg_id, seq_no, length) - def write(self) -> bytes: + def write(self, *args: Any) -> bytes: b = BytesIO() b.write(Long(self.msg_id)) diff --git a/pyrogram/api/core/msg_container.py b/pyrogram/raw/core/msg_container.py similarity index 79% rename from pyrogram/api/core/msg_container.py rename to pyrogram/raw/core/msg_container.py index dc6f755d74..aecf7dfc78 100644 --- a/pyrogram/api/core/msg_container.py +++ b/pyrogram/raw/core/msg_container.py @@ -17,28 +17,29 @@ # along with Pyrogram. If not, see . from io import BytesIO +from typing import List, Any from .message import Message -from .primitives import Int +from .primitives.int import Int from .tl_object import TLObject class MsgContainer(TLObject): - ID = 0x73f1f8dc + ID = 0x73F1F8DC __slots__ = ["messages"] QUALNAME = "MsgContainer" - def __init__(self, messages: list): + def __init__(self, messages: List[Message]): self.messages = messages @staticmethod - def read(b: BytesIO, *args) -> "MsgContainer": - count = Int.read(b) - return MsgContainer([Message.read(b) for _ in range(count)]) + def read(data: BytesIO, *args: Any) -> "MsgContainer": + count = Int.read(data) + return MsgContainer([Message.read(data) for _ in range(count)]) - def write(self) -> bytes: + def write(self, *args: Any) -> bytes: b = BytesIO() b.write(Int(self.ID, False)) diff --git a/pyrogram/api/core/primitives/__init__.py b/pyrogram/raw/core/primitives/__init__.py similarity index 89% rename from pyrogram/api/core/primitives/__init__.py rename to pyrogram/raw/core/primitives/__init__.py index f7b1c89ec8..9f6de65a0d 100644 --- a/pyrogram/api/core/primitives/__init__.py +++ b/pyrogram/raw/core/primitives/__init__.py @@ -22,5 +22,3 @@ from .int import Int, Long, Int128, Int256 from .string import String from .vector import Vector - -__all__ = ["Bool", "BoolFalse", "BoolTrue", "Bytes", "Double", "Int", "Long", "Int128", "Int256", "String", "Vector"] diff --git a/pyrogram/api/core/primitives/bool.py b/pyrogram/raw/core/primitives/bool.py similarity index 73% rename from pyrogram/api/core/primitives/bool.py rename to pyrogram/raw/core/primitives/bool.py index 966372255f..a8fb5b4ec5 100644 --- a/pyrogram/api/core/primitives/bool.py +++ b/pyrogram/raw/core/primitives/bool.py @@ -17,31 +17,32 @@ # along with Pyrogram. If not, see . from io import BytesIO +from typing import Any from ..tl_object import TLObject -class BoolFalse(TLObject): - ID = 0xbc799737 +class BoolFalse(bytes, TLObject): + ID = 0xBC799737 value = False @classmethod - def read(cls, *args) -> bool: + def read(cls, *args: Any) -> bool: return cls.value - def __new__(cls) -> bytes: + def __new__(cls) -> bytes: # type: ignore return cls.ID.to_bytes(4, "little") class BoolTrue(BoolFalse): - ID = 0x997275b5 + ID = 0x997275B5 value = True -class Bool(TLObject): +class Bool(bytes, TLObject): @classmethod - def read(cls, b: BytesIO) -> bool: - return int.from_bytes(b.read(4), "little") == BoolTrue.ID + def read(cls, data: BytesIO, *args: Any) -> bool: + return int.from_bytes(data.read(4), "little") == BoolTrue.ID - def __new__(cls, value: bool) -> BoolTrue or BoolFalse: + def __new__(cls, value: bool) -> bytes: # type: ignore return BoolTrue() if value else BoolFalse() diff --git a/pyrogram/api/core/primitives/bytes.py b/pyrogram/raw/core/primitives/bytes.py similarity index 74% rename from pyrogram/api/core/primitives/bytes.py rename to pyrogram/raw/core/primitives/bytes.py index 298ea5440a..8c95ebdec9 100644 --- a/pyrogram/api/core/primitives/bytes.py +++ b/pyrogram/raw/core/primitives/bytes.py @@ -17,26 +17,27 @@ # along with Pyrogram. If not, see . from io import BytesIO +from typing import Any from ..tl_object import TLObject -class Bytes(TLObject): - @staticmethod - def read(b: BytesIO, *args) -> bytes: - length = int.from_bytes(b.read(1), "little") +class Bytes(bytes, TLObject): + @classmethod + def read(cls, data: BytesIO, *args: Any) -> bytes: + length = int.from_bytes(data.read(1), "little") if length <= 253: - x = b.read(length) - b.read(-(length + 1) % 4) + x = data.read(length) + data.read(-(length + 1) % 4) else: - length = int.from_bytes(b.read(3), "little") - x = b.read(length) - b.read(-length % 4) + length = int.from_bytes(data.read(3), "little") + x = data.read(length) + data.read(-length % 4) return x - def __new__(cls, value: bytes) -> bytes: + def __new__(cls, value: bytes) -> bytes: # type: ignore length = len(value) if length <= 253: diff --git a/pyrogram/api/core/primitives/double.py b/pyrogram/raw/core/primitives/double.py similarity index 78% rename from pyrogram/api/core/primitives/double.py rename to pyrogram/raw/core/primitives/double.py index 42cf0031ee..43dd5d2c76 100644 --- a/pyrogram/api/core/primitives/double.py +++ b/pyrogram/raw/core/primitives/double.py @@ -18,14 +18,15 @@ from io import BytesIO from struct import unpack, pack +from typing import cast, Any from ..tl_object import TLObject -class Double(TLObject): - @staticmethod - def read(b: BytesIO, *args) -> float: - return unpack("d", b.read(8))[0] +class Double(bytes, TLObject): + @classmethod + def read(cls, data: BytesIO, *args: Any) -> float: + return cast(float, unpack("d", data.read(8))[0]) - def __new__(cls, value: float) -> bytes: + def __new__(cls, value: float) -> bytes: # type: ignore return pack("d", value) diff --git a/pyrogram/api/core/primitives/int.py b/pyrogram/raw/core/primitives/int.py similarity index 79% rename from pyrogram/api/core/primitives/int.py rename to pyrogram/raw/core/primitives/int.py index bbaf7f2fcf..7e6135d1db 100644 --- a/pyrogram/api/core/primitives/int.py +++ b/pyrogram/raw/core/primitives/int.py @@ -17,18 +17,19 @@ # along with Pyrogram. If not, see . from io import BytesIO +from typing import Any from ..tl_object import TLObject -class Int(TLObject): +class Int(bytes, TLObject): SIZE = 4 @classmethod - def read(cls, b: BytesIO, signed: bool = True) -> int: - return int.from_bytes(b.read(cls.SIZE), "little", signed=signed) + def read(cls, data: BytesIO, signed: bool = True, *args: Any) -> int: + return int.from_bytes(data.read(cls.SIZE), "little", signed=signed) - def __new__(cls, value: int, signed: bool = True) -> bytes: + def __new__(cls, value: int, signed: bool = True) -> bytes: # type: ignore return value.to_bytes(cls.SIZE, "little", signed=signed) diff --git a/pyrogram/api/core/primitives/string.py b/pyrogram/raw/core/primitives/string.py similarity index 76% rename from pyrogram/api/core/primitives/string.py rename to pyrogram/raw/core/primitives/string.py index a0995c5b9a..dd25b5bfbb 100644 --- a/pyrogram/api/core/primitives/string.py +++ b/pyrogram/raw/core/primitives/string.py @@ -17,14 +17,15 @@ # along with Pyrogram. If not, see . from io import BytesIO +from typing import cast -from . import Bytes +from .bytes import Bytes class String(Bytes): - @staticmethod - def read(b: BytesIO, *args) -> str: - return super(String, String).read(b).decode(errors="replace") + @classmethod + def read(cls, data: BytesIO, *args) -> str: # type: ignore + return cast(bytes, super(String, String).read(data)).decode(errors="replace") - def __new__(cls, value: str) -> bytes: + def __new__(cls, value: str) -> bytes: # type: ignore return super().__new__(cls, value.encode()) diff --git a/pyrogram/api/core/primitives/vector.py b/pyrogram/raw/core/primitives/vector.py similarity index 73% rename from pyrogram/api/core/primitives/vector.py rename to pyrogram/raw/core/primitives/vector.py index 2c60f5766c..afb7d591eb 100644 --- a/pyrogram/api/core/primitives/vector.py +++ b/pyrogram/raw/core/primitives/vector.py @@ -17,39 +17,36 @@ # along with Pyrogram. If not, see . from io import BytesIO +from typing import cast, Union, Any -from . import Int +from .int import Int from ..list import List from ..tl_object import TLObject -class Vector(TLObject): - ID = 0x1cb5c415 +class Vector(bytes, TLObject): + ID = 0x1CB5C415 # Method added to handle the special case when a query returns a bare Vector (of Ints); # i.e., RpcResult body starts with 0x1cb5c415 (Vector Id) - e.g., messages.GetMessagesViews. @staticmethod - def _read(b: BytesIO) -> TLObject or int: + def _read(b: BytesIO) -> Union[int, Any]: try: return TLObject.read(b) except KeyError: b.seek(-4, 1) return Int.read(b) - @staticmethod - def read(b: BytesIO, t: TLObject = None) -> list: + @classmethod + def read(cls, data: BytesIO, t: Any = None, *args: Any) -> List: return List( - t.read(b) if t - else Vector._read(b) - for _ in range(Int.read(b)) + t.read(data) if t + else Vector._read(data) + for _ in range(Int.read(data)) ) - def __new__(cls, value: list, t: TLObject = None) -> bytes: + def __new__(cls, value: list, t: Any = None) -> bytes: # type: ignore return b"".join( [Int(cls.ID, False), Int(len(value))] - + [ - t(i) if t - else i.write() - for i in value - ] + + [cast(bytes, t(i)) if t else i.write() for i in value] ) diff --git a/pyrogram/api/core/tl_object.py b/pyrogram/raw/core/tl_object.py similarity index 71% rename from pyrogram/api/core/tl_object.py rename to pyrogram/raw/core/tl_object.py index d9d5722f1b..0391ab85f6 100644 --- a/pyrogram/api/core/tl_object.py +++ b/pyrogram/raw/core/tl_object.py @@ -16,38 +16,38 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from collections import OrderedDict from io import BytesIO from json import dumps +from typing import cast, List, Any, Union, Dict from ..all import objects class TLObject: - __slots__ = [] + __slots__: List[str] = [] QUALNAME = "Base" - @staticmethod - def read(b: BytesIO, *args): # TODO: Rename b -> data - return objects[int.from_bytes(b.read(4), "little")].read(b, *args) + @classmethod + def read(cls, data: BytesIO, *args: Any) -> Any: + return cast(TLObject, objects[int.from_bytes(data.read(4), "little")]).read(data, *args) - def write(self, *args) -> bytes: + def write(self, *args: Any) -> bytes: pass @staticmethod - def default(obj: "TLObject"): + def default(obj: "TLObject") -> Union[str, Dict[str, str]]: if isinstance(obj, bytes): return repr(obj) - return OrderedDict( - [("_", obj.QUALNAME)] - + [ - (attr, getattr(obj, attr)) + return { + "_": obj.QUALNAME, + **{ + attr: getattr(obj, attr) for attr in obj.__slots__ if getattr(obj, attr) is not None - ] - ) + } + } def __str__(self) -> str: return dumps(self, indent=4, default=TLObject.default, ensure_ascii=False) @@ -56,13 +56,13 @@ def __repr__(self) -> str: return "pyrogram.api.{}({})".format( self.QUALNAME, ", ".join( - "{}={}".format(attr, repr(getattr(self, attr))) + f"{attr}={repr(getattr(self, attr))}" for attr in self.__slots__ if getattr(self, attr) is not None ) ) - def __eq__(self, other: "TLObject") -> bool: + def __eq__(self, other: Any) -> bool: for attr in self.__slots__: try: if getattr(self, attr) != getattr(other, attr): @@ -75,8 +75,11 @@ def __eq__(self, other: "TLObject") -> bool: def __len__(self) -> int: return len(self.write()) - def __getitem__(self, item): + def __getitem__(self, item: Any) -> Any: return getattr(self, item) - def __setitem__(self, key, value): + def __setitem__(self, key: Any, value: Any) -> Any: setattr(self, key, value) + + def __call__(self, *args: Any, **kwargs: Any) -> Any: + pass diff --git a/pyrogram/client/ext/base_client.py b/pyrogram/scaffold.py similarity index 65% rename from pyrogram/client/ext/base_client.py rename to pyrogram/scaffold.py index ca4e8f5ba1..0b3bf02185 100644 --- a/pyrogram/client/ext/base_client.py +++ b/pyrogram/scaffold.py @@ -24,36 +24,21 @@ from pathlib import Path from pyrogram import __version__ -from ..parser import Parser -from ...session.internals import MsgId +from pyrogram.parser import Parser +from pyrogram.session.internals import MsgId -class BaseClient: - class StopTransmission(StopAsyncIteration): - pass - - APP_VERSION = "Pyrogram {}".format(__version__) - - DEVICE_MODEL = "{} {}".format( - platform.python_implementation(), - platform.python_version() - ) - - SYSTEM_VERSION = "{} {}".format( - platform.system(), - platform.release() - ) +class Scaffold: + APP_VERSION = f"Pyrogram {__version__}" + DEVICE_MODEL = f"{platform.python_implementation()} {platform.python_version()}" + SYSTEM_VERSION = f"{platform.system()} {platform.release()}" LANG_CODE = "en" PARENT_DIR = Path(sys.argv[0]).parent INVITE_LINK_RE = re.compile(r"^(?:https?://)?(?:www\.)?(?:t(?:elegram)?\.(?:org|me|dog)/joinchat/)([\w-]+)$") - DIALOGS_AT_ONCE = 100 - UPDATES_WORKERS = 4 - DOWNLOAD_WORKERS = 4 - OFFLINE_SLEEP = 900 - WORKERS = 4 + WORKERS = min(32, os.cpu_count() + 4) WORKDIR = PARENT_DIR CONFIG_FILE = PARENT_DIR / "config.ini" @@ -76,20 +61,44 @@ class StopTransmission(StopAsyncIteration): mime_types_to_extensions = {} extensions_to_mime_types = {} - with open("{}/mime.types".format(os.path.dirname(__file__)), "r", encoding="UTF-8") as f: + with open(f"{os.path.dirname(__file__)}/mime.types", "r", encoding="UTF-8") as f: for match in re.finditer(r"^([^#\s]+)\s+(.+)$", f.read(), flags=re.M): mime_type, extensions = match.groups() - extensions = [".{}".format(ext) for ext in extensions.split(" ")] + extensions = [f".{ext}" for ext in extensions.split(" ")] for ext in extensions: extensions_to_mime_types[ext] = mime_type mime_types_to_extensions[mime_type] = " ".join(extensions) - is_idling = False - def __init__(self): + self.session_name = None + self.api_id = None + self.api_hash = None + self.app_version = None + self.device_model = None + self.system_version = None + self.lang_code = None + self.ipv6 = None + self.proxy = None + self.test_mode = None + self.bot_token = None + self.phone_number = None + self.phone_code = None + self.password = None + self.force_sms = None + self.workers = None + self.workdir = None + self.config_file = None + self.plugins = None + self.parse_mode = None + self.no_updates = None + self.takeout = None + self.sleep_threshold = None + + self.executor = None + self.storage = None self.rnd_id = MsgId @@ -98,21 +107,22 @@ def __init__(self): self.parse_mode = "combined" self.session = None + self.media_sessions = {} self.media_sessions_lock = asyncio.Lock() self.is_connected = None self.is_initialized = None + self.no_updates = None self.takeout_id = None - self.updates_queue = asyncio.Queue() - self.updates_worker_tasks = [] - self.download_queue = asyncio.Queue() - self.download_worker_tasks = [] + self.dispatcher = None self.disconnect_handler = None + self.loop = None + async def send(self, *args, **kwargs): pass @@ -172,3 +182,36 @@ def guess_mime_type(self, *args, **kwargs): def guess_extension(self, *args, **kwargs): pass + + def load_config(self, *args, **kwargs): + pass + + def load_session(self, *args, **kwargs): + pass + + def load_plugins(self, *args, **kwargs): + pass + + async def handle_download(self, *args, **kwargs): + pass + + async def start(self, *args, **kwargs): + pass + + async def stop(self, *args, **kwargs): + pass + + async def connect(self, *args, **kwargs): + pass + + async def authorize(self, *args, **kwargs): + pass + + async def disconnect(self, *args, **kwargs): + pass + + async def initialize(self, *args, **kwargs): + pass + + async def terminate(self, *args, **kwargs): + pass diff --git a/pyrogram/session/auth.py b/pyrogram/session/auth.py index 6795928a7b..2aa658216e 100644 --- a/pyrogram/session/auth.py +++ b/pyrogram/session/auth.py @@ -24,10 +24,10 @@ from os import urandom import pyrogram -from pyrogram.api import functions, types -from pyrogram.api.core import TLObject, Long, Int +from pyrogram import raw from pyrogram.connection import Connection -from pyrogram.crypto import AES, RSA, Prime +from pyrogram.crypto import aes, rsa, prime +from pyrogram.raw.core import TLObject, Long, Int from .internals import MsgId log = logging.getLogger(__name__) @@ -36,9 +36,9 @@ class Auth: MAX_RETRIES = 5 - def __init__(self, client: "pyrogram.Client", dc_id: int): + def __init__(self, client: "pyrogram.Client", dc_id: int, test_mode: bool): self.dc_id = dc_id - self.test_mode = client.storage.test_mode() + self.test_mode = test_mode self.ipv6 = client.ipv6 self.proxy = client.proxy @@ -78,40 +78,40 @@ async def create(self): self.connection = Connection(self.dc_id, self.test_mode, self.ipv6, self.proxy) try: - log.info("Start creating a new auth key on DC{}".format(self.dc_id)) + log.info(f"Start creating a new auth key on DC{self.dc_id}") 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 = await self.send(functions.ReqPqMulti(nonce=nonce)) - log.debug("Got ResPq: {}".format(res_pq.server_nonce)) - log.debug("Server public key fingerprints: {}".format(res_pq.server_public_key_fingerprints)) + log.debug(f"Send req_pq: {nonce}") + res_pq = await self.send(raw.functions.ReqPqMulti(nonce=nonce)) + log.debug(f"Got ResPq: {res_pq.server_nonce}") + log.debug(f"Server public key fingerprints: {res_pq.server_public_key_fingerprints}") for i in res_pq.server_public_key_fingerprints: - if i in RSA.server_public_keys: - log.debug("Using fingerprint: {}".format(i)) + if i in rsa.server_public_keys: + log.debug(f"Using fingerprint: {i}") public_key_fingerprint = i break else: - log.debug("Fingerprint unknown: {}".format(i)) + log.debug(f"Fingerprint unknown: {i}") else: raise Exception("Public key not found") # Step 3 pq = int.from_bytes(res_pq.pq, "big") - log.debug("Start PQ factorization: {}".format(pq)) + log.debug(f"Start PQ factorization: {pq}") start = time.time() - g = Prime.decompose(pq) + g = prime.decompose(pq) p, q = sorted((g, pq // g)) # p < q - log.debug("Done PQ factorization ({}s): {} {}".format(round(time.time() - start, 3), p, q)) + log.debug(f"Done PQ factorization ({round(time.time() - start, 3)}s): {p} {q}") # Step 4 server_nonce = res_pq.server_nonce new_nonce = int.from_bytes(urandom(32), "little", signed=True) - data = types.PQInnerData( + data = raw.types.PQInnerData( pq=res_pq.pq, p=p.to_bytes(4, "big"), q=q.to_bytes(4, "big"), @@ -123,14 +123,14 @@ async def create(self): sha = sha1(data).digest() padding = urandom(- (len(data) + len(sha)) % 255) data_with_hash = sha + data + padding - encrypted_data = RSA.encrypt(data_with_hash, public_key_fingerprint) + encrypted_data = rsa.encrypt(data_with_hash, public_key_fingerprint) log.debug("Done encrypt data with RSA") # Step 5. TODO: Handle "server_DH_params_fail". Code assumes response is ok log.debug("Send req_DH_params") server_dh_params = await self.send( - functions.ReqDHParams( + raw.functions.ReqDHParams( nonce=nonce, server_nonce=server_nonce, p=p.to_bytes(4, "big"), @@ -157,7 +157,7 @@ async def create(self): server_nonce = int.from_bytes(server_nonce, "little", signed=True) - answer_with_hash = AES.ige256_decrypt(encrypted_answer, tmp_aes_key, tmp_aes_iv) + answer_with_hash = aes.ige256_decrypt(encrypted_answer, tmp_aes_key, tmp_aes_iv) answer = answer_with_hash[20:] server_dh_inner_data = TLObject.read(BytesIO(answer)) @@ -167,7 +167,7 @@ async def create(self): dh_prime = int.from_bytes(server_dh_inner_data.dh_prime, "big") delta_time = server_dh_inner_data.server_time - time.time() - log.debug("Delta time: {}".format(round(delta_time, 3))) + log.debug(f"Delta time: {round(delta_time, 3)}") # Step 6 g = server_dh_inner_data.g @@ -176,7 +176,7 @@ async def create(self): retry_id = 0 - data = types.ClientDHInnerData( + data = raw.types.ClientDHInnerData( nonce=nonce, server_nonce=server_nonce, retry_id=retry_id, @@ -186,11 +186,11 @@ async def create(self): sha = sha1(data).digest() padding = urandom(- (len(data) + len(sha)) % 16) data_with_hash = sha + data + padding - encrypted_data = AES.ige256_encrypt(data_with_hash, tmp_aes_key, tmp_aes_iv) + 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 = await self.send( - functions.SetClientDHParams( + raw.functions.SetClientDHParams( nonce=nonce, server_nonce=server_nonce, encrypted_data=encrypted_data @@ -210,7 +210,7 @@ async def create(self): # Security checks ####################### - assert dh_prime == Prime.CURRENT_DH_PRIME + assert dh_prime == prime.CURRENT_DH_PRIME log.debug("DH parameters check: OK") # https://core.telegram.org/mtproto/security_guidelines#g-a-and-g-b-validation @@ -241,15 +241,11 @@ async def create(self): log.debug("Nonce fields check: OK") # Step 9 - server_salt = AES.xor(new_nonce[:8], server_nonce[:8]) + server_salt = aes.xor(new_nonce[:8], server_nonce[:8]) - log.debug("Server salt: {}".format(int.from_bytes(server_salt, "little"))) + log.debug(f"Server salt: {int.from_bytes(server_salt, 'little')}") - log.info( - "Done auth key exchange: {}".format( - set_client_dh_params_answer.__class__.__name__ - ) - ) + log.info(f"Done auth key exchange: {set_client_dh_params_answer.__class__.__name__}") except Exception as e: if retries_left: retries_left -= 1 diff --git a/pyrogram/session/internals/msg_factory.py b/pyrogram/session/internals/msg_factory.py index 3f4c302648..bf44b7d3c4 100644 --- a/pyrogram/session/internals/msg_factory.py +++ b/pyrogram/session/internals/msg_factory.py @@ -16,10 +16,9 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from pyrogram.api.functions import Ping -from pyrogram.api.types import MsgsAck, HttpWait - -from pyrogram.api.core import Message, MsgContainer, TLObject +from pyrogram.raw.core import Message, MsgContainer, TLObject +from pyrogram.raw.functions import Ping +from pyrogram.raw.types import MsgsAck, HttpWait from .msg_id import MsgId from .seq_no import SeqNo @@ -27,13 +26,14 @@ class MsgFactory: - def __init__(self): + def __init__(self, server_time: float = 0): self.seq_no = SeqNo() + self.server_time = server_time def __call__(self, body: TLObject) -> Message: return Message( body, - MsgId(), + MsgId(self.server_time), self.seq_no(not isinstance(body, not_content_related)), len(body) ) diff --git a/pyrogram/session/internals/msg_id.py b/pyrogram/session/internals/msg_id.py index e52815e94d..58474222c7 100644 --- a/pyrogram/session/internals/msg_id.py +++ b/pyrogram/session/internals/msg_id.py @@ -16,20 +16,18 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from threading import Lock -from time import time +from time import monotonic class MsgId: + reference_clock = monotonic() last_time = 0 - offset = 0 - lock = Lock() + msg_id_offset = 0 - def __new__(cls) -> int: - with cls.lock: - now = time() - cls.offset = cls.offset + 4 if now == cls.last_time else 0 - msg_id = int(now * 2 ** 32) + cls.offset - cls.last_time = now + def __new__(cls, server_time: float = 0) -> int: + now = monotonic() - cls.reference_clock + server_time + cls.msg_id_offset = cls.msg_id_offset + 4 if now == cls.last_time else 0 + msg_id = int(now * 2 ** 32) + cls.msg_id_offset + cls.last_time = now - return msg_id + return msg_id diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index beb4affacf..6c385199b4 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -18,18 +18,20 @@ import asyncio import logging +import time +from concurrent.futures.thread import ThreadPoolExecutor from datetime import datetime, timedelta from hashlib import sha1 from io import BytesIO import pyrogram from pyrogram import __copyright__, __license__, __version__ -from pyrogram.api import functions, types -from pyrogram.api.all import layer -from pyrogram.api.core import TLObject, MsgContainer, Int, Long, FutureSalt, FutureSalts +from pyrogram import raw from pyrogram.connection import Connection -from pyrogram.crypto import MTProto +from pyrogram.crypto import mtproto from pyrogram.errors import RPCError, InternalServerError, AuthKeyDuplicated, FloodWait +from pyrogram.raw.all import layer +from pyrogram.raw.core import TLObject, MsgContainer, Int, Long, FutureSalt, FutureSalts from .internals import MsgId, MsgFactory log = logging.getLogger(__name__) @@ -43,13 +45,13 @@ def __init__(self): class Session: INITIAL_SALT = 0x616e67656c696361 - NET_WORKERS = 1 START_TIMEOUT = 1 WAIT_TIMEOUT = 15 SLEEP_THRESHOLD = 60 MAX_RETRIES = 5 ACKS_THRESHOLD = 8 PING_INTERVAL = 5 + EXECUTOR_SIZE_THRESHOLD = 512 notice_displayed = False @@ -67,22 +69,26 @@ class Session: 64: "[64] invalid container" } + executor = ThreadPoolExecutor(2, thread_name_prefix="CryptoWorker") + def __init__( self, - client: pyrogram, + client: "pyrogram.Client", dc_id: int, auth_key: bytes, + test_mode: bool, is_media: bool = False, is_cdn: bool = False ): if not Session.notice_displayed: - print("Pyrogram v{}, {}".format(__version__, __copyright__)) - print("Licensed under the terms of the " + __license__, end="\n\n") + print(f"Pyrogram v{__version__}, {__copyright__}") + print(f"Licensed under the terms of the {__license__}", end="\n\n") Session.notice_displayed = True self.client = client self.dc_id = dc_id self.auth_key = auth_key + self.test_mode = test_mode self.is_media = is_media self.is_cdn = is_cdn @@ -90,14 +96,13 @@ def __init__( self.auth_key_id = sha1(auth_key).digest()[-8:] - self.session_id = Long(MsgId()) + self.session_id = Long(MsgId(time.time())) self.msg_factory = MsgFactory() self.current_salt = None self.pending_acks = set() - self.recv_queue = asyncio.Queue() self.results = {} self.ping_task = None @@ -106,16 +111,17 @@ def __init__( self.next_salt_task = None self.next_salt_task_event = asyncio.Event() - self.net_worker_task = None - self.recv_task = None + self.network_task = None self.is_connected = asyncio.Event() + self.loop = asyncio.get_event_loop() + async def start(self): while True: self.connection = Connection( self.dc_id, - self.client.storage.test_mode(), + self.test_mode, self.client.ipv6, self.client.proxy ) @@ -123,27 +129,27 @@ async def start(self): try: await self.connection.connect() - self.net_worker_task = asyncio.ensure_future(self.net_worker()) - self.recv_task = asyncio.ensure_future(self.recv()) + self.network_task = self.loop.create_task(self.network_worker()) self.current_salt = FutureSalt(0, 0, Session.INITIAL_SALT) self.current_salt = FutureSalt( 0, 0, (await self._send( - functions.Ping(ping_id=0), + raw.functions.Ping(ping_id=0), timeout=self.START_TIMEOUT )).new_server_salt ) - self.current_salt = \ - (await self._send(functions.GetFutureSalts(num=1), timeout=self.START_TIMEOUT)).salts[0] + self.current_salt = (await self._send( + raw.functions.GetFutureSalts(num=1), + timeout=self.START_TIMEOUT)).salts[0] - self.next_salt_task = asyncio.ensure_future(self.next_salt()) + self.next_salt_task = self.loop.create_task(self.next_salt_worker()) if not self.is_cdn: await self._send( - functions.InvokeWithLayer( + raw.functions.InvokeWithLayer( layer=layer, - query=functions.InitConnection( + query=raw.functions.InitConnection( api_id=self.client.api_id, app_version=self.client.app_version, device_model=self.client.device_model, @@ -151,17 +157,17 @@ async def start(self): system_lang_code=self.client.lang_code, lang_code=self.client.lang_code, lang_pack="", - query=functions.help.GetConfig(), + query=raw.functions.help.GetConfig(), ) ), timeout=self.START_TIMEOUT ) - self.ping_task = asyncio.ensure_future(self.ping()) + self.ping_task = self.loop.create_task(self.ping_worker()) - log.info("Session initialized: Layer {}".format(layer)) - log.info("Device: {} - {}".format(self.client.device_model, self.client.app_version)) - log.info("System: {} ({})".format(self.client.system_version, self.client.lang_code.upper())) + log.info(f"Session initialized: Layer {layer}") + log.info(f"Device: {self.client.device_model} - {self.client.app_version}") + log.info(f"System: {self.client.system_version} ({self.client.lang_code.upper()})") except AuthKeyDuplicated as e: await self.stop() @@ -195,11 +201,8 @@ async def stop(self): self.connection.close() - if self.recv_task: - await self.recv_task - - if self.net_worker_task: - await self.net_worker_task + if self.network_task: + await self.network_task for i in self.results.values(): i.event.set() @@ -216,76 +219,78 @@ async def restart(self): await self.stop() await self.start() - async def net_worker(self): - logging.info("NetWorkerTask started") + async def handle_packet(self, packet): + if len(packet) <= self.EXECUTOR_SIZE_THRESHOLD: + data = mtproto.unpack( + BytesIO(packet), + self.session_id, + self.auth_key, + self.auth_key_id + ) + else: + data = await self.loop.run_in_executor( + self.executor, + mtproto.unpack, + BytesIO(packet), + self.session_id, + self.auth_key, + self.auth_key_id + ) - while True: - packet = await self.recv_queue.get() + messages = ( + data.body.messages + if isinstance(data.body, MsgContainer) + else [data] + ) - if packet is None: - break + log.debug(f"Received:\n{data}") - try: - data = MTProto.unpack( - BytesIO(packet), - self.session_id, - self.auth_key, - self.auth_key_id - ) + for msg in messages: + if msg.seq_no == 0: + server_time = msg.msg_id / (2 ** 32) + self.msg_factory.server_time = server_time + log.info(f"Time synced: {datetime.utcfromtimestamp(server_time)} UTC") - messages = ( - data.body.messages - if isinstance(data.body, MsgContainer) - else [data] - ) + if msg.seq_no % 2 != 0: + if msg.msg_id in self.pending_acks: + continue + else: + self.pending_acks.add(msg.msg_id) - log.debug("Received:\n{}".format(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, (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_nowait(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: - await self._send(types.MsgsAck(msg_ids=list(self.pending_acks)), False) - except (OSError, TimeoutError): - pass - else: - self.pending_acks.clear() - except Exception as e: - log.error(e, exc_info=True) + if isinstance(msg.body, (raw.types.MsgDetailedInfo, raw.types.MsgNewDetailedInfo)): + self.pending_acks.add(msg.body.answer_msg_id) + continue - log.info("NetWorkerTask stopped") + if isinstance(msg.body, raw.types.NewSessionCreated): + continue - async def ping(self): + msg_id = None + + if isinstance(msg.body, (raw.types.BadMsgNotification, raw.types.BadServerSalt)): + msg_id = msg.body.bad_msg_id + elif isinstance(msg.body, (FutureSalts, raw.types.RpcResult)): + msg_id = msg.body.req_msg_id + elif isinstance(msg.body, raw.types.Pong): + msg_id = msg.body.msg_id + else: + if self.client is not None: + self.loop.create_task(self.client.handle_updates(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.debug(f"Send {len(self.pending_acks)} acks") + + try: + await self._send(raw.types.MsgsAck(msg_ids=list(self.pending_acks)), False) + except (OSError, TimeoutError): + pass + else: + self.pending_acks.clear() + + async def ping_worker(self): log.info("PingTask started") while True: @@ -298,7 +303,7 @@ async def ping(self): try: await self._send( - functions.PingDelayDisconnect( + raw.functions.PingDelayDisconnect( ping_id=0, disconnect_delay=self.WAIT_TIMEOUT + 10 ), False ) @@ -307,7 +312,7 @@ async def ping(self): log.info("PingTask stopped") - async def next_salt(self): + async def next_salt_worker(self): log.info("NextSaltTask started") while True: @@ -331,33 +336,31 @@ async def next_salt(self): break try: - self.current_salt = (await self._send(functions.GetFutureSalts(num=1))).salts[0] + self.current_salt = (await self._send(raw.functions.GetFutureSalts(num=1))).salts[0] except (OSError, TimeoutError, RPCError): self.connection.close() break log.info("NextSaltTask stopped") - async def recv(self): - log.info("RecvTask started") + async def network_worker(self): + log.info("NetworkTask started") while True: packet = await self.connection.recv() 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)))) + log.warning(f'Server sent "{Int.read(BytesIO(packet))}"') if self.is_connected.is_set(): - asyncio.ensure_future(self.restart()) + self.loop.create_task(self.restart()) break - self.recv_queue.put_nowait(packet) + self.loop.create_task(self.handle_packet(packet)) - log.info("RecvTask stopped") + log.info("NetworkTask stopped") async def _send(self, data: TLObject, wait_response: bool = True, timeout: float = WAIT_TIMEOUT): message = self.msg_factory(data) @@ -366,15 +369,26 @@ async def _send(self, data: TLObject, wait_response: bool = True, timeout: float if wait_response: self.results[msg_id] = Result() - log.debug("Sent:\n{}".format(message)) + log.debug(f"Sent:\n{message}") - payload = MTProto.pack( - message, - self.current_salt.salt, - self.session_id, - self.auth_key, - self.auth_key_id - ) + if len(message) <= self.EXECUTOR_SIZE_THRESHOLD: + payload = mtproto.pack( + message, + self.current_salt.salt, + self.session_id, + self.auth_key, + self.auth_key_id + ) + else: + payload = await self.loop.run_in_executor( + self.executor, + mtproto.pack, + message, + self.current_salt.salt, + self.session_id, + self.auth_key, + self.auth_key_id + ) try: await self.connection.send(payload) @@ -392,15 +406,15 @@ async def _send(self, data: TLObject, wait_response: bool = True, timeout: float if result is None: raise TimeoutError - elif isinstance(result, types.RpcError): - if isinstance(data, (functions.InvokeWithoutUpdates, functions.InvokeWithTakeout)): + elif isinstance(result, raw.types.RpcError): + if isinstance(data, (raw.functions.InvokeWithoutUpdates, raw.functions.InvokeWithTakeout)): data = data.query RPCError.raise_it(result, type(data)) - elif isinstance(result, types.BadMsgNotification): + elif isinstance(result, raw.types.BadMsgNotification): raise Exception(self.BAD_MSG_DESCRIPTION.get( result.error_code, - "Error code {}".format(result.error_code) + f"Error code {result.error_code}" )) else: return result @@ -417,7 +431,7 @@ async def send( except asyncio.TimeoutError: pass - if isinstance(data, (functions.InvokeWithoutUpdates, functions.InvokeWithTakeout)): + if isinstance(data, (raw.functions.InvokeWithoutUpdates, raw.functions.InvokeWithTakeout)): query = data.query else: query = data @@ -433,8 +447,7 @@ async def send( if amount > sleep_threshold: raise - log.warning('[{}] Sleeping for {}s (required by "{}")'.format( - self.client.session_name, amount, query)) + log.warning(f'[{self.client.session_name}] Sleeping for {amount}s (required by "{query}")') await asyncio.sleep(amount) except (OSError, TimeoutError, InternalServerError) as e: @@ -442,9 +455,7 @@ async def send( raise e from None (log.warning if retries < 2 else log.info)( - '[{}] Retrying "{}" due to {}'.format( - Session.MAX_RETRIES - retries + 1, - query, e)) + f'[{Session.MAX_RETRIES - retries + 1}] Retrying "{query}" due to {e}') await asyncio.sleep(0.5) diff --git a/pyrogram/client/storage/__init__.py b/pyrogram/storage/__init__.py similarity index 100% rename from pyrogram/client/storage/__init__.py rename to pyrogram/storage/__init__.py diff --git a/pyrogram/client/storage/file_storage.py b/pyrogram/storage/file_storage.py similarity index 93% rename from pyrogram/client/storage/file_storage.py rename to pyrogram/storage/file_storage.py index 07716ce5e5..a335761a0b 100644 --- a/pyrogram/client/storage/file_storage.py +++ b/pyrogram/storage/file_storage.py @@ -78,7 +78,7 @@ def update(self): self.version(version) - def open(self): + async def open(self): path = self.database file_exists = path.is_file() @@ -93,7 +93,7 @@ def open(self): path.rename(path.name + ".OLD") - log.warning('The old session file has been renamed to "{}.OLD"'.format(path.name)) + log.warning(f'The old session file has been renamed to "{path.name}.OLD"') self.migrate_from_json(session_json) @@ -102,7 +102,7 @@ def open(self): return if Path(path.name + ".OLD").is_file(): - log.warning('Old session file detected: "{}.OLD". You can remove this file now'.format(path.name)) + log.warning(f'Old session file detected: "{path.name}.OLD". You can remove this file now') self.conn = sqlite3.connect(str(path), timeout=1, check_same_thread=False) @@ -117,5 +117,5 @@ def open(self): except sqlite3.OperationalError: pass - def delete(self): + async def delete(self): os.remove(self.database) diff --git a/pyrogram/client/storage/memory_storage.py b/pyrogram/storage/memory_storage.py similarity index 81% rename from pyrogram/client/storage/memory_storage.py rename to pyrogram/storage/memory_storage.py index b698d2cef9..7e0094974c 100644 --- a/pyrogram/client/storage/memory_storage.py +++ b/pyrogram/storage/memory_storage.py @@ -30,7 +30,7 @@ class MemoryStorage(SQLiteStorage): def __init__(self, name: str): super().__init__(name) - def open(self): + async def open(self): self.conn = sqlite3.connect(":memory:", check_same_thread=False) self.create() @@ -42,12 +42,12 @@ def open(self): ) ) - self.dc_id(dc_id) - self.test_mode(test_mode) - self.auth_key(auth_key) - self.user_id(user_id) - self.is_bot(is_bot) - self.date(0) + await self.dc_id(dc_id) + await self.test_mode(test_mode) + await self.auth_key(auth_key) + await self.user_id(user_id) + await self.is_bot(is_bot) + await self.date(0) - def delete(self): - pass + async def delete(self): + raise NotImplementedError diff --git a/pyrogram/client/storage/schema.sql b/pyrogram/storage/schema.sql similarity index 95% rename from pyrogram/client/storage/schema.sql rename to pyrogram/storage/schema.sql index aa76c4b021..39c986b5d3 100644 --- a/pyrogram/client/storage/schema.sql +++ b/pyrogram/storage/schema.sql @@ -18,7 +18,8 @@ * along with Pyrogram. If not, see . */ -CREATE TABLE sessions ( +CREATE TABLE sessions +( dc_id INTEGER PRIMARY KEY, test_mode INTEGER, auth_key BLOB, @@ -27,7 +28,8 @@ CREATE TABLE sessions ( is_bot INTEGER ); -CREATE TABLE peers ( +CREATE TABLE peers +( id INTEGER PRIMARY KEY, access_hash INTEGER, type INTEGER NOT NULL, @@ -36,7 +38,8 @@ CREATE TABLE peers ( last_update_on INTEGER NOT NULL DEFAULT (CAST(STRFTIME('%s', 'now') AS INTEGER)) ); -CREATE TABLE version ( +CREATE TABLE version +( number INTEGER PRIMARY KEY ); diff --git a/pyrogram/client/storage/sqlite_storage.py b/pyrogram/storage/sqlite_storage.py similarity index 76% rename from pyrogram/client/storage/sqlite_storage.py rename to pyrogram/storage/sqlite_storage.py index 14279b0a27..2f3768df2d 100644 --- a/pyrogram/client/storage/sqlite_storage.py +++ b/pyrogram/storage/sqlite_storage.py @@ -23,30 +23,30 @@ from threading import Lock from typing import List, Tuple, Any -from pyrogram.api import types -from pyrogram.client.ext import utils +from pyrogram import raw from .storage import Storage +from .. import utils def get_input_peer(peer_id: int, access_hash: int, peer_type: str): if peer_type in ["user", "bot"]: - return types.InputPeerUser( + return raw.types.InputPeerUser( user_id=peer_id, access_hash=access_hash ) if peer_type == "group": - return types.InputPeerChat( + return raw.types.InputPeerChat( chat_id=-peer_id ) if peer_type in ["channel", "supergroup"]: - return types.InputPeerChannel( + return raw.types.InputPeerChannel( channel_id=utils.get_channel_id(peer_id), access_hash=access_hash ) - raise ValueError("Invalid peer type: {}".format(peer_type)) + raise ValueError(f"Invalid peer type: {peer_type}") class SQLiteStorage(Storage): @@ -74,23 +74,23 @@ def create(self): (2, None, None, 0, None, None) ) - def open(self): + async def open(self): raise NotImplementedError - def save(self): - self.date(int(time.time())) + async def save(self): + await self.date(int(time.time())) with self.lock: self.conn.commit() - def close(self): + async def close(self): with self.lock: self.conn.close() - def delete(self): + async def delete(self): raise NotImplementedError - def update_peers(self, peers: List[Tuple[int, int, str, str, str]]): + async def update_peers(self, peers: List[Tuple[int, int, str, str, str]]): with self.lock: self.conn.executemany( "REPLACE INTO peers (id, access_hash, type, username, phone_number)" @@ -98,39 +98,39 @@ def update_peers(self, peers: List[Tuple[int, int, str, str, str]]): peers ) - def get_peer_by_id(self, peer_id: int): + async def get_peer_by_id(self, peer_id: int): r = self.conn.execute( "SELECT id, access_hash, type FROM peers WHERE id = ?", (peer_id,) ).fetchone() if r is None: - raise KeyError("ID not found: {}".format(peer_id)) + raise KeyError(f"ID not found: {peer_id}") return get_input_peer(*r) - def get_peer_by_username(self, username: str): + async def get_peer_by_username(self, username: str): r = self.conn.execute( "SELECT id, access_hash, type, last_update_on FROM peers WHERE username = ?", (username,) ).fetchone() if r is None: - raise KeyError("Username not found: {}".format(username)) + raise KeyError(f"Username not found: {username}") if abs(time.time() - r[3]) > self.USERNAME_TTL: - raise KeyError("Username expired: {}".format(username)) + raise KeyError(f"Username expired: {username}") return get_input_peer(*r[:3]) - def get_peer_by_phone_number(self, phone_number: str): + async def get_peer_by_phone_number(self, phone_number: str): r = self.conn.execute( "SELECT id, access_hash, type FROM peers WHERE phone_number = ?", (phone_number,) ).fetchone() if r is None: - raise KeyError("Phone number not found: {}".format(phone_number)) + raise KeyError(f"Phone number not found: {phone_number}") return get_input_peer(*r) @@ -138,7 +138,7 @@ def _get(self): attr = inspect.stack()[2].function return self.conn.execute( - "SELECT {} FROM sessions".format(attr) + f"SELECT {attr} FROM sessions" ).fetchone()[0] def _set(self, value: Any): @@ -146,29 +146,29 @@ def _set(self, value: Any): with self.lock, self.conn: self.conn.execute( - "UPDATE sessions SET {} = ?".format(attr), + f"UPDATE sessions SET {attr} = ?", (value,) ) def _accessor(self, value: Any = object): return self._get() if value == object else self._set(value) - def dc_id(self, value: int = object): + async def dc_id(self, value: int = object): return self._accessor(value) - def test_mode(self, value: bool = object): + async def test_mode(self, value: bool = object): return self._accessor(value) - def auth_key(self, value: bytes = object): + async def auth_key(self, value: bytes = object): return self._accessor(value) - def date(self, value: int = object): + async def date(self, value: int = object): return self._accessor(value) - def user_id(self, value: int = object): + async def user_id(self, value: int = object): return self._accessor(value) - def is_bot(self, value: bool = object): + async def is_bot(self, value: bool = object): return self._accessor(value) def version(self, value: int = object): diff --git a/pyrogram/client/storage/storage.py b/pyrogram/storage/storage.py similarity index 65% rename from pyrogram/client/storage/storage.py rename to pyrogram/storage/storage.py index 8cd7e6f39e..3ef83027a0 100644 --- a/pyrogram/client/storage/storage.py +++ b/pyrogram/storage/storage.py @@ -28,56 +28,56 @@ class Storage: def __init__(self, name: str): self.name = name - def open(self): + async def open(self): raise NotImplementedError - def save(self): + async def save(self): raise NotImplementedError - def close(self): + async def close(self): raise NotImplementedError - def delete(self): + async def delete(self): raise NotImplementedError - def update_peers(self, peers: List[Tuple[int, int, str, str, str]]): + async def update_peers(self, peers: List[Tuple[int, int, str, str, str]]): raise NotImplementedError - def get_peer_by_id(self, peer_id: int): + async def get_peer_by_id(self, peer_id: int): raise NotImplementedError - def get_peer_by_username(self, username: str): + async def get_peer_by_username(self, username: str): raise NotImplementedError - def get_peer_by_phone_number(self, phone_number: str): + async def get_peer_by_phone_number(self, phone_number: str): raise NotImplementedError - def dc_id(self, value: int = object): + async def dc_id(self, value: int = object): raise NotImplementedError - def test_mode(self, value: bool = object): + async def test_mode(self, value: bool = object): raise NotImplementedError - def auth_key(self, value: bytes = object): + async def auth_key(self, value: bytes = object): raise NotImplementedError - def date(self, value: int = object): + async def date(self, value: int = object): raise NotImplementedError - def user_id(self, value: int = object): + async def user_id(self, value: int = object): raise NotImplementedError - def is_bot(self, value: bool = object): + async def is_bot(self, value: bool = object): raise NotImplementedError - def export_session_string(self): + async def export_session_string(self): return base64.urlsafe_b64encode( struct.pack( self.SESSION_STRING_FORMAT, - self.dc_id(), - self.test_mode(), - self.auth_key(), - self.user_id(), - self.is_bot() + await self.dc_id(), + await self.test_mode(), + await self.auth_key(), + await self.user_id(), + await self.is_bot() ) ).decode().rstrip("=") diff --git a/pyrogram/sync.py b/pyrogram/sync.py new file mode 100644 index 0000000000..80288e68d0 --- /dev/null +++ b/pyrogram/sync.py @@ -0,0 +1,80 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan +# +# 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 asyncio +import functools +import inspect +import threading + +from pyrogram import types +from pyrogram.methods import Methods +from pyrogram.methods.utilities import idle as idle_module + + +def async_to_sync(obj, name): + function = getattr(obj, name) + loop = asyncio.get_event_loop() + + async def consume_generator(coroutine): + return [i async for i in coroutine] + + @functools.wraps(function) + def async_to_sync_wrap(*args, **kwargs): + coroutine = function(*args, **kwargs) + + if loop.is_running(): + if threading.current_thread() is threading.main_thread(): + return coroutine + else: + if inspect.iscoroutine(coroutine): + return asyncio.run_coroutine_threadsafe(coroutine, loop).result() + + if inspect.isasyncgen(coroutine): + return asyncio.run_coroutine_threadsafe(consume_generator(coroutine), loop).result() + + if inspect.iscoroutine(coroutine): + return loop.run_until_complete(coroutine) + + if inspect.isasyncgen(coroutine): + return loop.run_until_complete(consume_generator(coroutine)) + + setattr(obj, name, async_to_sync_wrap) + + +def wrap(source): + for name in dir(source): + method = getattr(source, name) + + if not name.startswith("_"): + if inspect.iscoroutinefunction(method) or inspect.isasyncgenfunction(method): + async_to_sync(source, name) + + +# Wrap all Client's relevant methods +wrap(Methods) + +# Wrap types' bound methods +for class_name in dir(types): + cls = getattr(types, class_name) + + if inspect.isclass(cls): + wrap(cls) + +# Special case for idle, because it's not inside Methods +async_to_sync(idle_module, "idle") +idle = getattr(idle_module, "idle") diff --git a/pyrogram/client/ext/syncer.py b/pyrogram/syncer.py similarity index 87% rename from pyrogram/client/ext/syncer.py rename to pyrogram/syncer.py index 65fb210453..1a9d77f30e 100644 --- a/pyrogram/client/ext/syncer.py +++ b/pyrogram/syncer.py @@ -39,7 +39,7 @@ async def add(cls, client): cls.lock = asyncio.Lock() async with cls.lock: - cls.sync(client) + await cls.sync(client) cls.clients[id(client)] = client @@ -49,7 +49,7 @@ async def add(cls, client): @classmethod async def remove(cls, client): async with cls.lock: - cls.sync(client) + await cls.sync(client) del cls.clients[id(client)] @@ -73,19 +73,16 @@ async def worker(cls): except asyncio.TimeoutError: async with cls.lock: for client in cls.clients.values(): - cls.sync(client) + await cls.sync(client) else: break @classmethod - def sync(cls, client): + async def sync(cls, client): try: start = time.time() - client.storage.save() + await client.storage.save() except Exception as e: log.critical(e, exc_info=True) else: - log.info('Synced "{}" in {:.6} ms'.format( - client.storage.name, - (time.time() - start) * 1000 - )) + log.info(f'Synced "{client.storage.name}" in {(time.time() - start) * 1000:.6} ms') diff --git a/pyrogram/client/types/__init__.py b/pyrogram/types/__init__.py similarity index 100% rename from pyrogram/client/types/__init__.py rename to pyrogram/types/__init__.py index 1fa7c9cecf..f0a0665f91 100644 --- a/pyrogram/client/types/__init__.py +++ b/pyrogram/types/__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 .authorization import * from .bots_and_keyboards import * from .inline_mode import * from .input_media import * @@ -23,6 +24,5 @@ from .list import List from .messages_and_media import * from .object import Object -from .authorization import * from .update import * from .user_and_chats import * diff --git a/pyrogram/client/types/authorization/__init__.py b/pyrogram/types/authorization/__init__.py similarity index 100% rename from pyrogram/client/types/authorization/__init__.py rename to pyrogram/types/authorization/__init__.py index 18d57c935b..2848034100 100644 --- a/pyrogram/client/types/authorization/__init__.py +++ b/pyrogram/types/authorization/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from .terms_of_service import TermsOfService from .sent_code import SentCode +from .terms_of_service import TermsOfService __all__ = ["TermsOfService", "SentCode"] diff --git a/pyrogram/client/types/authorization/sent_code.py b/pyrogram/types/authorization/sent_code.py similarity index 74% rename from pyrogram/client/types/authorization/sent_code.py rename to pyrogram/types/authorization/sent_code.py index f1fd49da22..fcbfeace2b 100644 --- a/pyrogram/client/types/authorization/sent_code.py +++ b/pyrogram/types/authorization/sent_code.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from pyrogram.api import types +from pyrogram import raw from ..object import Object @@ -30,16 +30,16 @@ class SentCode(Object): *"flash_call"* (code is in the last 5 digits of the caller's phone number). phone_code_hash (``str``): - Confirmation code identifier useful for the next authorization steps (either :meth:`~Client.sign_in` or - :meth:`~Client.sign_up`). + Confirmation code identifier useful for the next authorization steps (either + :meth:`~pyrogram.Client.sign_in` or :meth:`~pyrogram.Client.sign_up`). next_type (``str``): - Type of the next code to be sent with :meth:`~Client.resend_code`. + Type of the next code to be sent with :meth:`~pyrogram.Client.resend_code`. Can be *"sms"* (code will be sent via SMS), *"call"* (code will be sent via voice call) or *"flash_call"* (code will be in the last 5 digits of caller's phone number). timeout (``int``): - Delay in seconds before calling :meth:`~Client.resend_code`. + Delay in seconds before calling :meth:`~pyrogram.Client.resend_code`. """ def __init__( @@ -57,25 +57,25 @@ def __init__( self.timeout = timeout @staticmethod - def _parse(sent_code: types.auth.SentCode) -> "SentCode": + def _parse(sent_code: raw.types.auth.SentCode) -> "SentCode": type = sent_code.type - if isinstance(type, types.auth.SentCodeTypeApp): + if isinstance(type, raw.types.auth.SentCodeTypeApp): type = "app" - elif isinstance(type, types.auth.SentCodeTypeSms): + elif isinstance(type, raw.types.auth.SentCodeTypeSms): type = "sms" - elif isinstance(type, types.auth.SentCodeTypeCall): + elif isinstance(type, raw.types.auth.SentCodeTypeCall): type = "call" - elif isinstance(type, types.auth.SentCodeTypeFlashCall): + elif isinstance(type, raw.types.auth.SentCodeTypeFlashCall): type = "flash_call" next_type = sent_code.next_type - if isinstance(next_type, types.auth.CodeTypeSms): + if isinstance(next_type, raw.types.auth.CodeTypeSms): next_type = "sms" - elif isinstance(next_type, types.auth.CodeTypeCall): + elif isinstance(next_type, raw.types.auth.CodeTypeCall): next_type = "call" - elif isinstance(next_type, types.auth.CodeTypeFlashCall): + elif isinstance(next_type, raw.types.auth.CodeTypeFlashCall): next_type = "flash_call" return SentCode( diff --git a/pyrogram/client/types/authorization/terms_of_service.py b/pyrogram/types/authorization/terms_of_service.py similarity index 77% rename from pyrogram/client/types/authorization/terms_of_service.py rename to pyrogram/types/authorization/terms_of_service.py index ab7e1348df..33a94d0a0b 100644 --- a/pyrogram/client/types/authorization/terms_of_service.py +++ b/pyrogram/types/authorization/terms_of_service.py @@ -18,13 +18,13 @@ from typing import List -from pyrogram.api import types -from ..messages_and_media import MessageEntity +from pyrogram import raw +from pyrogram import types from ..object import Object class TermsOfService(Object): - """Telegram's Terms of Service returned by :meth:`~Client.sign_in`. + """Telegram's Terms of Service returned by :meth:`~pyrogram.Client.sign_in`. Parameters: id (``str``): @@ -33,11 +33,11 @@ class TermsOfService(Object): text (``str``): Terms of Service text. - entities (List of :obj:`MessageEntity`): + entities (List of :obj:`~pyrogram.types.MessageEntity`): Special entities like URLs that appear in the text. """ - def __init__(self, *, id: str, text: str, entities: List[MessageEntity]): + def __init__(self, *, id: str, text: str, entities: List["types.MessageEntity"]): super().__init__() self.id = id @@ -45,12 +45,12 @@ def __init__(self, *, id: str, text: str, entities: List[MessageEntity]): self.entities = entities @staticmethod - def _parse(terms_of_service: types.help.TermsOfService) -> "TermsOfService": + def _parse(terms_of_service: "raw.types.help.TermsOfService") -> "TermsOfService": return TermsOfService( id=terms_of_service.id.data, text=terms_of_service.text, entities=[ - MessageEntity._parse(None, entity, {}) + types.MessageEntity._parse(None, entity, {}) for entity in terms_of_service.entities ] ) diff --git a/pyrogram/client/types/bots_and_keyboards/__init__.py b/pyrogram/types/bots_and_keyboards/__init__.py similarity index 100% rename from pyrogram/client/types/bots_and_keyboards/__init__.py rename to pyrogram/types/bots_and_keyboards/__init__.py diff --git a/pyrogram/client/types/bots_and_keyboards/callback_game.py b/pyrogram/types/bots_and_keyboards/callback_game.py similarity index 100% rename from pyrogram/client/types/bots_and_keyboards/callback_game.py rename to pyrogram/types/bots_and_keyboards/callback_game.py diff --git a/pyrogram/client/types/bots_and_keyboards/callback_query.py b/pyrogram/types/bots_and_keyboards/callback_query.py similarity index 79% rename from pyrogram/client/types/bots_and_keyboards/callback_query.py rename to pyrogram/types/bots_and_keyboards/callback_query.py index a15a7c35e3..326c78bb10 100644 --- a/pyrogram/client/types/bots_and_keyboards/callback_query.py +++ b/pyrogram/types/bots_and_keyboards/callback_query.py @@ -21,11 +21,11 @@ from typing import Union, List, Match import pyrogram -from pyrogram.api import types +from pyrogram import raw +from pyrogram import types from ..object import Object from ..update import Update -from ..user_and_chats import User -from ...ext import utils +from ... import utils class CallbackQuery(Object, Update): @@ -39,14 +39,14 @@ class CallbackQuery(Object, Update): id (``str``): Unique identifier for this query. - from_user (:obj:`User`): + from_user (:obj:`~pyrogram.types.User`): Sender. chat_instance (``str``, *optional*): Global identifier, uniquely corresponding to the chat to which the message with the callback button was sent. Useful for high scores in games. - message (:obj:`Message`, *optional*): + message (:obj:`~pyrogram.types.Message`, *optional*): Message with the callback button that originated the query. Note that message content and message date will not be available if the message is too old. @@ -67,11 +67,11 @@ class CallbackQuery(Object, Update): def __init__( self, *, - client: "pyrogram.BaseClient" = None, + client: "pyrogram.Client" = None, id: str, - from_user: User, + from_user: "types.User", chat_instance: str, - message: "pyrogram.Message" = None, + message: "types.Message" = None, inline_message_id: str = None, data: Union[str, bytes] = None, game_short_name: str = None, @@ -93,9 +93,9 @@ async def _parse(client, callback_query, users) -> "CallbackQuery": message = None inline_message_id = None - if isinstance(callback_query, types.UpdateBotCallbackQuery): + if isinstance(callback_query, raw.types.UpdateBotCallbackQuery): message = await client.get_messages(utils.get_peer_id(callback_query.peer), callback_query.msg_id) - elif isinstance(callback_query, types.UpdateInlineBotCallbackQuery): + elif isinstance(callback_query, raw.types.UpdateInlineBotCallbackQuery): inline_message_id = b64encode( pack( " "CallbackQuery": return CallbackQuery( id=str(callback_query.query_id), - from_user=User._parse(client, users[callback_query.user_id]), + from_user=types.User._parse(client, users[callback_query.user_id]), message=message, inline_message_id=inline_message_id, chat_instance=str(callback_query.chat_instance), @@ -125,7 +125,7 @@ async def _parse(client, callback_query, users) -> "CallbackQuery": ) async def answer(self, text: str = None, show_alert: bool = None, url: str = None, cache_time: int = 0): - """Bound method *answer* of :obj:`CallbackQuery`. + """Bound method *answer* of :obj:`~pyrogram.types.CallbackQuery`. Use this method as a shortcut for: @@ -173,11 +173,11 @@ async def edit_message_text( text: str, parse_mode: Union[str, None] = object, disable_web_page_preview: bool = None, - reply_markup: "pyrogram.InlineKeyboardMarkup" = None - ) -> Union["pyrogram.Message", bool]: + reply_markup: "types.InlineKeyboardMarkup" = None + ) -> Union["types.Message", bool]: """Edit the text of messages attached to callback queries. - Bound method *edit_message_text* of :obj:`CallbackQuery`. + Bound method *edit_message_text* of :obj:`~pyrogram.types.CallbackQuery`. Parameters: text (``str``): @@ -193,12 +193,12 @@ async def edit_message_text( disable_web_page_preview (``bool``, *optional*): Disables link previews for links in this message. - reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*): An InlineKeyboardMarkup object. Returns: - :obj:`Message` | ``bool``: On success, if the edited message was sent by the bot, the edited message is - returned, otherwise True is returned (message sent via the bot, as inline query result). + :obj:`~pyrogram.types.Message` | ``bool``: On success, if the edited message was sent by the bot, the edited + message is returned, otherwise True is returned (message sent via the bot, as inline query result). Raises: RPCError: In case of a Telegram RPC error. @@ -225,11 +225,11 @@ async def edit_message_caption( self, caption: str, parse_mode: Union[str, None] = object, - reply_markup: "pyrogram.InlineKeyboardMarkup" = None - ) -> Union["pyrogram.Message", bool]: + reply_markup: "types.InlineKeyboardMarkup" = None + ) -> Union["types.Message", bool]: """Edit the caption of media messages attached to callback queries. - Bound method *edit_message_caption* of :obj:`CallbackQuery`. + Bound method *edit_message_caption* of :obj:`~pyrogram.types.CallbackQuery`. Parameters: caption (``str``): @@ -242,12 +242,12 @@ async def edit_message_caption( Pass "html" to enable HTML-style parsing only. Pass None to completely disable style parsing. - reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*): An InlineKeyboardMarkup object. Returns: - :obj:`Message` | ``bool``: On success, if the edited message was sent by the bot, the edited message is - returned, otherwise True is returned (message sent via the bot, as inline query result). + :obj:`~pyrogram.types.Message` | ``bool``: On success, if the edited message was sent by the bot, the edited + message is returned, otherwise True is returned (message sent via the bot, as inline query result). Raises: RPCError: In case of a Telegram RPC error. @@ -256,23 +256,23 @@ async def edit_message_caption( async def edit_message_media( self, - media: "pyrogram.InputMedia", - reply_markup: "pyrogram.InlineKeyboardMarkup" = None - ) -> Union["pyrogram.Message", bool]: + media: "types.InputMedia", + reply_markup: "types.InlineKeyboardMarkup" = None + ) -> Union["types.Message", bool]: """Edit animation, audio, document, photo or video messages attached to callback queries. - Bound method *edit_message_media* of :obj:`CallbackQuery`. + Bound method *edit_message_media* of :obj:`~pyrogram.types.CallbackQuery`. Parameters: - media (:obj:`InputMedia`): + media (:obj:`~pyrogram.types.InputMedia`): One of the InputMedia objects describing an animation, audio, document, photo or video. - reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*): An InlineKeyboardMarkup object. Returns: - :obj:`Message` | ``bool``: On success, if the edited message was sent by the bot, the edited message is - returned, otherwise True is returned (message sent via the bot, as inline query result). + :obj:`~pyrogram.types.Message` | ``bool``: On success, if the edited message was sent by the bot, the edited + message is returned, otherwise True is returned (message sent via the bot, as inline query result). Raises: RPCError: In case of a Telegram RPC error. @@ -293,19 +293,19 @@ async def edit_message_media( async def edit_message_reply_markup( self, - reply_markup: "pyrogram.InlineKeyboardMarkup" = None - ) -> Union["pyrogram.Message", bool]: + reply_markup: "types.InlineKeyboardMarkup" = None + ) -> Union["types.Message", bool]: """Edit only the reply markup of messages attached to callback queries. - Bound method *edit_message_reply_markup* of :obj:`CallbackQuery`. + Bound method *edit_message_reply_markup* of :obj:`~pyrogram.types.CallbackQuery`. Parameters: - reply_markup (:obj:`InlineKeyboardMarkup`): + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`): An InlineKeyboardMarkup object. Returns: - :obj:`Message` | ``bool``: On success, if the edited message was sent by the bot, the edited message is - returned, otherwise True is returned (message sent via the bot, as inline query result). + :obj:`~pyrogram.types.Message` | ``bool``: On success, if the edited message was sent by the bot, the edited + message is returned, otherwise True is returned (message sent via the bot, as inline query result). Raises: RPCError: In case of a Telegram RPC error. diff --git a/pyrogram/client/types/bots_and_keyboards/force_reply.py b/pyrogram/types/bots_and_keyboards/force_reply.py similarity index 95% rename from pyrogram/client/types/bots_and_keyboards/force_reply.py rename to pyrogram/types/bots_and_keyboards/force_reply.py index 14870318f6..97c88db095 100644 --- a/pyrogram/client/types/bots_and_keyboards/force_reply.py +++ b/pyrogram/types/bots_and_keyboards/force_reply.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from pyrogram.api.types import ReplyKeyboardForceReply +from pyrogram import raw from ..object import Object @@ -52,7 +52,7 @@ def read(o): ) def write(self): - return ReplyKeyboardForceReply( + return raw.types.ReplyKeyboardForceReply( single_use=True, selective=self.selective or None ) diff --git a/pyrogram/client/types/bots_and_keyboards/game_high_score.py b/pyrogram/types/bots_and_keyboards/game_high_score.py similarity index 75% rename from pyrogram/client/types/bots_and_keyboards/game_high_score.py rename to pyrogram/types/bots_and_keyboards/game_high_score.py index 1e9b715764..a9af42ee27 100644 --- a/pyrogram/client/types/bots_and_keyboards/game_high_score.py +++ b/pyrogram/types/bots_and_keyboards/game_high_score.py @@ -17,17 +17,16 @@ # along with Pyrogram. If not, see . import pyrogram - -from pyrogram.api import types -from pyrogram.client.types.object import Object -from pyrogram.client.types.user_and_chats import User +from pyrogram import raw +from pyrogram import types +from ..object import Object class GameHighScore(Object): """One row of the high scores table for a game. Parameters: - user (:obj:`User`): + user (:obj:`~pyrogram.types.User`): User. score (``int``): @@ -40,8 +39,8 @@ class GameHighScore(Object): def __init__( self, *, - client: "pyrogram.BaseClient" = None, - user: User, + client: "pyrogram.Client" = None, + user: "types.User", score: int, position: int = None ): @@ -52,20 +51,20 @@ def __init__( self.position = position @staticmethod - def _parse(client, game_high_score: types.HighScore, users: dict) -> "GameHighScore": + def _parse(client, game_high_score: raw.types.HighScore, users: dict) -> "GameHighScore": users = {i.id: i for i in users} return GameHighScore( - user=User._parse(client, users[game_high_score.user_id]), + user=types.User._parse(client, users[game_high_score.user_id]), score=game_high_score.score, position=game_high_score.pos, client=client ) @staticmethod - def _parse_action(client, service: types.MessageService, users: dict): + def _parse_action(client, service: raw.types.MessageService, users: dict): return GameHighScore( - user=User._parse(client, users[service.from_id]), + user=types.User._parse(client, users[service.from_id]), score=service.action.score, client=client ) diff --git a/pyrogram/client/types/bots_and_keyboards/inline_keyboard_button.py b/pyrogram/types/bots_and_keyboards/inline_keyboard_button.py similarity index 85% rename from pyrogram/client/types/bots_and_keyboards/inline_keyboard_button.py rename to pyrogram/types/bots_and_keyboards/inline_keyboard_button.py index 0b186b401f..2d5212d9d4 100644 --- a/pyrogram/client/types/bots_and_keyboards/inline_keyboard_button.py +++ b/pyrogram/types/bots_and_keyboards/inline_keyboard_button.py @@ -18,12 +18,8 @@ from typing import Union -from pyrogram.api.types import ( - KeyboardButtonUrl, KeyboardButtonCallback, - KeyboardButtonSwitchInline, KeyboardButtonGame -) - -from .callback_game import CallbackGame +from pyrogram import raw +from pyrogram import types from ..object import Object @@ -66,7 +62,7 @@ def __init__( url: str = None, switch_inline_query: str = None, switch_inline_query_current_chat: str = None, - callback_game: CallbackGame = None + callback_game: "types.CallbackGame" = None ): super().__init__() @@ -80,13 +76,13 @@ def __init__( @staticmethod def read(o): - if isinstance(o, KeyboardButtonUrl): + if isinstance(o, raw.types.KeyboardButtonUrl): return InlineKeyboardButton( text=o.text, url=o.url ) - if isinstance(o, KeyboardButtonCallback): + if isinstance(o, raw.types.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: @@ -99,7 +95,7 @@ def read(o): callback_data=data ) - if isinstance(o, KeyboardButtonSwitchInline): + if isinstance(o, raw.types.KeyboardButtonSwitchInline): if o.same_peer: return InlineKeyboardButton( text=o.text, @@ -111,30 +107,30 @@ def read(o): switch_inline_query=o.query ) - if isinstance(o, KeyboardButtonGame): + if isinstance(o, raw.types.KeyboardButtonGame): return InlineKeyboardButton( text=o.text, - callback_game=CallbackGame() + callback_game=types.CallbackGame() ) def write(self): if self.callback_data is not None: # 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) + return raw.types.KeyboardButtonCallback(text=self.text, data=data) if self.url is not None: - return KeyboardButtonUrl(text=self.text, url=self.url) + return raw.types.KeyboardButtonUrl(text=self.text, url=self.url) if self.switch_inline_query is not None: - return KeyboardButtonSwitchInline(text=self.text, query=self.switch_inline_query) + return raw.types.KeyboardButtonSwitchInline(text=self.text, query=self.switch_inline_query) if self.switch_inline_query_current_chat is not None: - return KeyboardButtonSwitchInline( + return raw.types.KeyboardButtonSwitchInline( text=self.text, query=self.switch_inline_query_current_chat, same_peer=True ) if self.callback_game is not None: - return KeyboardButtonGame(text=self.text) + return raw.types.KeyboardButtonGame(text=self.text) diff --git a/pyrogram/client/types/bots_and_keyboards/inline_keyboard_markup.py b/pyrogram/types/bots_and_keyboards/inline_keyboard_markup.py similarity index 79% rename from pyrogram/client/types/bots_and_keyboards/inline_keyboard_markup.py rename to pyrogram/types/bots_and_keyboards/inline_keyboard_markup.py index fb79b4db29..6332c7cfa7 100644 --- a/pyrogram/client/types/bots_and_keyboards/inline_keyboard_markup.py +++ b/pyrogram/types/bots_and_keyboards/inline_keyboard_markup.py @@ -18,9 +18,8 @@ from typing import List -from pyrogram.api.types import ReplyInlineMarkup, KeyboardButtonRow - -from . import InlineKeyboardButton +from pyrogram import raw +from pyrogram import types from ..object import Object @@ -28,14 +27,11 @@ class InlineKeyboardMarkup(Object): """An inline keyboard that appears right next to the message it belongs to. Parameters: - inline_keyboard (List of List of :obj:`InlineKeyboardButton`): + inline_keyboard (List of List of :obj:`~pyrogram.types.InlineKeyboardButton`): List of button rows, each represented by a List of InlineKeyboardButton objects. """ - def __init__( - self, - inline_keyboard: List[List[InlineKeyboardButton]] - ): + def __init__(self, inline_keyboard: List[List["types.InlineKeyboardButton"]]): super().__init__() self.inline_keyboard = inline_keyboard @@ -48,7 +44,7 @@ def read(o): row = [] for j in i.buttons: - row.append(InlineKeyboardButton.read(j)) + row.append(types.InlineKeyboardButton.read(j)) inline_keyboard.append(row) @@ -57,8 +53,8 @@ def read(o): ) def write(self): - return ReplyInlineMarkup( - rows=[KeyboardButtonRow( + return raw.types.ReplyInlineMarkup( + rows=[raw.types.KeyboardButtonRow( buttons=[j.write() for j in i] ) for i in self.inline_keyboard] ) diff --git a/pyrogram/client/types/bots_and_keyboards/keyboard_button.py b/pyrogram/types/bots_and_keyboards/keyboard_button.py similarity index 82% rename from pyrogram/client/types/bots_and_keyboards/keyboard_button.py rename to pyrogram/types/bots_and_keyboards/keyboard_button.py index 022f19def1..855c11bd7e 100644 --- a/pyrogram/client/types/bots_and_keyboards/keyboard_button.py +++ b/pyrogram/types/bots_and_keyboards/keyboard_button.py @@ -16,9 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from pyrogram.api.types import KeyboardButton as RawKeyboardButton -from pyrogram.api.types import KeyboardButtonRequestPhone, KeyboardButtonRequestGeoLocation - +from pyrogram import raw from ..object import Object @@ -55,16 +53,16 @@ def __init__( @staticmethod def read(o): - if isinstance(o, RawKeyboardButton): + if isinstance(o, raw.types.KeyboardButton): return o.text - if isinstance(o, KeyboardButtonRequestPhone): + if isinstance(o, raw.types.KeyboardButtonRequestPhone): return KeyboardButton( text=o.text, request_contact=True ) - if isinstance(o, KeyboardButtonRequestGeoLocation): + if isinstance(o, raw.types.KeyboardButtonRequestGeoLocation): return KeyboardButton( text=o.text, request_location=True @@ -74,8 +72,8 @@ def write(self): # TODO: Enforce optional args mutual exclusiveness if self.request_contact: - return KeyboardButtonRequestPhone(text=self.text) + return raw.types.KeyboardButtonRequestPhone(text=self.text) elif self.request_location: - return KeyboardButtonRequestGeoLocation(text=self.text) + return raw.types.KeyboardButtonRequestGeoLocation(text=self.text) else: - return RawKeyboardButton(text=self.text) + return raw.types.KeyboardButton(text=self.text) diff --git a/pyrogram/client/types/bots_and_keyboards/reply_keyboard_markup.py b/pyrogram/types/bots_and_keyboards/reply_keyboard_markup.py similarity index 88% rename from pyrogram/client/types/bots_and_keyboards/reply_keyboard_markup.py rename to pyrogram/types/bots_and_keyboards/reply_keyboard_markup.py index f56087b95d..e4283c8bc4 100644 --- a/pyrogram/client/types/bots_and_keyboards/reply_keyboard_markup.py +++ b/pyrogram/types/bots_and_keyboards/reply_keyboard_markup.py @@ -18,10 +18,8 @@ from typing import List, Union -from pyrogram.api.types import KeyboardButtonRow -from pyrogram.api.types import ReplyKeyboardMarkup as RawReplyKeyboardMarkup - -from . import KeyboardButton +from pyrogram import raw +from pyrogram import types from ..object import Object @@ -29,7 +27,7 @@ class ReplyKeyboardMarkup(Object): """A custom keyboard with reply options. Parameters: - keyboard (List of List of :obj:`KeyboardButton`): + keyboard (List of List of :obj:`~pyrogram.types.KeyboardButton`): List of button rows, each represented by a List of KeyboardButton objects. resize_keyboard (``bool``, *optional*): @@ -52,7 +50,7 @@ class ReplyKeyboardMarkup(Object): def __init__( self, - keyboard: List[List[Union[KeyboardButton, str]]], + keyboard: List[List[Union["types.KeyboardButton", str]]], resize_keyboard: bool = None, one_time_keyboard: bool = None, selective: bool = None @@ -72,7 +70,7 @@ def read(kb): row = [] for j in i.buttons: - row.append(KeyboardButton.read(j)) + row.append(types.KeyboardButton.read(j)) keyboard.append(row) @@ -84,10 +82,10 @@ def read(kb): ) def write(self): - return RawReplyKeyboardMarkup( - rows=[KeyboardButtonRow( + return raw.types.ReplyKeyboardMarkup( + rows=[raw.types.KeyboardButtonRow( buttons=[ - KeyboardButton(j).write() + types.KeyboardButton(j).write() if isinstance(j, str) else j.write() for j in i ] diff --git a/pyrogram/client/types/bots_and_keyboards/reply_keyboard_remove.py b/pyrogram/types/bots_and_keyboards/reply_keyboard_remove.py similarity index 96% rename from pyrogram/client/types/bots_and_keyboards/reply_keyboard_remove.py rename to pyrogram/types/bots_and_keyboards/reply_keyboard_remove.py index 2143870a0d..f595d06d5d 100644 --- a/pyrogram/client/types/bots_and_keyboards/reply_keyboard_remove.py +++ b/pyrogram/types/bots_and_keyboards/reply_keyboard_remove.py @@ -16,8 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from pyrogram.api.types import ReplyKeyboardHide - +from pyrogram import raw from ..object import Object @@ -53,6 +52,6 @@ def read(o): ) def write(self): - return ReplyKeyboardHide( + return raw.types.ReplyKeyboardHide( selective=self.selective or None ) diff --git a/pyrogram/client/types/inline_mode/__init__.py b/pyrogram/types/inline_mode/__init__.py similarity index 100% rename from pyrogram/client/types/inline_mode/__init__.py rename to pyrogram/types/inline_mode/__init__.py index 4980377d4f..b532c2e909 100644 --- a/pyrogram/client/types/inline_mode/__init__.py +++ b/pyrogram/types/inline_mode/__init__.py @@ -16,12 +16,12 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from .chosen_inline_result import ChosenInlineResult from .inline_query import InlineQuery from .inline_query_result import InlineQueryResult from .inline_query_result_animation import InlineQueryResultAnimation from .inline_query_result_article import InlineQueryResultArticle from .inline_query_result_photo import InlineQueryResultPhoto -from .chosen_inline_result import ChosenInlineResult __all__ = [ "InlineQuery", "InlineQueryResult", "InlineQueryResultArticle", "InlineQueryResultPhoto", diff --git a/pyrogram/client/types/inline_mode/chosen_inline_result.py b/pyrogram/types/inline_mode/chosen_inline_result.py similarity index 67% rename from pyrogram/client/types/inline_mode/chosen_inline_result.py rename to pyrogram/types/inline_mode/chosen_inline_result.py index b7180b0b59..11185d9b9e 100644 --- a/pyrogram/client/types/inline_mode/chosen_inline_result.py +++ b/pyrogram/types/inline_mode/chosen_inline_result.py @@ -15,31 +15,15 @@ # # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -# -# 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 base64 import b64encode from struct import pack import pyrogram -from pyrogram.api import types -from pyrogram.client.types.object import Object -from pyrogram.client.types.update import Update -from pyrogram.client.types.user_and_chats import User -from pyrogram.client.types.messages_and_media import Location +from pyrogram import raw +from pyrogram import types +from ..object import Object +from ..update import Update class ChosenInlineResult(Object, Update): @@ -49,13 +33,13 @@ class ChosenInlineResult(Object, Update): result_id (``str``): The unique identifier for the result that was chosen. - from_user (:obj:`User`): + from_user (:obj:`~pyrogram.types.User`): The user that chose the result. query (``str``): The query that was used to obtain the result. - location (:obj:`Location`, *optional*): + location (:obj:`~pyrogram.types.Location`, *optional*): Sender location, only for bots that require user location. inline_message_id (``str``, *optional*): @@ -72,11 +56,11 @@ class ChosenInlineResult(Object, Update): def __init__( self, *, - client: "pyrogram.BaseClient" = None, + client: "pyrogram.Client" = None, result_id: str, - from_user: User, + from_user: "types.User", query: str, - location: "pyrogram.Location" = None, + location: "types.Location" = None, inline_message_id: str = None ): super().__init__(client) @@ -88,10 +72,10 @@ def __init__( self.inline_message_id = inline_message_id @staticmethod - def _parse(client, chosen_inline_result: types.UpdateBotInlineSend, users) -> "ChosenInlineResult": + def _parse(client, chosen_inline_result: raw.types.UpdateBotInlineSend, users) -> "ChosenInlineResult": inline_message_id = None - if isinstance(chosen_inline_result.msg_id, types.InputBotInlineMessageID): + if isinstance(chosen_inline_result.msg_id, raw.types.InputBotInlineMessageID): inline_message_id = b64encode( pack( " "C return ChosenInlineResult( result_id=str(chosen_inline_result.id), - from_user=User._parse(client, users[chosen_inline_result.user_id]), + from_user=types.User._parse(client, users[chosen_inline_result.user_id]), query=chosen_inline_result.query, - location=Location( + location=types.Location( longitude=chosen_inline_result.geo.long, latitude=chosen_inline_result.geo.lat, client=client diff --git a/pyrogram/client/types/inline_mode/inline_query.py b/pyrogram/types/inline_mode/inline_query.py similarity index 89% rename from pyrogram/client/types/inline_mode/inline_query.py rename to pyrogram/types/inline_mode/inline_query.py index c48bb05383..57bdffa059 100644 --- a/pyrogram/client/types/inline_mode/inline_query.py +++ b/pyrogram/types/inline_mode/inline_query.py @@ -19,12 +19,10 @@ from typing import List, Match import pyrogram -from pyrogram.api import types -from .inline_query_result import InlineQueryResult -from ..messages_and_media import Location +from pyrogram import raw +from pyrogram import types from ..object import Object from ..update import Update -from ..user_and_chats import User class InlineQuery(Object, Update): @@ -36,7 +34,7 @@ class InlineQuery(Object, Update): id (``str``): Unique identifier for this query. - from_user (:obj:`User`): + from_user (:obj:`~pyrogram.types.User`): Sender. query (``str``): @@ -45,7 +43,7 @@ class InlineQuery(Object, Update): offset (``str``): Offset of the results to be returned, can be controlled by the bot. - location (:obj:`Location`. *optional*): + location (:obj:`~pyrogram.types.Location`. *optional*): Sender location, only for bots that request user location. matches (List of regex Matches, *optional*): @@ -56,12 +54,12 @@ class InlineQuery(Object, Update): def __init__( self, *, - client: "pyrogram.BaseClient" = None, + client: "pyrogram.Client" = None, id: str, - from_user: User, + from_user: "types.User", query: str, offset: str, - location: Location = None, + location: "types.Location" = None, matches: List[Match] = None ): super().__init__(client) @@ -74,13 +72,13 @@ def __init__( self.matches = matches @staticmethod - def _parse(client, inline_query: types.UpdateBotInlineQuery, users: dict) -> "InlineQuery": + def _parse(client, inline_query: raw.types.UpdateBotInlineQuery, users: dict) -> "InlineQuery": return InlineQuery( id=str(inline_query.query_id), - from_user=User._parse(client, users[inline_query.user_id]), + from_user=types.User._parse(client, users[inline_query.user_id]), query=inline_query.query, offset=inline_query.offset, - location=Location( + location=types.Location( longitude=inline_query.geo.long, latitude=inline_query.geo.lat, client=client @@ -90,7 +88,7 @@ def _parse(client, inline_query: types.UpdateBotInlineQuery, users: dict) -> "In async def answer( self, - results: List[InlineQueryResult], + results: List["types.InlineQueryResult"], cache_time: int = 300, is_gallery: bool = False, is_personal: bool = False, @@ -98,7 +96,7 @@ async def answer( switch_pm_text: str = "", switch_pm_parameter: str = "" ): - """Bound method *answer* of :obj:`InlineQuery`. + """Bound method *answer* of :obj:`~pyrogram.types.InlineQuery`. Use this method as a shortcut for: @@ -115,7 +113,7 @@ async def answer( inline_query.answer([...]) Parameters: - results (List of :obj:`InlineQueryResult`): + results (List of :obj:`~pyrogram.types.InlineQueryResult`): A list of results for the inline query. cache_time (``int``, *optional*): diff --git a/pyrogram/types/inline_mode/inline_query_result.py b/pyrogram/types/inline_mode/inline_query_result.py new file mode 100644 index 0000000000..c70e52c0fd --- /dev/null +++ b/pyrogram/types/inline_mode/inline_query_result.py @@ -0,0 +1,70 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan +# +# 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 uuid import uuid4 + +from pyrogram import types +from ..object import Object + +"""- :obj:`~pyrogram.types.InlineQueryResultCachedAudio` + - :obj:`~pyrogram.types.InlineQueryResultCachedDocument` + - :obj:`~pyrogram.types.InlineQueryResultCachedGif` + - :obj:`~pyrogram.types.InlineQueryResultCachedMpeg4Gif` + - :obj:`~pyrogram.types.InlineQueryResultCachedPhoto` + - :obj:`~pyrogram.types.InlineQueryResultCachedSticker` + - :obj:`~pyrogram.types.InlineQueryResultCachedVideo` + - :obj:`~pyrogram.types.InlineQueryResultCachedVoice` + - :obj:`~pyrogram.types.InlineQueryResultAudio` + - :obj:`~pyrogram.types.InlineQueryResultContact` + - :obj:`~pyrogram.types.InlineQueryResultGame` + - :obj:`~pyrogram.types.InlineQueryResultDocument` + - :obj:`~pyrogram.types.InlineQueryResultGif` + - :obj:`~pyrogram.types.InlineQueryResultLocation` + - :obj:`~pyrogram.types.InlineQueryResultMpeg4Gif` + - :obj:`~pyrogram.types.InlineQueryResultPhoto` + - :obj:`~pyrogram.types.InlineQueryResultVenue` + - :obj:`~pyrogram.types.InlineQueryResultVideo` + - :obj:`~pyrogram.types.InlineQueryResultVoice`""" + + +class InlineQueryResult(Object): + """One result of an inline query. + + Pyrogram currently supports results of the following types: + + - :obj:`~pyrogram.types.InlineQueryResultArticle` + - :obj:`~pyrogram.types.InlineQueryResultPhoto` + - :obj:`~pyrogram.types.InlineQueryResultAnimation` + """ + + def __init__( + self, + type: str, + id: str, + input_message_content: "types.InputMessageContent", + reply_markup: "types.InlineKeyboardMarkup" + ): + super().__init__() + + self.type = type + self.id = str(uuid4()) if id is None else str(id) + self.input_message_content = input_message_content + self.reply_markup = reply_markup + + async def write(self): + pass diff --git a/pyrogram/client/types/inline_mode/inline_query_result_animation.py b/pyrogram/types/inline_mode/inline_query_result_animation.py similarity index 87% rename from pyrogram/client/types/inline_mode/inline_query_result_animation.py rename to pyrogram/types/inline_mode/inline_query_result_animation.py index d53bbd594e..bd0e7037e7 100644 --- a/pyrogram/client/types/inline_mode/inline_query_result_animation.py +++ b/pyrogram/types/inline_mode/inline_query_result_animation.py @@ -18,11 +18,10 @@ from typing import Union -from pyrogram.api import types +from pyrogram import raw +from pyrogram import types +from pyrogram.parser import Parser from .inline_query_result import InlineQueryResult -from ..bots_and_keyboards import InlineKeyboardMarkup -from ..input_message_content import InputMessageContent -from ...parser import Parser class InlineQueryResultAnimation(InlineQueryResult): @@ -61,10 +60,10 @@ class InlineQueryResultAnimation(InlineQueryResult): Pass "html" to enable HTML-style parsing only. Pass None to completely disable style parsing. - reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*): An InlineKeyboardMarkup object. - input_message_content (:obj:`InputMessageContent`): + input_message_content (:obj:`~pyrogram.types.InputMessageContent`): Content of the message to be sent instead of the photo. """ @@ -77,8 +76,8 @@ def __init__( description: str = None, caption: str = None, parse_mode: Union[str, None] = object, - reply_markup: InlineKeyboardMarkup = None, - input_message_content: InputMessageContent = None + reply_markup: "types.InlineKeyboardMarkup" = None, + input_message_content: "types.InputMessageContent" = None ): super().__init__("gif", id, input_message_content, reply_markup) @@ -92,7 +91,7 @@ def __init__( self.input_message_content = input_message_content async def write(self): - animation = types.InputWebDocument( + animation = raw.types.InputWebDocument( url=self.animation_url, size=0, mime_type="image/gif", @@ -102,14 +101,14 @@ async def write(self): if self.thumb_url is None: thumb = animation else: - thumb = types.InputWebDocument( + thumb = raw.types.InputWebDocument( url=self.thumb_url, size=0, mime_type="image/gif", attributes=[] ) - return types.InputBotInlineResult( + return raw.types.InputBotInlineResult( id=self.id, type=self.type, title=self.title, @@ -119,7 +118,7 @@ async def write(self): send_message=( self.input_message_content.write(self.reply_markup) if self.input_message_content - else types.InputBotInlineMessageMediaAuto( + else raw.types.InputBotInlineMessageMediaAuto( reply_markup=self.reply_markup.write() if self.reply_markup else None, **await(Parser(None)).parse(self.caption, self.parse_mode) ) diff --git a/pyrogram/client/types/inline_mode/inline_query_result_article.py b/pyrogram/types/inline_mode/inline_query_result_article.py similarity index 83% rename from pyrogram/client/types/inline_mode/inline_query_result_article.py rename to pyrogram/types/inline_mode/inline_query_result_article.py index 65e85a0fda..42bf6f0ab7 100644 --- a/pyrogram/client/types/inline_mode/inline_query_result_article.py +++ b/pyrogram/types/inline_mode/inline_query_result_article.py @@ -16,10 +16,10 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from pyrogram.api import types +from pyrogram import raw +from pyrogram import types + from .inline_query_result import InlineQueryResult -from ..bots_and_keyboards import InlineKeyboardMarkup -from ..input_message_content import InputMessageContent class InlineQueryResultArticle(InlineQueryResult): @@ -29,7 +29,7 @@ class InlineQueryResultArticle(InlineQueryResult): title (``str``): Title for the result. - input_message_content (:obj:`InputMessageContent`): + input_message_content (:obj:`~pyrogram.types.InputMessageContent`): Content of the message to be sent. id (``str``, *optional*): @@ -45,16 +45,16 @@ class InlineQueryResultArticle(InlineQueryResult): thumb_url (``str``, *optional*): URL of the thumbnail for the result. - reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*): Inline keyboard attached to the message. """ def __init__( self, title: str, - input_message_content: InputMessageContent, + input_message_content: "types.InputMessageContent", id: str = None, - reply_markup: InlineKeyboardMarkup = None, + reply_markup: "types.InlineKeyboardMarkup" = None, url: str = None, description: str = None, thumb_url: str = None @@ -67,14 +67,14 @@ def __init__( self.thumb_url = thumb_url async def write(self): - return types.InputBotInlineResult( + return raw.types.InputBotInlineResult( id=self.id, type=self.type, send_message=await self.input_message_content.write(self.reply_markup), title=self.title, description=self.description, url=self.url, - thumb=types.InputWebDocument( + thumb=raw.types.InputWebDocument( url=self.thumb_url, size=0, mime_type="image/jpeg", diff --git a/pyrogram/client/types/inline_mode/inline_query_result_photo.py b/pyrogram/types/inline_mode/inline_query_result_photo.py similarity index 86% rename from pyrogram/client/types/inline_mode/inline_query_result_photo.py rename to pyrogram/types/inline_mode/inline_query_result_photo.py index e3890b9bae..7287abc017 100644 --- a/pyrogram/client/types/inline_mode/inline_query_result_photo.py +++ b/pyrogram/types/inline_mode/inline_query_result_photo.py @@ -18,11 +18,10 @@ from typing import Union -from pyrogram.api import types +from pyrogram import raw +from pyrogram import types +from pyrogram.parser import Parser from .inline_query_result import InlineQueryResult -from ..bots_and_keyboards import InlineKeyboardMarkup -from ..input_message_content import InputMessageContent -from ...parser import Parser class InlineQueryResultPhoto(InlineQueryResult): @@ -61,10 +60,10 @@ class InlineQueryResultPhoto(InlineQueryResult): Pass "html" to enable HTML-style parsing only. Pass None to completely disable style parsing. - reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*): An InlineKeyboardMarkup object. - input_message_content (:obj:`InputMessageContent`): + input_message_content (:obj:`~pyrogram.types.InputMessageContent`): Content of the message to be sent instead of the photo. """ @@ -77,8 +76,8 @@ def __init__( description: str = None, caption: str = None, parse_mode: Union[str, None] = object, - reply_markup: InlineKeyboardMarkup = None, - input_message_content: InputMessageContent = None + reply_markup: "types.InlineKeyboardMarkup" = None, + input_message_content: "types.InputMessageContent" = None ): super().__init__("photo", id, input_message_content, reply_markup) @@ -92,7 +91,7 @@ def __init__( self.input_message_content = input_message_content async def write(self): - photo = types.InputWebDocument( + photo = raw.types.InputWebDocument( url=self.photo_url, size=0, mime_type="image/jpeg", @@ -102,14 +101,14 @@ async def write(self): if self.thumb_url is None: thumb = photo else: - thumb = types.InputWebDocument( + thumb = raw.types.InputWebDocument( url=self.thumb_url, size=0, mime_type="image/jpeg", attributes=[] ) - return types.InputBotInlineResult( + return raw.types.InputBotInlineResult( id=self.id, type=self.type, title=self.title, @@ -119,7 +118,7 @@ async def write(self): send_message=( await self.input_message_content.write(self.reply_markup) if self.input_message_content - else types.InputBotInlineMessageMediaAuto( + else raw.types.InputBotInlineMessageMediaAuto( reply_markup=self.reply_markup.write() if self.reply_markup else None, **await(Parser(None)).parse(self.caption, self.parse_mode) ) diff --git a/pyrogram/client/types/input_media/__init__.py b/pyrogram/types/input_media/__init__.py similarity index 100% rename from pyrogram/client/types/input_media/__init__.py rename to pyrogram/types/input_media/__init__.py diff --git a/pyrogram/client/types/input_media/input_media.py b/pyrogram/types/input_media/input_media.py similarity index 83% rename from pyrogram/client/types/input_media/input_media.py rename to pyrogram/types/input_media/input_media.py index 7d8497324c..f692cb8384 100644 --- a/pyrogram/client/types/input_media/input_media.py +++ b/pyrogram/types/input_media/input_media.py @@ -24,11 +24,11 @@ class InputMedia(Object): It should be one of: - - :obj:`InputMediaAnimation` - - :obj:`InputMediaDocument` - - :obj:`InputMediaAudio` - - :obj:`InputMediaPhoto` - - :obj:`InputMediaVideo` + - :obj:`~pyrogram.types.InputMediaAnimation` + - :obj:`~pyrogram.types.InputMediaDocument` + - :obj:`~pyrogram.types.InputMediaAudio` + - :obj:`~pyrogram.types.InputMediaPhoto` + - :obj:`~pyrogram.types.InputMediaVideo` """ def __init__(self, media: str, file_ref: str, caption: str, parse_mode: str): diff --git a/pyrogram/client/types/input_media/input_media_animation.py b/pyrogram/types/input_media/input_media_animation.py similarity index 98% rename from pyrogram/client/types/input_media/input_media_animation.py rename to pyrogram/types/input_media/input_media_animation.py index ee74668996..786a4ee012 100644 --- a/pyrogram/client/types/input_media/input_media_animation.py +++ b/pyrogram/types/input_media/input_media_animation.py @@ -18,7 +18,7 @@ from typing import Union -from . import InputMedia +from .input_media import InputMedia class InputMediaAnimation(InputMedia): diff --git a/pyrogram/client/types/input_media/input_media_audio.py b/pyrogram/types/input_media/input_media_audio.py similarity index 94% rename from pyrogram/client/types/input_media/input_media_audio.py rename to pyrogram/types/input_media/input_media_audio.py index 66cd9e1fb4..a5ae9fca4b 100644 --- a/pyrogram/client/types/input_media/input_media_audio.py +++ b/pyrogram/types/input_media/input_media_audio.py @@ -18,13 +18,13 @@ from typing import Union -from . import InputMedia +from .input_media import InputMedia class InputMediaAudio(InputMedia): """An audio to be sent inside an album. - It is intended to be used with :obj:`send_media_group() `. + It is intended to be used with :meth:`~pyrogram.Client.send_media_group`. Parameters: media (``str``): @@ -70,7 +70,7 @@ def __init__( caption: str = "", parse_mode: Union[str, None] = object, duration: int = 0, - performer: int = "", + performer: str = "", title: str = "" ): super().__init__(media, file_ref, caption, parse_mode) diff --git a/pyrogram/client/types/input_media/input_media_document.py b/pyrogram/types/input_media/input_media_document.py similarity index 98% rename from pyrogram/client/types/input_media/input_media_document.py rename to pyrogram/types/input_media/input_media_document.py index 7ff3ca0b2e..1df2c3c329 100644 --- a/pyrogram/client/types/input_media/input_media_document.py +++ b/pyrogram/types/input_media/input_media_document.py @@ -18,7 +18,7 @@ from typing import Union -from . import InputMedia +from .input_media import InputMedia class InputMediaDocument(InputMedia): diff --git a/pyrogram/client/types/input_media/input_media_photo.py b/pyrogram/types/input_media/input_media_photo.py similarity index 94% rename from pyrogram/client/types/input_media/input_media_photo.py rename to pyrogram/types/input_media/input_media_photo.py index f40bdef76c..f4e5df8e45 100644 --- a/pyrogram/client/types/input_media/input_media_photo.py +++ b/pyrogram/types/input_media/input_media_photo.py @@ -18,12 +18,12 @@ from typing import Union -from . import InputMedia +from .input_media import InputMedia class InputMediaPhoto(InputMedia): """A photo to be sent inside an album. - It is intended to be used with :obj:`send_media_group() `. + It is intended to be used with :obj:`~pyrogram.Client.send_media_group`. Parameters: media (``str``): diff --git a/pyrogram/client/types/input_media/input_media_video.py b/pyrogram/types/input_media/input_media_video.py similarity index 96% rename from pyrogram/client/types/input_media/input_media_video.py rename to pyrogram/types/input_media/input_media_video.py index 6b986a32e9..dc65d00296 100644 --- a/pyrogram/client/types/input_media/input_media_video.py +++ b/pyrogram/types/input_media/input_media_video.py @@ -18,12 +18,12 @@ from typing import Union -from . import InputMedia +from .input_media import InputMedia class InputMediaVideo(InputMedia): """A video to be sent inside an album. - It is intended to be used with :obj:`send_media_group() `. + It is intended to be used with :obj:`~pyrogram.Client.send_media_group`. Parameters: media (``str``): diff --git a/pyrogram/client/types/input_media/input_phone_contact.py b/pyrogram/types/input_media/input_phone_contact.py similarity index 93% rename from pyrogram/client/types/input_media/input_phone_contact.py rename to pyrogram/types/input_media/input_phone_contact.py index e2c3aca142..58c73252c0 100644 --- a/pyrogram/client/types/input_media/input_phone_contact.py +++ b/pyrogram/types/input_media/input_phone_contact.py @@ -16,8 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from pyrogram.api.types import InputPhoneContact as RawInputPhoneContact - +from pyrogram import raw from pyrogram.session.internals import MsgId from ..object import Object @@ -44,7 +43,7 @@ def __new__(cls, phone: str, first_name: str, last_name: str = ""): - return RawInputPhoneContact( + return raw.types.InputPhoneContact( client_id=MsgId(), phone="+" + phone.strip("+"), first_name=first_name, diff --git a/pyrogram/client/types/input_message_content/__init__.py b/pyrogram/types/input_message_content/__init__.py similarity index 100% rename from pyrogram/client/types/input_message_content/__init__.py rename to pyrogram/types/input_message_content/__init__.py diff --git a/pyrogram/client/types/input_message_content/input_message_content.py b/pyrogram/types/input_message_content/input_message_content.py similarity index 83% rename from pyrogram/client/types/input_message_content/input_message_content.py rename to pyrogram/types/input_message_content/input_message_content.py index f97e3cd9ad..4ecbc0a604 100644 --- a/pyrogram/client/types/input_message_content/input_message_content.py +++ b/pyrogram/types/input_message_content/input_message_content.py @@ -18,9 +18,9 @@ from ..object import Object -"""- :obj:`InputLocationMessageContent` - - :obj:`InputVenueMessageContent` - - :obj:`InputContactMessageContent`""" +"""- :obj:`~pyrogram.types.InputLocationMessageContent` + - :obj:`~pyrogram.types.InputVenueMessageContent` + - :obj:`~pyrogram.types.InputContactMessageContent`""" class InputMessageContent(Object): @@ -28,7 +28,7 @@ class InputMessageContent(Object): Pyrogram currently supports the following types: - - :obj:`InputTextMessageContent` + - :obj:`~pyrogram.types.InputTextMessageContent` """ def __init__(self): diff --git a/pyrogram/client/types/input_message_content/input_text_message_content.py b/pyrogram/types/input_message_content/input_text_message_content.py similarity index 95% rename from pyrogram/client/types/input_message_content/input_text_message_content.py rename to pyrogram/types/input_message_content/input_text_message_content.py index 1247011ea2..ac584cc202 100644 --- a/pyrogram/client/types/input_message_content/input_text_message_content.py +++ b/pyrogram/types/input_message_content/input_text_message_content.py @@ -18,9 +18,9 @@ from typing import Union -from pyrogram.api import types +from pyrogram import raw +from pyrogram.parser import Parser from .input_message_content import InputMessageContent -from ...parser import Parser class InputTextMessageContent(InputMessageContent): @@ -49,7 +49,7 @@ def __init__(self, message_text: str, parse_mode: Union[str, None] = object, dis self.disable_web_page_preview = disable_web_page_preview async def write(self, reply_markup): - return types.InputBotInlineMessageText( + return raw.types.InputBotInlineMessageText( no_webpage=self.disable_web_page_preview or None, reply_markup=reply_markup.write() if reply_markup else None, **await(Parser(None)).parse(self.message_text, self.parse_mode) diff --git a/pyrogram/client/types/list.py b/pyrogram/types/list.py similarity index 88% rename from pyrogram/client/types/list.py rename to pyrogram/types/list.py index 118c4304f3..2bdc425b56 100644 --- a/pyrogram/client/types/list.py +++ b/pyrogram/types/list.py @@ -27,6 +27,4 @@ def __str__(self): return Object.__str__(self) def __repr__(self): - return "pyrogram.client.types.list.List([{}])".format( - ",".join(Object.__repr__(i) for i in self) - ) + return f"pyrogram.types.List([{','.join(Object.__repr__(i) for i in self)}])" diff --git a/pyrogram/client/types/messages_and_media/__init__.py b/pyrogram/types/messages_and_media/__init__.py similarity index 100% rename from pyrogram/client/types/messages_and_media/__init__.py rename to pyrogram/types/messages_and_media/__init__.py index d24240436b..aeae6375fa 100644 --- a/pyrogram/client/types/messages_and_media/__init__.py +++ b/pyrogram/types/messages_and_media/__init__.py @@ -19,6 +19,7 @@ from .animation import Animation from .audio import Audio from .contact import Contact +from .dice import Dice from .document import Document from .game import Game from .location import Location @@ -35,7 +36,6 @@ from .video_note import VideoNote from .voice import Voice from .webpage import WebPage -from .dice import Dice __all__ = [ "Animation", "Audio", "Contact", "Document", "Game", "Location", "Message", "MessageEntity", "Photo", "Thumbnail", diff --git a/pyrogram/client/types/messages_and_media/animation.py b/pyrogram/types/messages_and_media/animation.py similarity index 88% rename from pyrogram/client/types/messages_and_media/animation.py rename to pyrogram/types/messages_and_media/animation.py index 497f943e2e..97f34e967d 100644 --- a/pyrogram/client/types/messages_and_media/animation.py +++ b/pyrogram/types/messages_and_media/animation.py @@ -20,10 +20,10 @@ from typing import List import pyrogram -from pyrogram.api import types -from .thumbnail import Thumbnail +from pyrogram import raw +from pyrogram import types +from pyrogram.utils import encode_file_id, encode_file_ref from ..object import Object -from ...ext.utils import encode_file_id, encode_file_ref class Animation(Object): @@ -57,14 +57,14 @@ class Animation(Object): date (``int``, *optional*): Date the animation was sent in Unix time. - thumbs (List of :obj:`Thumbnail`, *optional*): + thumbs (List of :obj:`~pyrogram.types.Thumbnail`, *optional*): Animation thumbnails. """ def __init__( self, *, - client: "pyrogram.BaseClient" = None, + client: "pyrogram.Client" = None, file_id: str, file_ref: str, width: int, @@ -74,7 +74,7 @@ def __init__( mime_type: str = None, file_size: int = None, date: int = None, - thumbs: List[Thumbnail] = None + thumbs: List["types.Thumbnail"] = None ): super().__init__(client) @@ -92,8 +92,8 @@ def __init__( @staticmethod def _parse( client, - animation: types.Document, - video_attributes: types.DocumentAttributeVideo, + animation: "raw.types.Document", + video_attributes: "raw.types.DocumentAttributeVideo", file_name: str ) -> "Animation": return Animation( @@ -114,6 +114,6 @@ def _parse( file_size=animation.size, file_name=file_name, date=animation.date, - thumbs=Thumbnail._parse(client, animation), + thumbs=types.Thumbnail._parse(client, animation), client=client ) diff --git a/pyrogram/client/types/messages_and_media/audio.py b/pyrogram/types/messages_and_media/audio.py similarity index 88% rename from pyrogram/client/types/messages_and_media/audio.py rename to pyrogram/types/messages_and_media/audio.py index bd36012635..a54b6c9fb6 100644 --- a/pyrogram/client/types/messages_and_media/audio.py +++ b/pyrogram/types/messages_and_media/audio.py @@ -20,10 +20,10 @@ from typing import List import pyrogram -from pyrogram.api import types -from .thumbnail import Thumbnail +from pyrogram import raw +from pyrogram import types +from pyrogram.utils import encode_file_id, encode_file_ref from ..object import Object -from ...ext.utils import encode_file_id, encode_file_ref class Audio(Object): @@ -57,14 +57,14 @@ class Audio(Object): title (``str``, *optional*): Title of the audio as defined by sender or by audio tags. - thumbs (List of :obj:`Thumbnail`, *optional*): + thumbs (List of :obj:`~pyrogram.types.Thumbnail`, *optional*): Thumbnails of the music file album cover. """ def __init__( self, *, - client: "pyrogram.BaseClient" = None, + client: "pyrogram.Client" = None, file_id: str, file_ref: str, duration: int, @@ -74,7 +74,7 @@ def __init__( date: int = None, performer: str = None, title: str = None, - thumbs: List[Thumbnail] = None + thumbs: List["types.Thumbnail"] = None ): super().__init__(client) @@ -92,8 +92,8 @@ def __init__( @staticmethod def _parse( client, - audio: types.Document, - audio_attributes: types.DocumentAttributeAudio, + audio: "raw.types.Document", + audio_attributes: "raw.types.DocumentAttributeAudio", file_name: str ) -> "Audio": return Audio( @@ -114,6 +114,6 @@ def _parse( file_size=audio.size, file_name=file_name, date=audio.date, - thumbs=Thumbnail._parse(client, audio), + thumbs=types.Thumbnail._parse(client, audio), client=client ) diff --git a/pyrogram/client/types/messages_and_media/contact.py b/pyrogram/types/messages_and_media/contact.py similarity index 92% rename from pyrogram/client/types/messages_and_media/contact.py rename to pyrogram/types/messages_and_media/contact.py index c2289ee144..8ce25d4033 100644 --- a/pyrogram/client/types/messages_and_media/contact.py +++ b/pyrogram/types/messages_and_media/contact.py @@ -17,8 +17,7 @@ # along with Pyrogram. If not, see . import pyrogram - -from pyrogram.api import types +from pyrogram import raw from ..object import Object @@ -45,7 +44,7 @@ class Contact(Object): def __init__( self, *, - client: "pyrogram.BaseClient" = None, + client: "pyrogram.Client" = None, phone_number: str, first_name: str, last_name: str = None, @@ -61,7 +60,7 @@ def __init__( self.vcard = vcard @staticmethod - def _parse(client, contact: types.MessageMediaContact) -> "Contact": + def _parse(client: "pyrogram.Client", contact: "raw.types.MessageMediaContact") -> "Contact": return Contact( phone_number=contact.phone_number, first_name=contact.first_name, diff --git a/pyrogram/client/types/messages_and_media/dice.py b/pyrogram/types/messages_and_media/dice.py similarity index 88% rename from pyrogram/client/types/messages_and_media/dice.py rename to pyrogram/types/messages_and_media/dice.py index e76cad1efc..e89fb38b40 100644 --- a/pyrogram/client/types/messages_and_media/dice.py +++ b/pyrogram/types/messages_and_media/dice.py @@ -17,7 +17,7 @@ # along with Pyrogram. If not, see . import pyrogram -from pyrogram.api import types +from pyrogram import raw from ..object import Object @@ -32,14 +32,14 @@ class Dice(Object): Value of the dice, 1-6 for currently supported base emoji. """ - def __init__(self, *, client: "pyrogram.BaseClient" = None, emoji: str, value: int): + def __init__(self, *, client: "pyrogram.Client" = None, emoji: str, value: int): super().__init__(client) self.emoji = emoji self.value = value @staticmethod - def _parse(client, dice: types.MessageMediaDice) -> "Dice": + def _parse(client, dice: "raw.types.MessageMediaDice") -> "Dice": return Dice( emoji=dice.emoticon, value=dice.value, diff --git a/pyrogram/client/types/messages_and_media/document.py b/pyrogram/types/messages_and_media/document.py similarity index 86% rename from pyrogram/client/types/messages_and_media/document.py rename to pyrogram/types/messages_and_media/document.py index ec6a5edc07..19975b74ef 100644 --- a/pyrogram/client/types/messages_and_media/document.py +++ b/pyrogram/types/messages_and_media/document.py @@ -20,10 +20,10 @@ from typing import List import pyrogram -from pyrogram.api import types -from .thumbnail import Thumbnail +from pyrogram import raw +from pyrogram import types +from pyrogram.utils import encode_file_id, encode_file_ref from ..object import Object -from ...ext.utils import encode_file_id, encode_file_ref class Document(Object): @@ -48,21 +48,21 @@ class Document(Object): date (``int``, *optional*): Date the document was sent in Unix time. - thumbs (List of :obj:`Thumbnail`, *optional*): + thumbs (List of :obj:`~pyrogram.types.Thumbnail`, *optional*): Document thumbnails as defined by sender. """ def __init__( self, *, - client: "pyrogram.BaseClient" = None, + client: "pyrogram.Client" = None, file_id: str, file_ref: str, file_name: str = None, mime_type: str = None, file_size: int = None, date: int = None, - thumbs: List[Thumbnail] = None + thumbs: List["types.Thumbnail"] = None ): super().__init__(client) @@ -75,7 +75,7 @@ def __init__( self.thumbs = thumbs @staticmethod - def _parse(client, document: types.Document, file_name: str) -> "Document": + def _parse(client, document: "raw.types.Document", file_name: str) -> "Document": return Document( file_id=encode_file_id( pack( @@ -91,6 +91,6 @@ def _parse(client, document: types.Document, file_name: str) -> "Document": mime_type=document.mime_type, file_size=document.size, date=document.date, - thumbs=Thumbnail._parse(client, document), + thumbs=types.Thumbnail._parse(client, document), client=client ) diff --git a/pyrogram/client/types/messages_and_media/game.py b/pyrogram/types/messages_and_media/game.py similarity index 79% rename from pyrogram/client/types/messages_and_media/game.py rename to pyrogram/types/messages_and_media/game.py index f828a2707e..a80888a2c5 100644 --- a/pyrogram/client/types/messages_and_media/game.py +++ b/pyrogram/types/messages_and_media/game.py @@ -17,9 +17,8 @@ # along with Pyrogram. If not, see . import pyrogram -from pyrogram.api import types -from .animation import Animation -from .photo import Photo +from pyrogram import raw +from pyrogram import types from ..object import Object @@ -40,10 +39,10 @@ class Game(Object): description (``str``): Description of the game. - photo (:obj:`Photo`): + photo (:obj:`~pyrogram.types.Photo`): Photo that will be displayed in the game message in chats. - animation (:obj:`Animation`, *optional*): + animation (:obj:`~pyrogram.types.Animation`, *optional*): Animation that will be displayed in the game message in chats. Upload via BotFather. """ @@ -51,13 +50,13 @@ class Game(Object): def __init__( self, *, - client: "pyrogram.BaseClient" = None, + client: "pyrogram.Client" = None, id: int, title: str, short_name: str, description: str, - photo: Photo, - animation: Animation = None + photo: "types.Photo", + animation: "types.Animation" = None ): super().__init__(client) @@ -69,8 +68,8 @@ def __init__( self.animation = animation @staticmethod - def _parse(client, message: types.Message) -> "Game": - game = message.media.game # type: types.Game + def _parse(client, message: "raw.types.Message") -> "Game": + game: "raw.types.Game" = message.media.game animation = None if game.document: @@ -78,14 +77,14 @@ def _parse(client, message: types.Message) -> "Game": file_name = getattr( attributes.get( - types.DocumentAttributeFilename, None + raw.types.DocumentAttributeFilename, None ), "file_name", None ) - animation = Animation._parse( + animation = types.Animation._parse( client, game.document, - attributes.get(types.DocumentAttributeVideo, None), + attributes.get(raw.types.DocumentAttributeVideo, None), file_name ) @@ -94,7 +93,7 @@ def _parse(client, message: types.Message) -> "Game": title=game.title, short_name=game.short_name, description=game.description, - photo=Photo._parse(client, game.photo), + photo=types.Photo._parse(client, game.photo), animation=animation, client=client ) diff --git a/pyrogram/client/types/messages_and_media/location.py b/pyrogram/types/messages_and_media/location.py similarity index 88% rename from pyrogram/client/types/messages_and_media/location.py rename to pyrogram/types/messages_and_media/location.py index 38af642f83..ac122a6d0e 100644 --- a/pyrogram/client/types/messages_and_media/location.py +++ b/pyrogram/types/messages_and_media/location.py @@ -18,7 +18,7 @@ import pyrogram -from pyrogram.api import types +from pyrogram import raw from ..object import Object @@ -36,7 +36,7 @@ class Location(Object): def __init__( self, *, - client: "pyrogram.BaseClient" = None, + client: "pyrogram.Client" = None, longitude: float, latitude: float ): @@ -46,8 +46,8 @@ def __init__( self.latitude = latitude @staticmethod - def _parse(client, geo_point: types.GeoPoint) -> "Location": - if isinstance(geo_point, types.GeoPoint): + def _parse(client, geo_point: "raw.types.GeoPoint") -> "Location": + if isinstance(geo_point, raw.types.GeoPoint): return Location( longitude=geo_point.long, latitude=geo_point.lat, diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py similarity index 83% rename from pyrogram/client/types/messages_and_media/message.py rename to pyrogram/types/messages_and_media/message.py index 8bf7cac4db..51bb4293d5 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -20,19 +20,13 @@ from typing import List, Match, Union, BinaryIO import pyrogram -from pyrogram.api import types -from pyrogram.client.types.input_media import InputMedia +from pyrogram import raw +from pyrogram import types +from pyrogram import utils from pyrogram.errors import MessageIdsEmpty -from .contact import Contact -from .location import Location -from .message_entity import MessageEntity -from ..messages_and_media.photo import Photo +from pyrogram.parser import utils as parser_utils, Parser from ..object import Object from ..update import Update -from ..user_and_chats.chat import Chat -from ..user_and_chats.user import User -from ...ext import utils -from ...parser import utils as parser_utils, Parser class Str(str): @@ -68,19 +62,19 @@ class Message(Object, Update): date (``int``, *optional*): Date the message was sent in Unix time. - chat (:obj:`Chat`, *optional*): + chat (:obj:`~pyrogram.types.Chat`, *optional*): Conversation the message belongs to. - from_user (:obj:`User`, *optional*): + from_user (:obj:`~pyrogram.types.User`, *optional*): Sender, empty for messages sent to channels. - forward_from (:obj:`User`, *optional*): + forward_from (:obj:`~pyrogram.types.User`, *optional*): For forwarded messages, sender of the original message. forward_sender_name (``str``, *optional*): For messages forwarded from users who have hidden their accounts, name of the user. - forward_from_chat (:obj:`Chat`, *optional*): + forward_from_chat (:obj:`~pyrogram.types.Chat`, *optional*): For messages forwarded from channels, information about the original channel. forward_from_message_id (``int``, *optional*): @@ -92,7 +86,7 @@ class Message(Object, Update): forward_date (``int``, *optional*): For forwarded messages, date the original message was sent in Unix time. - reply_to_message (:obj:`Message`, *optional*): + reply_to_message (:obj:`~pyrogram.types.Message`, *optional*): For replies, the original message. Note that the Message object in this field will not contain further reply_to_message fields even if it itself is a reply. @@ -129,38 +123,38 @@ class Message(Object, Update): *text.html* to get the marked up message text. In case there is no entity, the fields will contain the same text as *text*. - entities (List of :obj:`MessageEntity`, *optional*): + entities (List of :obj:`~pyrogram.types.MessageEntity`, *optional*): For text messages, special entities like usernames, URLs, bot commands, etc. that appear in the text. - caption_entities (List of :obj:`MessageEntity`, *optional*): + caption_entities (List of :obj:`~pyrogram.types.MessageEntity`, *optional*): For messages with a caption, special entities like usernames, URLs, bot commands, etc. that appear in the caption. - audio (:obj:`Audio`, *optional*): + audio (:obj:`~pyrogram.types.Audio`, *optional*): Message is an audio file, information about the file. - document (:obj:`Document`, *optional*): + document (:obj:`~pyrogram.types.Document`, *optional*): Message is a general file, information about the file. - photo (:obj:`Photo`, *optional*): + photo (:obj:`~pyrogram.types.Photo`, *optional*): Message is a photo, information about the photo. - sticker (:obj:`Sticker`, *optional*): + sticker (:obj:`~pyrogram.types.Sticker`, *optional*): Message is a sticker, information about the sticker. - animation (:obj:`Animation`, *optional*): + animation (:obj:`~pyrogram.types.Animation`, *optional*): Message is an animation, information about the animation. - game (:obj:`Game`, *optional*): + game (:obj:`~pyrogram.types.Game`, *optional*): Message is a game, information about the game. - video (:obj:`Video`, *optional*): + video (:obj:`~pyrogram.types.Video`, *optional*): Message is a video, information about the video. - voice (:obj:`Voice`, *optional*): + voice (:obj:`~pyrogram.types.Voice`, *optional*): Message is a voice message, information about the file. - video_note (:obj:`VideoNote`, *optional*): + video_note (:obj:`~pyrogram.types.VideoNote`, *optional*): Message is a video note, information about the video message. caption (``str``, *optional*): @@ -169,35 +163,35 @@ class Message(Object, Update): *caption.html* to get the marked up caption text. In case there is no caption entity, the fields will contain the same text as *caption*. - contact (:obj:`Contact`, *optional*): + contact (:obj:`~pyrogram.types.Contact`, *optional*): Message is a shared contact, information about the contact. - location (:obj:`Location`, *optional*): + location (:obj:`~pyrogram.types.Location`, *optional*): Message is a shared location, information about the location. - venue (:obj:`Venue`, *optional*): + venue (:obj:`~pyrogram.types.Venue`, *optional*): Message is a venue, information about the venue. - web_page (:obj:`WebPage`, *optional*): + web_page (:obj:`~pyrogram.types.WebPage`, *optional*): Message was sent with a webpage preview. - poll (:obj:`Poll`, *optional*): + poll (:obj:`~pyrogram.types.Poll`, *optional*): Message is a native poll, information about the poll. - dice (:obj:`Dice`, *optional*): + dice (:obj:`~pyrogram.types.Dice`, *optional*): A dice containing a value that is randomly generated by Telegram. - new_chat_members (List of :obj:`User`, *optional*): + new_chat_members (List of :obj:`~pyrogram.types.User`, *optional*): New members that were added to the group or supergroup and information about them (the bot itself may be one of these members). - left_chat_member (:obj:`User`, *optional*): + left_chat_member (:obj:`~pyrogram.types.User`, *optional*): A member was removed from the group, information about them (this member may be the bot itself). new_chat_title (``str``, *optional*): A chat title was changed to this value. - new_chat_photo (:obj:`Photo`, *optional*): + new_chat_photo (:obj:`~pyrogram.types.Photo`, *optional*): A chat photo was change to this value. delete_chat_photo (``bool``, *optional*): @@ -230,19 +224,19 @@ class Message(Object, Update): in interpreting it. But it is smaller than 52 bits, so a signed 64 bit integer or double-precision float type are safe for storing this identifier. - pinned_message (:obj:`Message`, *optional*): + pinned_message (:obj:`~pyrogram.types.Message`, *optional*): Specified message was pinned. Note that the Message object in this field will not contain further reply_to_message fields even if it is itself a reply. - game_high_score (:obj:`GameHighScore`, *optional*): + game_high_score (:obj:`~pyrogram.types.GameHighScore`, *optional*): The game score for a user. The reply_to_message field will contain the game Message. views (``int``, *optional*): Channel post views. - via_bot (:obj:`User`): + via_bot (:obj:`~pyrogram.types.User`): The information of the bot that generated the message from an inline query of a user. outgoing (``bool``, *optional*): @@ -261,9 +255,9 @@ class Message(Object, Update): command (List of ``str``, *optional*): A list containing the command and its arguments, if any. E.g.: "/start 1 2 3" would produce ["start", "1", "2", "3"]. - Only applicable when using :obj:`Filters.command `. + Only applicable when using :obj:`~pyrogram.filters.command`. - reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. """ @@ -273,14 +267,14 @@ class Message(Object, Update): def __init__( self, *, - client: "pyrogram.BaseClient" = None, + client: "pyrogram.Client" = None, message_id: int, date: int = None, - chat: Chat = None, - from_user: User = None, - forward_from: User = None, + chat: "types.Chat" = None, + from_user: "types.User" = None, + forward_from: "types.User" = None, forward_sender_name: str = None, - forward_from_chat: Chat = None, + forward_from_chat: "types.Chat" = None, forward_from_message_id: int = None, forward_signature: str = None, forward_date: int = None, @@ -295,28 +289,28 @@ def __init__( media_group_id: str = None, author_signature: str = None, text: Str = None, - entities: List["pyrogram.MessageEntity"] = None, - caption_entities: List["pyrogram.MessageEntity"] = None, - audio: "pyrogram.Audio" = None, - document: "pyrogram.Document" = None, - photo: "pyrogram.Photo" = None, - sticker: "pyrogram.Sticker" = None, - animation: "pyrogram.Animation" = None, - game: "pyrogram.Game" = None, - video: "pyrogram.Video" = None, - voice: "pyrogram.Voice" = None, - video_note: "pyrogram.VideoNote" = None, + entities: List["types.MessageEntity"] = None, + caption_entities: List["types.MessageEntity"] = None, + audio: "types.Audio" = None, + document: "types.Document" = None, + photo: "types.Photo" = None, + sticker: "types.Sticker" = None, + animation: "types.Animation" = None, + game: "types.Game" = None, + video: "types.Video" = None, + voice: "types.Voice" = None, + video_note: "types.VideoNote" = None, caption: Str = None, - contact: "pyrogram.Contact" = None, - location: "pyrogram.Location" = None, - venue: "pyrogram.Venue" = None, - web_page: "pyrogram.WebPage" = None, - poll: "pyrogram.Poll" = None, - dice: "pyrogram.Dice" = None, - new_chat_members: List[User] = None, - left_chat_member: User = None, + contact: "types.Contact" = None, + location: "types.Location" = None, + venue: "types.Venue" = None, + web_page: "types.WebPage" = None, + poll: "types.Poll" = None, + dice: "types.Dice" = None, + new_chat_members: List["types.User"] = None, + left_chat_member: "types.User" = None, new_chat_title: str = None, - new_chat_photo: "pyrogram.Photo" = None, + new_chat_photo: "types.Photo" = None, delete_chat_photo: bool = None, group_chat_created: bool = None, supergroup_chat_created: bool = None, @@ -326,15 +320,15 @@ def __init__( pinned_message: "Message" = None, game_high_score: int = None, views: int = None, - via_bot: User = None, + via_bot: "types.User" = None, outgoing: bool = None, matches: List[Match] = None, command: List[str] = None, reply_markup: Union[ - "pyrogram.InlineKeyboardMarkup", - "pyrogram.ReplyKeyboardMarkup", - "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply" + "types.InlineKeyboardMarkup", + "types.ReplyKeyboardMarkup", + "types.ReplyKeyboardRemove", + "types.ForceReply" ] = None ): super().__init__(client) @@ -398,12 +392,18 @@ def __init__( self.reply_markup = reply_markup @staticmethod - async def _parse(client, message: types.Message or types.MessageService or types.MessageEmpty, users: dict, - chats: dict, is_scheduled: bool = False, replies: int = 1): - if isinstance(message, types.MessageEmpty): + async def _parse( + client, + message: raw.base.Message, + users: dict, + chats: dict, + is_scheduled: bool = False, + replies: int = 1 + ): + if isinstance(message, raw.types.MessageEmpty): return Message(message_id=message.id, empty=True, client=client) - if isinstance(message, types.MessageService): + if isinstance(message, raw.types.MessageService): action = message.action new_chat_members = None @@ -416,32 +416,32 @@ async def _parse(client, message: types.Message or types.MessageService or types channel_chat_created = None new_chat_photo = None - if isinstance(action, types.MessageActionChatAddUser): - new_chat_members = [User._parse(client, users[i]) for i in action.users] - elif isinstance(action, types.MessageActionChatJoinedByLink): - new_chat_members = [User._parse(client, users[message.from_id])] - elif isinstance(action, types.MessageActionChatDeleteUser): - left_chat_member = User._parse(client, users[action.user_id]) - elif isinstance(action, types.MessageActionChatEditTitle): + if isinstance(action, raw.types.MessageActionChatAddUser): + new_chat_members = [types.User._parse(client, users[i]) for i in action.users] + elif isinstance(action, raw.types.MessageActionChatJoinedByLink): + new_chat_members = [types.User._parse(client, users[message.from_id])] + elif isinstance(action, raw.types.MessageActionChatDeleteUser): + left_chat_member = types.User._parse(client, users[action.user_id]) + elif isinstance(action, raw.types.MessageActionChatEditTitle): new_chat_title = action.title - elif isinstance(action, types.MessageActionChatDeletePhoto): + elif isinstance(action, raw.types.MessageActionChatDeletePhoto): delete_chat_photo = True - elif isinstance(action, types.MessageActionChatMigrateTo): + elif isinstance(action, raw.types.MessageActionChatMigrateTo): migrate_to_chat_id = action.channel_id - elif isinstance(action, types.MessageActionChannelMigrateFrom): + elif isinstance(action, raw.types.MessageActionChannelMigrateFrom): migrate_from_chat_id = action.chat_id - elif isinstance(action, types.MessageActionChatCreate): + elif isinstance(action, raw.types.MessageActionChatCreate): group_chat_created = True - elif isinstance(action, types.MessageActionChannelCreate): + elif isinstance(action, raw.types.MessageActionChannelCreate): channel_chat_created = True - elif isinstance(action, types.MessageActionChatEditPhoto): - new_chat_photo = Photo._parse(client, action.photo) + elif isinstance(action, raw.types.MessageActionChatEditPhoto): + new_chat_photo = types.Photo._parse(client, action.photo) parsed_message = Message( message_id=message.id, date=message.date, - chat=Chat._parse(client, message, users, chats), - from_user=User._parse(client, users.get(message.from_id, None)), + chat=types.Chat._parse(client, message, users, chats), + from_user=types.User._parse(client, users.get(message.from_id, None)), service=True, new_chat_members=new_chat_members, left_chat_member=left_chat_member, @@ -456,7 +456,7 @@ async def _parse(client, message: types.Message or types.MessageService or types # TODO: supergroup_chat_created ) - if isinstance(action, types.MessageActionPinMessage): + if isinstance(action, raw.types.MessageActionPinMessage): try: parsed_message.pinned_message = await client.get_messages( parsed_message.chat.id, @@ -466,8 +466,8 @@ async def _parse(client, message: types.Message or types.MessageService or types except MessageIdsEmpty: pass - if isinstance(action, types.MessageActionGameScore): - parsed_message.game_high_score = pyrogram.GameHighScore._parse_action(client, message, users) + if isinstance(action, raw.types.MessageActionGameScore): + parsed_message.game_high_score = types.GameHighScore._parse_action(client, message, users) if message.reply_to_msg_id and replies: try: @@ -481,9 +481,9 @@ async def _parse(client, message: types.Message or types.MessageService or types return parsed_message - if isinstance(message, types.Message): - entities = [MessageEntity._parse(client, entity, users) for entity in message.entities] - entities = pyrogram.List(filter(lambda x: x is not None, entities)) + if isinstance(message, raw.types.Message): + entities = [types.MessageEntity._parse(client, entity, users) for entity in message.entities] + entities = types.List(filter(lambda x: x is not None, entities)) forward_from = None forward_sender_name = None @@ -492,17 +492,17 @@ async def _parse(client, message: types.Message or types.MessageService or types forward_signature = None forward_date = None - forward_header = message.fwd_from # type: types.MessageFwdHeader + forward_header = message.fwd_from # type: raw.types.MessageFwdHeader if forward_header: forward_date = forward_header.date if forward_header.from_id: - forward_from = User._parse(client, users[forward_header.from_id]) + forward_from = types.User._parse(client, users[forward_header.from_id]) elif forward_header.from_name: forward_sender_name = forward_header.from_name else: - forward_from_chat = Chat._parse_channel_chat(client, chats[forward_header.channel_id]) + forward_from_chat = types.Chat._parse_channel_chat(client, chats[forward_header.channel_id]) forward_from_message_id = forward_header.channel_post forward_signature = forward_header.post_author @@ -525,87 +525,87 @@ async def _parse(client, message: types.Message or types.MessageService or types media = message.media if media: - if isinstance(media, types.MessageMediaPhoto): - photo = Photo._parse(client, media.photo, media.ttl_seconds) - elif isinstance(media, types.MessageMediaGeo): - location = Location._parse(client, media.geo) - elif isinstance(media, types.MessageMediaContact): - contact = Contact._parse(client, media) - elif isinstance(media, types.MessageMediaVenue): - venue = pyrogram.Venue._parse(client, media) - elif isinstance(media, types.MessageMediaGame): - game = pyrogram.Game._parse(client, message) - elif isinstance(media, types.MessageMediaDocument): + if isinstance(media, raw.types.MessageMediaPhoto): + photo = types.Photo._parse(client, media.photo, media.ttl_seconds) + elif isinstance(media, raw.types.MessageMediaGeo): + location = types.Location._parse(client, media.geo) + elif isinstance(media, raw.types.MessageMediaContact): + contact = types.Contact._parse(client, media) + elif isinstance(media, raw.types.MessageMediaVenue): + venue = types.Venue._parse(client, media) + elif isinstance(media, raw.types.MessageMediaGame): + game = types.Game._parse(client, message) + elif isinstance(media, raw.types.MessageMediaDocument): doc = media.document - if isinstance(doc, types.Document): + if isinstance(doc, raw.types.Document): attributes = {type(i): i for i in doc.attributes} file_name = getattr( attributes.get( - types.DocumentAttributeFilename, None + raw.types.DocumentAttributeFilename, None ), "file_name", None ) - if types.DocumentAttributeAudio in attributes: - audio_attributes = attributes[types.DocumentAttributeAudio] + if raw.types.DocumentAttributeAudio in attributes: + audio_attributes = attributes[raw.types.DocumentAttributeAudio] if audio_attributes.voice: - voice = pyrogram.Voice._parse(client, doc, audio_attributes) + voice = types.Voice._parse(client, doc, audio_attributes) else: - audio = pyrogram.Audio._parse(client, doc, audio_attributes, file_name) - elif types.DocumentAttributeAnimated in attributes: - video_attributes = attributes.get(types.DocumentAttributeVideo, None) + audio = types.Audio._parse(client, doc, audio_attributes, file_name) + elif raw.types.DocumentAttributeAnimated in attributes: + video_attributes = attributes.get(raw.types.DocumentAttributeVideo, None) - animation = pyrogram.Animation._parse(client, doc, video_attributes, file_name) - elif types.DocumentAttributeVideo in attributes: - video_attributes = attributes[types.DocumentAttributeVideo] + animation = types.Animation._parse(client, doc, video_attributes, file_name) + elif raw.types.DocumentAttributeVideo in attributes: + video_attributes = attributes[raw.types.DocumentAttributeVideo] if video_attributes.round_message: - video_note = pyrogram.VideoNote._parse(client, doc, video_attributes) + video_note = types.VideoNote._parse(client, doc, video_attributes) else: - video = pyrogram.Video._parse(client, doc, video_attributes, file_name, - media.ttl_seconds) - elif types.DocumentAttributeSticker in attributes: - sticker = await pyrogram.Sticker._parse( + video = types.Video._parse(client, doc, video_attributes, file_name, + media.ttl_seconds) + elif raw.types.DocumentAttributeSticker in attributes: + sticker = await types.Sticker._parse( client, doc, - attributes.get(types.DocumentAttributeImageSize, None), - attributes[types.DocumentAttributeSticker], + attributes.get(raw.types.DocumentAttributeImageSize, None), + attributes[raw.types.DocumentAttributeSticker], file_name ) else: - document = pyrogram.Document._parse(client, doc, file_name) - elif isinstance(media, types.MessageMediaWebPage): - if isinstance(media.webpage, types.WebPage): - web_page = pyrogram.WebPage._parse(client, media.webpage) + document = types.Document._parse(client, doc, file_name) + elif isinstance(media, raw.types.MessageMediaWebPage): + if isinstance(media.webpage, raw.types.WebPage): + web_page = types.WebPage._parse(client, media.webpage) else: media = None - elif isinstance(media, types.MessageMediaPoll): - poll = pyrogram.Poll._parse(client, media) - elif isinstance(media, types.MessageMediaDice): - dice = pyrogram.Dice._parse(client, media) + elif isinstance(media, raw.types.MessageMediaPoll): + poll = types.Poll._parse(client, media) + elif isinstance(media, raw.types.MessageMediaDice): + dice = types.Dice._parse(client, media) else: media = None reply_markup = message.reply_markup if reply_markup: - if isinstance(reply_markup, types.ReplyKeyboardForceReply): - reply_markup = pyrogram.ForceReply.read(reply_markup) - elif isinstance(reply_markup, types.ReplyKeyboardMarkup): - reply_markup = pyrogram.ReplyKeyboardMarkup.read(reply_markup) - elif isinstance(reply_markup, types.ReplyInlineMarkup): - reply_markup = pyrogram.InlineKeyboardMarkup.read(reply_markup) - elif isinstance(reply_markup, types.ReplyKeyboardHide): - reply_markup = pyrogram.ReplyKeyboardRemove.read(reply_markup) + if isinstance(reply_markup, raw.types.ReplyKeyboardForceReply): + reply_markup = types.ForceReply.read(reply_markup) + elif isinstance(reply_markup, raw.types.ReplyKeyboardMarkup): + reply_markup = types.ReplyKeyboardMarkup.read(reply_markup) + elif isinstance(reply_markup, raw.types.ReplyInlineMarkup): + reply_markup = types.InlineKeyboardMarkup.read(reply_markup) + elif isinstance(reply_markup, raw.types.ReplyKeyboardHide): + reply_markup = types.ReplyKeyboardRemove.read(reply_markup) else: reply_markup = None parsed_message = Message( message_id=message.id, date=message.date, - chat=Chat._parse(client, message, users, chats), - from_user=User._parse(client, users.get(message.from_id, None)), + chat=types.Chat._parse(client, message, users, chats), + from_user=types.User._parse(client, users.get(message.from_id, None)), text=( Str(message.message).init(entities) or None if media is None or web_page is not None @@ -655,7 +655,7 @@ async def _parse(client, message: types.Message or types.MessageService or types poll=poll, dice=dice, views=message.views, - via_bot=User._parse(client, users.get(message.via_bot_id, None)), + via_bot=types.User._parse(client, users.get(message.via_bot_id, None)), outgoing=message.out, reply_markup=reply_markup, client=client @@ -676,9 +676,9 @@ async def _parse(client, message: types.Message or types.MessageService or types @property def link(self) -> str: if self.chat.type in ("group", "supergroup", "channel") and self.chat.username: - return "https://t.me/{}/{}".format(self.chat.username, self.message_id) + return f"https://t.me/{self.chat.username}/{self.message_id}" else: - return "https://t.me/c/{}/{}".format(utils.get_channel_id(self.chat.id), self.message_id) + return f"https://t.me/c/{utils.get_channel_id(self.chat.id)}/{self.message_id}" async def reply_text( self, @@ -690,7 +690,7 @@ async def reply_text( reply_to_message_id: int = None, reply_markup=None ) -> "Message": - """Bound method *reply_text* of :obj:`Message`. + """Bound method *reply_text* of :obj:`~pyrogram.types.Message`. Use as a shortcut for: @@ -733,7 +733,7 @@ async def reply_text( reply_to_message_id (``int``, *optional*): If the message is a reply, ID of the original message. - reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. @@ -774,16 +774,16 @@ async def reply_animation( thumb: str = None, disable_notification: bool = None, reply_markup: Union[ - "pyrogram.InlineKeyboardMarkup", - "pyrogram.ReplyKeyboardMarkup", - "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply" + "types.InlineKeyboardMarkup", + "types.ReplyKeyboardMarkup", + "types.ReplyKeyboardRemove", + "types.ForceReply" ] = None, reply_to_message_id: int = None, progress: callable = None, progress_args: tuple = () ) -> "Message": - """Bound method *reply_animation* :obj:`Message`. + """Bound method *reply_animation* :obj:`~pyrogram.types.Message`. Use as a shortcut for: @@ -847,7 +847,7 @@ async def reply_animation( reply_to_message_id (``int``, *optional*): If the message is a reply, ID of the original message. - reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. @@ -874,8 +874,9 @@ async def reply_animation( You can either keep *\*args* or add every single extra argument in your function signature. Returns: - On success, the sent :obj:`Message` is returned. - In case the upload is deliberately stopped with :meth:`~Client.stop_transmission`, None is returned instead. + On success, the sent :obj:`~pyrogram.types.Message` is returned. + In case the upload is deliberately stopped with :meth:`~pyrogram.Client.stop_transmission`, None is returned + instead. Raises: RPCError: In case of a Telegram RPC error. @@ -917,15 +918,15 @@ async def reply_audio( disable_notification: bool = None, reply_to_message_id: int = None, reply_markup: Union[ - "pyrogram.InlineKeyboardMarkup", - "pyrogram.ReplyKeyboardMarkup", - "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply" + "types.InlineKeyboardMarkup", + "types.ReplyKeyboardMarkup", + "types.ReplyKeyboardRemove", + "types.ForceReply" ] = None, progress: callable = None, progress_args: tuple = () ) -> "Message": - """Bound method *reply_audio* of :obj:`Message`. + """Bound method *reply_audio* of :obj:`~pyrogram.types.Message`. Use as a shortcut for: @@ -989,7 +990,7 @@ async def reply_audio( reply_to_message_id (``int``, *optional*): If the message is a reply, ID of the original message. - reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. @@ -1016,8 +1017,9 @@ async def reply_audio( You can either keep *\*args* or add every single extra argument in your function signature. Returns: - On success, the sent :obj:`Message` is returned. - In case the upload is deliberately stopped with :meth:`~Client.stop_transmission`, None is returned instead. + On success, the sent :obj:`~pyrogram.types.Message` is returned. + In case the upload is deliberately stopped with :meth:`~pyrogram.Client.stop_transmission`, None is returned + instead. Raises: RPCError: In case of a Telegram RPC error. @@ -1055,13 +1057,13 @@ async def reply_cached_media( disable_notification: bool = None, reply_to_message_id: int = None, reply_markup: Union[ - "pyrogram.InlineKeyboardMarkup", - "pyrogram.ReplyKeyboardMarkup", - "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply" + "types.InlineKeyboardMarkup", + "types.ReplyKeyboardMarkup", + "types.ReplyKeyboardRemove", + "types.ForceReply" ] = None ) -> "Message": - """Bound method *reply_cached_media* of :obj:`Message`. + """Bound method *reply_cached_media* of :obj:`~pyrogram.types.Message`. Use as a shortcut for: @@ -1108,12 +1110,12 @@ async def reply_cached_media( reply_to_message_id (``int``, *optional*): If the message is a reply, ID of the original message. - reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. Returns: - On success, the sent :obj:`Message` is returned. + On success, the sent :obj:`~pyrogram.types.Message` is returned. Raises: RPCError: In case of a Telegram RPC error. @@ -1136,7 +1138,7 @@ async def reply_cached_media( ) async def reply_chat_action(self, action: str) -> bool: - """Bound method *reply_chat_action* of :obj:`Message`. + """Bound method *reply_chat_action* of :obj:`~pyrogram.types.Message`. Use as a shortcut for: @@ -1183,13 +1185,13 @@ async def reply_contact( disable_notification: bool = None, reply_to_message_id: int = None, reply_markup: Union[ - "pyrogram.InlineKeyboardMarkup", - "pyrogram.ReplyKeyboardMarkup", - "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply" + "types.InlineKeyboardMarkup", + "types.ReplyKeyboardMarkup", + "types.ReplyKeyboardRemove", + "types.ForceReply" ] = None ) -> "Message": - """Bound method *reply_contact* of :obj:`Message`. + """Bound method *reply_contact* of :obj:`~pyrogram.types.Message`. Use as a shortcut for: @@ -1231,12 +1233,12 @@ async def reply_contact( reply_to_message_id (``int``, *optional*): If the message is a reply, ID of the original message. - reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. Returns: - On success, the sent :obj:`Message` is returned. + On success, the sent :obj:`~pyrogram.types.Message` is returned. Raises: RPCError: In case of a Telegram RPC error. @@ -1269,15 +1271,15 @@ async def reply_document( disable_notification: bool = None, reply_to_message_id: int = None, reply_markup: Union[ - "pyrogram.InlineKeyboardMarkup", - "pyrogram.ReplyKeyboardMarkup", - "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply" + "types.InlineKeyboardMarkup", + "types.ReplyKeyboardMarkup", + "types.ReplyKeyboardRemove", + "types.ForceReply" ] = None, progress: callable = None, progress_args: tuple = () ) -> "Message": - """Bound method *reply_document* of :obj:`Message`. + """Bound method *reply_document* of :obj:`~pyrogram.types.Message`. Use as a shortcut for: @@ -1332,7 +1334,7 @@ async def reply_document( reply_to_message_id (``int``, *optional*): If the message is a reply, ID of the original message. - reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. @@ -1359,8 +1361,9 @@ async def reply_document( You can either keep *\*args* or add every single extra argument in your function signature. Returns: - On success, the sent :obj:`Message` is returned. - In case the upload is deliberately stopped with :meth:`~Client.stop_transmission`, None is returned instead. + On success, the sent :obj:`~pyrogram.types.Message` is returned. + In case the upload is deliberately stopped with :meth:`~pyrogram.Client.stop_transmission`, None is returned + instead. Raises: RPCError: In case of a Telegram RPC error. @@ -1392,13 +1395,13 @@ async def reply_game( disable_notification: bool = None, reply_to_message_id: int = None, reply_markup: Union[ - "pyrogram.InlineKeyboardMarkup", - "pyrogram.ReplyKeyboardMarkup", - "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply" + "types.InlineKeyboardMarkup", + "types.ReplyKeyboardMarkup", + "types.ReplyKeyboardRemove", + "types.ForceReply" ] = None ) -> "Message": - """Bound method *reply_game* of :obj:`Message`. + """Bound method *reply_game* of :obj:`~pyrogram.types.Message`. Use as a shortcut for: @@ -1430,12 +1433,12 @@ async def reply_game( reply_to_message_id (``int``, *optional*): If the message is a reply, ID of the original message. - reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*): An object for an inline keyboard. If empty, one ‘Play game_title’ button will be shown automatically. If not empty, the first button must launch the game. Returns: - On success, the sent :obj:`Message` is returned. + On success, the sent :obj:`~pyrogram.types.Message` is returned. Raises: RPCError: In case of a Telegram RPC error. @@ -1463,7 +1466,7 @@ async 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* of :obj:`~pyrogram.types.Message`. Use as a shortcut for: @@ -1531,13 +1534,13 @@ async def reply_location( disable_notification: bool = None, reply_to_message_id: int = None, reply_markup: Union[ - "pyrogram.InlineKeyboardMarkup", - "pyrogram.ReplyKeyboardMarkup", - "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply" + "types.InlineKeyboardMarkup", + "types.ReplyKeyboardMarkup", + "types.ReplyKeyboardRemove", + "types.ForceReply" ] = None ) -> "Message": - """Bound method *reply_location* of :obj:`Message`. + """Bound method *reply_location* of :obj:`~pyrogram.types.Message`. Use as a shortcut for: @@ -1573,12 +1576,12 @@ async def reply_location( reply_to_message_id (``int``, *optional*): If the message is a reply, ID of the original message - reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. Returns: - On success, the sent :obj:`Message` is returned. + On success, the sent :obj:`~pyrogram.types.Message` is returned. Raises: RPCError: In case of a Telegram RPC error. @@ -1600,12 +1603,12 @@ async def reply_location( async def reply_media_group( self, - media: List[Union["pyrogram.InputMediaPhoto", "pyrogram.InputMediaVideo"]], + media: List[Union["types.InputMediaPhoto", "types.InputMediaVideo"]], quote: bool = None, disable_notification: bool = None, reply_to_message_id: int = None - ) -> "Message": - """Bound method *reply_media_group* of :obj:`Message`. + ) -> List["types.Message"]: + """Bound method *reply_media_group* of :obj:`~pyrogram.types.Message`. Use as a shortcut for: @@ -1623,8 +1626,8 @@ async def reply_media_group( Parameters: media (``list``): - A list containing either :obj:`InputMediaPhoto ` or - :obj:`InputMediaVideo ` objects + A list containing either :obj:`~pyrogram.types.InputMediaPhoto` or + :obj:`~pyrogram.types.InputMediaVideo` objects describing photos and videos to be sent, must include 2–10 items. quote (``bool``, *optional*): @@ -1640,7 +1643,7 @@ async def reply_media_group( If the message is a reply, ID of the original message. Returns: - On success, a :obj:`Messages` object is returned containing all the + On success, a :obj:`~pyrogram.types.Messages` object is returned containing all the single messages sent. Raises: @@ -1670,15 +1673,15 @@ async def reply_photo( disable_notification: bool = None, reply_to_message_id: int = None, reply_markup: Union[ - "pyrogram.InlineKeyboardMarkup", - "pyrogram.ReplyKeyboardMarkup", - "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply" + "types.InlineKeyboardMarkup", + "types.ReplyKeyboardMarkup", + "types.ReplyKeyboardRemove", + "types.ForceReply" ] = None, progress: callable = None, progress_args: tuple = () ) -> "Message": - """Bound method *reply_photo* of :obj:`Message`. + """Bound method *reply_photo* of :obj:`~pyrogram.types.Message`. Use as a shortcut for: @@ -1732,7 +1735,7 @@ async def reply_photo( reply_to_message_id (``int``, *optional*): If the message is a reply, ID of the original message. - reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. @@ -1759,8 +1762,9 @@ async def reply_photo( You can either keep *\*args* or add every single extra argument in your function signature. Returns: - On success, the sent :obj:`Message` is returned. - In case the upload is deliberately stopped with :meth:`~Client.stop_transmission`, None is returned instead. + On success, the sent :obj:`~pyrogram.types.Message` is returned. + In case the upload is deliberately stopped with :meth:`~pyrogram.Client.stop_transmission`, None is returned + instead. Raises: RPCError: In case of a Telegram RPC error. @@ -1793,13 +1797,13 @@ async def reply_poll( disable_notification: bool = None, reply_to_message_id: int = None, reply_markup: Union[ - "pyrogram.InlineKeyboardMarkup", - "pyrogram.ReplyKeyboardMarkup", - "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply" + "types.InlineKeyboardMarkup", + "types.ReplyKeyboardMarkup", + "types.ReplyKeyboardRemove", + "types.ForceReply" ] = None ) -> "Message": - """Bound method *reply_poll* of :obj:`Message`. + """Bound method *reply_poll* of :obj:`~pyrogram.types.Message`. Use as a shortcut for: @@ -1835,12 +1839,12 @@ async def reply_poll( reply_to_message_id (``int``, *optional*): If the message is a reply, ID of the original message. - reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. Returns: - On success, the sent :obj:`Message` is returned. + On success, the sent :obj:`~pyrogram.types.Message` is returned. Raises: RPCError: In case of a Telegram RPC error. @@ -1868,15 +1872,15 @@ async def reply_sticker( disable_notification: bool = None, reply_to_message_id: int = None, reply_markup: Union[ - "pyrogram.InlineKeyboardMarkup", - "pyrogram.ReplyKeyboardMarkup", - "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply" + "types.InlineKeyboardMarkup", + "types.ReplyKeyboardMarkup", + "types.ReplyKeyboardRemove", + "types.ForceReply" ] = None, progress: callable = None, progress_args: tuple = () ) -> "Message": - """Bound method *reply_sticker* of :obj:`Message`. + """Bound method *reply_sticker* of :obj:`~pyrogram.types.Message`. Use as a shortcut for: @@ -1915,7 +1919,7 @@ async def reply_sticker( reply_to_message_id (``int``, *optional*): If the message is a reply, ID of the original message. - reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. @@ -1942,8 +1946,9 @@ async def reply_sticker( You can either keep *\*args* or add every single extra argument in your function signature. Returns: - On success, the sent :obj:`Message` is returned. - In case the upload is deliberately stopped with :meth:`~Client.stop_transmission`, None is returned instead. + On success, the sent :obj:`~pyrogram.types.Message` is returned. + In case the upload is deliberately stopped with :meth:`~pyrogram.Client.stop_transmission`, None is returned + instead. Raises: RPCError: In case of a Telegram RPC error. @@ -1977,13 +1982,13 @@ async def reply_venue( disable_notification: bool = None, reply_to_message_id: int = None, reply_markup: Union[ - "pyrogram.InlineKeyboardMarkup", - "pyrogram.ReplyKeyboardMarkup", - "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply" + "types.InlineKeyboardMarkup", + "types.ReplyKeyboardMarkup", + "types.ReplyKeyboardRemove", + "types.ForceReply" ] = None ) -> "Message": - """Bound method *reply_venue* of :obj:`Message`. + """Bound method *reply_venue* of :obj:`~pyrogram.types.Message`. Use as a shortcut for: @@ -2034,12 +2039,12 @@ async def reply_venue( reply_to_message_id (``int``, *optional*): If the message is a reply, ID of the original message - reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. Returns: - On success, the sent :obj:`Message` is returned. + On success, the sent :obj:`~pyrogram.types.Message` is returned. Raises: RPCError: In case of a Telegram RPC error. @@ -2078,15 +2083,15 @@ async def reply_video( disable_notification: bool = None, reply_to_message_id: int = None, reply_markup: Union[ - "pyrogram.InlineKeyboardMarkup", - "pyrogram.ReplyKeyboardMarkup", - "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply" + "types.InlineKeyboardMarkup", + "types.ReplyKeyboardMarkup", + "types.ReplyKeyboardRemove", + "types.ForceReply" ] = None, progress: callable = None, progress_args: tuple = () ) -> "Message": - """Bound method *reply_video* of :obj:`Message`. + """Bound method *reply_video* of :obj:`~pyrogram.types.Message`. Use as a shortcut for: @@ -2153,7 +2158,7 @@ async def reply_video( reply_to_message_id (``int``, *optional*): If the message is a reply, ID of the original message. - reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. @@ -2180,8 +2185,9 @@ async def reply_video( You can either keep *\*args* or add every single extra argument in your function signature. Returns: - On success, the sent :obj:`Message` is returned. - In case the upload is deliberately stopped with :meth:`~Client.stop_transmission`, None is returned instead. + On success, the sent :obj:`~pyrogram.types.Message` is returned. + In case the upload is deliberately stopped with :meth:`~pyrogram.Client.stop_transmission`, None is returned + instead. Raises: RPCError: In case of a Telegram RPC error. @@ -2221,15 +2227,15 @@ async def reply_video_note( disable_notification: bool = None, reply_to_message_id: int = None, reply_markup: Union[ - "pyrogram.InlineKeyboardMarkup", - "pyrogram.ReplyKeyboardMarkup", - "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply" + "types.InlineKeyboardMarkup", + "types.ReplyKeyboardMarkup", + "types.ReplyKeyboardRemove", + "types.ForceReply" ] = None, progress: callable = None, progress_args: tuple = () ) -> "Message": - """Bound method *reply_video_note* of :obj:`Message`. + """Bound method *reply_video_note* of :obj:`~pyrogram.types.Message`. Use as a shortcut for: @@ -2280,7 +2286,7 @@ async def reply_video_note( reply_to_message_id (``int``, *optional*): If the message is a reply, ID of the original message - reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. @@ -2307,8 +2313,9 @@ async def reply_video_note( You can either keep *\*args* or add every single extra argument in your function signature. Returns: - On success, the sent :obj:`Message` is returned. - In case the upload is deliberately stopped with :meth:`~Client.stop_transmission`, None is returned instead. + On success, the sent :obj:`~pyrogram.types.Message` is returned. + In case the upload is deliberately stopped with :meth:`~pyrogram.Client.stop_transmission`, None is returned + instead. Raises: RPCError: In case of a Telegram RPC error. @@ -2344,15 +2351,15 @@ async def reply_voice( disable_notification: bool = None, reply_to_message_id: int = None, reply_markup: Union[ - "pyrogram.InlineKeyboardMarkup", - "pyrogram.ReplyKeyboardMarkup", - "pyrogram.ReplyKeyboardRemove", - "pyrogram.ForceReply" + "types.InlineKeyboardMarkup", + "types.ReplyKeyboardMarkup", + "types.ReplyKeyboardRemove", + "types.ForceReply" ] = None, progress: callable = None, progress_args: tuple = () ) -> "Message": - """Bound method *reply_voice* of :obj:`Message`. + """Bound method *reply_voice* of :obj:`~pyrogram.types.Message`. Use as a shortcut for: @@ -2404,7 +2411,7 @@ async def reply_voice( reply_to_message_id (``int``, *optional*): If the message is a reply, ID of the original message - reply_markup (:obj:`InlineKeyboardMarkup` | :obj:`ReplyKeyboardMarkup` | :obj:`ReplyKeyboardRemove` | :obj:`ForceReply`, *optional*): + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. @@ -2431,8 +2438,9 @@ async def reply_voice( You can either keep *\*args* or add every single extra argument in your function signature. Returns: - On success, the sent :obj:`Message` is returned. - In case the upload is deliberately stopped with :meth:`~Client.stop_transmission`, None is returned instead. + On success, the sent :obj:`~pyrogram.types.Message` is returned. + In case the upload is deliberately stopped with :meth:`~pyrogram.Client.stop_transmission`, None is returned + instead. Raises: RPCError: In case of a Telegram RPC error. @@ -2462,9 +2470,9 @@ async def edit_text( text: str, parse_mode: Union[str, None] = object, disable_web_page_preview: bool = None, - reply_markup: "pyrogram.InlineKeyboardMarkup" = None + reply_markup: "types.InlineKeyboardMarkup" = None ) -> "Message": - """Bound method *edit_text* of :obj:`Message`. + """Bound method *edit_text* of :obj:`~pyrogram.types.Message`. Use as a shortcut for: @@ -2495,11 +2503,11 @@ async def edit_text( disable_web_page_preview (``bool``, *optional*): Disables link previews for links in this message. - reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*): An InlineKeyboardMarkup object. Returns: - On success, the edited :obj:`Message` is returned. + On success, the edited :obj:`~pyrogram.types.Message` is returned. Raises: RPCError: In case of a Telegram RPC error. @@ -2519,9 +2527,9 @@ async def edit_caption( self, caption: str, parse_mode: Union[str, None] = object, - reply_markup: "pyrogram.InlineKeyboardMarkup" = None + reply_markup: "types.InlineKeyboardMarkup" = None ) -> "Message": - """Bound method *edit_caption* of :obj:`Message`. + """Bound method *edit_caption* of :obj:`~pyrogram.types.Message`. Use as a shortcut for: @@ -2549,11 +2557,11 @@ async def edit_caption( Pass "html" to enable HTML-style parsing only. Pass None to completely disable style parsing. - reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*): An InlineKeyboardMarkup object. Returns: - On success, the edited :obj:`Message` is returned. + On success, the edited :obj:`~pyrogram.types.Message` is returned. Raises: RPCError: In case of a Telegram RPC error. @@ -2566,8 +2574,12 @@ async def edit_caption( reply_markup=reply_markup ) - async def edit_media(self, media: InputMedia, reply_markup: "pyrogram.InlineKeyboardMarkup" = None) -> "Message": - """Bound method *edit_media* of :obj:`Message`. + async def edit_media( + self, + media: "types.InputMedia", + reply_markup: "types.InlineKeyboardMarkup" = None + ) -> "Message": + """Bound method *edit_media* of :obj:`~pyrogram.types.Message`. Use as a shortcut for: @@ -2585,14 +2597,14 @@ async def edit_media(self, media: InputMedia, reply_markup: "pyrogram.InlineKeyb message.edit_media(media) Parameters: - media (:obj:`InputMedia`): + media (:obj:`~pyrogram.types.InputMedia`): One of the InputMedia objects describing an animation, audio, document, photo or video. - reply_markup (:obj:`InlineKeyboardMarkup`, *optional*): + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*): An InlineKeyboardMarkup object. Returns: - On success, the edited :obj:`Message` is returned. + On success, the edited :obj:`~pyrogram.types.Message` is returned. Raises: RPCError: In case of a Telegram RPC error. @@ -2604,8 +2616,8 @@ async def edit_media(self, media: InputMedia, reply_markup: "pyrogram.InlineKeyb reply_markup=reply_markup ) - async def edit_reply_markup(self, reply_markup: "pyrogram.InlineKeyboardMarkup" = None) -> "Message": - """Bound method *edit_reply_markup* of :obj:`Message`. + async def edit_reply_markup(self, reply_markup: "types.InlineKeyboardMarkup" = None) -> "Message": + """Bound method *edit_reply_markup* of :obj:`~pyrogram.types.Message`. Use as a shortcut for: @@ -2623,12 +2635,12 @@ async def edit_reply_markup(self, reply_markup: "pyrogram.InlineKeyboardMarkup" message.edit_reply_markup(inline_reply_markup) Parameters: - reply_markup (:obj:`InlineKeyboardMarkup`): + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`): An InlineKeyboardMarkup object. Returns: On success, if edited message is sent by the bot, the edited - :obj:`Message` is returned, otherwise True is returned. + :obj:`~pyrogram.types.Message` is returned, otherwise True is returned. Raises: RPCError: In case of a Telegram RPC error. @@ -2646,8 +2658,8 @@ async def forward( as_copy: bool = False, remove_caption: bool = False, schedule_date: int = None - ) -> "Message": - """Bound method *forward* of :obj:`Message`. + ) -> Union["types.Message", List["types.Message"]]: + """Bound method *forward* of :obj:`~pyrogram.types.Message`. Use as a shortcut for: @@ -2696,7 +2708,7 @@ async def forward( if self.service: raise ValueError("Unable to copy service messages") - if self.game and not self._client.is_bot: + if self.game and not await self._client.storage.is_bot(): raise ValueError("Users cannot send messages with Game media type") if self.text: @@ -2805,7 +2817,7 @@ async def forward( ) async def delete(self, revoke: bool = True): - """Bound method *delete* of :obj:`Message`. + """Bound method *delete* of :obj:`~pyrogram.types.Message`. Use as a shortcut for: @@ -2841,7 +2853,7 @@ async def delete(self, revoke: bool = True): ) async def click(self, x: int or str = 0, y: int = None, quote: bool = None, timeout: int = 10): - """Bound method *click* of :obj:`Message`. + """Bound method *click* of :obj:`~pyrogram.types.Message`. Use as a shortcut for clicking a button attached to the message instead of: @@ -2893,7 +2905,7 @@ async def click(self, x: int or str = 0, y: int = None, quote: bool = None, time Timeout in seconds. Returns: - - The result of :meth:`~Client.request_callback_answer` in case of inline callback button clicks. + - The result of :meth:`~pyrogram.Client.request_callback_answer` in case of inline callback button clicks. - The result of :meth:`~Message.reply()` in case of normal button clicks. - A string in case the inline button is a URL, a *switch_inline_query* or a *switch_inline_query_current_chat* button. @@ -2904,10 +2916,10 @@ async def click(self, x: int or str = 0, y: int = None, quote: bool = None, time TimeoutError: In case, after clicking an inline button, the bot fails to answer within the timeout. """ - if isinstance(self.reply_markup, pyrogram.ReplyKeyboardMarkup): + if isinstance(self.reply_markup, types.ReplyKeyboardMarkup): keyboard = self.reply_markup.keyboard is_inline = False - elif isinstance(self.reply_markup, pyrogram.InlineKeyboardMarkup): + elif isinstance(self.reply_markup, types.InlineKeyboardMarkup): keyboard = self.reply_markup.inline_keyboard is_inline = True else: @@ -2921,12 +2933,12 @@ async def click(self, x: int or str = 0, y: int = None, quote: bool = None, time for button in row ][x] except IndexError: - raise ValueError("The button at index {} doesn't exist".format(x)) + raise ValueError(f"The button at index {x} doesn't exist") elif isinstance(x, int) and isinstance(y, int): try: button = keyboard[y][x] except IndexError: - raise ValueError("The button at position ({}, {}) doesn't exist".format(x, y)) + raise ValueError(f"The button at position ({x}, {y}) doesn't exist") elif isinstance(x, str) and y is None: label = x.encode("utf-16", "surrogatepass").decode("utf-16") @@ -2938,7 +2950,7 @@ async def click(self, x: int or str = 0, y: int = None, quote: bool = None, time if label == button.text ][0] except IndexError: - raise ValueError("The button with label '{}' doesn't exists".format(x)) + raise ValueError(f"The button with label '{x}' doesn't exists") else: raise ValueError("Invalid arguments") @@ -2963,8 +2975,8 @@ async def click(self, x: int or str = 0, y: int = None, quote: bool = None, time async def retract_vote( self, - ) -> "pyrogram.Poll": - """Bound method *retract_vote* of :obj:`Message`. + ) -> "types.Poll": + """Bound method *retract_vote* of :obj:`~pyrogram.types.Message`. Use as a shortcut for: @@ -2981,7 +2993,7 @@ async def retract_vote( message.retract_vote() Returns: - :obj:`Poll`: On success, the poll with the retracted vote is returned. + :obj:`~pyrogram.types.Poll`: On success, the poll with the retracted vote is returned. Raises: RPCError: In case of a Telegram RPC error. @@ -2999,7 +3011,7 @@ async def download( progress: callable = None, progress_args: tuple = () ) -> str: - """Bound method *download* of :obj:`Message`. + """Bound method *download* of :obj:`~pyrogram.types.Message`. Use as a shortcut for: @@ -3063,8 +3075,8 @@ async def download( async def vote( self, option: int, - ) -> "pyrogram.Poll": - """Bound method *vote* of :obj:`Message`. + ) -> "types.Poll": + """Bound method *vote* of :obj:`~pyrogram.types.Message`. Use as a shortcut for: @@ -3086,7 +3098,7 @@ async def vote( Index of the poll option you want to vote for (0 to 9). Returns: - :obj:`Poll`: On success, the poll with the chosen option is returned. + :obj:`~pyrogram.types.Poll`: On success, the poll with the chosen option is returned. Raises: RPCError: In case of a Telegram RPC error. @@ -3095,11 +3107,11 @@ async def vote( return await self._client.vote_poll( chat_id=self.chat.id, message_id=self.message_id, - option=option + options=option ) - async def pin(self, disable_notification: bool = None) -> "Message": - """Bound method *pin* of :obj:`Message`. + async def pin(self, disable_notification: bool = None) -> bool: + """Bound method *pin* of :obj:`~pyrogram.types.Message`. Use as a shortcut for: diff --git a/pyrogram/client/types/messages_and_media/message_entity.py b/pyrogram/types/messages_and_media/message_entity.py similarity index 68% rename from pyrogram/client/types/messages_and_media/message_entity.py rename to pyrogram/types/messages_and_media/message_entity.py index a1e39a0f62..a88a91c859 100644 --- a/pyrogram/client/types/messages_and_media/message_entity.py +++ b/pyrogram/types/messages_and_media/message_entity.py @@ -17,10 +17,9 @@ # along with Pyrogram. If not, see . import pyrogram - -from pyrogram.api import types +from pyrogram import raw +from pyrogram import types from ..object import Object -from ..user_and_chats.user import User class MessageEntity(Object): @@ -43,38 +42,38 @@ class MessageEntity(Object): url (``str``, *optional*): For "text_link" only, url that will be opened after user taps on the text. - user (:obj:`User`, *optional*): + user (:obj:`~pyrogram.types.User`, *optional*): For "text_mention" only, the mentioned user. """ ENTITIES = { - types.MessageEntityMention.ID: "mention", - types.MessageEntityHashtag.ID: "hashtag", - types.MessageEntityCashtag.ID: "cashtag", - types.MessageEntityBotCommand.ID: "bot_command", - types.MessageEntityUrl.ID: "url", - types.MessageEntityEmail.ID: "email", - types.MessageEntityBold.ID: "bold", - types.MessageEntityItalic.ID: "italic", - types.MessageEntityCode.ID: "code", - types.MessageEntityPre.ID: "pre", - types.MessageEntityUnderline.ID: "underline", - types.MessageEntityStrike.ID: "strike", - types.MessageEntityBlockquote.ID: "blockquote", - types.MessageEntityTextUrl.ID: "text_link", - types.MessageEntityMentionName.ID: "text_mention", - types.MessageEntityPhone.ID: "phone_number" + raw.types.MessageEntityMention.ID: "mention", + raw.types.MessageEntityHashtag.ID: "hashtag", + raw.types.MessageEntityCashtag.ID: "cashtag", + raw.types.MessageEntityBotCommand.ID: "bot_command", + raw.types.MessageEntityUrl.ID: "url", + raw.types.MessageEntityEmail.ID: "email", + raw.types.MessageEntityBold.ID: "bold", + raw.types.MessageEntityItalic.ID: "italic", + raw.types.MessageEntityCode.ID: "code", + raw.types.MessageEntityPre.ID: "pre", + raw.types.MessageEntityUnderline.ID: "underline", + raw.types.MessageEntityStrike.ID: "strike", + raw.types.MessageEntityBlockquote.ID: "blockquote", + raw.types.MessageEntityTextUrl.ID: "text_link", + raw.types.MessageEntityMentionName.ID: "text_mention", + raw.types.MessageEntityPhone.ID: "phone_number" } def __init__( self, *, - client: "pyrogram.BaseClient" = None, + client: "pyrogram.Client" = None, type: str, offset: int, length: int, url: str = None, - user: User = None + user: "types.User" = None ): super().__init__(client) @@ -96,6 +95,6 @@ def _parse(client, entity, users: dict) -> "MessageEntity" or None: offset=entity.offset, length=entity.length, url=getattr(entity, "url", None), - user=User._parse(client, users.get(getattr(entity, "user_id", None), None)), + user=types.User._parse(client, users.get(getattr(entity, "user_id", None), None)), client=client ) diff --git a/pyrogram/client/types/messages_and_media/photo.py b/pyrogram/types/messages_and_media/photo.py similarity index 82% rename from pyrogram/client/types/messages_and_media/photo.py rename to pyrogram/types/messages_and_media/photo.py index fe0678aba3..a6f572d43b 100644 --- a/pyrogram/client/types/messages_and_media/photo.py +++ b/pyrogram/types/messages_and_media/photo.py @@ -20,10 +20,10 @@ from typing import List import pyrogram -from pyrogram.api import types -from .thumbnail import Thumbnail +from pyrogram import raw +from pyrogram import types +from pyrogram.utils import encode_file_id, encode_file_ref from ..object import Object -from ...ext.utils import encode_file_id, encode_file_ref class Photo(Object): @@ -51,14 +51,14 @@ class Photo(Object): ttl_seconds (``int``, *optional*): Time-to-live seconds, for secret photos. - thumbs (List of :obj:`Thumbnail`, *optional*): + thumbs (List of :obj:`~pyrogram.types.Thumbnail`, *optional*): Available thumbnails of this photo. """ def __init__( self, *, - client: "pyrogram.BaseClient" = None, + client: "pyrogram.Client" = None, file_id: str, file_ref: str, width: int, @@ -66,7 +66,7 @@ def __init__( file_size: int, date: int, ttl_seconds: int = None, - thumbs: List[Thumbnail] = None + thumbs: List["types.Thumbnail"] = None ): super().__init__(client) @@ -80,9 +80,9 @@ def __init__( self.thumbs = thumbs @staticmethod - def _parse(client, photo: types.Photo, ttl_seconds: int = None) -> "Photo": - if isinstance(photo, types.Photo): - big = list(filter(lambda p: isinstance(p, types.PhotoSize), photo.sizes))[-1] + def _parse(client, photo: "raw.types.Photo", ttl_seconds: int = None) -> "Photo": + if isinstance(photo, raw.types.Photo): + big = list(filter(lambda p: isinstance(p, raw.types.PhotoSize), photo.sizes))[-1] return Photo( file_id=encode_file_id( @@ -99,6 +99,6 @@ def _parse(client, photo: types.Photo, ttl_seconds: int = None) -> "Photo": file_size=big.size, date=photo.date, ttl_seconds=ttl_seconds, - thumbs=Thumbnail._parse(client, photo), + thumbs=types.Thumbnail._parse(client, photo), client=client ) diff --git a/pyrogram/client/types/messages_and_media/poll.py b/pyrogram/types/messages_and_media/poll.py similarity index 88% rename from pyrogram/client/types/messages_and_media/poll.py rename to pyrogram/types/messages_and_media/poll.py index d1dd2b219c..459c24bb3f 100644 --- a/pyrogram/client/types/messages_and_media/poll.py +++ b/pyrogram/types/messages_and_media/poll.py @@ -19,8 +19,8 @@ from typing import List, Union import pyrogram -from pyrogram.api import types -from .poll_option import PollOption +from pyrogram import raw +from pyrogram import types from ..object import Object from ..update import Update @@ -35,7 +35,7 @@ class Poll(Object, Update): question (``str``): Poll question, 1-255 characters. - options (List of :obj:`PollOption`): + options (List of :obj:`~pyrogram.types.PollOption`): List of poll options. total_voter_count (``int``): @@ -60,10 +60,10 @@ class Poll(Object, Update): def __init__( self, *, - client: "pyrogram.BaseClient" = None, + client: "pyrogram.Client" = None, id: str, question: str, - options: List[PollOption], + options: List["types.PollOption"], total_voter_count: int, is_closed: bool, is_anonymous: bool = None, @@ -86,9 +86,9 @@ def __init__( self.chosen_option = chosen_option @staticmethod - def _parse(client, media_poll: Union[types.MessageMediaPoll, types.UpdateMessagePoll]) -> "Poll": - poll = media_poll.poll # type: types.Poll - results = media_poll.results.results # type: types.PollResults + def _parse(client, media_poll: Union["raw.types.MessageMediaPoll", "raw.types.UpdateMessagePoll"]) -> "Poll": + poll = media_poll.poll # type: raw.types.Poll + results = media_poll.results.results chosen_option = None options = [] @@ -103,7 +103,7 @@ def _parse(client, media_poll: Union[types.MessageMediaPoll, types.UpdateMessage chosen_option = i options.append( - PollOption( + types.PollOption( text=answer.text, voter_count=voter_count, data=answer.option, @@ -125,7 +125,7 @@ def _parse(client, media_poll: Union[types.MessageMediaPoll, types.UpdateMessage ) @staticmethod - def _parse_update(client, update: types.UpdateMessagePoll): + def _parse_update(client, update: "raw.types.UpdateMessagePoll"): if update.poll is not None: return Poll._parse(client, update) @@ -138,7 +138,7 @@ def _parse_update(client, update: types.UpdateMessagePoll): chosen_option = i options.append( - PollOption( + types.PollOption( text="", voter_count=result.voters, data=result.option, diff --git a/pyrogram/client/types/messages_and_media/poll_option.py b/pyrogram/types/messages_and_media/poll_option.py similarity index 96% rename from pyrogram/client/types/messages_and_media/poll_option.py rename to pyrogram/types/messages_and_media/poll_option.py index da7daff327..93fda559ad 100644 --- a/pyrogram/client/types/messages_and_media/poll_option.py +++ b/pyrogram/types/messages_and_media/poll_option.py @@ -38,7 +38,7 @@ class PollOption(Object): def __init__( self, *, - client: "pyrogram.BaseClient" = None, + client: "pyrogram.Client" = None, text: str, voter_count: int, data: bytes diff --git a/pyrogram/client/types/messages_and_media/sticker.py b/pyrogram/types/messages_and_media/sticker.py similarity index 84% rename from pyrogram/client/types/messages_and_media/sticker.py rename to pyrogram/types/messages_and_media/sticker.py index aca7d3a31b..76e7264fda 100644 --- a/pyrogram/client/types/messages_and_media/sticker.py +++ b/pyrogram/types/messages_and_media/sticker.py @@ -22,11 +22,11 @@ from async_lru import alru_cache import pyrogram -from pyrogram.api import types, functions +from pyrogram import raw +from pyrogram import types from pyrogram.errors import StickersetInvalid -from .thumbnail import Thumbnail +from pyrogram.utils import encode_file_id, encode_file_ref from ..object import Object -from ...ext.utils import encode_file_id, encode_file_ref class Sticker(Object): @@ -66,7 +66,7 @@ class Sticker(Object): set_name (``str``, *optional*): Name of the sticker set to which the sticker belongs. - thumbs (List of :obj:`Thumbnail`, *optional*): + thumbs (List of :obj:`~pyrogram.types.Thumbnail`, *optional*): Sticker thumbnails in the .webp or .jpg format. """ @@ -75,7 +75,7 @@ class Sticker(Object): def __init__( self, *, - client: "pyrogram.BaseClient" = None, + client: "pyrogram.Client" = None, file_id: str, file_ref: str, width: int, @@ -87,7 +87,7 @@ def __init__( date: int = None, emoji: str = None, set_name: str = None, - thumbs: List[Thumbnail] = None + thumbs: List["types.Thumbnail"] = None ): super().__init__(client) @@ -110,8 +110,8 @@ def __init__( async def _get_sticker_set_name(send, input_sticker_set_id): try: return (await send( - functions.messages.GetStickerSet( - stickerset=types.InputStickerSetID( + raw.functions.messages.GetStickerSet( + stickerset=raw.types.InputStickerSetID( id=input_sticker_set_id[0], access_hash=input_sticker_set_id[1] ) @@ -121,11 +121,16 @@ async def _get_sticker_set_name(send, input_sticker_set_id): return None @staticmethod - async def _parse(client, sticker: types.Document, image_size_attributes: types.DocumentAttributeImageSize, - sticker_attributes: types.DocumentAttributeSticker, file_name: str) -> "Sticker": + async def _parse( + client, + sticker: "raw.types.Document", + image_size_attributes: "raw.types.DocumentAttributeImageSize", + sticker_attributes: "raw.types.DocumentAttributeSticker", + file_name: str + ) -> "Sticker": sticker_set = sticker_attributes.stickerset - if isinstance(sticker_set, types.InputStickerSetID): + if isinstance(sticker_set, raw.types.InputStickerSetID): input_sticker_set_id = (sticker_set.id, sticker_set.access_hash) set_name = await Sticker._get_sticker_set_name(client.send, input_sticker_set_id) else: @@ -152,6 +157,6 @@ async def _parse(client, sticker: types.Document, image_size_attributes: types.D mime_type=sticker.mime_type, file_name=file_name, date=sticker.date, - thumbs=Thumbnail._parse(client, sticker), + thumbs=types.Thumbnail._parse(client, sticker), client=client ) diff --git a/pyrogram/client/types/messages_and_media/stripped_thumbnail.py b/pyrogram/types/messages_and_media/stripped_thumbnail.py similarity index 88% rename from pyrogram/client/types/messages_and_media/stripped_thumbnail.py rename to pyrogram/types/messages_and_media/stripped_thumbnail.py index 546f9a486a..8f22dd80f7 100644 --- a/pyrogram/client/types/messages_and_media/stripped_thumbnail.py +++ b/pyrogram/types/messages_and_media/stripped_thumbnail.py @@ -17,7 +17,7 @@ # along with Pyrogram. If not, see . import pyrogram -from pyrogram.api import types +from pyrogram import raw from ..object import Object @@ -32,7 +32,7 @@ class StrippedThumbnail(Object): def __init__( self, *, - client: "pyrogram.BaseClient" = None, + client: "pyrogram.Client" = None, data: bytes ): super().__init__(client) @@ -40,7 +40,7 @@ def __init__( self.data = data @staticmethod - def _parse(client, stripped_thumbnail: types.PhotoStrippedSize) -> "StrippedThumbnail": + def _parse(client, stripped_thumbnail: "raw.types.PhotoStrippedSize") -> "StrippedThumbnail": return StrippedThumbnail( data=stripped_thumbnail.bytes, client=client diff --git a/pyrogram/client/types/messages_and_media/thumbnail.py b/pyrogram/types/messages_and_media/thumbnail.py similarity index 86% rename from pyrogram/client/types/messages_and_media/thumbnail.py rename to pyrogram/types/messages_and_media/thumbnail.py index c48b8fb572..cd9eeb5914 100644 --- a/pyrogram/client/types/messages_and_media/thumbnail.py +++ b/pyrogram/types/messages_and_media/thumbnail.py @@ -20,9 +20,9 @@ from typing import Union, List import pyrogram -from pyrogram.api import types -from pyrogram.client.ext.utils import encode_file_id -from .stripped_thumbnail import StrippedThumbnail +from pyrogram import raw +from pyrogram import types +from pyrogram.utils import encode_file_id from ..object import Object @@ -46,7 +46,7 @@ class Thumbnail(Object): def __init__( self, *, - client: "pyrogram.BaseClient" = None, + client: "pyrogram.Client" = None, file_id: str, width: int, height: int, @@ -62,12 +62,12 @@ def __init__( @staticmethod def _parse( client, - media: Union[types.Photo, types.Document] - ) -> Union[List[Union[StrippedThumbnail, "Thumbnail"]], None]: - if isinstance(media, types.Photo): + media: Union["raw.types.Photo", "raw.types.Document"] + ) -> Union[List[Union["types.StrippedThumbnail", "Thumbnail"]], None]: + if isinstance(media, raw.types.Photo): raw_thumbnails = media.sizes[:-1] media_type = 2 - elif isinstance(media, types.Document): + elif isinstance(media, raw.types.Document): raw_thumbnails = media.thumbs media_type = 14 @@ -82,7 +82,7 @@ def _parse( # TODO: Enable this # if isinstance(thumbnail, types.PhotoStrippedSize): # thumbnails.append(StrippedThumbnail._parse(client, thumbnail)) - if isinstance(thumbnail, types.PhotoSize): + if isinstance(thumbnail, raw.types.PhotoSize): thumbnails.append( Thumbnail( file_id=encode_file_id( diff --git a/pyrogram/client/types/messages_and_media/venue.py b/pyrogram/types/messages_and_media/venue.py similarity index 86% rename from pyrogram/client/types/messages_and_media/venue.py rename to pyrogram/types/messages_and_media/venue.py index a638bd4cdc..05f3e33e53 100644 --- a/pyrogram/client/types/messages_and_media/venue.py +++ b/pyrogram/types/messages_and_media/venue.py @@ -17,8 +17,8 @@ # along with Pyrogram. If not, see . import pyrogram -from pyrogram.api import types -from .location import Location +from pyrogram import raw +from pyrogram import types from ..object import Object @@ -26,7 +26,7 @@ class Venue(Object): """A venue. Parameters: - location (:obj:`Location`): + location (:obj:`~pyrogram.types.Location`): Venue location. title (``str``): @@ -47,8 +47,8 @@ class Venue(Object): def __init__( self, *, - client: "pyrogram.BaseClient" = None, - location: Location, + client: "pyrogram.Client" = None, + location: "types.Location", title: str, address: str, foursquare_id: str = None, @@ -63,9 +63,9 @@ def __init__( self.foursquare_type = foursquare_type @staticmethod - def _parse(client, venue: types.MessageMediaVenue): + def _parse(client, venue: "raw.types.MessageMediaVenue"): return Venue( - location=Location._parse(client, venue.geo), + location=types.Location._parse(client, venue.geo), title=venue.title, address=venue.address, foursquare_id=venue.venue_id or None, diff --git a/pyrogram/client/types/messages_and_media/video.py b/pyrogram/types/messages_and_media/video.py similarity index 89% rename from pyrogram/client/types/messages_and_media/video.py rename to pyrogram/types/messages_and_media/video.py index 16388b181c..182ef94efa 100644 --- a/pyrogram/client/types/messages_and_media/video.py +++ b/pyrogram/types/messages_and_media/video.py @@ -20,10 +20,10 @@ from typing import List import pyrogram -from pyrogram.api import types -from .thumbnail import Thumbnail +from pyrogram import raw +from pyrogram import types +from pyrogram.utils import encode_file_id, encode_file_ref from ..object import Object -from ...ext.utils import encode_file_id, encode_file_ref class Video(Object): @@ -63,14 +63,14 @@ class Video(Object): ttl_seconds (``int``. *optional*): Time-to-live seconds, for secret photos. - thumbs (List of :obj:`Thumbnail`, *optional*): + thumbs (List of :obj:`~pyrogram.types.Thumbnail`, *optional*): Video thumbnails. """ def __init__( self, *, - client: "pyrogram.BaseClient" = None, + client: "pyrogram.Client" = None, file_id: str, file_ref: str, width: int, @@ -82,7 +82,7 @@ def __init__( file_size: int = None, date: int = None, ttl_seconds: int = None, - thumbs: List[Thumbnail] = None + thumbs: List["types.Thumbnail"] = None ): super().__init__(client) @@ -102,8 +102,8 @@ def __init__( @staticmethod def _parse( client, - video: types.Document, - video_attributes: types.DocumentAttributeVideo, + video: "raw.types.Document", + video_attributes: "raw.types.DocumentAttributeVideo", file_name: str, ttl_seconds: int = None ) -> "Video": @@ -127,6 +127,6 @@ def _parse( file_size=video.size, date=video.date, ttl_seconds=ttl_seconds, - thumbs=Thumbnail._parse(client, video), + thumbs=types.Thumbnail._parse(client, video), client=client ) diff --git a/pyrogram/client/types/messages_and_media/video_note.py b/pyrogram/types/messages_and_media/video_note.py similarity index 84% rename from pyrogram/client/types/messages_and_media/video_note.py rename to pyrogram/types/messages_and_media/video_note.py index 1feb5a2d06..e12117eb68 100644 --- a/pyrogram/client/types/messages_and_media/video_note.py +++ b/pyrogram/types/messages_and_media/video_note.py @@ -20,10 +20,10 @@ from typing import List import pyrogram -from pyrogram.api import types -from .thumbnail import Thumbnail +from pyrogram import raw +from pyrogram import types +from pyrogram.utils import encode_file_id, encode_file_ref from ..object import Object -from ...ext.utils import encode_file_id, encode_file_ref class VideoNote(Object): @@ -51,19 +51,19 @@ class VideoNote(Object): date (``int``, *optional*): Date the video note was sent in Unix time. - thumbs (List of :obj:`Thumbnail`, *optional*): + thumbs (List of :obj:`~pyrogram.types.Thumbnail`, *optional*): Video thumbnails. """ def __init__( self, *, - client: "pyrogram.BaseClient" = None, + client: "pyrogram.Client" = None, file_id: str, file_ref: str, length: int, duration: int, - thumbs: List[Thumbnail] = None, + thumbs: List["types.Thumbnail"] = None, mime_type: str = None, file_size: int = None, date: int = None @@ -80,7 +80,11 @@ def __init__( self.thumbs = thumbs @staticmethod - def _parse(client, video_note: types.Document, video_attributes: types.DocumentAttributeVideo) -> "VideoNote": + def _parse( + client, + video_note: "raw.types.Document", + video_attributes: "raw.types.DocumentAttributeVideo" + ) -> "VideoNote": return VideoNote( file_id=encode_file_id( pack( @@ -97,6 +101,6 @@ def _parse(client, video_note: types.Document, video_attributes: types.DocumentA file_size=video_note.size, mime_type=video_note.mime_type, date=video_note.date, - thumbs=Thumbnail._parse(client, video_note), + thumbs=types.Thumbnail._parse(client, video_note), client=client ) diff --git a/pyrogram/client/types/messages_and_media/voice.py b/pyrogram/types/messages_and_media/voice.py similarity index 91% rename from pyrogram/client/types/messages_and_media/voice.py rename to pyrogram/types/messages_and_media/voice.py index dec82af9a3..987d233f64 100644 --- a/pyrogram/client/types/messages_and_media/voice.py +++ b/pyrogram/types/messages_and_media/voice.py @@ -19,9 +19,9 @@ from struct import pack import pyrogram -from pyrogram.api import types +from pyrogram import raw +from pyrogram.utils import encode_file_id, encode_file_ref from ..object import Object -from ...ext.utils import encode_file_id, encode_file_ref class Voice(Object): @@ -53,7 +53,7 @@ class Voice(Object): def __init__( self, *, - client: "pyrogram.BaseClient" = None, + client: "pyrogram.Client" = None, file_id: str, file_ref: str, duration: int, @@ -73,7 +73,7 @@ def __init__( self.date = date @staticmethod - def _parse(client, voice: types.Document, attributes: types.DocumentAttributeAudio) -> "Voice": + def _parse(client, voice: "raw.types.Document", attributes: "raw.types.DocumentAttributeAudio") -> "Voice": return Voice( file_id=encode_file_id( pack( diff --git a/pyrogram/client/types/messages_and_media/webpage.py b/pyrogram/types/messages_and_media/webpage.py similarity index 73% rename from pyrogram/client/types/messages_and_media/webpage.py rename to pyrogram/types/messages_and_media/webpage.py index ebebfc1c87..edd94934c1 100644 --- a/pyrogram/client/types/messages_and_media/webpage.py +++ b/pyrogram/types/messages_and_media/webpage.py @@ -17,7 +17,8 @@ # along with Pyrogram. If not, see . import pyrogram -from pyrogram.api import types +from pyrogram import raw +from pyrogram import types from ..object import Object @@ -49,19 +50,19 @@ class WebPage(Object): description (``str``, *optional*): Description of this webpage. - audio (:obj:`Audio`, *optional*): + audio (:obj:`~pyrogram.types.Audio`, *optional*): Webpage preview is an audio file, information about the file. - document (:obj:`Document`, *optional*): + document (:obj:`~pyrogram.types.Document`, *optional*): Webpage preview is a general file, information about the file. - photo (:obj:`Photo`, *optional*): + photo (:obj:`~pyrogram.types.Photo`, *optional*): Webpage preview is a photo, information about the photo. - animation (:obj:`Animation`, *optional*): + animation (:obj:`~pyrogram.types.Animation`, *optional*): Webpage preview is an animation, information about the animation. - video (:obj:`Video`, *optional*): + video (:obj:`~pyrogram.types.Video`, *optional*): Webpage preview is a video, information about the video. embed_url (``str``, *optional*): @@ -86,7 +87,7 @@ class WebPage(Object): def __init__( self, *, - client: "pyrogram.BaseClient" = None, + client: "pyrogram.Client" = None, id: str, url: str, display_url: str, @@ -94,11 +95,11 @@ def __init__( site_name: str = None, title: str = None, description: str = None, - audio: "pyrogram.Audio" = None, - document: "pyrogram.Document" = None, - photo: "pyrogram.Photo" = None, - animation: "pyrogram.Animation" = None, - video: "pyrogram.Video" = None, + audio: "types.Audio" = None, + document: "types.Document" = None, + photo: "types.Photo" = None, + animation: "types.Animation" = None, + video: "types.Video" = None, embed_url: str = None, embed_type: str = None, embed_width: int = None, @@ -128,41 +129,41 @@ def __init__( self.author = author @staticmethod - def _parse(client, webpage: types.WebPage) -> "WebPage": + def _parse(client, webpage: "raw.types.WebPage") -> "WebPage": audio = None document = None photo = None animation = None video = None - if isinstance(webpage.photo, types.Photo): - photo = pyrogram.Photo._parse(client, webpage.photo) + if isinstance(webpage.photo, raw.types.Photo): + photo = types.Photo._parse(client, webpage.photo) doc = webpage.document - if isinstance(doc, types.Document): + if isinstance(doc, raw.types.Document): attributes = {type(i): i for i in doc.attributes} file_name = getattr( attributes.get( - types.DocumentAttributeFilename, None + raw.types.DocumentAttributeFilename, None ), "file_name", None ) - if types.DocumentAttributeAudio in attributes: - audio_attributes = attributes[types.DocumentAttributeAudio] - audio = pyrogram.Audio._parse(client, doc, audio_attributes, file_name) + if raw.types.DocumentAttributeAudio in attributes: + audio_attributes = attributes[raw.types.DocumentAttributeAudio] + audio = types.Audio._parse(client, doc, audio_attributes, file_name) - elif types.DocumentAttributeAnimated in attributes: - video_attributes = attributes.get(types.DocumentAttributeVideo, None) - animation = pyrogram.Animation._parse(client, doc, video_attributes, file_name) + elif raw.types.DocumentAttributeAnimated in attributes: + video_attributes = attributes.get(raw.types.DocumentAttributeVideo, None) + animation = types.Animation._parse(client, doc, video_attributes, file_name) - elif types.DocumentAttributeVideo in attributes: - video_attributes = attributes[types.DocumentAttributeVideo] - video = pyrogram.Video._parse(client, doc, video_attributes, file_name) + elif raw.types.DocumentAttributeVideo in attributes: + video_attributes = attributes[raw.types.DocumentAttributeVideo] + video = types.Video._parse(client, doc, video_attributes, file_name) else: - document = pyrogram.Document._parse(client, doc, file_name) + document = types.Document._parse(client, doc, file_name) return WebPage( id=str(webpage.id), diff --git a/pyrogram/client/types/object.py b/pyrogram/types/object.py similarity index 77% rename from pyrogram/client/types/object.py rename to pyrogram/types/object.py index 750e0be765..887b8e08a9 100644 --- a/pyrogram/client/types/object.py +++ b/pyrogram/types/object.py @@ -17,7 +17,6 @@ # along with Pyrogram. If not, see . import typing -from collections import OrderedDict from datetime import datetime from json import dumps @@ -26,18 +25,18 @@ class Meta(type, metaclass=type("", (type,), {"__str__": lambda _: "~hi"})): def __str__(self): - return "".format(self.__name__) + return f"" class Object(metaclass=Meta): - def __init__(self, client: "pyrogram.BaseClient" = None): + def __init__(self, client: "pyrogram.Client" = None): self._client = client - def bind(self, client: "pyrogram.BaseClient"): + def bind(self, client: "pyrogram.Client"): """Bind a Client instance to this Pyrogram Object Parameters: - client (:obj:`Client`): + client (:obj:`~pyrogram.types.Client`): The Client instance to bind this object with. Useful to re-enable bound methods after serializing and deserializing Pyrogram objects with ``repr`` and ``eval``. """ @@ -53,27 +52,29 @@ def default(obj: "Object"): if isinstance(obj, typing.Match): return repr(obj) - return OrderedDict( - [("_", "pyrogram." + obj.__class__.__name__)] - + [ - (attr, "*" * len(getattr(obj, attr))) - if attr == "phone_number" - else (attr, str(datetime.fromtimestamp(getattr(obj, attr)))) - if attr.endswith("date") - else (attr, getattr(obj, attr)) + return { + "_": obj.__class__.__name__, + **{ + attr: ( + "*" * len(getattr(obj, attr)) + if attr == "phone_number" else + str(datetime.fromtimestamp(getattr(obj, attr))) + if attr.endswith("date") else + getattr(obj, attr) + ) for attr in filter(lambda x: not x.startswith("_"), obj.__dict__) if getattr(obj, attr) is not None - ] - ) + } + } def __str__(self) -> str: return dumps(self, indent=4, default=Object.default, ensure_ascii=False) def __repr__(self) -> str: - return "pyrogram.{}({})".format( + return "pyrogram.types.{}({})".format( self.__class__.__name__, ", ".join( - "{}={}".format(attr, repr(getattr(self, attr))) + f"{attr}={repr(getattr(self, attr))}" for attr in filter(lambda x: not x.startswith("_"), self.__dict__) if getattr(self, attr) is not None ) diff --git a/pyrogram/client/types/update.py b/pyrogram/types/update.py similarity index 83% rename from pyrogram/client/types/update.py rename to pyrogram/types/update.py index 3f70f58007..7231a92b04 100644 --- a/pyrogram/client/types/update.py +++ b/pyrogram/types/update.py @@ -16,17 +16,12 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -class StopPropagation(StopAsyncIteration): - pass - - -class ContinuePropagation(StopAsyncIteration): - pass +import pyrogram class Update: def stop_propagation(self): - raise StopPropagation + raise pyrogram.StopPropagation def continue_propagation(self): - raise ContinuePropagation + raise pyrogram.ContinuePropagation diff --git a/pyrogram/client/types/user_and_chats/__init__.py b/pyrogram/types/user_and_chats/__init__.py similarity index 100% rename from pyrogram/client/types/user_and_chats/__init__.py rename to pyrogram/types/user_and_chats/__init__.py diff --git a/pyrogram/client/types/user_and_chats/chat.py b/pyrogram/types/user_and_chats/chat.py similarity index 81% rename from pyrogram/client/types/user_and_chats/chat.py rename to pyrogram/types/user_and_chats/chat.py index 85ef0a1f87..a0f40eed58 100644 --- a/pyrogram/client/types/user_and_chats/chat.py +++ b/pyrogram/types/user_and_chats/chat.py @@ -19,12 +19,10 @@ from typing import Union, List, Generator, Optional import pyrogram -from pyrogram.api import types -from .chat_permissions import ChatPermissions -from .chat_photo import ChatPhoto -from .restriction import Restriction +from pyrogram import raw +from pyrogram import types +from pyrogram import utils from ..object import Object -from ...ext import utils class Chat(Object): @@ -65,51 +63,51 @@ class Chat(Object): last_name (``str``, *optional*): Last name of the other party in a private chat, for private chats. - photo (:obj:`ChatPhoto`, *optional*): + photo (:obj:`~pyrogram.types.ChatPhoto`, *optional*): Chat photo. Suitable for downloads only. description (``str``, *optional*): Bio, for private chats and bots or description for groups, supergroups and channels. - Returned only in :meth:`~Client.get_chat`. + Returned only in :meth:`~pyrogram.Client.get_chat`. invite_link (``str``, *optional*): Chat invite link, for groups, supergroups and channels. - Returned only in :meth:`~Client.get_chat`. + Returned only in :meth:`~pyrogram.Client.get_chat`. - pinned_message (:obj:`Message`, *optional*): + pinned_message (:obj:`~pyrogram.types.Message`, *optional*): Pinned message, for groups, supergroups channels and own chat. - Returned only in :meth:`~Client.get_chat`. + Returned only in :meth:`~pyrogram.Client.get_chat`. sticker_set_name (``str``, *optional*): For supergroups, name of group sticker set. - Returned only in :meth:`~Client.get_chat`. + Returned only in :meth:`~pyrogram.Client.get_chat`. can_set_sticker_set (``bool``, *optional*): True, if the group sticker set can be changed by you. - Returned only in :meth:`~Client.get_chat`. + Returned only in :meth:`~pyrogram.Client.get_chat`. members_count (``int``, *optional*): Chat members count, for groups, supergroups and channels only. - restrictions (List of :obj:`Restriction`, *optional*): + restrictions (List of :obj:`~pyrogram.types.Restriction`, *optional*): The list of reasons why this chat might be unavailable to some users. This field is available only in case *is_restricted* is True. - permissions (:obj:`ChatPermissions` *optional*): + permissions (:obj:`~pyrogram.types.ChatPermissions` *optional*): Default chat member permissions, for groups and supergroups. distance (``int``, *optional*): Distance in meters of this group chat from your location. - Returned only in :meth:`~Client.get_nearby_chats`. + Returned only in :meth:`~pyrogram.Client.get_nearby_chats`. - linked_chat (:obj:`Chat`, *optional*): + linked_chat (:obj:`~pyrogram.types.Chat`, *optional*): The linked discussion group (in case of channels) or the linked channel (in case of supergroups). """ def __init__( self, *, - client: "pyrogram.BaseClient" = None, + client: "pyrogram.Client" = None, id: int, type: str, is_verified: bool = None, @@ -121,17 +119,17 @@ def __init__( username: str = None, first_name: str = None, last_name: str = None, - photo: ChatPhoto = None, + photo: "types.ChatPhoto" = None, description: str = None, invite_link: str = None, pinned_message=None, sticker_set_name: str = None, can_set_sticker_set: bool = None, members_count: int = None, - restrictions: List[Restriction] = None, - permissions: "pyrogram.ChatPermissions" = None, + restrictions: List["types.Restriction"] = None, + permissions: "types.ChatPermissions" = None, distance: int = None, - linked_chat: "pyrogram.Chat" = None + linked_chat: "types.Chat" = None ): super().__init__(client) @@ -159,7 +157,7 @@ def __init__( self.linked_chat = linked_chat @staticmethod - def _parse_user_chat(client, user: types.User) -> "Chat": + def _parse_user_chat(client, user: raw.types.User) -> "Chat": peer_id = user.id return Chat( @@ -172,13 +170,13 @@ def _parse_user_chat(client, user: types.User) -> "Chat": username=user.username, first_name=user.first_name, last_name=user.last_name, - photo=ChatPhoto._parse(client, user.photo, peer_id, user.access_hash), - restrictions=pyrogram.List([Restriction._parse(r) for r in user.restriction_reason]) or None, + photo=types.ChatPhoto._parse(client, user.photo, peer_id, user.access_hash), + restrictions=types.List([types.Restriction._parse(r) for r in user.restriction_reason]) or None, client=client ) @staticmethod - def _parse_chat_chat(client, chat: types.Chat) -> "Chat": + def _parse_chat_chat(client, chat: raw.types.Chat) -> "Chat": peer_id = -chat.id return Chat( @@ -186,14 +184,14 @@ def _parse_chat_chat(client, chat: types.Chat) -> "Chat": type="group", title=chat.title, is_creator=getattr(chat, "creator", None), - photo=ChatPhoto._parse(client, getattr(chat, "photo", None), peer_id, 0), - permissions=ChatPermissions._parse(getattr(chat, "default_banned_rights", None)), + photo=types.ChatPhoto._parse(client, getattr(chat, "photo", None), peer_id, 0), + permissions=types.ChatPermissions._parse(getattr(chat, "default_banned_rights", None)), members_count=getattr(chat, "participants_count", None), client=client ) @staticmethod - def _parse_channel_chat(client, channel: types.Channel) -> "Chat": + def _parse_channel_chat(client, channel: raw.types.Channel) -> "Chat": peer_id = utils.get_channel_id(channel.id) restriction_reason = getattr(channel, "restriction_reason", []) @@ -206,35 +204,35 @@ def _parse_channel_chat(client, channel: types.Channel) -> "Chat": is_scam=getattr(channel, "scam", None), title=channel.title, username=getattr(channel, "username", None), - photo=ChatPhoto._parse(client, getattr(channel, "photo", None), peer_id, channel.access_hash), - restrictions=pyrogram.List([Restriction._parse(r) for r in restriction_reason]) or None, - permissions=ChatPermissions._parse(getattr(channel, "default_banned_rights", None)), + photo=types.ChatPhoto._parse(client, getattr(channel, "photo", None), peer_id, channel.access_hash), + restrictions=types.List([types.Restriction._parse(r) for r in restriction_reason]) or None, + permissions=types.ChatPermissions._parse(getattr(channel, "default_banned_rights", None)), members_count=getattr(channel, "participants_count", None), client=client ) @staticmethod - def _parse(client, message: types.Message or types.MessageService, users: dict, chats: dict) -> "Chat": - if isinstance(message.to_id, types.PeerUser): + def _parse(client, message: raw.types.Message or raw.types.MessageService, users: dict, chats: dict) -> "Chat": + if isinstance(message.to_id, raw.types.PeerUser): return Chat._parse_user_chat(client, users[message.to_id.user_id if message.out else message.from_id]) - if isinstance(message.to_id, types.PeerChat): + if isinstance(message.to_id, raw.types.PeerChat): return Chat._parse_chat_chat(client, chats[message.to_id.chat_id]) return Chat._parse_channel_chat(client, chats[message.to_id.channel_id]) @staticmethod def _parse_dialog(client, peer, users: dict, chats: dict): - if isinstance(peer, types.PeerUser): + if isinstance(peer, raw.types.PeerUser): return Chat._parse_user_chat(client, users[peer.user_id]) - elif isinstance(peer, types.PeerChat): + elif isinstance(peer, raw.types.PeerChat): return Chat._parse_chat_chat(client, chats[peer.chat_id]) else: return Chat._parse_channel_chat(client, chats[peer.channel_id]) @staticmethod - async def _parse_full(client, chat_full: types.messages.ChatFull or types.UserFull) -> "Chat": - if isinstance(chat_full, types.UserFull): + async def _parse_full(client, chat_full: raw.types.messages.ChatFull or raw.types.UserFull) -> "Chat": + if isinstance(chat_full, raw.types.UserFull): parsed_chat = Chat._parse_user_chat(client, chat_full.user) parsed_chat.description = chat_full.about @@ -252,15 +250,15 @@ async def _parse_full(client, chat_full: types.messages.ChatFull or types.UserFu if full_chat.id == c.id: chat = c - if isinstance(full_chat, types.ChannelFull): + if isinstance(full_chat, raw.types.ChannelFull): if full_chat.linked_chat_id == c.id: linked_chat = c - if isinstance(full_chat, types.ChatFull): + if isinstance(full_chat, raw.types.ChatFull): parsed_chat = Chat._parse_chat_chat(client, chat) parsed_chat.description = full_chat.about or None - if isinstance(full_chat.participants, types.ChatParticipants): + if isinstance(full_chat.participants, raw.types.ChatParticipants): parsed_chat.members_count = len(full_chat.participants.participants) else: parsed_chat = Chat._parse_channel_chat(client, chat) @@ -278,22 +276,22 @@ async def _parse_full(client, chat_full: types.messages.ChatFull or types.UserFu message_ids=full_chat.pinned_msg_id ) - if isinstance(full_chat.exported_invite, types.ChatInviteExported): + if isinstance(full_chat.exported_invite, raw.types.ChatInviteExported): parsed_chat.invite_link = full_chat.exported_invite.link return parsed_chat @staticmethod - def _parse_chat(client, chat: Union[types.Chat, types.User, types.Channel]) -> "Chat": - if isinstance(chat, types.Chat): + def _parse_chat(client, chat: Union[raw.types.Chat, raw.types.User, raw.types.Channel]) -> "Chat": + if isinstance(chat, raw.types.Chat): return Chat._parse_chat_chat(client, chat) - elif isinstance(chat, types.User): + elif isinstance(chat, raw.types.User): return Chat._parse_user_chat(client, chat) else: return Chat._parse_channel_chat(client, chat) async def archive(self): - """Bound method *archive* of :obj:`Chat`. + """Bound method *archive* of :obj:`~pyrogram.types.Chat`. Use as a shortcut for: @@ -316,7 +314,7 @@ async def archive(self): return await self._client.archive_chats(self.id) async def unarchive(self): - """Bound method *unarchive* of :obj:`Chat`. + """Bound method *unarchive* of :obj:`~pyrogram.types.Chat`. Use as a shortcut for: @@ -340,7 +338,7 @@ async def unarchive(self): # TODO: Remove notes about "All Members Are Admins" for basic groups, the attribute doesn't exist anymore async def set_title(self, title: str) -> bool: - """Bound method *set_title* of :obj:`Chat`. + """Bound method *set_title* of :obj:`~pyrogram.types.Chat`. Use as a shortcut for: @@ -378,7 +376,7 @@ async def set_title(self, title: str) -> bool: ) async def set_description(self, description: str) -> bool: - """Bound method *set_description* of :obj:`Chat`. + """Bound method *set_description* of :obj:`~pyrogram.types.Chat`. Use as a shortcut for: @@ -412,7 +410,7 @@ async def set_description(self, description: str) -> bool: ) async def set_photo(self, photo: str) -> bool: - """Bound method *set_photo* of :obj:`Chat`. + """Bound method *set_photo* of :obj:`~pyrogram.types.Chat`. Use as a shortcut for: @@ -430,7 +428,7 @@ async def set_photo(self, photo: str) -> bool: Parameters: photo (``str``): - New chat photo. You can pass a :obj:`Photo` id or a file path to upload a new photo. + New chat photo. You can pass a :obj:`~pyrogram.types.Photo` id or a file path to upload a new photo. Returns: ``bool``: True on success. @@ -449,8 +447,8 @@ async def kick_member( self, user_id: Union[int, str], until_date: int = 0 - ) -> Union["pyrogram.Message", bool]: - """Bound method *kick_member* of :obj:`Chat`. + ) -> Union["types.Message", bool]: + """Bound method *kick_member* of :obj:`~pyrogram.types.Chat`. Use as a shortcut for: @@ -482,7 +480,7 @@ async def kick_member( considered to be banned forever. Defaults to 0 (ban forever). Returns: - :obj:`Message` | ``bool``: On success, a service message will be returned (when applicable), otherwise, in + :obj:`~pyrogram.types.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: @@ -499,7 +497,7 @@ async def unban_member( self, user_id: Union[int, str] ) -> bool: - """Bound method *unban_member* of :obj:`Chat`. + """Bound method *unban_member* of :obj:`~pyrogram.types.Chat`. Use as a shortcut for: @@ -535,10 +533,10 @@ async def unban_member( async def restrict_member( self, user_id: Union[int, str], - permissions: ChatPermissions, + permissions: "types.ChatPermissions", until_date: int = 0, - ) -> "pyrogram.Chat": - """Bound method *unban_member* of :obj:`Chat`. + ) -> "types.Chat": + """Bound method *unban_member* of :obj:`~pyrogram.types.Chat`. Use as a shortcut for: @@ -560,7 +558,7 @@ async def restrict_member( Unique identifier (int) or username (str) of the target user. For a contact that exists in your Telegram address book you can use his phone number (str). - permissions (:obj:`ChatPermissions`): + permissions (:obj:`~pyrogram.types.ChatPermissions`): New user permissions. until_date (``int``, *optional*): @@ -569,7 +567,7 @@ async def restrict_member( considered to be banned forever. Defaults to 0 (ban forever). Returns: - :obj:`Chat`: On success, a chat object is returned. + :obj:`~pyrogram.types.Chat`: On success, a chat object is returned. Raises: RPCError: In case of a Telegram RPC error. @@ -594,7 +592,7 @@ async def promote_member( can_pin_messages: bool = False, can_promote_members: bool = False ) -> bool: - """Bound method *promote_member* of :obj:`Chat`. + """Bound method *promote_member* of :obj:`~pyrogram.types.Chat`. Use as a shortcut for: @@ -663,7 +661,7 @@ async def promote_member( ) async def join(self): - """Bound method *join* of :obj:`Chat`. + """Bound method *join* of :obj:`~pyrogram.types.Chat`. Use as a shortcut for: @@ -680,7 +678,7 @@ async def join(self): This only works for public groups, channels that have set a username or linked chats. Returns: - :obj:`Chat`: On success, a chat object is returned. + :obj:`~pyrogram.types.Chat`: On success, a chat object is returned. Raises: RPCError: In case of a Telegram RPC error. @@ -689,7 +687,7 @@ async def join(self): return await self._client.join_chat(self.username or self.id) async def leave(self): - """Bound method *leave* of :obj:`Chat`. + """Bound method *leave* of :obj:`~pyrogram.types.Chat`. Use as a shortcut for: @@ -709,7 +707,7 @@ async def leave(self): return await self._client.leave_chat(self.id) async def export_invite_link(self): - """Bound method *export_invite_link* of :obj:`Chat`. + """Bound method *export_invite_link* of :obj:`~pyrogram.types.Chat`. Use as a shortcut for: @@ -734,8 +732,8 @@ async def export_invite_link(self): async def get_member( self, user_id: Union[int, str], - ) -> "pyrogram.ChatMember": - """Bound method *get_member* of :obj:`Chat`. + ) -> "types.ChatMember": + """Bound method *get_member* of :obj:`~pyrogram.types.Chat`. Use as a shortcut for: @@ -752,7 +750,7 @@ async def get_member( chat.get_member(user_id) Returns: - :obj:`ChatMember`: On success, a chat member is returned. + :obj:`~pyrogram.types.ChatMember`: On success, a chat member is returned. """ return await self._client.get_chat_member( @@ -766,8 +764,8 @@ async def get_members( limit: int = 200, query: str = "", filter: str = "all" - ) -> List["pyrogram.ChatMember"]: - """Bound method *get_members* of :obj:`Chat`. + ) -> List["types.ChatMember"]: + """Bound method *get_members* of :obj:`~pyrogram.types.Chat`. Use as a shortcut for: @@ -782,7 +780,7 @@ async def get_members( chat.get_members() Returns: - List of :obj:`ChatMember`: On success, a list of chat members is returned. + List of :obj:`~pyrogram.types.ChatMember`: On success, a list of chat members is returned. """ return await self._client.get_chat_members( @@ -798,8 +796,8 @@ def iter_members( limit: int = 0, query: str = "", filter: str = "all" - ) -> Optional[Generator["pyrogram.ChatMember", None, None]]: - """Bound method *iter_members* of :obj:`Chat`. + ) -> Optional[Generator["types.ChatMember", None, None]]: + """Bound method *iter_members* of :obj:`~pyrogram.types.Chat`. Use as a shortcut for: @@ -815,7 +813,7 @@ def iter_members( print(member.user.first_name) Returns: - ``Generator``: A generator yielding :obj:`ChatMember` objects. + ``Generator``: A generator yielding :obj:`~pyrogram.types.ChatMember` objects. """ return self._client.iter_chat_members( @@ -830,7 +828,7 @@ async def add_members( user_ids: Union[Union[int, str], List[Union[int, str]]], forward_limit: int = 100 ) -> bool: - """Bound method *add_members* of :obj:`Chat`. + """Bound method *add_members* of :obj:`~pyrogram.types.Chat`. Use as a shortcut for: diff --git a/pyrogram/client/types/user_and_chats/chat_member.py b/pyrogram/types/user_and_chats/chat_member.py similarity index 89% rename from pyrogram/client/types/user_and_chats/chat_member.py rename to pyrogram/types/user_and_chats/chat_member.py index 203be137bf..31a27402c9 100644 --- a/pyrogram/client/types/user_and_chats/chat_member.py +++ b/pyrogram/types/user_and_chats/chat_member.py @@ -17,8 +17,8 @@ # along with Pyrogram. If not, see . import pyrogram - -from pyrogram.api import types +from pyrogram import raw +from pyrogram import types from ..object import Object @@ -26,7 +26,7 @@ class ChatMember(Object): """Contains information about one member of a chat. Parameters: - user (:obj:`User`): + user (:obj:`~pyrogram.types.User`): Information about the user. status (``str``): @@ -45,14 +45,14 @@ class ChatMember(Object): Date when the user joined, unix time. Not available for creator. - invited_by (:obj:`User`, *optional*): + invited_by (:obj:`~pyrogram.types.User`, *optional*): Administrators and self member only. Information about the user who invited this member. In case the user joined by himself this will be the same as "user". - promoted_by (:obj:`User`, *optional*): + promoted_by (:obj:`~pyrogram.types.User`, *optional*): Administrators only. Information about the user who promoted this member as administrator. - restricted_by (:obj:`User`, *optional*): + restricted_by (:obj:`~pyrogram.types.User`, *optional*): Restricted and kicked only. Information about the user who restricted or kicked this member. is_member (``bool``, *optional*): @@ -128,15 +128,15 @@ class ChatMember(Object): def __init__( self, *, - client: "pyrogram.BaseClient" = None, - user: "pyrogram.User", + client: "pyrogram.Client" = None, + user: "types.User", status: str, title: str = None, until_date: int = None, joined_date: int = None, - invited_by: "pyrogram.User" = None, - promoted_by: "pyrogram.User" = None, - restricted_by: "pyrogram.User" = None, + invited_by: "types.User" = None, + promoted_by: "types.User" = None, + restricted_by: "types.User" = None, is_member: bool = None, # Admin permissions @@ -193,14 +193,16 @@ def __init__( @staticmethod def _parse(client, member, users) -> "ChatMember": - user = pyrogram.User._parse(client, users[member.user_id]) + user = types.User._parse(client, users[member.user_id]) invited_by = ( - pyrogram.User._parse(client, users[member.inviter_id]) + types.User._parse(client, users[member.inviter_id]) if getattr(member, "inviter_id", None) else None ) - if isinstance(member, (types.ChannelParticipant, types.ChannelParticipantSelf, types.ChatParticipant)): + if isinstance(member, (raw.types.ChannelParticipant, + raw.types.ChannelParticipantSelf, + raw.types.ChatParticipant)): return ChatMember( user=user, status="member", @@ -209,7 +211,7 @@ def _parse(client, member, users) -> "ChatMember": client=client ) - if isinstance(member, (types.ChannelParticipantCreator, types.ChatParticipantCreator)): + if isinstance(member, (raw.types.ChannelParticipantCreator, raw.types.ChatParticipantCreator)): return ChatMember( user=user, status="creator", @@ -217,7 +219,7 @@ def _parse(client, member, users) -> "ChatMember": client=client ) - if isinstance(member, types.ChatParticipantAdmin): + if isinstance(member, raw.types.ChatParticipantAdmin): return ChatMember( user=user, status="administrator", @@ -226,7 +228,7 @@ def _parse(client, member, users) -> "ChatMember": client=client ) - if isinstance(member, types.ChannelParticipantAdmin): + if isinstance(member, raw.types.ChannelParticipantAdmin): permissions = member.admin_rights return ChatMember( @@ -235,7 +237,7 @@ def _parse(client, member, users) -> "ChatMember": title=member.rank, joined_date=member.date, invited_by=invited_by, - promoted_by=pyrogram.User._parse(client, users[member.promoted_by]), + promoted_by=types.User._parse(client, users[member.promoted_by]), can_be_edited=member.can_edit, can_change_info=permissions.change_info, can_post_messages=permissions.post_messages, @@ -248,7 +250,7 @@ def _parse(client, member, users) -> "ChatMember": client=client ) - if isinstance(member, types.ChannelParticipantBanned): + if isinstance(member, raw.types.ChannelParticipantBanned): denied_permissions = member.banned_rights return ChatMember( @@ -257,7 +259,7 @@ def _parse(client, member, users) -> "ChatMember": until_date=denied_permissions.until_date, joined_date=member.date, is_member=not member.left, - restricted_by=pyrogram.User._parse(client, users[member.kicked_by]), + restricted_by=types.User._parse(client, users[member.kicked_by]), can_send_messages=not denied_permissions.send_messages, can_send_media_messages=not denied_permissions.send_media, can_send_stickers=not denied_permissions.send_stickers, diff --git a/pyrogram/client/types/user_and_chats/chat_permissions.py b/pyrogram/types/user_and_chats/chat_permissions.py similarity index 96% rename from pyrogram/client/types/user_and_chats/chat_permissions.py rename to pyrogram/types/user_and_chats/chat_permissions.py index 03d3e0721c..2d9306adde 100644 --- a/pyrogram/client/types/user_and_chats/chat_permissions.py +++ b/pyrogram/types/user_and_chats/chat_permissions.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from pyrogram.api import types +from pyrogram import raw from ..object import Object @@ -103,8 +103,8 @@ def __init__( self.can_pin_messages = can_pin_messages @staticmethod - def _parse(denied_permissions: types.ChatBannedRights) -> "ChatPermissions": - if isinstance(denied_permissions, types.ChatBannedRights): + def _parse(denied_permissions: "raw.types.ChatBannedRights") -> "ChatPermissions": + if isinstance(denied_permissions, raw.types.ChatBannedRights): return ChatPermissions( can_send_messages=not denied_permissions.send_messages, can_send_media_messages=not denied_permissions.send_media, diff --git a/pyrogram/client/types/user_and_chats/chat_photo.py b/pyrogram/types/user_and_chats/chat_photo.py similarity index 86% rename from pyrogram/client/types/user_and_chats/chat_photo.py rename to pyrogram/types/user_and_chats/chat_photo.py index 1966b1be60..58a9fcc1be 100644 --- a/pyrogram/client/types/user_and_chats/chat_photo.py +++ b/pyrogram/types/user_and_chats/chat_photo.py @@ -17,12 +17,13 @@ # along with Pyrogram. If not, see . from struct import pack +from typing import Union import pyrogram -from pyrogram.api import types -from pyrogram.client.ext import utils +from pyrogram import raw +from pyrogram.utils import encode_file_id from ..object import Object -from ...ext.utils import encode_file_id +from ... import utils class ChatPhoto(Object): @@ -41,7 +42,7 @@ class ChatPhoto(Object): def __init__( self, *, - client: "pyrogram.BaseClient" = None, + client: "pyrogram.Client" = None, small_file_id: str, big_file_id: str ): @@ -51,8 +52,13 @@ def __init__( self.big_file_id = big_file_id @staticmethod - def _parse(client, chat_photo: types.UserProfilePhoto or types.ChatPhoto, peer_id: int, peer_access_hash: int): - if not isinstance(chat_photo, (types.UserProfilePhoto, types.ChatPhoto)): + def _parse( + client, + chat_photo: Union["raw.types.UserProfilePhoto", "raw.types.ChatPhoto"], + peer_id: int, + peer_access_hash: int + ): + if not isinstance(chat_photo, (raw.types.UserProfilePhoto, raw.types.ChatPhoto)): return None if peer_access_hash is None: diff --git a/pyrogram/client/types/user_and_chats/chat_preview.py b/pyrogram/types/user_and_chats/chat_preview.py similarity index 79% rename from pyrogram/client/types/user_and_chats/chat_preview.py rename to pyrogram/types/user_and_chats/chat_preview.py index fa48a319f3..1a0f9c94f9 100644 --- a/pyrogram/client/types/user_and_chats/chat_preview.py +++ b/pyrogram/types/user_and_chats/chat_preview.py @@ -19,10 +19,9 @@ from typing import List import pyrogram -from pyrogram.api import types -from ..messages_and_media import Photo +from pyrogram import raw +from pyrogram import types from ..object import Object -from ..user_and_chats.user import User class ChatPreview(Object): @@ -38,22 +37,22 @@ class ChatPreview(Object): members_count (``int``): Chat members count. - photo (:obj:`Photo`, *optional*): + photo (:obj:`~pyrogram.types.Photo`, *optional*): Chat photo. - members (List of :obj:`User`, *optional*): + members (List of :obj:`~pyrogram.types.User`, *optional*): Preview of some of the chat members. """ def __init__( self, *, - client: "pyrogram.BaseClient" = None, + client: "pyrogram.Client" = None, title: str, type: str, members_count: int, - photo: Photo = None, - members: List[User] = None + photo: "types.Photo" = None, + members: List["types.User"] = None ): super().__init__(client) @@ -64,15 +63,15 @@ def __init__( self.members = members @staticmethod - def _parse(client, chat_invite: types.ChatInvite) -> "ChatPreview": + def _parse(client, chat_invite: "raw.types.ChatInvite") -> "ChatPreview": return ChatPreview( title=chat_invite.title, type=("group" if not chat_invite.channel else "channel" if chat_invite.broadcast else "supergroup"), members_count=chat_invite.participants_count, - photo=Photo._parse(client, chat_invite.photo), - members=[User._parse(client, user) for user in chat_invite.participants] or None, + photo=types.Photo._parse(client, chat_invite.photo), + members=[types.User._parse(client, user) for user in chat_invite.participants] or None, client=client ) diff --git a/pyrogram/client/types/user_and_chats/dialog.py b/pyrogram/types/user_and_chats/dialog.py similarity index 83% rename from pyrogram/client/types/user_and_chats/dialog.py rename to pyrogram/types/user_and_chats/dialog.py index bbe06a2c69..b0212a2739 100644 --- a/pyrogram/client/types/user_and_chats/dialog.py +++ b/pyrogram/types/user_and_chats/dialog.py @@ -17,21 +17,20 @@ # along with Pyrogram. If not, see . import pyrogram - -from pyrogram.api import types +from pyrogram import raw +from pyrogram import types from ..object import Object -from ..user_and_chats import Chat -from ...ext import utils +from ... import utils class Dialog(Object): """A user's dialog. Parameters: - chat (:obj:`Chat `): + chat (:obj:`~pyrogram.types.Chat`): Conversation the dialog belongs to. - top_message (:obj:`Message`): + top_message (:obj:`~pyrogram.types.Message`): The last message sent in the dialog at this time. unread_messages_count (``int``): @@ -50,9 +49,9 @@ class Dialog(Object): def __init__( self, *, - client: "pyrogram.BaseClient" = None, - chat: Chat, - top_message: "pyrogram.Message", + client: "pyrogram.Client" = None, + chat: "types.Chat", + top_message: "types.Message", unread_messages_count: int, unread_mentions_count: int, unread_mark: bool, @@ -68,9 +67,9 @@ def __init__( self.is_pinned = is_pinned @staticmethod - def _parse(client, dialog: types.Dialog, messages, users, chats) -> "Dialog": + def _parse(client, dialog: "raw.types.Dialog", messages, users, chats) -> "Dialog": return Dialog( - chat=Chat._parse_dialog(client, dialog.peer, users, chats), + chat=types.Chat._parse_dialog(client, dialog.peer, users, chats), top_message=messages.get(utils.get_peer_id(dialog.peer)), unread_messages_count=dialog.unread_count, unread_mentions_count=dialog.unread_mentions_count, diff --git a/pyrogram/client/types/user_and_chats/restriction.py b/pyrogram/types/user_and_chats/restriction.py similarity index 93% rename from pyrogram/client/types/user_and_chats/restriction.py rename to pyrogram/types/user_and_chats/restriction.py index abf04b7787..fd8b7a63fe 100644 --- a/pyrogram/client/types/user_and_chats/restriction.py +++ b/pyrogram/types/user_and_chats/restriction.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from pyrogram.api import types +from pyrogram import raw from ..object import Object @@ -42,7 +42,7 @@ def __init__(self, *, platform: str, reason: str, text: str): self.text = text @staticmethod - def _parse(restriction: types.RestrictionReason) -> "Restriction": + def _parse(restriction: "raw.types.RestrictionReason") -> "Restriction": return Restriction( platform=restriction.platform, reason=restriction.reason, diff --git a/pyrogram/client/types/user_and_chats/user.py b/pyrogram/types/user_and_chats/user.py similarity index 79% rename from pyrogram/client/types/user_and_chats/user.py rename to pyrogram/types/user_and_chats/user.py index f386e1616b..e9a9518220 100644 --- a/pyrogram/client/types/user_and_chats/user.py +++ b/pyrogram/types/user_and_chats/user.py @@ -16,17 +16,49 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import html from typing import List import pyrogram -from pyrogram.api import types -from pyrogram.client.ext import Link -from .chat_photo import ChatPhoto -from .restriction import Restriction +from pyrogram import raw +from pyrogram import types from ..object import Object from ..update import Update +class Link(str): + HTML = "{text}" + MD = "[{text}]({url})" + + def __init__(self, url: str, text: str, style: str): + super().__init__() + + self.url = url + self.text = text + self.style = style + + @staticmethod + def format(url: str, text: str, style: str): + if style in ["md", "markdown"]: + fmt = Link.MD + elif style in ["combined", "html", None]: + fmt = Link.HTML + else: + raise ValueError(f"{style} is not a valid style/parse mode") + + return fmt.format(url=url, text=html.escape(text)) + + # noinspection PyArgumentList + def __new__(cls, url, text, style): + return str.__new__(cls, Link.format(url, text, style)) + + def __call__(self, other: str = None, *, style: str = None): + return Link.format(self.url, other or self.text, style or self.style) + + def __str__(self): + return Link.format(self.url, self.text, self.style) + + class User(Object, Update): """A Telegram user or bot. @@ -100,10 +132,10 @@ class User(Object, Update): phone_number (``str``, *optional*): User's phone number. - photo (:obj:`ChatPhoto `, *optional*): + photo (:obj:`~pyrogram.types.ChatPhoto`, *optional*): User's or bot's current profile photo. Suitable for downloads only. - restrictions (List of :obj:`Restriction`, *optional*): + restrictions (List of :obj:`~pyrogram.types.Restriction`, *optional*): The list of reasons why this bot might be unavailable to some users. This field is available only in case *is_restricted* is True. """ @@ -111,7 +143,7 @@ class User(Object, Update): def __init__( self, *, - client: "pyrogram.BaseClient" = None, + client: "pyrogram.Client" = None, id: int, is_self: bool = None, is_contact: bool = None, @@ -131,8 +163,8 @@ def __init__( language_code: str = None, dc_id: int = None, phone_number: str = None, - photo: ChatPhoto = None, - restrictions: List[Restriction] = None + photo: "types.ChatPhoto" = None, + restrictions: List["types.Restriction"] = None ): super().__init__(client) @@ -160,10 +192,10 @@ def __init__( @property def mention(self): - return Link("tg://user?id={}".format(self.id), self.first_name, self._client.parse_mode) + return Link(f"tg://user?id={self.id}", self.first_name, self._client.parse_mode) @staticmethod - def _parse(client, user: types.User) -> "User" or None: + def _parse(client, user: "raw.types.User") -> "User" or None: if user is None: return None @@ -185,22 +217,22 @@ def _parse(client, user: types.User) -> "User" or None: language_code=user.lang_code, dc_id=getattr(user.photo, "dc_id", None), phone_number=user.phone, - photo=ChatPhoto._parse(client, user.photo, user.id, user.access_hash), - restrictions=pyrogram.List([Restriction._parse(r) for r in user.restriction_reason]) or None, + photo=types.ChatPhoto._parse(client, user.photo, user.id, user.access_hash), + restrictions=types.List([types.Restriction._parse(r) for r in user.restriction_reason]) or None, client=client ) @staticmethod - def _parse_status(user_status: types.UpdateUserStatus, is_bot: bool = False): - if isinstance(user_status, types.UserStatusOnline): + def _parse_status(user_status: "raw.types.UpdateUserStatus", is_bot: bool = False): + if isinstance(user_status, raw.types.UserStatusOnline): status, date = "online", user_status.expires - elif isinstance(user_status, types.UserStatusOffline): + elif isinstance(user_status, raw.types.UserStatusOffline): status, date = "offline", user_status.was_online - elif isinstance(user_status, types.UserStatusRecently): + elif isinstance(user_status, raw.types.UserStatusRecently): status, date = "recently", None - elif isinstance(user_status, types.UserStatusLastWeek): + elif isinstance(user_status, raw.types.UserStatusLastWeek): status, date = "within_week", None - elif isinstance(user_status, types.UserStatusLastMonth): + elif isinstance(user_status, raw.types.UserStatusLastMonth): status, date = "within_month", None else: status, date = "long_time_ago", None @@ -224,7 +256,7 @@ def _parse_status(user_status: types.UpdateUserStatus, is_bot: bool = False): } @staticmethod - def _parse_user_status(client, user_status: types.UpdateUserStatus): + def _parse_user_status(client, user_status: "raw.types.UpdateUserStatus"): return User( id=user_status.user_id, **User._parse_status(user_status.status), @@ -232,7 +264,7 @@ def _parse_user_status(client, user_status: types.UpdateUserStatus): ) async def archive(self): - """Bound method *archive* of :obj:`User`. + """Bound method *archive* of :obj:`~pyrogram.types.User`. Use as a shortcut for: @@ -255,7 +287,7 @@ async def archive(self): return await self._client.archive_chats(self.id) async def unarchive(self): - """Bound method *unarchive* of :obj:`User`. + """Bound method *unarchive* of :obj:`~pyrogram.types.User`. Use as a shortcut for: @@ -278,7 +310,7 @@ async def unarchive(self): return await self._client.unarchive_chats(self.id) def block(self): - """Bound method *block* of :obj:`User`. + """Bound method *block* of :obj:`~pyrogram.types.User`. Use as a shortcut for: @@ -301,7 +333,7 @@ def block(self): return self._client.block_user(self.id) def unblock(self): - """Bound method *unblock* of :obj:`User`. + """Bound method *unblock* of :obj:`~pyrogram.types.User`. Use as a shortcut for: @@ -324,7 +356,7 @@ def unblock(self): return self._client.unblock_user(self.id) def get_common_chats(self): - """Bound method *get_common_chats* of :obj:`User`. + """Bound method *get_common_chats* of :obj:`~pyrogram.types.User`. Use as a shortcut for: diff --git a/pyrogram/client/ext/utils.py b/pyrogram/utils.py similarity index 58% rename from pyrogram/client/ext/utils.py rename to pyrogram/utils.py index 9359ff0596..89ee7f6c79 100644 --- a/pyrogram/client/ext/utils.py +++ b/pyrogram/utils.py @@ -18,16 +18,18 @@ import asyncio import base64 +import functools +import hashlib +import os import struct -import sys from concurrent.futures.thread import ThreadPoolExecutor +from getpass import getpass from typing import List from typing import Union -import pyrogram -from pyrogram.api.types import PeerUser, PeerChat, PeerChannel -from . import BaseClient -from ...api import types +from pyrogram import raw +from pyrogram import types +from pyrogram.scaffold import Scaffold def decode_file_id(s: str) -> bytes: @@ -83,18 +85,15 @@ def decode_file_ref(file_ref: str) -> bytes: return base64.urlsafe_b64decode(file_ref + "=" * (-len(file_ref) % 4)) -async def ainput(prompt: str = ""): - print(prompt, end="", flush=True) - +async def ainput(prompt: str = "", *, hide: bool = False): with ThreadPoolExecutor(1) as executor: - return (await asyncio.get_event_loop().run_in_executor( - executor, sys.stdin.readline - )).rstrip() + func = functools.partial(getpass if hide else input, prompt) + return await asyncio.get_event_loop().run_in_executor(executor, func) def get_offset_date(dialogs): for m in reversed(dialogs.messages): - if isinstance(m, types.MessageEmpty): + if isinstance(m, raw.types.MessageEmpty): continue else: return m.date @@ -106,32 +105,30 @@ def get_input_media_from_file_id( file_id_str: str, file_ref: str = None, expected_media_type: int = None -) -> Union[types.InputMediaPhoto, types.InputMediaDocument]: +) -> Union["raw.types.InputMediaPhoto", "raw.types.InputMediaDocument"]: try: decoded = decode_file_id(file_id_str) except Exception: - raise ValueError("Failed to decode file_id: {}".format(file_id_str)) + raise ValueError(f"Failed to decode file_id: {file_id_str}") else: media_type = decoded[0] if expected_media_type is not None: if media_type != expected_media_type: - media_type_str = BaseClient.MEDIA_TYPE_ID.get(media_type, None) - expected_media_type_str = BaseClient.MEDIA_TYPE_ID.get(expected_media_type, None) + media_type_str = Scaffold.MEDIA_TYPE_ID.get(media_type, None) + expected_media_type_str = Scaffold.MEDIA_TYPE_ID.get(expected_media_type, None) - raise ValueError( - 'Expected: "{}", got "{}" file_id instead'.format(expected_media_type_str, media_type_str) - ) + raise ValueError(f'Expected: "{expected_media_type_str}", got "{media_type_str}" file_id instead') if media_type in (0, 1, 14): - raise ValueError("This file_id can only be used for download: {}".format(file_id_str)) + raise ValueError(f"This file_id can only be used for download: {file_id_str}") if media_type == 2: unpacked = struct.unpack(" List["pyrogram.Message"]: +async def parse_messages(client, messages: "raw.types.messages.Messages", replies: int = 1) -> List["types.Message"]: users = {i.id: i for i in messages.users} chats = {i.id: i for i in messages.chats} if not messages.messages: - return pyrogram.List() + return types.List() parsed_messages = [] for message in messages.messages: - parsed_messages.append(await pyrogram.Message._parse(client, message, users, chats, replies=0)) + parsed_messages.append(await types.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} @@ -183,10 +180,10 @@ async def parse_messages(client, messages: types.messages.Messages, replies: int if reply.message_id == reply_id: message.reply_to_message = reply - return pyrogram.List(parsed_messages) + return types.List(parsed_messages) -def parse_deleted_messages(client, update) -> List["pyrogram.Message"]: +def parse_deleted_messages(client, update) -> List["types.Message"]: messages = update.messages channel_id = getattr(update, "channel_id", None) @@ -194,9 +191,9 @@ def parse_deleted_messages(client, update) -> List["pyrogram.Message"]: for message in messages: parsed_messages.append( - pyrogram.Message( + types.Message( message_id=message, - chat=pyrogram.Chat( + chat=types.Chat( id=get_channel_id(channel_id), type="channel", client=client @@ -205,14 +202,14 @@ def parse_deleted_messages(client, update) -> List["pyrogram.Message"]: ) ) - return pyrogram.List(parsed_messages) + return types.List(parsed_messages) -def unpack_inline_message_id(inline_message_id: str) -> types.InputBotInlineMessageID: +def unpack_inline_message_id(inline_message_id: str) -> "raw.types.InputBotInlineMessageID": r = inline_message_id + "=" * (-len(inline_message_id) % 4) - r = struct.unpack(" types.InputBotInlineMess MAX_USER_ID = 2147483647 -def get_peer_id(peer: Union[PeerUser, PeerChat, PeerChannel]) -> int: - if isinstance(peer, PeerUser): +def get_peer_id(peer: raw.base.Peer) -> int: + if isinstance(peer, raw.types.PeerUser): return peer.user_id - if isinstance(peer, PeerChat): + if isinstance(peer, raw.types.PeerChat): return -peer.chat_id - if isinstance(peer, PeerChannel): + if isinstance(peer, raw.types.PeerChannel): return MAX_CHANNEL_ID - peer.channel_id - raise ValueError("Peer type invalid: {}".format(peer)) + raise ValueError(f"Peer type invalid: {peer}") def get_peer_type(peer_id: int) -> str: @@ -248,8 +245,91 @@ def get_peer_type(peer_id: int) -> str: elif 0 < peer_id <= MAX_USER_ID: return "user" - raise ValueError("Peer id invalid: {}".format(peer_id)) + raise ValueError(f"Peer id invalid: {peer_id}") def get_channel_id(peer_id: int) -> int: return MAX_CHANNEL_ID - peer_id + + +def btoi(b: bytes) -> int: + return int.from_bytes(b, "big") + + +def itob(i: int) -> bytes: + return i.to_bytes(256, "big") + + +def sha256(data: bytes) -> bytes: + return hashlib.sha256(data).digest() + + +def xor(a: bytes, b: bytes) -> bytes: + return bytes(i ^ j for i, j in zip(a, b)) + + +def compute_password_hash(algo: raw.types.PasswordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow, + password: str) -> bytes: + hash1 = sha256(algo.salt1 + password.encode() + algo.salt1) + hash2 = sha256(algo.salt2 + hash1 + algo.salt2) + hash3 = hashlib.pbkdf2_hmac("sha512", hash2, algo.salt1, 100000) + + return sha256(algo.salt2 + hash3 + algo.salt2) + + +# noinspection PyPep8Naming +def compute_password_check(r: raw.types.account.Password, password: str) -> raw.types.InputCheckPasswordSRP: + algo = r.current_algo + + p_bytes = algo.p + p = btoi(algo.p) + + g_bytes = itob(algo.g) + g = algo.g + + B_bytes = r.srp_B + B = btoi(B_bytes) + + srp_id = r.srp_id + + x_bytes = compute_password_hash(algo, password) + x = btoi(x_bytes) + + g_x = pow(g, x, p) + + k_bytes = sha256(p_bytes + g_bytes) + k = btoi(k_bytes) + + kg_x = (k * g_x) % p + + while True: + a_bytes = os.urandom(256) + a = btoi(a_bytes) + + A = pow(g, a, p) + A_bytes = itob(A) + + u = btoi(sha256(A_bytes + B_bytes)) + + if u > 0: + break + + g_b = (B - kg_x) % p + + ux = u * x + a_ux = a + ux + S = pow(g_b, a_ux, p) + S_bytes = itob(S) + + K_bytes = sha256(S_bytes) + + M1_bytes = sha256( + xor(sha256(p_bytes), sha256(g_bytes)) + + sha256(algo.salt1) + + sha256(algo.salt2) + + A_bytes + + B_bytes + + K_bytes + ) + + return raw.types.InputCheckPasswordSRP(srp_id=srp_id, A=A_bytes, M1=M1_bytes) diff --git a/requirements.txt b/requirements.txt index 1af9706190..72ed1aa51a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,3 @@ pyaes==1.6.1 pysocks==1.7.1 -async_lru==1.0.1 -async_generator==1.10 \ No newline at end of file +async_lru==1.0.2 \ No newline at end of file diff --git a/setup.py b/setup.py index 0e9bb2dbfa..3b88bab70e 100644 --- a/setup.py +++ b/setup.py @@ -39,7 +39,7 @@ class Clean(Command): DIST = ["./build", "./dist", "./Pyrogram.egg-info"] - API = ["pyrogram/errors/exceptions", "pyrogram/api/functions", "pyrogram/api/types", "pyrogram/api/all.py"] + API = ["pyrogram/errors/exceptions", "pyrogram/raw/functions", "pyrogram/raw/types", "pyrogram/raw/all.py"] DOCS = [ "docs/source/telegram", "docs/build", "docs/source/api/methods", "docs/source/api/types", "docs/source/api/bound-methods" @@ -139,17 +139,17 @@ def run(self): author_email="dan@pyrogram.org", license="LGPLv3+", classifiers=[ - "Development Status :: 4 - Beta", + "Development Status :: 5 - Production/Stable", "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", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", "Programming Language :: Python :: Implementation", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", @@ -167,11 +167,11 @@ def run(self): "Source": "https://github.com/pyrogram/pyrogram", "Documentation": "https://docs.pyrogram.org", }, - python_requires="~=3.5", + python_requires="~=3.6", packages=find_packages(exclude=["compiler*"]), package_data={ - "pyrogram.client.ext": ["mime.types"], - "pyrogram.client.storage": ["schema.sql"] + "pyrogram": ["mime.types"], + "pyrogram.storage": ["schema.sql"] }, zip_safe=False, install_requires=requires, From 23b1450f11b5b564f20b1f41934327d6c02fb20c Mon Sep 17 00:00:00 2001 From: Accipiter7 <64995687+Accipiter7@users.noreply.github.com> Date: Sat, 22 Aug 2020 11:46:16 +0530 Subject: [PATCH 0279/1185] [Client] Fixed ImportError (#471) --- 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 624c98c616..66201638c1 100644 --- a/pyrogram/client/client.py +++ b/pyrogram/client/client.py @@ -27,7 +27,7 @@ from configparser import ConfigParser from hashlib import sha256, md5 from importlib import import_module -from pathlib import Path, Pureapath +from pathlib import Path, PurePath from signal import signal, SIGINT, SIGTERM, SIGABRT from typing import Union, List, BinaryIO From fc07f8d5d1f31b46823c746eea210e0c647d5b0a Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 22 Aug 2020 08:17:51 +0200 Subject: [PATCH 0280/1185] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 61ece5685c..a6f0b6cf42 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ app.run() **Pyrogram** is a modern, elegant and easy-to-use [Telegram](https://telegram.org/) 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). +the [MTProto API](https://docs.pyrogram.org/topics/mtproto-vs-botapi). ### Features @@ -44,7 +44,7 @@ the [MTProto API](https://core.telegram.org/api#telegram-api). - **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. -- **Asynchronous**: Allows both synchronous and asynchronous usages to fit all usage needs. +- **Asynchronous**: Allows both synchronous and asynchronous models to fit all usage needs. - **Documented**: API methods, types and public interfaces are all [well documented](https://docs.pyrogram.org). - **Type-hinted**: Types and methods are all type-hinted, enabling excellent editor support. - **Updated**, to make use of the latest Telegram API version and features. From bc62b3f6aefdd491027d2c0de4de58e46370a741 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 22 Aug 2020 09:22:14 +0200 Subject: [PATCH 0281/1185] Implement a way to deal with failing inline requests This is due to Telegram raising an error in case bots not in DC4 try to communicate with chats that live in DC4. --- .../methods/messages/edit_inline_media.py | 10 ++- .../messages/edit_inline_reply_markup.py | 11 ++- pyrogram/methods/messages/edit_inline_text.py | 10 ++- pyrogram/methods/messages/inline_session.py | 75 +++++++++++++++++++ 4 files changed, 100 insertions(+), 6 deletions(-) create mode 100644 pyrogram/methods/messages/inline_session.py diff --git a/pyrogram/methods/messages/edit_inline_media.py b/pyrogram/methods/messages/edit_inline_media.py index 76b716f1ab..a6a893ae5e 100644 --- a/pyrogram/methods/messages/edit_inline_media.py +++ b/pyrogram/methods/messages/edit_inline_media.py @@ -22,6 +22,7 @@ from pyrogram import types from pyrogram import utils from pyrogram.scaffold import Scaffold +from .inline_session import get_session class EditInlineMedia(Scaffold): @@ -105,9 +106,14 @@ async def edit_inline_media( else: media = utils.get_input_media_from_file_id(media.media, media.file_ref, 5) - return await self.send( + unpacked = utils.unpack_inline_message_id(inline_message_id) + dc_id = unpacked.dc_id + + session = get_session(self, dc_id) + + return await session.send( raw.functions.messages.EditInlineBotMessage( - id=utils.unpack_inline_message_id(inline_message_id), + id=unpacked, media=media, reply_markup=reply_markup.write() if reply_markup else None, **await self.parser.parse(caption, parse_mode) diff --git a/pyrogram/methods/messages/edit_inline_reply_markup.py b/pyrogram/methods/messages/edit_inline_reply_markup.py index 5d1b24635b..9905bcf595 100644 --- a/pyrogram/methods/messages/edit_inline_reply_markup.py +++ b/pyrogram/methods/messages/edit_inline_reply_markup.py @@ -20,6 +20,7 @@ from pyrogram import types from pyrogram import utils from pyrogram.scaffold import Scaffold +from .inline_session import get_session class EditInlineReplyMarkup(Scaffold): @@ -51,9 +52,15 @@ async def edit_inline_reply_markup( InlineKeyboardMarkup([[ InlineKeyboardButton("New button", callback_data="new_data")]])) """ - return await self.send( + + unpacked = utils.unpack_inline_message_id(inline_message_id) + dc_id = unpacked.dc_id + + session = get_session(self, dc_id) + + return await session.send( raw.functions.messages.EditInlineBotMessage( - id=utils.unpack_inline_message_id(inline_message_id), + id=unpacked, reply_markup=reply_markup.write() if reply_markup else None, ) ) diff --git a/pyrogram/methods/messages/edit_inline_text.py b/pyrogram/methods/messages/edit_inline_text.py index ebb2f91a81..c1363181c7 100644 --- a/pyrogram/methods/messages/edit_inline_text.py +++ b/pyrogram/methods/messages/edit_inline_text.py @@ -22,6 +22,7 @@ from pyrogram import types from pyrogram import utils from pyrogram.scaffold import Scaffold +from .inline_session import get_session class EditInlineText(Scaffold): @@ -72,9 +73,14 @@ async def edit_inline_text( disable_web_page_preview=True) """ - return await self.send( + unpacked = utils.unpack_inline_message_id(inline_message_id) + dc_id = unpacked.dc_id + + session = get_session(self, dc_id) + + return await session.send( raw.functions.messages.EditInlineBotMessage( - id=utils.unpack_inline_message_id(inline_message_id), + id=unpacked, no_webpage=disable_web_page_preview or None, reply_markup=reply_markup.write() if reply_markup else None, **await self.parser.parse(text, parse_mode) diff --git a/pyrogram/methods/messages/inline_session.py b/pyrogram/methods/messages/inline_session.py new file mode 100644 index 0000000000..681ea14fed --- /dev/null +++ b/pyrogram/methods/messages/inline_session.py @@ -0,0 +1,75 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan +# +# 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 asyncio import Lock + +import pyrogram +from pyrogram import raw +from pyrogram.errors import AuthBytesInvalid +from pyrogram.session import Session +from pyrogram.session.auth import Auth + +lock = Lock() +session = None +dest_dc_id = 4 + + +async def get_session(client: "pyrogram.Client", dc_id: int): + if dc_id != dest_dc_id: + return client + + if dc_id == await client.storage.dc_id(): + return client + + async with lock: + global session + + if session is not None: + return session + + session = Session( + client, dest_dc_id, + await Auth(client, dest_dc_id, False).create(), + False, is_media=True + ) + + await session.start() + + for _ in range(3): + exported_auth = await session.send( + raw.functions.auth.ExportAuthorization( + dc_id=dest_dc_id + ) + ) + + try: + await session.send( + raw.functions.auth.ImportAuthorization( + id=exported_auth.id, + bytes=exported_auth.bytes + ) + ) + except AuthBytesInvalid: + continue + else: + break + else: + await session.stop() + raise AuthBytesInvalid + + return session From d7be2c90a152b4a2b225f4998e693aeb6dfd076f Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 22 Aug 2020 09:50:37 +0200 Subject: [PATCH 0282/1185] Fix run() not dealing properly with coroutines --- pyrogram/methods/utilities/run.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/pyrogram/methods/utilities/run.py b/pyrogram/methods/utilities/run.py index dc49a524a4..dccce0f22e 100644 --- a/pyrogram/methods/utilities/run.py +++ b/pyrogram/methods/utilities/run.py @@ -17,6 +17,7 @@ # along with Pyrogram. If not, see . import asyncio +import inspect from pyrogram.methods.utilities.idle import idle from pyrogram.scaffold import Scaffold @@ -52,6 +53,11 @@ def run(self, coroutine=None): if coroutine is not None: run(coroutine) else: - self.start() - run(idle()) - self.stop() + if inspect.iscoroutinefunction(self.start): + run(self.start()) + run(idle()) + run(self.stop()) + else: + self.start() + run(idle()) + self.stop() From fbded4e23bafef1b42b95381e7e6abd640844840 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 22 Aug 2020 11:26:11 +0200 Subject: [PATCH 0283/1185] Fix time going out of sync when starting new sessions --- pyrogram/session/internals/msg_factory.py | 5 ++--- pyrogram/session/internals/msg_id.py | 15 +++++++++++++-- pyrogram/session/session.py | 7 +++---- 3 files changed, 18 insertions(+), 9 deletions(-) diff --git a/pyrogram/session/internals/msg_factory.py b/pyrogram/session/internals/msg_factory.py index bf44b7d3c4..98c88b3f89 100644 --- a/pyrogram/session/internals/msg_factory.py +++ b/pyrogram/session/internals/msg_factory.py @@ -26,14 +26,13 @@ class MsgFactory: - def __init__(self, server_time: float = 0): + def __init__(self): self.seq_no = SeqNo() - self.server_time = server_time def __call__(self, body: TLObject) -> Message: return Message( body, - MsgId(self.server_time), + MsgId(), self.seq_no(not isinstance(body, not_content_related)), len(body) ) diff --git a/pyrogram/session/internals/msg_id.py b/pyrogram/session/internals/msg_id.py index 58474222c7..72b8e81f02 100644 --- a/pyrogram/session/internals/msg_id.py +++ b/pyrogram/session/internals/msg_id.py @@ -16,18 +16,29 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import logging +from datetime import datetime from time import monotonic +log = logging.getLogger(__name__) + class MsgId: reference_clock = monotonic() last_time = 0 msg_id_offset = 0 + server_time = 0 - def __new__(cls, server_time: float = 0) -> int: - now = monotonic() - cls.reference_clock + server_time + def __new__(cls) -> int: + now = monotonic() - cls.reference_clock + cls.server_time cls.msg_id_offset = cls.msg_id_offset + 4 if now == cls.last_time else 0 msg_id = int(now * 2 ** 32) + cls.msg_id_offset cls.last_time = now return msg_id + + @classmethod + def set_server_time(cls, server_time: int): + if not cls.server_time: + cls.server_time = server_time + log.info(f"Time synced: {datetime.utcfromtimestamp(server_time)} UTC") diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index 6c385199b4..b25b216689 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -23,6 +23,7 @@ from datetime import datetime, timedelta from hashlib import sha1 from io import BytesIO +import os import pyrogram from pyrogram import __copyright__, __license__, __version__ @@ -96,7 +97,7 @@ def __init__( self.auth_key_id = sha1(auth_key).digest()[-8:] - self.session_id = Long(MsgId(time.time())) + self.session_id = os.urandom(8) self.msg_factory = MsgFactory() self.current_salt = None @@ -247,9 +248,7 @@ async def handle_packet(self, packet): for msg in messages: if msg.seq_no == 0: - server_time = msg.msg_id / (2 ** 32) - self.msg_factory.server_time = server_time - log.info(f"Time synced: {datetime.utcfromtimestamp(server_time)} UTC") + MsgId.set_server_time(msg.msg_id / (2 ** 32)) if msg.seq_no % 2 != 0: if msg.msg_id in self.pending_acks: From 8f51f1597a6a7b04e2782869367ceff80b3eeb52 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 22 Aug 2020 11:30:27 +0200 Subject: [PATCH 0284/1185] Allow pruning the new generated files from pyrogram.raw.base --- MANIFEST.in | 1 + setup.py | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index 67900cba5e..100375d781 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -7,4 +7,5 @@ recursive-include pyrogram mime.types schema.sql prune pyrogram/errors/exceptions prune pyrogram/raw/functions prune pyrogram/raw/types +prune pyrogram/raw/base exclude pyrogram/raw/all.py \ No newline at end of file diff --git a/setup.py b/setup.py index 3b88bab70e..28942b6b36 100644 --- a/setup.py +++ b/setup.py @@ -39,7 +39,10 @@ class Clean(Command): DIST = ["./build", "./dist", "./Pyrogram.egg-info"] - API = ["pyrogram/errors/exceptions", "pyrogram/raw/functions", "pyrogram/raw/types", "pyrogram/raw/all.py"] + API = [ + "pyrogram/errors/exceptions", "pyrogram/raw/functions", "pyrogram/raw/types", "pyrogram/raw/base", + "pyrogram/raw/all.py" + ] DOCS = [ "docs/source/telegram", "docs/build", "docs/source/api/methods", "docs/source/api/types", "docs/source/api/bound-methods" From a822c5126167552ea610b34eb8845a899afe3578 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 22 Aug 2020 11:30:42 +0200 Subject: [PATCH 0285/1185] Update beta version --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 65ac3d7175..997dfdc9ed 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "1.0.0b1" +__version__ = "1.0.0b2" __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)" __copyright__ = "Copyright (C) 2017-2020 Dan " From 5f087e5f824cfb6c65446060f5168ec869e2912c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 22 Aug 2020 14:05:54 +0200 Subject: [PATCH 0286/1185] Use create_task instead of ensure_future --- pyrogram/dispatcher.py | 6 +++--- pyrogram/methods/advanced/save_file.py | 4 ++-- pyrogram/methods/utilities/restart.py | 2 +- pyrogram/methods/utilities/stop.py | 2 +- pyrogram/syncer.py | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pyrogram/dispatcher.py b/pyrogram/dispatcher.py index 40a6dcf48f..00c513496b 100644 --- a/pyrogram/dispatcher.py +++ b/pyrogram/dispatcher.py @@ -115,7 +115,7 @@ async def start(self): self.locks_list.append(asyncio.Lock()) self.handler_worker_tasks.append( - asyncio.ensure_future(self.handler_worker(self.locks_list[-1])) + self.loop.create_task(self.handler_worker(self.locks_list[-1])) ) logging.info(f"Started {self.client.workers} HandlerTasks") @@ -148,7 +148,7 @@ async def fn(): for lock in self.locks_list: lock.release() - asyncio.ensure_future(fn()) + self.loop.create_task(fn()) def remove_handler(self, handler, group: int): async def fn(): @@ -164,7 +164,7 @@ async def fn(): for lock in self.locks_list: lock.release() - asyncio.ensure_future(fn()) + self.loop.create_task(fn()) async def handler_worker(self, lock): while True: diff --git a/pyrogram/methods/advanced/save_file.py b/pyrogram/methods/advanced/save_file.py index 4e594b6485..4bdaedf5cb 100644 --- a/pyrogram/methods/advanced/save_file.py +++ b/pyrogram/methods/advanced/save_file.py @@ -101,7 +101,7 @@ async def worker(session): return try: - await asyncio.ensure_future(session.send(data)) + await self.loop.create_task(session.send(data)) except Exception as e: log.error(e) @@ -139,7 +139,7 @@ async def worker(session): await self.storage.test_mode(), is_media=True ) for _ in range(pool_size) ] - workers = [asyncio.ensure_future(worker(session)) for session in pool for _ in range(workers_count)] + workers = [self.loop.create_task(worker(session)) for session in pool for _ in range(workers_count)] queue = asyncio.Queue(16) try: diff --git a/pyrogram/methods/utilities/restart.py b/pyrogram/methods/utilities/restart.py index ad842bc4b3..22c145b536 100644 --- a/pyrogram/methods/utilities/restart.py +++ b/pyrogram/methods/utilities/restart.py @@ -65,6 +65,6 @@ async def do_it(): if block: await do_it() else: - asyncio.ensure_future(do_it()) + self.loop.create_task(do_it()) return self diff --git a/pyrogram/methods/utilities/stop.py b/pyrogram/methods/utilities/stop.py index b48a143142..b686211628 100644 --- a/pyrogram/methods/utilities/stop.py +++ b/pyrogram/methods/utilities/stop.py @@ -60,6 +60,6 @@ async def do_it(): if block: await do_it() else: - asyncio.ensure_future(do_it()) + self.loop.create_task(do_it()) return self diff --git a/pyrogram/syncer.py b/pyrogram/syncer.py index 1a9d77f30e..db46e9694c 100644 --- a/pyrogram/syncer.py +++ b/pyrogram/syncer.py @@ -59,7 +59,7 @@ async def remove(cls, client): @classmethod def start(cls): cls.event.clear() - asyncio.ensure_future(cls.worker()) + asyncio.get_event_loop().create_task(cls.worker()) @classmethod def stop(cls): From 303712f599136c72e9ca3b8b7abfdde0c439a3c6 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 22 Aug 2020 16:09:38 +0200 Subject: [PATCH 0287/1185] Update docs --- compiler/docs/template/bound-methods.rst | 2 +- compiler/docs/template/types.rst | 4 +- docs/source/api/handlers.rst | 2 +- docs/source/intro/install.rst | 4 +- docs/source/intro/quickstart.rst | 4 +- docs/source/start/errors.rst | 2 +- docs/source/start/examples/bot_keyboards.rst | 3 +- docs/source/start/examples/echobot.rst | 10 +-- docs/source/start/examples/inline_queries.rst | 6 +- docs/source/start/examples/welcomebot.rst | 14 ++-- docs/source/start/invoking.rst | 13 ++- docs/source/start/updates.rst | 10 ++- docs/source/topics/create-filters.rst | 83 ++++++++++++++++--- pyrogram/client.py | 2 +- 14 files changed, 110 insertions(+), 49 deletions(-) diff --git a/compiler/docs/template/bound-methods.rst b/compiler/docs/template/bound-methods.rst index 963b5251c9..13a51b047e 100644 --- a/compiler/docs/template/bound-methods.rst +++ b/compiler/docs/template/bound-methods.rst @@ -15,7 +15,7 @@ some of the required arguments. @app.on_message() def hello(client, message) - message.reply("hi") + message.reply_text("hi") app.run() diff --git a/compiler/docs/template/types.rst b/compiler/docs/template/types.rst index 4cc0cf924e..e6c9a5d7cc 100644 --- a/compiler/docs/template/types.rst +++ b/compiler/docs/template/types.rst @@ -1,12 +1,12 @@ Available Types =============== -This page is about Pyrogram types. All types listed here are accessible through the main package directly. +This page is about Pyrogram types. All types listed here are accessible through ``types`` package. .. code-block:: python :emphasize-lines: 1 - from pyrogram import User, Message, ... + from pyrogram.types import User, Message, ... .. note:: diff --git a/docs/source/api/handlers.rst b/docs/source/api/handlers.rst index 80d2cf73a9..4e79d139bc 100644 --- a/docs/source/api/handlers.rst +++ b/docs/source/api/handlers.rst @@ -5,7 +5,7 @@ Handlers are used to instruct Pyrogram about which kind of updates you'd like to For a much more convenient way of registering callback functions have a look at :doc:`Decorators ` instead. .. code-block:: python - :emphasize-lines: 1, 10 + :emphasize-lines: 2, 11 from pyrogram import Client from pyrogram.handlers import MessageHandler diff --git a/docs/source/intro/install.rst b/docs/source/intro/install.rst index 9056d1f50f..813961dcac 100644 --- a/docs/source/intro/install.rst +++ b/docs/source/intro/install.rst @@ -40,12 +40,12 @@ Pyrogram is always evolving, although new releases on PyPI are published only wh doesn't mean you can't try new features right now! 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 +you can install the development version straight from the ``master`` branch using this command (note "master.zip" in the link): .. code-block:: text - $ pip3 install -U https://github.com/pyrogram/pyrogram/archive/develop.zip + $ pip3 install -U https://github.com/pyrogram/pyrogram/archive/master.zip Verifying --------- diff --git a/docs/source/intro/quickstart.rst b/docs/source/intro/quickstart.rst index 593403b99c..eeb9848264 100644 --- a/docs/source/intro/quickstart.rst +++ b/docs/source/intro/quickstart.rst @@ -1,8 +1,8 @@ Quick Start =========== -The next few steps serve as a quick start for all new Pyrogrammers that want to see Pyrogram in action as fast as -possible. Let's go! +The next few steps serve as a quick start for all new :term:`Pyrogrammers ` that want to see Pyrogram in +action 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 ff9ccf5b59..f5747d7741 100644 --- a/docs/source/start/errors.rst +++ b/docs/source/start/errors.rst @@ -1,7 +1,7 @@ Error Handling ============== -Errors are inevitable when working with the API, and they must be correctly handled with ``try..except`` blocks in order +Errors are inevitable when working with the API, and they can be correctly handled with ``try...except`` blocks in order to control the behaviour of your application. Pyrogram errors all live inside the ``errors`` package: .. code-block:: python diff --git a/docs/source/start/examples/bot_keyboards.rst b/docs/source/start/examples/bot_keyboards.rst index 343e22c927..6e288c4314 100644 --- a/docs/source/start/examples/bot_keyboards.rst +++ b/docs/source/start/examples/bot_keyboards.rst @@ -11,7 +11,8 @@ like send_audio(), send_document(), send_location(), etc... .. code-block:: python - from pyrogram import Client, ReplyKeyboardMarkup, InlineKeyboardMarkup, InlineKeyboardButton + from pyrogram import Client + from pyrogram.types import ReplyKeyboardMarkup, InlineKeyboardMarkup, InlineKeyboardButton # Create a client using your bot token app = Client("my_bot", bot_token="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11") diff --git a/docs/source/start/examples/echobot.rst b/docs/source/start/examples/echobot.rst index a751a2cddd..61dc9929da 100644 --- a/docs/source/start/examples/echobot.rst +++ b/docs/source/start/examples/echobot.rst @@ -3,19 +3,19 @@ echobot This simple echo bot replies to every private text message. -It uses the @on_message decorator to register a MessageHandler and applies two filters on it: -Filters.text and Filters.private to make sure it will reply to private text messages only. +It uses the ``@on_message`` decorator to register a ``MessageHandler`` and applies two filters on it: +``filters.text`` and ``filters.private`` to make sure it will reply to private text messages only. .. code-block:: python - from pyrogram import Client, Filters + from pyrogram import Client, filters app = Client("my_account") - @app.on_message(Filters.text & Filters.private) + @app.on_message(filters.text & filters.private) def echo(client, message): - message.reply(message.text) + message.reply_text(message.text) app.run() # Automatically start() and idle() \ No newline at end of file diff --git a/docs/source/start/examples/inline_queries.rst b/docs/source/start/examples/inline_queries.rst index 158394e8d1..023b9c6ed1 100644 --- a/docs/source/start/examples/inline_queries.rst +++ b/docs/source/start/examples/inline_queries.rst @@ -8,9 +8,9 @@ It uses the @on_inline_query decorator to register an InlineQueryHandler. .. code-block:: python - from pyrogram import ( - Client, InlineQueryResultArticle, InputTextMessageContent, InlineKeyboardMarkup, InlineKeyboardButton - ) + from pyrogram import Client + from pyrogram.types import (InlineQueryResultArticle, InputTextMessageContent, + InlineKeyboardMarkup, InlineKeyboardButton) app = Client("my_bot", bot_token="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11") diff --git a/docs/source/start/examples/welcomebot.rst b/docs/source/start/examples/welcomebot.rst index a3bb32998a..cdd7f022d7 100644 --- a/docs/source/start/examples/welcomebot.rst +++ b/docs/source/start/examples/welcomebot.rst @@ -1,14 +1,12 @@ welcomebot ========== -This is the Welcome Bot in @PyrogramChat. - -It uses the Emoji module to easily add emojis in your text messages and Filters +This example uses the ``emoji`` module to easily add emoji in your text messages and ``filters`` to make it only work for specific messages in a specific chat. .. code-block:: python - from pyrogram import Client, Emoji, Filters + from pyrogram import Client, emoji, filters TARGET = "PyrogramChat" # Target chat. Can also be a list of multiple chat ids/usernames MENTION = "[{}](tg://user?id={})" # User mention markup @@ -18,16 +16,16 @@ to make it only work for specific messages in a specific chat. # Filter in only new_chat_members updates generated in TARGET chat - @app.on_message(Filters.chat(TARGET) & Filters.new_chat_members) + @app.on_message(filters.chat(TARGET) & filters.new_chat_members) def welcome(client, message): # Build the new members list (with mentions) by using their first_name - new_members = [MENTION.format(i.first_name, i.id) for i in message.new_chat_members] + new_members = [u.mention for u in message.new_chat_members] # Build the welcome message by using an emoji and the list we built above - text = MESSAGE.format(Emoji.SPARKLES, ", ".join(new_members)) + text = MESSAGE.format(emoji.SPARKLES, ", ".join(new_members)) # Send the welcome message, without the web page preview - message.reply(text, disable_web_page_preview=True) + message.reply_text(text, disable_web_page_preview=True) app.run() # Automatically start() and idle() \ No newline at end of file diff --git a/docs/source/start/invoking.rst b/docs/source/start/invoking.rst index 0abea089e9..b8eddb9e62 100644 --- a/docs/source/start/invoking.rst +++ b/docs/source/start/invoking.rst @@ -14,8 +14,7 @@ account; we are now aiming towards the core of the library. It's time to start p Basic Usage ----------- -Making API method calls with Pyrogram is very simple. Here's a basic example we are going to examine step by step and -then expand to explain what happens underneath: +Making API method calls with Pyrogram is very simple. Here's a basic example we are going to examine step by step: .. code-block:: python @@ -56,9 +55,9 @@ Basic step-by-step Context Manager --------------- -The ``with`` statement starts a context manager, which is used as a shortcut to automatically call -:meth:`~pyrogram.Client.start` and :meth:`~pyrogram.Client.stop`, which are methods required for Pyrogram to work -properly. The context manager does also gracefully stop the client, even in case of unhandled exceptions in your code. +The ``with`` statement starts a context manager used as a shortcut to automatically call :meth:`~pyrogram.Client.start` +and :meth:`~pyrogram.Client.stop`, which are methods required for Pyrogram to work properly. The context manager does +also gracefully stop the client, even in case of unhandled exceptions in your code. This is how Pyrogram looks without the context manager: @@ -111,8 +110,8 @@ Asynchronous step-by-step async with app: await app.send_message("me", "Hi!") -#. Finally, we tell Python to schedule our ``main()`` async function, which in turn will execute Pyrogram's code. Using - :meth:`~pyrogram.Client.run` this way is a friendly alternative for the much more verbose +#. Finally, we tell Python to schedule our ``main()`` async function, which in turn will execute Pyrogram's methods. + Using :meth:`~pyrogram.Client.run` this way is a friendly alternative for the much more verbose ``asyncio.get_event_loop().run_until_complete(main())``: .. code-block:: python diff --git a/docs/source/start/updates.rst b/docs/source/start/updates.rst index ef569bf903..a13d7fd771 100644 --- a/docs/source/start/updates.rst +++ b/docs/source/start/updates.rst @@ -26,8 +26,8 @@ Registering a Handler --------------------- To explain how handlers work let's examine the one which will be in charge for handling :class:`~pyrogram.types.Message` -updates coming from all around your chats. Every other handler shares the same setup logic; you should not have -troubles settings them up once you learn from this section. +updates coming from all around your chats. Every other kind of handler shares the same setup logic and you should not +have troubles settings them up once you learn from this section. Using Decorators ^^^^^^^^^^^^^^^^ @@ -98,3 +98,9 @@ The same about asynchronous handlers applies for :meth:`~pyrogram.Client.add_han async def my_function(client, message): await message.forward("me") + +.. note:: + + From now on, you'll see examples using synchronous code (i.e.: without ``async`` and ``await``, unless when actually + relevant). This is done to keep snippets concise and more readable. Once you get the idea behind a feature, you can + easily turn examples asynchronous later on. diff --git a/docs/source/topics/create-filters.rst b/docs/source/topics/create-filters.rst index 3e87a3deed..191eeb05b8 100644 --- a/docs/source/topics/create-filters.rst +++ b/docs/source/topics/create-filters.rst @@ -2,7 +2,8 @@ Creating Filters ================ Pyrogram already provides lots of built-in :class:`~pyrogram.filters` to work with, but in case you can't find a -specific one for your needs or want to build a custom filter by yourself you can use :meth:`~pyrogram.filters.create`. +specific one for your needs or want to build a custom filter by yourself you can use +:meth:`filters.create() `. .. contents:: Contents :backlinks: none @@ -37,29 +38,45 @@ Basic Filters For this basic filter we will be using only the first parameter of :meth:`~pyrogram.filters.create`. -The code below creates a simple filter for hardcoded, static callback data. This filter will only allow callback queries -containing "pyrogram" as data, that is, the function *func* you pass returns True in case the callback query data -equals to ``"pyrogram"``. +The heart of a filter is its callback function, which accepts three arguments *(self, client, update)* and returns +either ``True``, in case you want the update to pass the filter or ``False`` otherwise. + +In this example we are matching the query data to "pyrogram", which means that the filter will only allow callback +queries containing "pyrogram" as data: .. code-block:: python from pyrogram import filters - static_data_filter = filters.create(lambda _, query: query.data == "pyrogram") + static_data_filter = filters.create(lambda _, __, query: query.data == "pyrogram") + +The first two arguments of the callback function are unused here and because of this we named them using underscores. -The ``lambda`` operator in python is used to create small anonymous functions and is perfect for this example, the same -could be achieved with a normal function, but we don't really need it as it makes sense only inside the filter's scope: +The ``lambda`` operator in python is used to create small anonymous functions and is perfect for this example. The same +can be achieved with a normal function, but we don't really need it as it makes sense only inside the filter's scope: .. code-block:: python from pyrogram import filters - def func(_, query): + def func(_, __, query): return query.data == "pyrogram" static_data_filter = filters.create(func) -The filter usage remains the same: +Asynchronous filters are also possible. Sadly, Python itself doesn't have an ``async lambda``, so we are left with +using a named function: + +.. code-block:: python + + from pyrogram import filters + + async def func(_, __, query): + return query.data == "pyrogram" + + static_data_filter = filters.create(func) + +Finally, the filter usage remains the same: .. code-block:: python @@ -70,8 +87,10 @@ The filter usage remains the same: Filters with Arguments ---------------------- -A much cooler filter would be one that accepts "pyrogram" or any other data as argument at usage time. -A dynamic filter like this will make use of named arguments for the :meth:`~pyrogram.filters.create` method. +A much cooler filter would be one that accepts "pyrogram" or any other string as argument at usage time. +A dynamic filter like this will make use of named arguments for the :meth:`~pyrogram.filters.create` method and the +first argument of the callback function, which is a reference to the filter object itself holding the extra data passed +via named arguments. This is how a dynamic custom filter looks like: @@ -81,14 +100,52 @@ This is how a dynamic custom filter looks like: def dynamic_data_filter(data): return filters.create( - lambda flt, query: flt.data == query.data, + lambda flt, _, query: flt.data == query.data, data=data # "data" kwarg is accessed with "flt.data" above ) -And its usage: +And its asynchronous variant: + +.. code-block:: python + + from pyrogram import filters + + def dynamic_data_filter(data): + async def func(flt, _, query): + return flt.data == query.data + + # "data" kwarg is accessed with "flt.data" above + return filters.create(func, data=data) + +And finally its usage: .. code-block:: python @app.on_callback_query(dynamic_data_filter("pyrogram")) def pyrogram_data(_, query): query.answer("it works!") + + +Method Calls Inside Filters +--------------------------- + +The missing piece we haven't covered yet is the second argument of a filter callback function, namely, the ``client`` +argument. This is a reference to the :obj:`~pyrogram.Client` instance that is running the filter and it is useful in +case you would like to make some API calls before deciding whether the filter should allow the update or not: + +.. code-block:: python + + def func(_, client, query): + # r = client.some_api_method() + # check response "r" and decide to return True or False + ... + +Asynchronous filters making API calls work fine as well. Just remember that you need to put ``async`` in front of +function definitions and ``await`` in front of method calls: + +.. code-block:: python + + async def func(_, client, query): + # r = await client.some_api_method() + # check response "r" and decide to return True or False + ... \ No newline at end of file diff --git a/pyrogram/client.py b/pyrogram/client.py index a293efd544..ca0440aa50 100644 --- a/pyrogram/client.py +++ b/pyrogram/client.py @@ -58,7 +58,7 @@ class Client(Methods, Scaffold): session_name (``str``): Pass a string of your choice to give a name to the client session, e.g.: "*my_account*". This name will be used to save a file on disk that stores details needed to reconnect without asking again for credentials. - Alternatively, if you don't want a file to be saved on disk, pass the special name "**:memory:**" to start + Alternatively, if you don't want a file to be saved on disk, pass the special name ``":memory:"`` to start an in-memory session that will be discarded as soon as you stop the Client. In order to reconnect again using a memory storage without having to login again, you can use :meth:`~pyrogram.Client.export_session_string` before stopping the client to get a session string you can From 350ec152bccfd8529daf0d95449d09fc408c397d Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 23 Aug 2020 07:35:07 +0200 Subject: [PATCH 0288/1185] .gitignore docs generated files --- .gitignore | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitignore b/.gitignore index 357f483fbf..ff9ee02406 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,10 @@ pyrogram/raw/functions/ pyrogram/raw/types/ pyrogram/raw/base/ pyrogram/raw/all.py +docs/source/telegram +docs/source/api/methods/ +docs/source/api/bound-methods/ +docs/source/api/types/ # PyCharm stuff .idea/ From 228828459cd09eb3d90952c33854927027030836 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 24 Aug 2020 09:16:25 +0200 Subject: [PATCH 0289/1185] Update FAQs --- docs/source/faq.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/source/faq.rst b/docs/source/faq.rst index a8386732ce..e3f92c1149 100644 --- a/docs/source/faq.rst +++ b/docs/source/faq.rst @@ -47,8 +47,9 @@ 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. +- **Asynchronous**: Allows both synchronous and asynchronous models to fit all usage needs. +- **Documented**: API methods, types and public interfaces are all well documented. +- **Type-hinted**: Types and methods are all type-hinted, enabling excellent editor support. - **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 :doc:`Smart Plugin ` system allows to write components with minimal From 3e3d77fdaff8874bdbcae32811af5de0f3602ac6 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 24 Aug 2020 09:20:10 +0200 Subject: [PATCH 0290/1185] Implement short-circuit evaluation for filters AND and OR operations will not evaluate the second operand in case the first one is, respectively, False and True. --- pyrogram/filters.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pyrogram/filters.py b/pyrogram/filters.py index 45762aee7d..e9b177ae39 100644 --- a/pyrogram/filters.py +++ b/pyrogram/filters.py @@ -70,6 +70,10 @@ async def __call__(self, client: "pyrogram.Client", update: Update): client, update ) + # short circuit + if not x: + return False + if inspect.iscoroutinefunction(self.other.__call__): y = await self.other(client, update) else: @@ -97,6 +101,10 @@ async def __call__(self, client: "pyrogram.Client", update: Update): client, update ) + # short circuit + if x: + return True + if inspect.iscoroutinefunction(self.other.__call__): y = await self.other(client, update) else: From 01ec5d7f1d840e8de1a2448c6d97bebcdb1f463b Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 24 Aug 2020 09:51:21 +0200 Subject: [PATCH 0291/1185] Fix a bad docstring indentation in filters.regex --- pyrogram/filters.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/filters.py b/pyrogram/filters.py index e9b177ae39..63628a60ad 100644 --- a/pyrogram/filters.py +++ b/pyrogram/filters.py @@ -781,8 +781,8 @@ def regex(pattern: Union[str, Pattern], flags: int = 0): stored in the ``matches`` field of the update object itself. Parameters: - pattern (``str`` | ``Pattern``): - The regex pattern as string or as pre-compiled pattern. + pattern (``str`` | ``Pattern``): + The regex pattern as string or as pre-compiled pattern. flags (``int``, *optional*): Regex flags. From d86d8530ce11090e878e1c75d6225dbf3f4e1d93 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 24 Aug 2020 10:57:38 +0200 Subject: [PATCH 0292/1185] Add FAQ about PyInstaller FileNotFoundError --- docs/source/faq.rst | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/source/faq.rst b/docs/source/faq.rst index e3f92c1149..5141b593d8 100644 --- a/docs/source/faq.rst +++ b/docs/source/faq.rst @@ -342,6 +342,17 @@ sqlite3.OperationalError: unable to open database file Stackoverflow to the rescue: https://stackoverflow.com/questions/4636970 +FileNotFoundError when using PyInstaller +---------------------------------------- + +Pyrogram uses two files that are not Python files, which are not included automatically in the PyInstaller bundle: + +- ``pyrogram/mime.types`` +- ``pyrogram/storage/schema.sql`` + +To fix the issue, you have to locate your local Pyrogram installation and pass those files to PyInstaller. More info in +their docs https://pyinstaller.readthedocs.io/en/stable/spec-files.html#adding-files-to-the-bundle. + My verification code expires immediately! ----------------------------------------- From b057dcb39db63b7fce8683aa8705dfcca02f16a9 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 24 Aug 2020 15:24:06 +0200 Subject: [PATCH 0293/1185] Small fixes around docs --- docs/source/topics/debugging.rst | 6 +++--- pyrogram/types/object.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/source/topics/debugging.rst b/docs/source/topics/debugging.rst index 995ce9e5bb..ac0b396fd0 100644 --- a/docs/source/topics/debugging.rst +++ b/docs/source/topics/debugging.rst @@ -31,7 +31,7 @@ Consider the following code: print(dan) # User This will show a JSON representation of the object returned by :meth:`~pyrogram.Client.get_users`, which is a -:class:`~pyrogram.User` instance, in this case. The output on your terminal will be something similar to this: +:class:`~pyrogram.types.User` instance, in this case. The output on your terminal will be something similar to this: .. code-block:: json @@ -116,14 +116,14 @@ error. The correct way to get the object type is by using the built-in function .. code-block:: text - + And to check if an object is an instance of a given class, you use the built-in function ``isinstance()``: .. code-block:: python :name: this-py - from pyrogram import UserStatus + from pyrogram.types import UserStatus dan_status = dan.status print(isinstance(dan_status, UserStatus)) diff --git a/pyrogram/types/object.py b/pyrogram/types/object.py index 887b8e08a9..a447a0edcf 100644 --- a/pyrogram/types/object.py +++ b/pyrogram/types/object.py @@ -25,7 +25,7 @@ class Meta(type, metaclass=type("", (type,), {"__str__": lambda _: "~hi"})): def __str__(self): - return f"" + return f"" class Object(metaclass=Meta): From 65b5229fe8c94ea358a1d2d005a269fc5db7c7f0 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 25 Aug 2020 09:32:39 +0200 Subject: [PATCH 0294/1185] Use inspect's iscoroutinefunction, not asyncio's --- pyrogram/client.py | 3 ++- pyrogram/dispatcher.py | 3 ++- pyrogram/methods/advanced/save_file.py | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/pyrogram/client.py b/pyrogram/client.py index ca0440aa50..568496115a 100644 --- a/pyrogram/client.py +++ b/pyrogram/client.py @@ -18,6 +18,7 @@ import asyncio import functools +import inspect import logging import os import re @@ -1025,7 +1026,7 @@ async def get_file( offset += limit if progress: - if asyncio.iscoroutinefunction(progress): + if inspect.iscoroutinefunction(progress): await progress( min(offset, file_size) if file_size != 0 else offset, file_size, diff --git a/pyrogram/dispatcher.py b/pyrogram/dispatcher.py index 00c513496b..2541214e7b 100644 --- a/pyrogram/dispatcher.py +++ b/pyrogram/dispatcher.py @@ -17,6 +17,7 @@ # along with Pyrogram. If not, see . import asyncio +import inspect import logging from collections import OrderedDict @@ -203,7 +204,7 @@ async def handler_worker(self, lock): continue try: - if asyncio.iscoroutinefunction(handler.callback): + if inspect.iscoroutinefunction(handler.callback): await handler.callback(self.client, *args) else: await self.loop.run_in_executor( diff --git a/pyrogram/methods/advanced/save_file.py b/pyrogram/methods/advanced/save_file.py index 4bdaedf5cb..a9bc5c79e6 100644 --- a/pyrogram/methods/advanced/save_file.py +++ b/pyrogram/methods/advanced/save_file.py @@ -18,6 +18,7 @@ import asyncio import functools +import inspect import io import logging import math @@ -182,7 +183,7 @@ async def worker(session): file_part += 1 if progress: - if asyncio.iscoroutinefunction(progress): + if inspect.iscoroutinefunction(progress): await progress(min(file_part * part_size, file_size), file_size, *progress_args) else: func = functools.partial( From 37edeb06b23b4806ee7e7fd7a88bbaaf316a1f62 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 25 Aug 2020 12:07:08 +0200 Subject: [PATCH 0295/1185] Update Pyrogram to v1.0.0 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 997dfdc9ed..a0afc88f88 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "1.0.0b2" +__version__ = "1.0.0" __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)" __copyright__ = "Copyright (C) 2017-2020 Dan " From d44e92065570dfb42e5fb9599f5995d07b76db91 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 25 Aug 2020 14:08:03 +0200 Subject: [PATCH 0296/1185] Update robots.txt --- docs/robots.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/robots.txt b/docs/robots.txt index e7799fdd78..3e416f245f 100644 --- a/docs/robots.txt +++ b/docs/robots.txt @@ -2,7 +2,6 @@ User-agent: * Allow: / -Disallow: /dev* -Disallow: /v0* +Disallow: /old* Sitemap: https://docs.pyrogram.org/sitemap.xml \ No newline at end of file From 093d1e0dcb15c931f8af7a96b095a08a44685ac0 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 25 Aug 2020 14:08:53 +0200 Subject: [PATCH 0297/1185] Fix filters.user and .chat breaking when no initial collection is passed --- pyrogram/handlers/handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/handlers/handler.py b/pyrogram/handlers/handler.py index d4d41ea56c..1f3d2af612 100644 --- a/pyrogram/handlers/handler.py +++ b/pyrogram/handlers/handler.py @@ -30,7 +30,7 @@ def __init__(self, callback: Callable, filters: Filter = None): self.filters = filters async def check(self, client: "pyrogram.Client", update: Update): - if self.filters: + if callable(self.filters): if inspect.iscoroutinefunction(self.filters.__call__): return await self.filters(client, update) else: From 1311974fde3e19addeaa80f8922536983c47356e Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 25 Aug 2020 14:17:39 +0200 Subject: [PATCH 0298/1185] Fix idle() not working in Windows - Remove event-based idling - Add back while-true-based idling --- pyrogram/methods/utilities/idle.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/pyrogram/methods/utilities/idle.py b/pyrogram/methods/utilities/idle.py index 86131ea218..044e41d11f 100644 --- a/pyrogram/methods/utilities/idle.py +++ b/pyrogram/methods/utilities/idle.py @@ -18,12 +18,11 @@ import asyncio import logging -import signal +from signal import signal, SIGINT, SIGTERM, SIGABRT log = logging.getLogger(__name__) -loop = asyncio.get_event_loop() -event = asyncio.Event() +is_idling = False async def idle(): @@ -64,15 +63,18 @@ async def idle(): app2.stop() app3.stop() """ + global is_idling - def handler(): - log.info("Stop signal received") - event.set() + def signal_handler(_, __): + global is_idling - asyncio.get_event_loop().add_signal_handler(signal.SIGINT, handler) + logging.info("Stop signal received ({}). Exiting...".format(_)) + is_idling = False - log.info("Idle started") - await event.wait() + for s in (SIGINT, SIGTERM, SIGABRT): + signal(s, signal_handler) - log.info("Idle stopped") - event.clear() + is_idling = True + + while is_idling: + await asyncio.sleep(1) From 1fbe88d4b281c19d9cb3e9ff1b50cbc23639060e Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 25 Aug 2020 14:18:29 +0200 Subject: [PATCH 0299/1185] Update Pyrogram to v1.0.1 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index a0afc88f88..4cfa3e5bb6 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "1.0.0" +__version__ = "1.0.1" __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)" __copyright__ = "Copyright (C) 2017-2020 Dan " From f06562b73da3bcd57da06bd68e0ea10820ab2293 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 26 Aug 2020 07:43:57 +0200 Subject: [PATCH 0300/1185] Add missing await keywords --- pyrogram/methods/messages/edit_inline_media.py | 2 +- pyrogram/methods/messages/edit_inline_reply_markup.py | 2 +- pyrogram/methods/messages/edit_inline_text.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyrogram/methods/messages/edit_inline_media.py b/pyrogram/methods/messages/edit_inline_media.py index a6a893ae5e..000a0660c3 100644 --- a/pyrogram/methods/messages/edit_inline_media.py +++ b/pyrogram/methods/messages/edit_inline_media.py @@ -109,7 +109,7 @@ async def edit_inline_media( unpacked = utils.unpack_inline_message_id(inline_message_id) dc_id = unpacked.dc_id - session = get_session(self, dc_id) + session = await get_session(self, dc_id) return await session.send( raw.functions.messages.EditInlineBotMessage( diff --git a/pyrogram/methods/messages/edit_inline_reply_markup.py b/pyrogram/methods/messages/edit_inline_reply_markup.py index 9905bcf595..26fa22cf11 100644 --- a/pyrogram/methods/messages/edit_inline_reply_markup.py +++ b/pyrogram/methods/messages/edit_inline_reply_markup.py @@ -56,7 +56,7 @@ async def edit_inline_reply_markup( unpacked = utils.unpack_inline_message_id(inline_message_id) dc_id = unpacked.dc_id - session = get_session(self, dc_id) + session = await get_session(self, dc_id) return await session.send( raw.functions.messages.EditInlineBotMessage( diff --git a/pyrogram/methods/messages/edit_inline_text.py b/pyrogram/methods/messages/edit_inline_text.py index c1363181c7..e6f5d11360 100644 --- a/pyrogram/methods/messages/edit_inline_text.py +++ b/pyrogram/methods/messages/edit_inline_text.py @@ -76,7 +76,7 @@ async def edit_inline_text( unpacked = utils.unpack_inline_message_id(inline_message_id) dc_id = unpacked.dc_id - session = get_session(self, dc_id) + session = await get_session(self, dc_id) return await session.send( raw.functions.messages.EditInlineBotMessage( From 15f504a91f87c7d911e7e63d33eb04c68708a57e Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 26 Aug 2020 08:09:12 +0200 Subject: [PATCH 0301/1185] Fix captions being "None" for inline media results --- pyrogram/types/inline_mode/inline_query_result_animation.py | 2 +- pyrogram/types/inline_mode/inline_query_result_photo.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/types/inline_mode/inline_query_result_animation.py b/pyrogram/types/inline_mode/inline_query_result_animation.py index bd0e7037e7..72c290f007 100644 --- a/pyrogram/types/inline_mode/inline_query_result_animation.py +++ b/pyrogram/types/inline_mode/inline_query_result_animation.py @@ -74,7 +74,7 @@ def __init__( id: str = None, title: str = None, description: str = None, - caption: str = None, + caption: str = "", parse_mode: Union[str, None] = object, reply_markup: "types.InlineKeyboardMarkup" = None, input_message_content: "types.InputMessageContent" = None diff --git a/pyrogram/types/inline_mode/inline_query_result_photo.py b/pyrogram/types/inline_mode/inline_query_result_photo.py index 7287abc017..6d6ae99d1c 100644 --- a/pyrogram/types/inline_mode/inline_query_result_photo.py +++ b/pyrogram/types/inline_mode/inline_query_result_photo.py @@ -74,7 +74,7 @@ def __init__( id: str = None, title: str = None, description: str = None, - caption: str = None, + caption: str = "", parse_mode: Union[str, None] = object, reply_markup: "types.InlineKeyboardMarkup" = None, input_message_content: "types.InputMessageContent" = None From c5dd474f935ff7df947a6a57be8ea3c8fe982c96 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 26 Aug 2020 08:10:34 +0200 Subject: [PATCH 0302/1185] Document undocumented properties --- compiler/docs/compiler.py | 3 ++- pyrogram/types/messages_and_media/message.py | 4 +--- pyrogram/types/user_and_chats/user.py | 5 +++++ 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index 315c6b73a8..54fd9b9f13 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -416,7 +416,8 @@ def get_title_list(s: str) -> list: title = "{}".format(type) f2.write(title + "\n" + "=" * len(title) + "\n\n") - f2.write(".. autoclass:: pyrogram.types.{}()".format(type)) + f2.write(".. autoclass:: pyrogram.types.{}()\n".format(type)) + f2.write(" :members:\n") f.write(template.format(**fmt_keys)) diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index 51bb4293d5..9950df4ac5 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -245,9 +245,6 @@ class Message(Object, Update): Messages sent from yourself to other chats are outgoing (*outgoing* is True). An exception is made for your own personal chat; messages sent there will be incoming. - link (``str``): - A link to the message, only for groups and channels. - matches (List of regex Matches, *optional*): A list containing all `Match Objects `_ that match the text of this message. Only applicable when using :obj:`Filters.regex `. @@ -675,6 +672,7 @@ async def _parse( @property def link(self) -> str: + """Generate a link to this message, only for groups and channels.""" if self.chat.type in ("group", "supergroup", "channel") and self.chat.username: return f"https://t.me/{self.chat.username}/{self.message_id}" else: diff --git a/pyrogram/types/user_and_chats/user.py b/pyrogram/types/user_and_chats/user.py index e9a9518220..7174a3d4f6 100644 --- a/pyrogram/types/user_and_chats/user.py +++ b/pyrogram/types/user_and_chats/user.py @@ -192,6 +192,11 @@ def __init__( @property def mention(self): + """Generate a text mention for this user. + + You can use ``user.mention()`` to mention the user using their first name (styled using html), or + ``user.mention("another name")`` for a custom name. To choose a different style + ("html" or "md"/"markdown") use ``user.mention(style="md")``.""" return Link(f"tg://user?id={self.id}", self.first_name, self._client.parse_mode) @staticmethod From ebf222bbb7d05abb1f8e6c229fe828e35fab2368 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 26 Aug 2020 08:12:11 +0200 Subject: [PATCH 0303/1185] Add the parameter hide_password to Client --- pyrogram/client.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/pyrogram/client.py b/pyrogram/client.py index 568496115a..688b1a5eb2 100644 --- a/pyrogram/client.py +++ b/pyrogram/client.py @@ -166,6 +166,11 @@ class Client(Methods, Scaffold): request that raises a flood wait will be automatically invoked again after sleeping for the required amount of time. Flood wait exceptions requiring higher waiting times will be raised. Defaults to 60 (seconds). + + hide_password (``bool``, *optional*): + Pass True to hide the password when typing it during the login. + Defaults to False, because ``getpass`` (the library used) is known to be problematic in some + terminal environments. """ def __init__( @@ -192,7 +197,8 @@ def __init__( parse_mode: str = Scaffold.PARSE_MODES[0], no_updates: bool = None, takeout: bool = None, - sleep_threshold: int = Session.SLEEP_THRESHOLD + sleep_threshold: int = Session.SLEEP_THRESHOLD, + hide_password: bool = False ): super().__init__() @@ -220,6 +226,7 @@ def __init__( self.no_updates = no_updates self.takeout = takeout self.sleep_threshold = sleep_threshold + self.hide_password = hide_password self.executor = ThreadPoolExecutor(self.workers, thread_name_prefix="Handler") @@ -328,7 +335,7 @@ async def authorize(self) -> User: print("Password hint: {}".format(await self.get_password_hint())) if not self.password: - self.password = await ainput("Enter password (empty to recover): ", hide=True) + self.password = await ainput("Enter password (empty to recover): ", hide=self.hide_password) try: if not self.password: From 7c987889f0742b5d3ed8512cdc37e4026a2ef300 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 26 Aug 2020 09:01:01 +0200 Subject: [PATCH 0304/1185] Add sleep_threshold parameter to send() method - Decrease the default sleep threshold from 60 to 10 seconds - Use a higher sleep threshold for generator methods --- pyrogram/client.py | 2 +- pyrogram/methods/advanced/send.py | 13 +++++++++++-- pyrogram/methods/chats/get_chat_members.py | 3 ++- pyrogram/methods/chats/get_dialogs.py | 8 ++++++-- pyrogram/methods/messages/get_history.py | 5 +++-- pyrogram/methods/messages/get_messages.py | 2 +- pyrogram/methods/messages/search_global.py | 3 ++- pyrogram/methods/messages/search_messages.py | 3 ++- pyrogram/methods/messages/send_media_group.py | 3 ++- pyrogram/session/session.py | 9 ++++----- 10 files changed, 34 insertions(+), 17 deletions(-) diff --git a/pyrogram/client.py b/pyrogram/client.py index 688b1a5eb2..ba5cfb49ed 100644 --- a/pyrogram/client.py +++ b/pyrogram/client.py @@ -165,7 +165,7 @@ class Client(Methods, Scaffold): Set a sleep threshold for flood wait exceptions happening globally in this client instance, below which any request that raises a flood wait will be automatically invoked again after sleeping for the required amount of time. Flood wait exceptions requiring higher waiting times will be raised. - Defaults to 60 (seconds). + Defaults to 10 seconds. hide_password (``bool``, *optional*): Pass True to hide the password when typing it during the login. diff --git a/pyrogram/methods/advanced/send.py b/pyrogram/methods/advanced/send.py index fa6b9f44ce..a438a8be45 100644 --- a/pyrogram/methods/advanced/send.py +++ b/pyrogram/methods/advanced/send.py @@ -27,7 +27,13 @@ class Send(Scaffold): - async def send(self, data: TLObject, retries: int = Session.MAX_RETRIES, timeout: float = Session.WAIT_TIMEOUT): + async def send( + self, + data: TLObject, + retries: int = Session.MAX_RETRIES, + timeout: float = Session.WAIT_TIMEOUT, + sleep_threshold: float = None + ): """Send raw Telegram queries. This method makes it possible to manually call every single Telegram API method in a low-level manner. @@ -50,6 +56,9 @@ async def send(self, data: TLObject, retries: int = Session.MAX_RETRIES, timeout timeout (``float``): Timeout in seconds. + sleep_threshold (``float``): + Sleep threshold in seconds. + Returns: ``RawType``: The raw type response generated by the query. @@ -65,7 +74,7 @@ async def send(self, data: TLObject, retries: int = Session.MAX_RETRIES, timeout if self.takeout_id: data = raw.functions.InvokeWithTakeout(takeout_id=self.takeout_id, query=data) - r = await self.session.send(data, retries, timeout, self.sleep_threshold) + r = await self.session.send(data, retries, timeout, sleep_threshold or self.sleep_threshold) await self.fetch_peers(getattr(r, "users", [])) await self.fetch_peers(getattr(r, "chats", [])) diff --git a/pyrogram/methods/chats/get_chat_members.py b/pyrogram/methods/chats/get_chat_members.py index e5a25f4191..d3e87c7d03 100644 --- a/pyrogram/methods/chats/get_chat_members.py +++ b/pyrogram/methods/chats/get_chat_members.py @@ -141,7 +141,8 @@ async def get_chat_members( offset=offset, limit=limit, hash=0 - ) + ), + sleep_threshold=60 ) members = r.participants diff --git a/pyrogram/methods/chats/get_dialogs.py b/pyrogram/methods/chats/get_dialogs.py index 8549fb139f..01bc0fd074 100644 --- a/pyrogram/methods/chats/get_dialogs.py +++ b/pyrogram/methods/chats/get_dialogs.py @@ -66,7 +66,10 @@ async def get_dialogs( """ if pinned_only: - r = await self.send(raw.functions.messages.GetPinnedDialogs(folder_id=0)) + r = await self.send( + raw.functions.messages.GetPinnedDialogs(folder_id=0), + sleep_threshold=60 + ) else: r = await self.send( raw.functions.messages.GetDialogs( @@ -76,7 +79,8 @@ async def get_dialogs( limit=limit, hash=0, exclude_pinned=True - ) + ), + sleep_threshold=60 ) users = {i.id: i for i in r.users} diff --git a/pyrogram/methods/messages/get_history.py b/pyrogram/methods/messages/get_history.py index 00c9f435df..130c00bb12 100644 --- a/pyrogram/methods/messages/get_history.py +++ b/pyrogram/methods/messages/get_history.py @@ -20,7 +20,7 @@ from typing import Union, List from pyrogram import raw -# from pyrogram import types +from pyrogram import types from pyrogram import utils from pyrogram.scaffold import Scaffold @@ -95,7 +95,8 @@ async def get_history( max_id=0, min_id=0, hash=0 - ) + ), + sleep_threshold=60 ) ) diff --git a/pyrogram/methods/messages/get_messages.py b/pyrogram/methods/messages/get_messages.py index d99bcb2f95..3503f16ade 100644 --- a/pyrogram/methods/messages/get_messages.py +++ b/pyrogram/methods/messages/get_messages.py @@ -111,7 +111,7 @@ async def get_messages( else: rpc = raw.functions.messages.GetMessages(id=ids) - r = await self.send(rpc) + r = await self.send(rpc, sleep_threshold=-1) messages = await utils.parse_messages(self, r, replies=replies) diff --git a/pyrogram/methods/messages/search_global.py b/pyrogram/methods/messages/search_global.py index 60c0c752ab..17f6d445cf 100644 --- a/pyrogram/methods/messages/search_global.py +++ b/pyrogram/methods/messages/search_global.py @@ -74,7 +74,8 @@ async def search_global( offset_peer=offset_peer, offset_id=offset_id, limit=limit - ) + ), + sleep_threshold=60 ), replies=0 ) diff --git a/pyrogram/methods/messages/search_messages.py b/pyrogram/methods/messages/search_messages.py index 44132dcef0..8744fe8118 100644 --- a/pyrogram/methods/messages/search_messages.py +++ b/pyrogram/methods/messages/search_messages.py @@ -80,7 +80,8 @@ async def get_chunk( else None ), hash=0 - ) + ), + sleep_threshold=60 ) return await utils.parse_messages(client, r) diff --git a/pyrogram/methods/messages/send_media_group.py b/pyrogram/methods/messages/send_media_group.py index 6c9bd04d4f..e35984b868 100644 --- a/pyrogram/methods/messages/send_media_group.py +++ b/pyrogram/methods/messages/send_media_group.py @@ -176,7 +176,8 @@ async def send_media_group( multi_media=multi_media, silent=disable_notification or None, reply_to_msg_id=reply_to_message_id - ) + ), + sleep_threshold=60 ) return await utils.parse_messages( diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index b25b216689..bca1dc8f71 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -18,12 +18,11 @@ import asyncio import logging -import time +import os from concurrent.futures.thread import ThreadPoolExecutor from datetime import datetime, timedelta from hashlib import sha1 from io import BytesIO -import os import pyrogram from pyrogram import __copyright__, __license__, __version__ @@ -32,7 +31,7 @@ from pyrogram.crypto import mtproto from pyrogram.errors import RPCError, InternalServerError, AuthKeyDuplicated, FloodWait from pyrogram.raw.all import layer -from pyrogram.raw.core import TLObject, MsgContainer, Int, Long, FutureSalt, FutureSalts +from pyrogram.raw.core import TLObject, MsgContainer, Int, FutureSalt, FutureSalts from .internals import MsgId, MsgFactory log = logging.getLogger(__name__) @@ -48,7 +47,7 @@ class Session: INITIAL_SALT = 0x616e67656c696361 START_TIMEOUT = 1 WAIT_TIMEOUT = 15 - SLEEP_THRESHOLD = 60 + SLEEP_THRESHOLD = 10 MAX_RETRIES = 5 ACKS_THRESHOLD = 8 PING_INTERVAL = 5 @@ -443,7 +442,7 @@ async def send( except FloodWait as e: amount = e.x - if amount > sleep_threshold: + if amount > sleep_threshold > 0: raise log.warning(f'[{self.client.session_name}] Sleeping for {amount}s (required by "{query}")') From f909e1e4ea53126944b44b727011bc6c4f94885f Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 26 Aug 2020 09:07:26 +0200 Subject: [PATCH 0305/1185] Fix "invalid escape" warnings --- pyrogram/methods/advanced/save_file.py | 4 ++-- pyrogram/methods/messages/download_media.py | 4 ++-- pyrogram/methods/messages/send_animation.py | 4 ++-- pyrogram/methods/messages/send_audio.py | 4 ++-- pyrogram/methods/messages/send_document.py | 4 ++-- pyrogram/methods/messages/send_photo.py | 4 ++-- pyrogram/methods/messages/send_sticker.py | 4 ++-- pyrogram/methods/messages/send_video.py | 4 ++-- pyrogram/methods/messages/send_video_note.py | 4 ++-- pyrogram/methods/messages/send_voice.py | 4 ++-- 10 files changed, 20 insertions(+), 20 deletions(-) diff --git a/pyrogram/methods/advanced/save_file.py b/pyrogram/methods/advanced/save_file.py index a9bc5c79e6..9d77073365 100644 --- a/pyrogram/methods/advanced/save_file.py +++ b/pyrogram/methods/advanced/save_file.py @@ -82,8 +82,8 @@ async def save_file( The total size of the file. *args (``tuple``, *optional*): - Extra custom arguments as defined in the *progress_args* parameter. - You can either keep *\*args* or add every single extra argument in your function signature. + Extra custom arguments as defined in the ``progress_args`` parameter. + You can either keep ``*args`` or add every single extra argument in your function signature. Returns: ``InputFile``: On success, the uploaded file is returned in form of an InputFile object. diff --git a/pyrogram/methods/messages/download_media.py b/pyrogram/methods/messages/download_media.py index 24092caca7..cf63c74caa 100644 --- a/pyrogram/methods/messages/download_media.py +++ b/pyrogram/methods/messages/download_media.py @@ -107,8 +107,8 @@ async def download_media( The total size of the file. *args (``tuple``, *optional*): - Extra custom arguments as defined in the *progress_args* parameter. - You can either keep *\*args* or add every single extra argument in your function signature. + Extra custom arguments as defined in the ``progress_args`` parameter. + You can either keep ``*args`` or add every single extra argument in your function signature. Returns: ``str`` | ``None``: On success, the absolute path of the downloaded file is returned, otherwise, in case diff --git a/pyrogram/methods/messages/send_animation.py b/pyrogram/methods/messages/send_animation.py index 9851c11bef..896ed5c593 100644 --- a/pyrogram/methods/messages/send_animation.py +++ b/pyrogram/methods/messages/send_animation.py @@ -139,8 +139,8 @@ async def send_animation( The total size of the file. *args (``tuple``, *optional*): - Extra custom arguments as defined in the *progress_args* parameter. - You can either keep *\*args* or add every single extra argument in your function signature. + Extra custom arguments as defined in the ``progress_args`` parameter. + You can either keep ``*args`` or add every single extra argument in your function signature. Returns: :obj:`~pyrogram.types.Message` | ``None``: On success, the sent animation message is returned, otherwise, diff --git a/pyrogram/methods/messages/send_audio.py b/pyrogram/methods/messages/send_audio.py index fc5213cc9a..45204716d9 100644 --- a/pyrogram/methods/messages/send_audio.py +++ b/pyrogram/methods/messages/send_audio.py @@ -135,8 +135,8 @@ async def send_audio( The total size of the file. *args (``tuple``, *optional*): - Extra custom arguments as defined in the *progress_args* parameter. - You can either keep *\*args* or add every single extra argument in your function signature. + Extra custom arguments as defined in the ``progress_args`` parameter. + You can either keep ``*args`` or add every single extra argument in your function signature. Returns: :obj:`~pyrogram.types.Message` | ``None``: On success, the sent audio message is returned, otherwise, in diff --git a/pyrogram/methods/messages/send_document.py b/pyrogram/methods/messages/send_document.py index 0c77a94086..0e5203da1b 100644 --- a/pyrogram/methods/messages/send_document.py +++ b/pyrogram/methods/messages/send_document.py @@ -122,8 +122,8 @@ async def send_document( The total size of the file. *args (``tuple``, *optional*): - Extra custom arguments as defined in the *progress_args* parameter. - You can either keep *\*args* or add every single extra argument in your function signature. + Extra custom arguments as defined in the ``progress_args`` parameter. + You can either keep ``*args`` or add every single extra argument in your function signature. Returns: :obj:`~pyrogram.types.Message` | ``None``: On success, the sent document message is returned, otherwise, in diff --git a/pyrogram/methods/messages/send_photo.py b/pyrogram/methods/messages/send_photo.py index 06a6688be1..d6e65acf90 100644 --- a/pyrogram/methods/messages/send_photo.py +++ b/pyrogram/methods/messages/send_photo.py @@ -116,8 +116,8 @@ async def send_photo( The total size of the file. *args (``tuple``, *optional*): - Extra custom arguments as defined in the *progress_args* parameter. - You can either keep *\*args* or add every single extra argument in your function signature. + Extra custom arguments as defined in the ``progress_args`` parameter. + You can either keep ``*args`` or add every single extra argument in your function signature. Returns: :obj:`~pyrogram.types.Message` | ``None``: On success, the sent photo message is returned, otherwise, in diff --git a/pyrogram/methods/messages/send_sticker.py b/pyrogram/methods/messages/send_sticker.py index 6ab211f894..565e3420da 100644 --- a/pyrogram/methods/messages/send_sticker.py +++ b/pyrogram/methods/messages/send_sticker.py @@ -98,8 +98,8 @@ async def send_sticker( The total size of the file. *args (``tuple``, *optional*): - Extra custom arguments as defined in the *progress_args* parameter. - You can either keep *\*args* or add every single extra argument in your function signature. + Extra custom arguments as defined in the ``progress_args`` parameter. + You can either keep ``*args`` or add every single extra argument in your function signature. Returns: :obj:`~pyrogram.types.Message` | ``None``: On success, the sent sticker message is returned, otherwise, diff --git a/pyrogram/methods/messages/send_video.py b/pyrogram/methods/messages/send_video.py index 3b2bc03eb2..9185d64bd1 100644 --- a/pyrogram/methods/messages/send_video.py +++ b/pyrogram/methods/messages/send_video.py @@ -139,8 +139,8 @@ async def send_video( The total size of the file. *args (``tuple``, *optional*): - Extra custom arguments as defined in the *progress_args* parameter. - You can either keep *\*args* or add every single extra argument in your function signature. + Extra custom arguments as defined in the ``progress_args`` parameter. + You can either keep ``*args`` or add every single extra argument in your function signature. Returns: :obj:`~pyrogram.types.Message` | ``None``: On success, the sent video message is returned, otherwise, in diff --git a/pyrogram/methods/messages/send_video_note.py b/pyrogram/methods/messages/send_video_note.py index 82a3688bd3..d40335afcf 100644 --- a/pyrogram/methods/messages/send_video_note.py +++ b/pyrogram/methods/messages/send_video_note.py @@ -112,8 +112,8 @@ async def send_video_note( The total size of the file. *args (``tuple``, *optional*): - Extra custom arguments as defined in the *progress_args* parameter. - You can either keep *\*args* or add every single extra argument in your function signature. + Extra custom arguments as defined in the ``progress_args`` parameter. + You can either keep ``*args`` or add every single extra argument in your function signature. Returns: :obj:`~pyrogram.types.Message` | ``None``: On success, the sent video note message is returned, otherwise, diff --git a/pyrogram/methods/messages/send_voice.py b/pyrogram/methods/messages/send_voice.py index 58d401d79d..0bbf72e671 100644 --- a/pyrogram/methods/messages/send_voice.py +++ b/pyrogram/methods/messages/send_voice.py @@ -114,8 +114,8 @@ async def send_voice( The total size of the file. *args (``tuple``, *optional*): - Extra custom arguments as defined in the *progress_args* parameter. - You can either keep *\*args* or add every single extra argument in your function signature. + Extra custom arguments as defined in the ``progress_args`` parameter. + You can either keep ``*args`` or add every single extra argument in your function signature. Returns: :obj:`~pyrogram.types.Message` | ``None``: On success, the sent voice message is returned, otherwise, in From bab9359df843ceacf944e7e16571e575c8f77913 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 26 Aug 2020 09:07:54 +0200 Subject: [PATCH 0306/1185] Use a better way to document properties --- compiler/docs/compiler.py | 1 - pyrogram/types/messages_and_media/message.py | 40 ++++++++++---------- pyrogram/types/user_and_chats/user.py | 11 +++--- 3 files changed, 27 insertions(+), 25 deletions(-) diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index 54fd9b9f13..fdb4972dab 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -417,7 +417,6 @@ def get_title_list(s: str) -> list: f2.write(title + "\n" + "=" * len(title) + "\n\n") f2.write(".. autoclass:: pyrogram.types.{}()\n".format(type)) - f2.write(" :members:\n") f.write(template.format(**fmt_keys)) diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index 9950df4ac5..0fad3f4175 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -257,6 +257,9 @@ class Message(Object, Update): reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. + + link (``str``, *property*): + Generate a link to this message, only for groups and channels. """ # TODO: Add game missing field. Also invoice, successful_payment, connected_website @@ -672,7 +675,6 @@ async def _parse( @property def link(self) -> str: - """Generate a link to this message, only for groups and channels.""" if self.chat.type in ("group", "supergroup", "channel") and self.chat.username: return f"https://t.me/{self.chat.username}/{self.message_id}" else: @@ -868,8 +870,8 @@ async def reply_animation( The total size of the file. *args (``tuple``, *optional*): - Extra custom arguments as defined in the *progress_args* parameter. - You can either keep *\*args* or add every single extra argument in your function signature. + Extra custom arguments as defined in the ``progress_args`` parameter. + You can either keep ``*args`` or add every single extra argument in your function signature. Returns: On success, the sent :obj:`~pyrogram.types.Message` is returned. @@ -1011,8 +1013,8 @@ async def reply_audio( The total size of the file. *args (``tuple``, *optional*): - Extra custom arguments as defined in the *progress_args* parameter. - You can either keep *\*args* or add every single extra argument in your function signature. + Extra custom arguments as defined in the ``progress_args`` parameter. + You can either keep ``*args`` or add every single extra argument in your function signature. Returns: On success, the sent :obj:`~pyrogram.types.Message` is returned. @@ -1355,8 +1357,8 @@ async def reply_document( The total size of the file. *args (``tuple``, *optional*): - Extra custom arguments as defined in the *progress_args* parameter. - You can either keep *\*args* or add every single extra argument in your function signature. + Extra custom arguments as defined in the ``progress_args`` parameter. + You can either keep ``*args`` or add every single extra argument in your function signature. Returns: On success, the sent :obj:`~pyrogram.types.Message` is returned. @@ -1756,8 +1758,8 @@ async def reply_photo( The total size of the file. *args (``tuple``, *optional*): - Extra custom arguments as defined in the *progress_args* parameter. - You can either keep *\*args* or add every single extra argument in your function signature. + Extra custom arguments as defined in the ``progress_args`` parameter. + You can either keep ``*args`` or add every single extra argument in your function signature. Returns: On success, the sent :obj:`~pyrogram.types.Message` is returned. @@ -1940,8 +1942,8 @@ async def reply_sticker( The total size of the file. *args (``tuple``, *optional*): - Extra custom arguments as defined in the *progress_args* parameter. - You can either keep *\*args* or add every single extra argument in your function signature. + Extra custom arguments as defined in the ``progress_args`` parameter. + You can either keep ``*args`` or add every single extra argument in your function signature. Returns: On success, the sent :obj:`~pyrogram.types.Message` is returned. @@ -2179,8 +2181,8 @@ async def reply_video( The total size of the file. *args (``tuple``, *optional*): - Extra custom arguments as defined in the *progress_args* parameter. - You can either keep *\*args* or add every single extra argument in your function signature. + Extra custom arguments as defined in the ``progress_args`` parameter. + You can either keep ``*args`` or add every single extra argument in your function signature. Returns: On success, the sent :obj:`~pyrogram.types.Message` is returned. @@ -2307,8 +2309,8 @@ async def reply_video_note( The total size of the file. *args (``tuple``, *optional*): - Extra custom arguments as defined in the *progress_args* parameter. - You can either keep *\*args* or add every single extra argument in your function signature. + Extra custom arguments as defined in the ``progress_args`` parameter. + You can either keep ``*args`` or add every single extra argument in your function signature. Returns: On success, the sent :obj:`~pyrogram.types.Message` is returned. @@ -2432,8 +2434,8 @@ async def reply_voice( The total size of the file. *args (``tuple``, *optional*): - Extra custom arguments as defined in the *progress_args* parameter. - You can either keep *\*args* or add every single extra argument in your function signature. + Extra custom arguments as defined in the ``progress_args`` parameter. + You can either keep ``*args`` or add every single extra argument in your function signature. Returns: On success, the sent :obj:`~pyrogram.types.Message` is returned. @@ -3052,8 +3054,8 @@ async def download( The total size of the file. *args (``tuple``, *optional*): - Extra custom arguments as defined in the *progress_args* parameter. - You can either keep *\*args* or add every single extra argument in your function signature. + Extra custom arguments as defined in the ``progress_args`` parameter. + You can either keep ``*args`` or add every single extra argument in your function signature. Returns: On success, the absolute path of the downloaded file as string is returned, None otherwise. diff --git a/pyrogram/types/user_and_chats/user.py b/pyrogram/types/user_and_chats/user.py index 7174a3d4f6..f159576038 100644 --- a/pyrogram/types/user_and_chats/user.py +++ b/pyrogram/types/user_and_chats/user.py @@ -138,6 +138,12 @@ class User(Object, Update): restrictions (List of :obj:`~pyrogram.types.Restriction`, *optional*): The list of reasons why this bot might be unavailable to some users. This field is available only in case *is_restricted* is True. + + mention (``str``, *property*): + Generate a text mention for this user. + You can use ``user.mention()`` to mention the user using their first name (styled using html), or + ``user.mention("another name")`` for a custom name. To choose a different style + ("html" or "md"/"markdown") use ``user.mention(style="md")``. """ def __init__( @@ -192,11 +198,6 @@ def __init__( @property def mention(self): - """Generate a text mention for this user. - - You can use ``user.mention()`` to mention the user using their first name (styled using html), or - ``user.mention("another name")`` for a custom name. To choose a different style - ("html" or "md"/"markdown") use ``user.mention(style="md")``.""" return Link(f"tg://user?id={self.id}", self.first_name, self._client.parse_mode) @staticmethod From 7719c49062d50244409173c19eca7010a6e10ea5 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 26 Aug 2020 09:14:24 +0200 Subject: [PATCH 0307/1185] Update Pyrogram to v1.0.2 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 4cfa3e5bb6..08003aeba2 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "1.0.1" +__version__ = "1.0.2" __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)" __copyright__ = "Copyright (C) 2017-2020 Dan " From eed3221ecb602f5c1c733b63942d1c0a9b1597e1 Mon Sep 17 00:00:00 2001 From: Mahesh0253 <44301650+Mahesh0253@users.noreply.github.com> Date: Wed, 26 Aug 2020 23:16:19 +0530 Subject: [PATCH 0308/1185] Fixed bad f-string (#476) --- pyrogram/parser/html.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/parser/html.py b/pyrogram/parser/html.py index 7fa1d94d2e..7be5dc956c 100644 --- a/pyrogram/parser/html.py +++ b/pyrogram/parser/html.py @@ -155,7 +155,7 @@ def unparse(text: str, entities: list): if entity_type in ("bold", "italic", "underline", "strike"): start_tag = f"<{entity_type[0]}>" - end_tag = "f" + end_tag = f"" elif entity_type in ("code", "pre", "blockquote"): start_tag = f"<{entity_type}>" end_tag = f"" From 582e29dece1ae1a682e4311f8219db47f77de055 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 27 Aug 2020 10:59:28 +0200 Subject: [PATCH 0309/1185] Allow passing sleep_threshold=0 to always raise flood waits --- 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 bca1dc8f71..3aecaab3af 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -442,7 +442,7 @@ async def send( except FloodWait as e: amount = e.x - if amount > sleep_threshold > 0: + if amount > sleep_threshold >= 0: raise log.warning(f'[{self.client.session_name}] Sleeping for {amount}s (required by "{query}")') From d489157f224bf5e5b20ad0f9c15eb5f42654aefc Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 27 Aug 2020 11:08:22 +0200 Subject: [PATCH 0310/1185] Update Pyrogram to v1.0.3 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 08003aeba2..898a5f377d 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "1.0.2" +__version__ = "1.0.3" __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)" __copyright__ = "Copyright (C) 2017-2020 Dan " From c0049ba43b346e734e1916edc35068ce5627efff Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 27 Aug 2020 14:12:28 +0200 Subject: [PATCH 0311/1185] Strictly check if sleep_threshold is None --- pyrogram/methods/advanced/send.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pyrogram/methods/advanced/send.py b/pyrogram/methods/advanced/send.py index a438a8be45..661d1be7e0 100644 --- a/pyrogram/methods/advanced/send.py +++ b/pyrogram/methods/advanced/send.py @@ -74,7 +74,12 @@ async def send( if self.takeout_id: data = raw.functions.InvokeWithTakeout(takeout_id=self.takeout_id, query=data) - r = await self.session.send(data, retries, timeout, sleep_threshold or self.sleep_threshold) + r = await self.session.send( + data, retries, timeout, + (sleep_threshold + if sleep_threshold is not None + else self.sleep_threshold) + ) await self.fetch_peers(getattr(r, "users", [])) await self.fetch_peers(getattr(r, "chats", [])) From 50c620709923340cda8cc80a099cb45ea4dac6bf Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 27 Aug 2020 14:12:51 +0200 Subject: [PATCH 0312/1185] Actually fix inline callback queries coming from different DCs --- pyrogram/methods/messages/edit_inline_media.py | 3 ++- pyrogram/methods/messages/edit_inline_reply_markup.py | 3 ++- pyrogram/methods/messages/edit_inline_text.py | 3 ++- pyrogram/methods/messages/inline_session.py | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/pyrogram/methods/messages/edit_inline_media.py b/pyrogram/methods/messages/edit_inline_media.py index 000a0660c3..f607462d34 100644 --- a/pyrogram/methods/messages/edit_inline_media.py +++ b/pyrogram/methods/messages/edit_inline_media.py @@ -117,5 +117,6 @@ async def edit_inline_media( media=media, reply_markup=reply_markup.write() if reply_markup else None, **await self.parser.parse(caption, parse_mode) - ) + ), + sleep_threshold=self.sleep_threshold ) diff --git a/pyrogram/methods/messages/edit_inline_reply_markup.py b/pyrogram/methods/messages/edit_inline_reply_markup.py index 26fa22cf11..8213c86e47 100644 --- a/pyrogram/methods/messages/edit_inline_reply_markup.py +++ b/pyrogram/methods/messages/edit_inline_reply_markup.py @@ -62,5 +62,6 @@ async def edit_inline_reply_markup( raw.functions.messages.EditInlineBotMessage( id=unpacked, reply_markup=reply_markup.write() if reply_markup else None, - ) + ), + sleep_threshold=self.sleep_threshold ) diff --git a/pyrogram/methods/messages/edit_inline_text.py b/pyrogram/methods/messages/edit_inline_text.py index e6f5d11360..4558da57d6 100644 --- a/pyrogram/methods/messages/edit_inline_text.py +++ b/pyrogram/methods/messages/edit_inline_text.py @@ -84,5 +84,6 @@ async def edit_inline_text( no_webpage=disable_web_page_preview or None, reply_markup=reply_markup.write() if reply_markup else None, **await self.parser.parse(text, parse_mode) - ) + ), + sleep_threshold=self.sleep_threshold ) diff --git a/pyrogram/methods/messages/inline_session.py b/pyrogram/methods/messages/inline_session.py index 681ea14fed..4ad7201068 100644 --- a/pyrogram/methods/messages/inline_session.py +++ b/pyrogram/methods/messages/inline_session.py @@ -51,7 +51,7 @@ async def get_session(client: "pyrogram.Client", dc_id: int): await session.start() for _ in range(3): - exported_auth = await session.send( + exported_auth = await client.send( raw.functions.auth.ExportAuthorization( dc_id=dest_dc_id ) From d385aae1d2571efaa0c7dbd6084c5dd656503f1d Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 27 Aug 2020 14:20:30 +0200 Subject: [PATCH 0313/1185] Types don't need to inherit from TLObject --- compiler/api/template/type.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/api/template/type.txt b/compiler/api/template/type.txt index 4dd6a2503b..3d327282fd 100644 --- a/compiler/api/template/type.txt +++ b/compiler/api/template/type.txt @@ -10,7 +10,7 @@ from pyrogram.raw.core import TLObject # noinspection PyRedeclaration -class {name}(TLObject): # type: ignore +class {name}: # type: ignore """{docstring} """ From cecf5a19a4fad8daf66f9b1c5a53de0b9125c0b0 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 27 Aug 2020 14:32:25 +0200 Subject: [PATCH 0314/1185] Add dc_id to Chat objects --- pyrogram/types/user_and_chats/chat.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pyrogram/types/user_and_chats/chat.py b/pyrogram/types/user_and_chats/chat.py index a0f40eed58..8ae130c6a2 100644 --- a/pyrogram/types/user_and_chats/chat.py +++ b/pyrogram/types/user_and_chats/chat.py @@ -70,6 +70,12 @@ class Chat(Object): Bio, for private chats and bots or description for groups, supergroups and channels. Returned only in :meth:`~pyrogram.Client.get_chat`. + dc_id (``int``, *optional*): + The chat assigned DC (data center). Available only in case the chat has a photo. + Note that this information is approximate; it is based on where Telegram stores the current chat photo. + It is accurate only in case the owner has set the chat photo, otherwise the dc_id will be the one assigned + to the administrator who set the current chat photo. + invite_link (``str``, *optional*): Chat invite link, for groups, supergroups and channels. Returned only in :meth:`~pyrogram.Client.get_chat`. @@ -121,6 +127,7 @@ def __init__( last_name: str = None, photo: "types.ChatPhoto" = None, description: str = None, + dc_id: int = None, invite_link: str = None, pinned_message=None, sticker_set_name: str = None, @@ -146,6 +153,7 @@ def __init__( self.last_name = last_name self.photo = photo self.description = description + self.dc_id = dc_id self.invite_link = invite_link self.pinned_message = pinned_message self.sticker_set_name = sticker_set_name @@ -172,6 +180,7 @@ def _parse_user_chat(client, user: raw.types.User) -> "Chat": last_name=user.last_name, photo=types.ChatPhoto._parse(client, user.photo, peer_id, user.access_hash), restrictions=types.List([types.Restriction._parse(r) for r in user.restriction_reason]) or None, + dc_id=getattr(user.photo, "dc_id", None), client=client ) @@ -187,6 +196,7 @@ def _parse_chat_chat(client, chat: raw.types.Chat) -> "Chat": photo=types.ChatPhoto._parse(client, getattr(chat, "photo", None), peer_id, 0), permissions=types.ChatPermissions._parse(getattr(chat, "default_banned_rights", None)), members_count=getattr(chat, "participants_count", None), + dc_id=getattr(chat.photo, "dc_id", None), client=client ) @@ -208,6 +218,7 @@ def _parse_channel_chat(client, channel: raw.types.Channel) -> "Chat": restrictions=types.List([types.Restriction._parse(r) for r in restriction_reason]) or None, permissions=types.ChatPermissions._parse(getattr(channel, "default_banned_rights", None)), members_count=getattr(channel, "participants_count", None), + dc_id=getattr(channel.photo, "dc_id", None), client=client ) From 899bd7bda3c9efc17c3c6336d3c433bd5aeed630 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 27 Aug 2020 14:33:04 +0200 Subject: [PATCH 0315/1185] Update Pyrogram to v1.0.4 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 898a5f377d..194c38a3fd 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "1.0.3" +__version__ = "1.0.4" __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)" __copyright__ = "Copyright (C) 2017-2020 Dan " From 4dd068643da09642384483aab922787a61fa46c4 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 29 Aug 2020 15:11:21 +0200 Subject: [PATCH 0316/1185] Fix get_profile_photos not working properly in channels --- pyrogram/methods/users/get_profile_photos.py | 22 ++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/pyrogram/methods/users/get_profile_photos.py b/pyrogram/methods/users/get_profile_photos.py index b27687a14f..8a222564f2 100644 --- a/pyrogram/methods/users/get_profile_photos.py +++ b/pyrogram/methods/users/get_profile_photos.py @@ -65,6 +65,14 @@ async def get_profile_photos( peer_id = await self.resolve_peer(chat_id) if isinstance(peer_id, raw.types.InputPeerChannel): + r = await self.send( + raw.functions.channels.GetFullChannel( + channel=peer_id + ) + ) + + current = types.Photo._parse(self, r.full_chat.chat_photo) or [] + r = await utils.parse_messages( self, await self.send( @@ -75,7 +83,7 @@ async def get_profile_photos( min_date=0, max_date=0, offset_id=0, - add_offset=offset, + add_offset=0, limit=limit, max_id=0, min_id=0, @@ -84,7 +92,17 @@ async def get_profile_photos( ) ) - return types.List([message.new_chat_photo for message in r][:limit]) + extra = [message.new_chat_photo for message in r] + + if extra: + if current: + photos = ([current] + extra) if current.file_id != extra[0].file_id else extra + else: + photos = extra + else: + photos = [current] + + return types.List(photos[offset:limit]) else: r = await self.send( raw.functions.photos.GetUserPhotos( From 22d9077e2bb11e350d9a408aedbe29b60e9c0373 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 29 Aug 2020 15:12:45 +0200 Subject: [PATCH 0317/1185] Warn users in case they try to use base types as arguments --- compiler/api/compiler.py | 3 ++- compiler/api/template/type.txt | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/compiler/api/compiler.py b/compiler/api/compiler.py index e956085f32..6c3dcb27ec 100644 --- a/compiler/api/compiler.py +++ b/compiler/api/compiler.py @@ -336,7 +336,8 @@ def start(format: bool = False): docstring=docstring, name=type, qualname=qualtype, - types=", ".join([f"raw.types.{c}" for c in constructors]) + types=", ".join([f"raw.types.{c}" for c in constructors]), + doc_name=snake(type).replace("_", "-") ) ) diff --git a/compiler/api/template/type.txt b/compiler/api/template/type.txt index 3d327282fd..99310359d5 100644 --- a/compiler/api/template/type.txt +++ b/compiler/api/template/type.txt @@ -15,3 +15,9 @@ class {name}: # type: ignore """ QUALNAME = "pyrogram.raw.base.{qualname}" + + def __init__(self): + raise TypeError("Base types can only be used for type checking purposes: " + "you tried to use a base type instance as argument, " + "but you need to instantiate one of its constructors instead. " + "More info: https://docs.pyrogram.org/telegram/base/{doc_name}") From 92bc59e688d46638fd609baf3dcbb20419c68022 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 29 Aug 2020 15:46:39 +0200 Subject: [PATCH 0318/1185] Fix dispatcher not obeying to the INFO logging level --- pyrogram/dispatcher.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/dispatcher.py b/pyrogram/dispatcher.py index 2541214e7b..db3344da93 100644 --- a/pyrogram/dispatcher.py +++ b/pyrogram/dispatcher.py @@ -119,7 +119,7 @@ async def start(self): self.loop.create_task(self.handler_worker(self.locks_list[-1])) ) - logging.info(f"Started {self.client.workers} HandlerTasks") + log.info(f"Started {self.client.workers} HandlerTasks") async def stop(self): if not self.client.no_updates: @@ -132,7 +132,7 @@ async def stop(self): self.handler_worker_tasks.clear() self.groups.clear() - logging.info(f"Stopped {self.client.workers} HandlerTasks") + log.info(f"Stopped {self.client.workers} HandlerTasks") def add_handler(self, handler, group: int): async def fn(): From c689273167d6cb23ba881edfd32ed12e2321bdb7 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 29 Aug 2020 15:50:36 +0200 Subject: [PATCH 0319/1185] Add force_document argument for send_document --- pyrogram/methods/messages/send_document.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pyrogram/methods/messages/send_document.py b/pyrogram/methods/messages/send_document.py index 0e5203da1b..0ee814a587 100644 --- a/pyrogram/methods/messages/send_document.py +++ b/pyrogram/methods/messages/send_document.py @@ -38,6 +38,7 @@ async def send_document( caption: str = "", parse_mode: Union[str, None] = object, file_name: str = None, + force_document: bool = None, disable_notification: bool = None, reply_to_message_id: int = None, schedule_date: int = None, @@ -89,6 +90,11 @@ async def send_document( File name of the document sent. Defaults to file's path basename. + force_document (``bool``, *optional*): + Pass True to force sending files as document. Useful for video files that need to be sent as + document messages instead of video messages. + Defaults to False. + disable_notification (``bool``, *optional*): Sends the message silently. Users will receive a notification with no sound. @@ -157,7 +163,7 @@ def progress(current, total): media = raw.types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(document) or "application/zip", file=file, - force_file=True, + force_file=force_document or None, thumb=thumb, attributes=[ raw.types.DocumentAttributeFilename(file_name=file_name or os.path.basename(document)) From 5e3f2ab94795bacbad6b56febb1a6b16938f355c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 29 Aug 2020 16:58:49 +0200 Subject: [PATCH 0320/1185] Replace monotonic() with perf_counter() It seems like monotonic() goes out of sync after some time. --- pyrogram/session/internals/msg_id.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyrogram/session/internals/msg_id.py b/pyrogram/session/internals/msg_id.py index 72b8e81f02..95155d3c85 100644 --- a/pyrogram/session/internals/msg_id.py +++ b/pyrogram/session/internals/msg_id.py @@ -18,19 +18,19 @@ import logging from datetime import datetime -from time import monotonic +from time import perf_counter log = logging.getLogger(__name__) class MsgId: - reference_clock = monotonic() + reference_clock = perf_counter() last_time = 0 msg_id_offset = 0 server_time = 0 def __new__(cls) -> int: - now = monotonic() - cls.reference_clock + cls.server_time + now = perf_counter() - cls.reference_clock + cls.server_time cls.msg_id_offset = cls.msg_id_offset + 4 if now == cls.last_time else 0 msg_id = int(now * 2 ** 32) + cls.msg_id_offset cls.last_time = now From 2c99926825bdc45f7808711efa4b155653f07339 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 29 Aug 2020 16:59:30 +0200 Subject: [PATCH 0321/1185] Update Pyrogram to v1.0.5 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 194c38a3fd..32d4a5166c 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "1.0.4" +__version__ = "1.0.5" __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)" __copyright__ = "Copyright (C) 2017-2020 Dan " From 71bdfe6c407e0622cc337f51dc8383abc89880e4 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 29 Aug 2020 17:26:16 +0200 Subject: [PATCH 0322/1185] Fix get_profile_photos not working correctly in case of no chat photos --- pyrogram/methods/users/get_profile_photos.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyrogram/methods/users/get_profile_photos.py b/pyrogram/methods/users/get_profile_photos.py index 8a222564f2..6ccfb8f8cf 100644 --- a/pyrogram/methods/users/get_profile_photos.py +++ b/pyrogram/methods/users/get_profile_photos.py @@ -100,7 +100,10 @@ async def get_profile_photos( else: photos = extra else: - photos = [current] + if current: + photos = [current] + else: + photos = [] return types.List(photos[offset:limit]) else: From 15bdb3de4fdafc39ed88b4bad7732c1d7af8e40b Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 29 Aug 2020 17:28:39 +0200 Subject: [PATCH 0323/1185] Attempt to fix clock issues when running on AWS Lambda --- pyrogram/session/internals/msg_id.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyrogram/session/internals/msg_id.py b/pyrogram/session/internals/msg_id.py index 95155d3c85..384a3e504f 100644 --- a/pyrogram/session/internals/msg_id.py +++ b/pyrogram/session/internals/msg_id.py @@ -24,7 +24,7 @@ class MsgId: - reference_clock = perf_counter() + reference_clock = 0 last_time = 0 msg_id_offset = 0 server_time = 0 @@ -40,5 +40,6 @@ def __new__(cls) -> int: @classmethod def set_server_time(cls, server_time: int): if not cls.server_time: + cls.reference_clock = perf_counter() cls.server_time = server_time log.info(f"Time synced: {datetime.utcfromtimestamp(server_time)} UTC") From 84f6973bbb4693572510fdc4ea55d4386c68d0a5 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 29 Aug 2020 17:28:55 +0200 Subject: [PATCH 0324/1185] Update Pyrogram to v1.0.6 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 32d4a5166c..f967dfc595 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "1.0.5" +__version__ = "1.0.6" __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)" __copyright__ = "Copyright (C) 2017-2020 Dan " From 352ea594834d12cafa8375047f9bc97f7f259992 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 29 Aug 2020 17:37:08 +0200 Subject: [PATCH 0325/1185] Update bug_report.md --- .github/ISSUE_TEMPLATE/bug_report.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 6a2c6e7999..c46b935a5e 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -8,7 +8,7 @@ about: Create a bug report affecting the library ## Checklist - [ ] I am sure the error is coming from Pyrogram's code and not elsewhere. - [ ] I have searched in the issue tracker for similar bug reports, including closed ones. -- [ ] I ran `pip3 install -U https://github.com/pyrogram/pyrogram/archive/develop.zip` and reproduced the issue using the latest development version. +- [ ] I ran `pip3 install -U https://github.com/pyrogram/pyrogram/archive/master.zip` and reproduced the issue using the latest development version. ## Description A clear and concise description of the problem. From ecdba6be6f634354b13b5fac0f97205eb23fc705 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 29 Aug 2020 20:52:09 +0200 Subject: [PATCH 0326/1185] Add a new way to support Pyrogram - Add Hetzner link for cloud credits. --- docs/source/index.rst | 7 +++++++ docs/source/support-pyrogram.rst | 8 ++++++++ 2 files changed, 15 insertions(+) diff --git a/docs/source/index.rst b/docs/source/index.rst index 0fda6da652..18c69412b8 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -53,6 +53,13 @@ Contents are organized into sections composed of self-contained topics which can following them in order using the :guilabel:`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. +.. admonition :: Cloud Credits + :class: attention + + If you need a cloud server to host your applications, we recommend using **Hetzner Cloud**. Sign up with + `this link `_ to get €20 in cloud credits and help support Pyrogram as + well. + First Steps ^^^^^^^^^^^ diff --git a/docs/source/support-pyrogram.rst b/docs/source/support-pyrogram.rst index 2505585e96..aa662d49e8 100644 --- a/docs/source/support-pyrogram.rst +++ b/docs/source/support-pyrogram.rst @@ -11,6 +11,13 @@ found it to be useful, give Pyrogram a `Star on GitHub`_. Your appreciation mean ----- +Cloud Credits +------------- + +If you need a cloud server to host your applications, try **Hetzner Cloud**. You can sign up with +`this link `_ to get €20 in cloud credits and help support Pyrogram and +my `other projects`_. + Donate ------ @@ -25,5 +32,6 @@ PayPal button below. Thank you! --- `Dan`_ .. _Star on GitHub: https://github.com/pyrogram/pyrogram +.. _other projects: https://github.com/delivrance .. _other works: https://github.com/delivrance .. _Dan: https://t.me/haskell From 6a5469edaf026a75b8b715d218439bbe60f84b43 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 30 Aug 2020 10:57:31 +0200 Subject: [PATCH 0327/1185] Don't attempt to retrieve chat attributes from empty messages Fixes #479 --- pyrogram/utils.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/pyrogram/utils.py b/pyrogram/utils.py index 89ee7f6c79..807d98a024 100644 --- a/pyrogram/utils.py +++ b/pyrogram/utils.py @@ -167,8 +167,17 @@ async def parse_messages(client, messages: "raw.types.messages.Messages", replie reply_message_ids = [i[0] for i in filter(lambda x: x[1] is not None, messages_with_replies.items())] if reply_message_ids: + # We need a chat id, but some messages might be empty (no chat attribute available) + # Scan until we find a message with a chat available (there must be one, because we are fetching replies) + for m in parsed_messages: + if m.chat: + chat_id = m.chat.id + break + else: + chat_id = 0 + reply_messages = await client.get_messages( - parsed_messages[0].chat.id, + chat_id, reply_to_message_ids=reply_message_ids, replies=replies - 1 ) From 33d04b5916afbc312c84b7c0df334a2ad90b8f68 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 30 Aug 2020 10:58:55 +0200 Subject: [PATCH 0328/1185] Change default get/iter_chat_members filter to "recent" --- pyrogram/methods/chats/get_chat_members.py | 4 ++-- pyrogram/methods/chats/iter_chat_members.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pyrogram/methods/chats/get_chat_members.py b/pyrogram/methods/chats/get_chat_members.py index d3e87c7d03..d55a741062 100644 --- a/pyrogram/methods/chats/get_chat_members.py +++ b/pyrogram/methods/chats/get_chat_members.py @@ -42,7 +42,7 @@ async def get_chat_members( offset: int = 0, limit: int = 200, query: str = "", - filter: str = Filters.ALL + filter: str = Filters.RECENT ) -> List["types.ChatMember"]: """Get a chunk of the members list of a chat. @@ -78,7 +78,7 @@ async def get_chat_members( *"recent"* - recent members only, *"administrators"* - chat administrators only. Only applicable to supergroups and channels. - Defaults to *"all"*. + Defaults to *"recent"*. .. [1] Server limit: on supergroups, you can get up to 10,000 members for a single query and up to 200 members on channels. diff --git a/pyrogram/methods/chats/iter_chat_members.py b/pyrogram/methods/chats/iter_chat_members.py index 2954bcdba4..59def3c1da 100644 --- a/pyrogram/methods/chats/iter_chat_members.py +++ b/pyrogram/methods/chats/iter_chat_members.py @@ -43,7 +43,7 @@ async def iter_chat_members( chat_id: Union[int, str], limit: int = 0, query: str = "", - filter: str = Filters.ALL + filter: str = Filters.RECENT ) -> Optional[AsyncGenerator["types.ChatMember", None]]: """Iterate through the members of a chat sequentially. @@ -72,7 +72,7 @@ async def iter_chat_members( *"bots"* - bots only, *"recent"* - recent members only, *"administrators"* - chat administrators only. - Defaults to *"all"*. + Defaults to *"recent"*. Returns: ``Generator``: A generator yielding :obj:`~pyrogram.types.ChatMember` objects. From a18e0242fa231a4fd29da310933c8ea5f15ba0c8 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 30 Aug 2020 11:17:20 +0200 Subject: [PATCH 0329/1185] Fix raw API not being properly imported when using repr/eval Fixes #423 --- compiler/api/compiler.py | 2 +- pyrogram/__init__.py | 2 +- pyrogram/raw/core/tl_object.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/compiler/api/compiler.py b/compiler/api/compiler.py index 6c3dcb27ec..acaf9731c5 100644 --- a/compiler/api/compiler.py +++ b/compiler/api/compiler.py @@ -489,7 +489,7 @@ def start(format: bool = False): docstring=docstring, slots=slots, id=c.id, - qualname=f"pyrogram.raw.{c.section}.{c.qualname}", + qualname=f"{c.section}.{c.qualname}", arguments=arguments, fields=fields, read_types=read_types, diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index f967dfc595..af2c2ce996 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -33,6 +33,6 @@ class ContinuePropagation(StopAsyncIteration): pass -from . import types, filters, handlers, emoji +from . import raw, types, filters, handlers, emoji from .client import Client from .sync import idle diff --git a/pyrogram/raw/core/tl_object.py b/pyrogram/raw/core/tl_object.py index 0391ab85f6..48b3ec9134 100644 --- a/pyrogram/raw/core/tl_object.py +++ b/pyrogram/raw/core/tl_object.py @@ -53,7 +53,7 @@ def __str__(self) -> str: return dumps(self, indent=4, default=TLObject.default, ensure_ascii=False) def __repr__(self) -> str: - return "pyrogram.api.{}({})".format( + return "pyrogram.raw.{}({})".format( self.QUALNAME, ", ".join( f"{attr}={repr(getattr(self, attr))}" From 35ec32891342998e1aff125873a80817b5567487 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 30 Aug 2020 11:23:42 +0200 Subject: [PATCH 0330/1185] Update message.py --- pyrogram/client/types/messages_and_media/message.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/client/types/messages_and_media/message.py b/pyrogram/client/types/messages_and_media/message.py index c6d58de39b..9a8d57a982 100644 --- a/pyrogram/client/types/messages_and_media/message.py +++ b/pyrogram/client/types/messages_and_media/message.py @@ -2687,9 +2687,9 @@ def forward( """ if as_copy: if self.service: - log.warning("Unable to copy service messages, message_id: {} from chat.id: {}".format(self.message_id, self.chat.id)) + log.warning(f"Service messages cannot be copied. chat_id: {self.chat.id}, message_id: {self.message_id}") elif self.game and not self._client.is_bot: - log.warning("Users cannot send messages with Game media type, message_id: {} from chat.id: {}".format(self.message_id, self.chat.id)) + log.warning(f"Users cannot send messages with Game media type. chat_id: {self.chat.id}, message_id: {self.message_id}") elif self.text: return self._client.send_message( chat_id, From 8f2e4f03b763a7edd4e107f731aa3fb5fc74fda4 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 30 Aug 2020 13:18:10 +0200 Subject: [PATCH 0331/1185] Fix access to invalid attributes when parsing a chat dc_id --- pyrogram/types/user_and_chats/chat.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyrogram/types/user_and_chats/chat.py b/pyrogram/types/user_and_chats/chat.py index 8ae130c6a2..44d6e156ae 100644 --- a/pyrogram/types/user_and_chats/chat.py +++ b/pyrogram/types/user_and_chats/chat.py @@ -180,7 +180,7 @@ def _parse_user_chat(client, user: raw.types.User) -> "Chat": last_name=user.last_name, photo=types.ChatPhoto._parse(client, user.photo, peer_id, user.access_hash), restrictions=types.List([types.Restriction._parse(r) for r in user.restriction_reason]) or None, - dc_id=getattr(user.photo, "dc_id", None), + dc_id=getattr(getattr(user, "photo", None), "dc_id", None), client=client ) @@ -196,7 +196,7 @@ def _parse_chat_chat(client, chat: raw.types.Chat) -> "Chat": photo=types.ChatPhoto._parse(client, getattr(chat, "photo", None), peer_id, 0), permissions=types.ChatPermissions._parse(getattr(chat, "default_banned_rights", None)), members_count=getattr(chat, "participants_count", None), - dc_id=getattr(chat.photo, "dc_id", None), + dc_id=getattr(getattr(chat, "photo", None), "dc_id", None), client=client ) @@ -218,7 +218,7 @@ def _parse_channel_chat(client, channel: raw.types.Channel) -> "Chat": restrictions=types.List([types.Restriction._parse(r) for r in restriction_reason]) or None, permissions=types.ChatPermissions._parse(getattr(channel, "default_banned_rights", None)), members_count=getattr(channel, "participants_count", None), - dc_id=getattr(channel.photo, "dc_id", None), + dc_id=getattr(getattr(channel, "photo", None), "dc_id", None), client=client ) From be62ac365f26ff2594c3f1d52c59dc623d29d634 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 30 Aug 2020 22:19:55 +0200 Subject: [PATCH 0332/1185] Use a reasonable sleep threshold when downloading files --- pyrogram/client.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pyrogram/client.py b/pyrogram/client.py index ba5cfb49ed..e7683c26b4 100644 --- a/pyrogram/client.py +++ b/pyrogram/client.py @@ -930,7 +930,8 @@ async def get_file( location=location, offset=offset, limit=limit - ) + ), + sleep_threshold=30 ) if isinstance(r, raw.types.upload.File): @@ -961,7 +962,8 @@ async def get_file( location=location, offset=offset, limit=limit - ) + ), + sleep_threshold=30 ) elif isinstance(r, raw.types.upload.FileCdnRedirect): From 617f0c9b301015b841e8f8a22e9e0298db0a2567 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 31 Aug 2020 00:17:33 +0200 Subject: [PATCH 0333/1185] Set the main event loop in threads where there's no event loop #480 --- pyrogram/__init__.py | 5 +++++ pyrogram/scaffold.py | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index af2c2ce996..5936ce7c22 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -33,6 +33,11 @@ class ContinuePropagation(StopAsyncIteration): pass +import asyncio + from . import raw, types, filters, handlers, emoji from .client import Client from .sync import idle + +# Save the main thread loop for future references +main_event_loop = asyncio.get_event_loop() diff --git a/pyrogram/scaffold.py b/pyrogram/scaffold.py index 0b3bf02185..a118955c29 100644 --- a/pyrogram/scaffold.py +++ b/pyrogram/scaffold.py @@ -23,6 +23,7 @@ import sys from pathlib import Path +import pyrogram from pyrogram import __version__ from pyrogram.parser import Parser from pyrogram.session.internals import MsgId @@ -73,6 +74,13 @@ class Scaffold: mime_types_to_extensions[mime_type] = " ".join(extensions) def __init__(self): + try: + asyncio.get_event_loop() + except RuntimeError: + # This happens when creating Client instances inside different threads that don't have an event loop. + # Set the main event loop in this thread. + asyncio.set_event_loop(pyrogram.main_event_loop) + self.session_name = None self.api_id = None self.api_hash = None From 44880f7efe31f157f88b32779e160682ea8b500c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 2 Sep 2020 13:07:30 +0200 Subject: [PATCH 0334/1185] Use the current loop instead of the main loop in case there is one available #482 --- pyrogram/sync.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pyrogram/sync.py b/pyrogram/sync.py index 80288e68d0..2bca9700bb 100644 --- a/pyrogram/sync.py +++ b/pyrogram/sync.py @@ -28,7 +28,7 @@ def async_to_sync(obj, name): function = getattr(obj, name) - loop = asyncio.get_event_loop() + main_loop = asyncio.get_event_loop() async def consume_generator(coroutine): return [i async for i in coroutine] @@ -37,6 +37,11 @@ async def consume_generator(coroutine): def async_to_sync_wrap(*args, **kwargs): coroutine = function(*args, **kwargs) + try: + loop = asyncio.get_event_loop() + except RuntimeError: + loop = main_loop + if loop.is_running(): if threading.current_thread() is threading.main_thread(): return coroutine From 58667d2ae886518a9e93e82030935bebb2716c2f Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 5 Sep 2020 12:26:22 +0200 Subject: [PATCH 0335/1185] Update Pyrogram to v1.0.7 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 5936ce7c22..39b83df745 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "1.0.6" +__version__ = "1.0.7" __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)" __copyright__ = "Copyright (C) 2017-2020 Dan " From ae88c851bbc98785abb829a748d68803f3a44c37 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 5 Sep 2020 12:44:48 +0200 Subject: [PATCH 0336/1185] Fix sync callback progress not working properly for downloads - Reduce duplicated code - Fixes #484 --- pyrogram/client.py | 28 ++++++++++++++------------ pyrogram/methods/advanced/save_file.py | 16 +++++++-------- 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/pyrogram/client.py b/pyrogram/client.py index e7683c26b4..2577745a9c 100644 --- a/pyrogram/client.py +++ b/pyrogram/client.py @@ -949,7 +949,8 @@ async def get_file( offset += limit if progress: - await progress( + func = functools.partial( + progress, min(offset, file_size) if file_size != 0 else offset, @@ -957,6 +958,11 @@ async def get_file( *progress_args ) + if inspect.iscoroutinefunction(progress): + await func() + else: + await self.loop.run_in_executor(self.executor, func) + r = await session.send( raw.functions.upload.GetFile( location=location, @@ -1035,20 +1041,16 @@ async def get_file( offset += limit if progress: + func = functools.partial( + progress, + min(offset, file_size) if file_size != 0 else offset, + file_size, + *progress_args + ) + if inspect.iscoroutinefunction(progress): - await progress( - min(offset, file_size) if file_size != 0 else offset, - file_size, - *progress_args - ) + await func() else: - func = functools.partial( - progress, - min(offset, file_size) if file_size != 0 else offset, - file_size, - *progress_args - ) - await self.loop.run_in_executor(self.executor, func) if len(chunk) < limit: diff --git a/pyrogram/methods/advanced/save_file.py b/pyrogram/methods/advanced/save_file.py index 9d77073365..e97279515b 100644 --- a/pyrogram/methods/advanced/save_file.py +++ b/pyrogram/methods/advanced/save_file.py @@ -183,16 +183,16 @@ async def worker(session): file_part += 1 if progress: + func = functools.partial( + progress, + min(file_part * part_size, file_size), + file_size, + *progress_args + ) + if inspect.iscoroutinefunction(progress): - await progress(min(file_part * part_size, file_size), file_size, *progress_args) + await func() else: - func = functools.partial( - progress, - min(file_part * part_size, file_size), - file_size, - *progress_args - ) - await self.loop.run_in_executor(self.executor, func) except StopTransmission: raise From 3127edde6834813ee5117964d720753e259a1bb5 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 16 Sep 2020 09:16:31 +0200 Subject: [PATCH 0337/1185] Add missing method to the mixin delete_user_history --- pyrogram/methods/chats/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyrogram/methods/chats/__init__.py b/pyrogram/methods/chats/__init__.py index 6e4a9228cf..b55d1be7ce 100644 --- a/pyrogram/methods/chats/__init__.py +++ b/pyrogram/methods/chats/__init__.py @@ -24,6 +24,7 @@ from .delete_channel import DeleteChannel from .delete_chat_photo import DeleteChatPhoto from .delete_supergroup import DeleteSupergroup +from .delete_user_history import DeleteUserHistory from .export_chat_invite_link import ExportChatInviteLink from .get_chat import GetChat from .get_chat_member import GetChatMember @@ -86,6 +87,7 @@ class Chats( DeleteSupergroup, GetNearbyChats, SetAdministratorTitle, - SetSlowMode + SetSlowMode, + DeleteUserHistory ): pass From 5ee932b326b8baa1e5ff90b60a82904cfd702f93 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 30 Sep 2020 19:12:12 +0200 Subject: [PATCH 0338/1185] Update API Schema to Layer 119 --- compiler/api/source/main_api.tl | 83 ++++++++++++++++++++++----------- 1 file changed, 57 insertions(+), 26 deletions(-) diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl index 9b50b802b7..9f65522874 100644 --- a/compiler/api/source/main_api.tl +++ b/compiler/api/source/main_api.tl @@ -106,7 +106,7 @@ channel#d31a961e flags:# creator:flags.0?true left:flags.2?true broadcast:flags. channelForbidden#289da732 flags:# broadcast:flags.5?true megagroup:flags.8?true id:int access_hash:long title:string until_date:flags.16?int = Chat; chatFull#1b7c9db3 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:int about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int = ChatFull; -channelFull#f0e6672a flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true id:int about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?int migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?int location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int = ChatFull; +channelFull#f0e6672a flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true id:int about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?int migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?int location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int = ChatFull; chatParticipant#c8d7493e user_id:int inviter_id:int date:int = ChatParticipant; chatParticipantCreator#da13538a user_id:int = ChatParticipant; @@ -119,8 +119,8 @@ chatPhotoEmpty#37c1011c = ChatPhoto; chatPhoto#d20b9f3c flags:# has_video:flags.0?true photo_small:FileLocation photo_big:FileLocation dc_id:int = ChatPhoto; messageEmpty#83e5de54 id:int = Message; -message#452c0e65 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true id:int from_id:flags.8?int to_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to_msg_id:flags.3?int date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector views:flags.10?int edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long restriction_reason:flags.22?Vector = Message; -messageService#9e19a1f6 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true legacy:flags.19?true id:int from_id:flags.8?int to_id:Peer reply_to_msg_id:flags.3?int date:int action:MessageAction = Message; +message#58ae39c9 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true id:int from_id:flags.8?Peer peer_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long restriction_reason:flags.22?Vector = Message; +messageService#286fa604 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true legacy:flags.19?true id:int from_id:flags.8?Peer peer_id:Peer reply_to:flags.3?MessageReplyHeader date:int action:MessageAction = Message; messageMediaEmpty#3ded6320 = MessageMedia; messageMediaPhoto#695150d7 flags:# photo:flags.0?Photo ttl_seconds:flags.2?int = MessageMedia; @@ -170,6 +170,7 @@ photoSizeEmpty#e17e23c type:string = PhotoSize; photoSize#77bfb61b type:string location:FileLocation w:int h:int size:int = PhotoSize; photoCachedSize#e9a734fa type:string location:FileLocation w:int h:int bytes:bytes = PhotoSize; photoStrippedSize#e0b0bc2e type:string bytes:bytes = PhotoSize; +photoSizeProgressive#5aa86a51 type:string location:FileLocation w:int h:int sizes:Vector = PhotoSize; geoPointEmpty#1117dd5f = GeoPoint; geoPoint#296f104 long:double lat:double access_hash:long = GeoPoint; @@ -209,8 +210,6 @@ contact#f911c994 user_id:int mutual:Bool = Contact; importedContact#d0028438 user_id:int client_id:long = ImportedContact; -contactBlocked#561bc879 user_id:int date:int = ContactBlocked; - contactStatus#d3680c61 user_id:int status:UserStatus = ContactStatus; contacts.contactsNotModified#b74ba9d2 = contacts.Contacts; @@ -218,8 +217,8 @@ contacts.contacts#eae87e42 contacts:Vector saved_count:int users:Vector contacts.importedContacts#77d01c3b imported:Vector popular_invites:Vector retry_contacts:Vector users:Vector = contacts.ImportedContacts; -contacts.blocked#1c138d15 blocked:Vector users:Vector = contacts.Blocked; -contacts.blockedSlice#900802a1 count:int blocked:Vector users:Vector = contacts.Blocked; +contacts.blocked#ade1591 blocked:Vector chats:Vector users:Vector = contacts.Blocked; +contacts.blockedSlice#e1664194 count:int blocked:Vector chats:Vector users:Vector = contacts.Blocked; messages.dialogs#15ba6c40 dialogs:Vector messages:Vector chats:Vector users:Vector = messages.Dialogs; messages.dialogsSlice#71e094f3 count:int dialogs:Vector messages:Vector chats:Vector users:Vector = messages.Dialogs; @@ -270,7 +269,6 @@ updateEncryptedMessagesRead#38fe25b7 chat_id:int max_date:int date:int = Update; updateChatParticipantAdd#ea4b0e5c chat_id:int user_id:int inviter_id:int date:int version:int = Update; updateChatParticipantDelete#6e5f8c22 chat_id:int user_id:int version:int = Update; updateDcOptions#8e5e9873 dc_options:Vector = Update; -updateUserBlocked#80ece81a user_id:int blocked:Bool = Update; updateNotifySettings#bec268ef peer:NotifyPeer notify_settings:PeerNotifySettings = Update; updateServiceNotification#ebe46819 flags:# popup:flags.0?true inbox_date:flags.1?int type:string message:string media:MessageMedia entities:Vector = Update; updatePrivacy#ee3b272a key:PrivacyKey rules:Vector = Update; @@ -336,6 +334,11 @@ updateDialogFilterOrder#a5d72105 order:Vector = Update; updateDialogFilters#3504914f = Update; updatePhoneCallSignalingData#2661bf09 phone_call_id:long data:bytes = Update; updateChannelParticipant#65d2b464 flags:# channel_id:int date:int user_id:int prev_participant:flags.0?ChannelParticipant new_participant:flags.1?ChannelParticipant qts:int = Update; +updateChannelMessageForwards#6e8a84df channel_id:int id:int forwards:int = Update; +updateReadChannelDiscussionInbox#1cc7de54 flags:# channel_id:int top_msg_id:int read_max_id:int broadcast_id:flags.0?int broadcast_post:flags.0?int = Update; +updateReadChannelDiscussionOutbox#4638a26c channel_id:int top_msg_id:int read_max_id:int = Update; +updatePeerBlocked#246a4b22 peer_id:Peer blocked:Bool = Update; +updateChannelUserTyping#ff2abe9f flags:# channel_id:int top_msg_id:flags.0?int user_id:int action:SendMessageAction = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -345,8 +348,8 @@ updates.differenceSlice#a8fb1981 new_messages:Vector new_encrypted_mess updates.differenceTooLong#4afe8f6d pts:int = updates.Difference; updatesTooLong#e317af7e = Updates; -updateShortMessage#914fbf11 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true id:int user_id:int message:string pts:int pts_count:int date:int fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to_msg_id:flags.3?int entities:flags.7?Vector = Updates; -updateShortChatMessage#16812688 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true id:int from_id:int chat_id:int message:string pts:int pts_count:int date:int fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to_msg_id:flags.3?int entities:flags.7?Vector = Updates; +updateShortMessage#2296d2c8 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true id:int user_id:int message:string pts:int pts_count:int date:int fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to:flags.3?MessageReplyHeader entities:flags.7?Vector = Updates; +updateShortChatMessage#402d5dbb flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true id:int from_id:int chat_id:int message:string pts:int pts_count:int date:int fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to:flags.3?MessageReplyHeader entities:flags.7?Vector = Updates; updateShort#78d4dec1 update:Update date:int = Updates; updatesCombined#725b04c3 updates:Vector users:Vector chats:Vector date:int seq_start:int seq:int = Updates; updates#74ae4240 updates:Vector users:Vector chats:Vector date:int seq:int = Updates; @@ -525,7 +528,7 @@ botInfo#98e81d3a user_id:int description:string commands:Vector = Bo keyboardButton#a2fa4880 text:string = KeyboardButton; keyboardButtonUrl#258aff05 text:string url:string = KeyboardButton; -keyboardButtonCallback#683a5e46 text:string data:bytes = KeyboardButton; +keyboardButtonCallback#35bbdb6b flags:# requires_password:flags.0?true text:string data:bytes = KeyboardButton; keyboardButtonRequestPhone#b16a6c29 text:string = KeyboardButton; keyboardButtonRequestGeoLocation#fc796b3f text:string = KeyboardButton; keyboardButtonSwitchInline#568a748 flags:# same_peer:flags.0?true text:string query:string = KeyboardButton; @@ -579,7 +582,7 @@ channelMessagesFilter#cd77d957 flags:# exclude_new_messages:flags.1?true ranges: channelParticipant#15ebac1d user_id:int date:int = ChannelParticipant; channelParticipantSelf#a3289a6d user_id:int inviter_id:int date:int = ChannelParticipant; -channelParticipantCreator#808d15a4 flags:# user_id:int rank:flags.0?string = ChannelParticipant; +channelParticipantCreator#447dca4b flags:# user_id:int admin_rights:ChatAdminRights rank:flags.0?string = ChannelParticipant; channelParticipantAdmin#ccbebbaf flags:# can_edit:flags.0?true self:flags.1?true user_id:int inviter_id:flags.1?int promoted_by:int date:int admin_rights:ChatAdminRights rank:flags.2?string = ChannelParticipant; channelParticipantBanned#1c0facaf flags:# left:flags.0?true user_id:int kicked_by:int date:int banned_rights:ChatBannedRights = ChannelParticipant; @@ -626,7 +629,7 @@ messages.botResults#947ca848 flags:# gallery:flags.0?true query_id:long next_off exportedMessageLink#5dab1af4 link:string html:string = ExportedMessageLink; -messageFwdHeader#353a686b flags:# from_id:flags.0?int from_name:flags.5?string date:int channel_id:flags.1?int channel_post:flags.2?int post_author:flags.3?string saved_from_peer:flags.4?Peer saved_from_msg_id:flags.4?int psa_type:flags.6?string = MessageFwdHeader; +messageFwdHeader#5f777dce flags:# from_id:flags.0?Peer from_name:flags.5?string date:int channel_post:flags.2?int post_author:flags.3?string saved_from_peer:flags.4?Peer saved_from_msg_id:flags.4?int psa_type:flags.6?string = MessageFwdHeader; auth.codeTypeSms#72a3158c = auth.CodeType; auth.codeTypeCall#741cd3e3 = auth.CodeType; @@ -1007,7 +1010,7 @@ chatOnlines#f041e250 onlines:int = ChatOnlines; statsURL#47a971e0 url:string = StatsURL; -chatAdminRights#5fb224d5 flags:# change_info:flags.0?true post_messages:flags.1?true edit_messages:flags.2?true delete_messages:flags.3?true ban_users:flags.4?true invite_users:flags.5?true pin_messages:flags.7?true add_admins:flags.9?true = ChatAdminRights; +chatAdminRights#5fb224d5 flags:# change_info:flags.0?true post_messages:flags.1?true edit_messages:flags.2?true delete_messages:flags.3?true ban_users:flags.4?true invite_users:flags.5?true pin_messages:flags.7?true add_admins:flags.9?true anonymous:flags.10?true = ChatAdminRights; chatBannedRights#9f120418 flags:# view_messages:flags.0?true send_messages:flags.1?true send_media:flags.2?true send_stickers:flags.3?true send_gifs:flags.4?true send_games:flags.5?true send_inline:flags.6?true embed_links:flags.7?true send_polls:flags.8?true change_info:flags.10?true invite_users:flags.15?true pin_messages:flags.17?true until_date:int = ChatBannedRights; @@ -1128,6 +1131,27 @@ stats.megagroupStats#ef7ff916 period:StatsDateRangeDays members:StatsAbsValueAnd globalPrivacySettings#bea2f424 flags:# archive_and_mute_new_noncontact_peers:flags.0?Bool = GlobalPrivacySettings; +help.countryCode#4203c5ef flags:# country_code:string prefixes:flags.0?Vector patterns:flags.1?Vector = help.CountryCode; + +help.country#c3878e23 flags:# hidden:flags.0?true iso2:string default_name:string name:flags.1?string country_codes:Vector = help.Country; + +help.countriesListNotModified#93cc1f32 = help.CountriesList; +help.countriesList#87d0759e countries:Vector hash:int = help.CountriesList; + +messageViews#455b853d flags:# views:flags.0?int forwards:flags.1?int replies:flags.2?MessageReplies = MessageViews; + +messages.messageViews#b6c4f543 views:Vector chats:Vector users:Vector = messages.MessageViews; + +stats.messageStats#8999f295 views_graph:StatsGraph = stats.MessageStats; + +messages.discussionMessage#f5dd8f9d flags:# messages:Vector max_id:flags.0?int read_inbox_max_id:flags.1?int read_outbox_max_id:flags.2?int chats:Vector users:Vector = messages.DiscussionMessage; + +messageReplyHeader#a6d57763 flags:# reply_to_msg_id:int reply_to_peer_id:flags.0?Peer reply_to_top_id:flags.1?int = MessageReplyHeader; + +messageReplies#4128faac flags:# comments:flags.0?true replies:int replies_pts:int recent_repliers:flags.1?Vector channel_id:flags.0?int max_id:flags.2?int read_max_id:flags.3?int = MessageReplies; + +peerBlocked#e8fd8014 peer_id:Peer date:int = PeerBlocked; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -1235,8 +1259,8 @@ contacts.getContacts#c023849f hash:int = contacts.Contacts; contacts.importContacts#2c800be5 contacts:Vector = contacts.ImportedContacts; contacts.deleteContacts#96a0e00 id:Vector = Updates; contacts.deleteByPhones#1013fd9e phones:Vector = Bool; -contacts.block#332b49fc id:InputUser = Bool; -contacts.unblock#e54100bd id:InputUser = Bool; +contacts.block#68cc1411 id:InputPeer = Bool; +contacts.unblock#bea65d50 id:InputPeer = Bool; contacts.getBlocked#f57c350f offset:int limit:int = contacts.Blocked; contacts.search#11f812d8 q:string limit:int = contacts.Found; contacts.resolveUsername#f93ccba3 username:string = contacts.ResolvedPeer; @@ -1248,19 +1272,20 @@ contacts.toggleTopPeers#8514bdda enabled:Bool = Bool; contacts.addContact#e8f463d0 flags:# add_phone_privacy_exception:flags.0?true id:InputUser first_name:string last_name:string phone:string = Updates; contacts.acceptContact#f831a20f id:InputUser = Updates; contacts.getLocated#d348bc44 flags:# background:flags.1?true geo_point:InputGeoPoint self_expires:flags.0?int = Updates; +contacts.blockFromReplies#29a8962c flags:# delete_message:flags.0?true delete_history:flags.1?true report_spam:flags.2?true msg_id:int = Updates; messages.getMessages#63c66506 id:Vector = messages.Messages; messages.getDialogs#a0ee3b73 flags:# exclude_pinned:flags.0?true folder_id:flags.1?int offset_date:int offset_id:int offset_peer:InputPeer limit:int hash:int = messages.Dialogs; messages.getHistory#dcbb8260 peer:InputPeer offset_id:int offset_date:int add_offset:int limit:int max_id:int min_id:int hash:int = messages.Messages; -messages.search#8614ef68 flags:# peer:InputPeer q:string from_id:flags.0?InputUser filter:MessagesFilter min_date:int max_date:int offset_id:int add_offset:int limit:int max_id:int min_id:int hash:int = messages.Messages; +messages.search#4e17810b flags:# peer:InputPeer q:string from_id:flags.0?InputUser top_msg_id:flags.1?int filter:MessagesFilter min_date:int max_date:int offset_id:int add_offset:int limit:int max_id:int min_id:int hash:int = messages.Messages; messages.readHistory#e306d3a peer:InputPeer max_id:int = messages.AffectedMessages; messages.deleteHistory#1c015b09 flags:# just_clear:flags.0?true revoke:flags.1?true peer:InputPeer max_id:int = messages.AffectedHistory; messages.deleteMessages#e58e95d2 flags:# revoke:flags.0?true id:Vector = messages.AffectedMessages; messages.receivedMessages#5a954c0 max_id:int = Vector; -messages.setTyping#a3825e50 peer:InputPeer action:SendMessageAction = Bool; +messages.setTyping#58943ee2 flags:# peer:InputPeer top_msg_id:flags.0?int action:SendMessageAction = Bool; messages.sendMessage#520c3870 flags:# no_webpage:flags.1?true silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int = Updates; messages.sendMedia#3491eba9 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int media:InputMedia message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int = Updates; -messages.forwardMessages#d9fee60e flags:# silent:flags.5?true background:flags.6?true with_my_score:flags.8?true grouped:flags.9?true from_peer:InputPeer id:Vector random_id:Vector to_peer:InputPeer schedule_date:flags.10?int = Updates; +messages.forwardMessages#d9fee60e flags:# silent:flags.5?true background:flags.6?true with_my_score:flags.8?true from_peer:InputPeer id:Vector random_id:Vector to_peer:InputPeer schedule_date:flags.10?int = Updates; messages.reportSpam#cf1592db peer:InputPeer = Bool; messages.getPeerSettings#3672e09c peer:InputPeer = PeerSettings; messages.report#bd82b658 peer:InputPeer id:Vector reason:ReportReason = Bool; @@ -1277,8 +1302,8 @@ messages.acceptEncryption#3dbc0415 peer:InputEncryptedChat g_b:bytes key_fingerp messages.discardEncryption#edd923c5 chat_id:int = Bool; messages.setEncryptedTyping#791451ed peer:InputEncryptedChat typing:Bool = Bool; messages.readEncryptedHistory#7f4b690a peer:InputEncryptedChat max_date:int = Bool; -messages.sendEncrypted#a9776773 peer:InputEncryptedChat random_id:long data:bytes = messages.SentEncryptedMessage; -messages.sendEncryptedFile#9a901b66 peer:InputEncryptedChat random_id:long data:bytes file:InputEncryptedFile = messages.SentEncryptedMessage; +messages.sendEncrypted#44fa7a15 flags:# silent:flags.0?true peer:InputEncryptedChat random_id:long data:bytes = messages.SentEncryptedMessage; +messages.sendEncryptedFile#5559481d flags:# silent:flags.0?true peer:InputEncryptedChat random_id:long data:bytes file:InputEncryptedFile = messages.SentEncryptedMessage; messages.sendEncryptedService#32d439a4 peer:InputEncryptedChat random_id:long data:bytes = messages.SentEncryptedMessage; messages.receivedQueue#55a5bb66 max_qts:int = Vector; messages.reportEncryptedSpam#4b0c8c0f peer:InputEncryptedChat = Bool; @@ -1293,10 +1318,10 @@ messages.getStickerSet#2619a90e stickerset:InputStickerSet = messages.StickerSet messages.installStickerSet#c78fe460 stickerset:InputStickerSet archived:Bool = messages.StickerSetInstallResult; messages.uninstallStickerSet#f96e55de stickerset:InputStickerSet = Bool; messages.startBot#e6df7378 bot:InputUser peer:InputPeer random_id:long start_param:string = Updates; -messages.getMessagesViews#c4c8a55d peer:InputPeer id:Vector increment:Bool = Vector; +messages.getMessagesViews#5784d3e1 peer:InputPeer id:Vector increment:Bool = messages.MessageViews; messages.editChatAdmin#a9e69f2e chat_id:int user_id:InputUser is_admin:Bool = Bool; messages.migrateChat#15a3b8e3 chat_id:int = Updates; -messages.searchGlobal#bf7225a4 flags:# folder_id:flags.0?int q:string offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; +messages.searchGlobal#4bc6589a flags:# folder_id:flags.0?int q:string filter:MessagesFilter min_date:int max_date:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; messages.reorderStickerSets#78337739 flags:# masks:flags.0?true order:Vector = Bool; messages.getDocumentByHash#338e2464 sha256:bytes size:int mime_type:string = Document; messages.getSavedGifs#83bf3d52 hash:int = messages.SavedGifs; @@ -1307,7 +1332,7 @@ messages.sendInlineBotResult#220815b0 flags:# silent:flags.5?true background:fla messages.getMessageEditData#fda68d36 peer:InputPeer id:int = messages.MessageEditData; messages.editMessage#48f71778 flags:# no_webpage:flags.1?true peer:InputPeer id:int message:flags.11?string media:flags.14?InputMedia reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.15?int = Updates; messages.editInlineBotMessage#83557dba flags:# no_webpage:flags.1?true id:InputBotInlineMessageID message:flags.11?string media:flags.14?InputMedia reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector = Bool; -messages.getBotCallbackAnswer#810a9fec flags:# game:flags.1?true peer:InputPeer msg_id:int data:flags.0?bytes = messages.BotCallbackAnswer; +messages.getBotCallbackAnswer#9342ca07 flags:# game:flags.1?true peer:InputPeer msg_id:int data:flags.0?bytes password:flags.2?InputCheckPasswordSRP = messages.BotCallbackAnswer; messages.setBotCallbackAnswer#d58f130a flags:# alert:flags.1?true query_id:long message:flags.0?string url:flags.2?string cache_time:int = Bool; messages.getPeerDialogs#e470bcfd peers:Vector = messages.PeerDialogs; messages.saveDraft#bc39e14b flags:# no_webpage:flags.1?true reply_to_msg_id:flags.0?int peer:InputPeer message:string entities:flags.3?Vector = Bool; @@ -1372,6 +1397,9 @@ messages.getSuggestedDialogFilters#a29cd42c = Vector; messages.updateDialogFilter#1ad4a04a flags:# id:int filter:flags.0?DialogFilter = Bool; messages.updateDialogFiltersOrder#c563c1e4 order:Vector = Bool; messages.getOldFeaturedStickers#5fe7025b offset:int limit:int hash:int = messages.FeaturedStickers; +messages.getReplies#24b581ba peer:InputPeer msg_id:int offset_id:int offset_date:int add_offset:int limit:int max_id:int min_id:int hash:int = messages.Messages; +messages.getDiscussionMessage#446972fd peer:InputPeer msg_id:int = messages.DiscussionMessage; +messages.readDiscussion#f731a9f4 peer:InputPeer msg_id:int read_max_id:int = Bool; updates.getState#edd4882a = updates.State; updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference; @@ -1412,6 +1440,7 @@ help.editUserInfo#66b91b70 user_id:InputUser message:string entities:Vector = messages.AffectedMessages; @@ -1432,7 +1461,7 @@ channels.joinChannel#24b524c5 channel:InputChannel = Updates; channels.leaveChannel#f836aa95 channel:InputChannel = Updates; channels.inviteToChannel#199f3a6c channel:InputChannel users:Vector = Updates; channels.deleteChannel#c0111fe3 channel:InputChannel = Updates; -channels.exportMessageLink#ceb77163 channel:InputChannel id:int grouped:Bool = ExportedMessageLink; +channels.exportMessageLink#e63fadeb flags:# grouped:flags.0?true thread:flags.1?true channel:InputChannel id:int = ExportedMessageLink; channels.toggleSignatures#1f69b606 channel:InputChannel enabled:Bool = Updates; channels.getAdminedPublicChannels#f8b036af flags:# by_location:flags.0?true check_limit:flags.1?true = messages.Chats; channels.editBanned#72796912 channel:InputChannel user_id:InputUser banned_rights:ChatBannedRights = Updates; @@ -1489,5 +1518,7 @@ folders.deleteFolder#1c295881 folder_id:int = Updates; stats.getBroadcastStats#ab42441a flags:# dark:flags.0?true channel:InputChannel = stats.BroadcastStats; stats.loadAsyncGraph#621d5fa0 flags:# token:string x:flags.0?long = StatsGraph; stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel = stats.MegagroupStats; +stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; +stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats; -// LAYER 117 \ No newline at end of file +// LAYER 119 \ No newline at end of file From 4632879ee4d1742600005141b6b0b5aaa38de2af Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 30 Sep 2020 20:26:57 +0200 Subject: [PATCH 0339/1185] Changes for L119 --- pyrogram/client.py | 2 +- pyrogram/methods/chats/get_dialogs.py | 10 ++++---- .../bots_and_keyboards/game_high_score.py | 4 ++-- pyrogram/types/messages_and_media/message.py | 24 +++++++++++-------- pyrogram/types/user_and_chats/chat.py | 17 +++++++++---- pyrogram/types/user_and_chats/chat_member.py | 22 +++++++++++++++-- pyrogram/utils.py | 15 ++++++++++++ 7 files changed, 69 insertions(+), 25 deletions(-) diff --git a/pyrogram/client.py b/pyrogram/client.py index 2577745a9c..28c65c16a0 100644 --- a/pyrogram/client.py +++ b/pyrogram/client.py @@ -539,7 +539,7 @@ async def handle_updates(self, updates): getattr( getattr( update, "message", None - ), "to_id", None + ), "peer_id", None ), "channel_id", None ) or getattr(update, "channel_id", None) diff --git a/pyrogram/methods/chats/get_dialogs.py b/pyrogram/methods/chats/get_dialogs.py index 01bc0fd074..b437012b77 100644 --- a/pyrogram/methods/chats/get_dialogs.py +++ b/pyrogram/methods/chats/get_dialogs.py @@ -89,15 +89,15 @@ async def get_dialogs( messages = {} for message in r.messages: - to_id = message.to_id + peer_id = message.peer_id - if isinstance(to_id, raw.types.PeerUser): + if isinstance(peer_id, raw.types.PeerUser): if message.out: - chat_id = to_id.user_id + chat_id = peer_id.user_id else: - chat_id = message.from_id + chat_id = utils.get_raw_peer_id(message.from_id) else: - chat_id = utils.get_peer_id(to_id) + chat_id = utils.get_peer_id(peer_id) messages[chat_id] = await types.Message._parse(self, message, users, chats) diff --git a/pyrogram/types/bots_and_keyboards/game_high_score.py b/pyrogram/types/bots_and_keyboards/game_high_score.py index a9af42ee27..a744886207 100644 --- a/pyrogram/types/bots_and_keyboards/game_high_score.py +++ b/pyrogram/types/bots_and_keyboards/game_high_score.py @@ -17,7 +17,7 @@ # along with Pyrogram. If not, see . import pyrogram -from pyrogram import raw +from pyrogram import raw, utils from pyrogram import types from ..object import Object @@ -64,7 +64,7 @@ def _parse(client, game_high_score: raw.types.HighScore, users: dict) -> "GameHi @staticmethod def _parse_action(client, service: raw.types.MessageService, users: dict): return GameHighScore( - user=types.User._parse(client, users[service.from_id]), + user=types.User._parse(client, users[utils.get_raw_peer_id(service.from_id)]), score=service.action.score, client=client ) diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index 42d85e9aec..df6a5a9551 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -422,7 +422,7 @@ async def _parse( if isinstance(action, raw.types.MessageActionChatAddUser): new_chat_members = [types.User._parse(client, users[i]) for i in action.users] elif isinstance(action, raw.types.MessageActionChatJoinedByLink): - new_chat_members = [types.User._parse(client, users[message.from_id])] + new_chat_members = [types.User._parse(client, users[utils.get_raw_peer_id(message.from_id)])] elif isinstance(action, raw.types.MessageActionChatDeleteUser): left_chat_member = types.User._parse(client, users[action.user_id]) elif isinstance(action, raw.types.MessageActionChatEditTitle): @@ -444,7 +444,7 @@ async def _parse( message_id=message.id, date=message.date, chat=types.Chat._parse(client, message, users, chats), - from_user=types.User._parse(client, users.get(message.from_id, None)), + from_user=types.User._parse(client, users.get(utils.get_raw_peer_id(message.from_id), None)), service=True, new_chat_members=new_chat_members, left_chat_member=left_chat_member, @@ -472,7 +472,7 @@ async def _parse( if isinstance(action, raw.types.MessageActionGameScore): parsed_message.game_high_score = types.GameHighScore._parse_action(client, message, users) - if message.reply_to_msg_id and replies: + if message.reply_to and replies: try: parsed_message.reply_to_message = await client.get_messages( parsed_message.chat.id, @@ -501,13 +501,17 @@ async def _parse( forward_date = forward_header.date if forward_header.from_id: - forward_from = types.User._parse(client, users[forward_header.from_id]) + raw_peer_id = utils.get_raw_peer_id(forward_header.from_id) + peer_id = utils.get_peer_id(forward_header.from_id) + + if peer_id > 0: + forward_from = types.User._parse(client, users[raw_peer_id]) + else: + forward_from_chat = types.Chat._parse_channel_chat(client, chats[raw_peer_id]) + forward_from_message_id = forward_header.channel_post + forward_signature = forward_header.post_author elif forward_header.from_name: forward_sender_name = forward_header.from_name - else: - forward_from_chat = types.Chat._parse_channel_chat(client, chats[forward_header.channel_id]) - forward_from_message_id = forward_header.channel_post - forward_signature = forward_header.post_author photo = None location = None @@ -608,7 +612,7 @@ async def _parse( message_id=message.id, date=message.date, chat=types.Chat._parse(client, message, users, chats), - from_user=types.User._parse(client, users.get(message.from_id, None)), + from_user=types.User._parse(client, users.get(utils.get_raw_peer_id(message.from_id), None)), text=( Str(message.message).init(entities) or None if media is None or web_page is not None @@ -664,7 +668,7 @@ async def _parse( client=client ) - if message.reply_to_msg_id and replies: + if message.reply_to and replies: try: parsed_message.reply_to_message = await client.get_messages( parsed_message.chat.id, diff --git a/pyrogram/types/user_and_chats/chat.py b/pyrogram/types/user_and_chats/chat.py index 44d6e156ae..5576e6ad49 100644 --- a/pyrogram/types/user_and_chats/chat.py +++ b/pyrogram/types/user_and_chats/chat.py @@ -224,13 +224,20 @@ def _parse_channel_chat(client, channel: raw.types.Channel) -> "Chat": @staticmethod def _parse(client, message: raw.types.Message or raw.types.MessageService, users: dict, chats: dict) -> "Chat": - if isinstance(message.to_id, raw.types.PeerUser): - return Chat._parse_user_chat(client, users[message.to_id.user_id if message.out else message.from_id]) + if isinstance(message.peer_id, raw.types.PeerUser): + return Chat._parse_user_chat( + client, + users[ + message.peer_id.user_id + if message.out + else utils.get_raw_peer_id(message.from_id) + ] + ) - if isinstance(message.to_id, raw.types.PeerChat): - return Chat._parse_chat_chat(client, chats[message.to_id.chat_id]) + if isinstance(message.peer_id, raw.types.PeerChat): + return Chat._parse_chat_chat(client, chats[message.peer_id.chat_id]) - return Chat._parse_channel_chat(client, chats[message.to_id.channel_id]) + return Chat._parse_channel_chat(client, chats[message.peer_id.channel_id]) @staticmethod def _parse_dialog(client, peer, users: dict, chats: dict): diff --git a/pyrogram/types/user_and_chats/chat_member.py b/pyrogram/types/user_and_chats/chat_member.py index 31a27402c9..69ab4955f6 100644 --- a/pyrogram/types/user_and_chats/chat_member.py +++ b/pyrogram/types/user_and_chats/chat_member.py @@ -211,11 +211,10 @@ def _parse(client, member, users) -> "ChatMember": client=client ) - if isinstance(member, (raw.types.ChannelParticipantCreator, raw.types.ChatParticipantCreator)): + if isinstance(member, raw.types.ChatParticipantCreator): return ChatMember( user=user, status="creator", - title=getattr(member, "rank", None), client=client ) @@ -228,6 +227,25 @@ def _parse(client, member, users) -> "ChatMember": client=client ) + if isinstance(member, raw.types.ChannelParticipantCreator): + permissions = member.admin_rights + + return ChatMember( + user=user, + status="creator", + title=member.rank, + invited_by=invited_by, + can_change_info=permissions.change_info, + can_post_messages=permissions.post_messages, + can_edit_messages=permissions.edit_messages, + can_delete_messages=permissions.delete_messages, + can_restrict_members=permissions.ban_users, + can_invite_users=permissions.invite_users, + can_pin_messages=permissions.pin_messages, + can_promote_members=permissions.add_admins, + client=client + ) + if isinstance(member, raw.types.ChannelParticipantAdmin): permissions = member.admin_rights diff --git a/pyrogram/utils.py b/pyrogram/utils.py index 807d98a024..296a7c58c9 100644 --- a/pyrogram/utils.py +++ b/pyrogram/utils.py @@ -231,7 +231,22 @@ def unpack_inline_message_id(inline_message_id: str) -> "raw.types.InputBotInlin MAX_USER_ID = 2147483647 +def get_raw_peer_id(peer: raw.base.Peer) -> Union[int, None]: + """Get the raw peer id from a Peer object""" + if isinstance(peer, raw.types.PeerUser): + return peer.user_id + + if isinstance(peer, raw.types.PeerChat): + return peer.chat_id + + if isinstance(peer, raw.types.PeerChannel): + return peer.channel_id + + return None + + def get_peer_id(peer: raw.base.Peer) -> int: + """Get the non-raw peer id from a Peer object""" if isinstance(peer, raw.types.PeerUser): return peer.user_id From f3e515a2f20cf9148aa1d7668ef167f3452509be Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 1 Oct 2020 19:37:03 +0200 Subject: [PATCH 0340/1185] Update emoji.py --- pyrogram/emoji.py | 564 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 474 insertions(+), 90 deletions(-) diff --git a/pyrogram/emoji.py b/pyrogram/emoji.py index 7c55df601f..8d781cab04 100644 --- a/pyrogram/emoji.py +++ b/pyrogram/emoji.py @@ -455,12 +455,12 @@ MAN_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd" MAN_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe" MAN_DARK_SKIN_TONE = "\U0001f468\U0001f3ff" -MAN_BEARD = "\U0001f9d4" -MAN_LIGHT_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fb" -MAN_MEDIUM_LIGHT_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fc" -MAN_MEDIUM_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fd" -MAN_MEDIUM_DARK_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fe" -MAN_DARK_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3ff" +PERSON_BEARD = "\U0001f9d4" +PERSON_LIGHT_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fb" +PERSON_MEDIUM_LIGHT_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fc" +PERSON_MEDIUM_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fd" +PERSON_MEDIUM_DARK_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fe" +PERSON_DARK_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3ff" MAN_RED_HAIR = "\U0001f468\u200d\U0001f9b0" MAN_LIGHT_SKIN_TONE_RED_HAIR = "\U0001f468\U0001f3fb\u200d\U0001f9b0" MAN_MEDIUM_LIGHT_SKIN_TONE_RED_HAIR = "\U0001f468\U0001f3fc\u200d\U0001f9b0" @@ -3408,114 +3408,498 @@ REGIONAL_INDICATOR_SYMBOL_LETTER_X = "\U0001f1fd" REGIONAL_INDICATOR_SYMBOL_LETTER_Y = "\U0001f1fe" REGIONAL_INDICATOR_SYMBOL_LETTER_Z = "\U0001f1ff" -TAG_VERTICAL_LINE = "\U000e007c" -TAG_LATIN_CAPITAL_LETTER_O = "\U000e004f" -TAG_LOW_LINE = "\U000e005f" -TAG_LATIN_SMALL_LETTER_E = "\U000e0065" +FACE_EXHALING = "\U0001f62e\u200d\U0001f4a8" +FACE_WITH_SPIRAL_EYES = "\U0001f635\u200d\U0001f4ab" +FACE_IN_CLOUDS = "\U0001f636\u200d\U0001f32b" +HEART_ON_FIRE = "\u2764\ufe0f\u200d\U0001f525" +MENDING_HEART = "\u2764\ufe0f\u200d\U0001fa79" +WOMAN_BEARD = "\U0001f9d4\u200d\u2640\ufe0f" +WOMAN_LIGHT_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fb\u200d\u2640\ufe0f" +WOMAN_MEDIUM_LIGHT_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fc\u200d\u2640\ufe0f" +WOMAN_MEDIUM_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fd\u200d\u2640\ufe0f" +WOMAN_MEDIUM_DARK_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fe\u200d\u2640\ufe0f" +WOMAN_DARK_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3ff\u200d\u2640\ufe0f" +MAN_BEARD = "\U0001f9d4\u200d\u2642\ufe0f" +MAN_LIGHT_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fb\u200d\u2642\ufe0f" +MAN_MEDIUM_LIGHT_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fc\u200d\u2642\ufe0f" +MAN_MEDIUM_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fd\u200d\u2642\ufe0f" +MAN_MEDIUM_DARK_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fe\u200d\u2642\ufe0f" +MAN_DARK_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3ff\u200d\u2642\ufe0f" +COUPLE_WITH_HEART_LIGHT_SKIN_TONE = "\U0001f491\U0001f3fb" +COUPLE_WITH_HEART_MEDIUM_LIGHT_SKIN_TONE = "\U0001f491\U0001f3fc" +COUPLE_WITH_HEART_MEDIUM_SKIN_TONE = "\U0001f491\U0001f3fd" +COUPLE_WITH_HEART_MEDIUM_DARK_SKIN_TONE = "\U0001f491\U0001f3fe" +COUPLE_WITH_HEART_DARK_SKIN_TONE = "\U0001f491\U0001f3ff" +KISS_LIGHT_SKIN_TONE = "\U0001f48f\U0001f3fb" +KISS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f48f\U0001f3fc" +KISS_MEDIUM_SKIN_TONE = "\U0001f48f\U0001f3fd" +KISS_MEDIUM_DARK_SKIN_TONE = "\U0001f48f\U0001f3fe" +KISS_DARK_SKIN_TONE = "\U0001f48f\U0001f3ff" +COUPLE_WITH_HEART_MAN_MAN_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb" +COUPLE_WITH_HEART_MAN_MAN_LIGHT_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc" +COUPLE_WITH_HEART_MAN_MAN_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd" +COUPLE_WITH_HEART_MAN_MAN_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe" +COUPLE_WITH_HEART_MAN_MAN_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff" +COUPLE_WITH_HEART_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb" +COUPLE_WITH_HEART_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc" +COUPLE_WITH_HEART_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd" +COUPLE_WITH_HEART_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe" +COUPLE_WITH_HEART_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff" +COUPLE_WITH_HEART_MAN_MAN_MEDIUM_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb" +COUPLE_WITH_HEART_MAN_MAN_MEDIUM_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc" +COUPLE_WITH_HEART_MAN_MAN_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd" +COUPLE_WITH_HEART_MAN_MAN_MEDIUM_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe" +COUPLE_WITH_HEART_MAN_MAN_MEDIUM_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff" +COUPLE_WITH_HEART_MAN_MAN_MEDIUM_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb" +COUPLE_WITH_HEART_MAN_MAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc" +COUPLE_WITH_HEART_MAN_MAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd" +COUPLE_WITH_HEART_MAN_MAN_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe" +COUPLE_WITH_HEART_MAN_MAN_MEDIUM_DARK_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff" +COUPLE_WITH_HEART_MAN_MAN_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb" +COUPLE_WITH_HEART_MAN_MAN_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc" +COUPLE_WITH_HEART_MAN_MAN_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd" +COUPLE_WITH_HEART_MAN_MAN_DARK_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe" +COUPLE_WITH_HEART_MAN_MAN_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff" +COUPLE_WITH_HEART_WOMAN_MAN_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb" +COUPLE_WITH_HEART_WOMAN_MAN_LIGHT_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc" +COUPLE_WITH_HEART_WOMAN_MAN_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd" +COUPLE_WITH_HEART_WOMAN_MAN_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe" +COUPLE_WITH_HEART_WOMAN_MAN_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff" +COUPLE_WITH_HEART_WOMAN_WOMAN_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fb" +COUPLE_WITH_HEART_WOMAN_WOMAN_LIGHT_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fc" +COUPLE_WITH_HEART_WOMAN_WOMAN_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fd" +COUPLE_WITH_HEART_WOMAN_WOMAN_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fe" +COUPLE_WITH_HEART_WOMAN_WOMAN_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3ff" +COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb" +COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc" +COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd" +COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe" +COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff" +COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fb" +COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fc" +COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fd" +COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fe" +COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3ff" +COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb" +COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc" +COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd" +COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe" +COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff" +COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fb" +COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fc" +COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fd" +COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fe" +COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3ff" +COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb" +COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc" +COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd" +COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe" +COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff" +COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fb" +COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fc" +COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fd" +COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fe" +COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3ff" +COUPLE_WITH_HEART_WOMAN_MAN_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb" +COUPLE_WITH_HEART_WOMAN_MAN_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc" +COUPLE_WITH_HEART_WOMAN_MAN_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd" +COUPLE_WITH_HEART_WOMAN_MAN_DARK_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe" +COUPLE_WITH_HEART_WOMAN_MAN_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff" +COUPLE_WITH_HEART_WOMAN_WOMAN_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fb" +COUPLE_WITH_HEART_WOMAN_WOMAN_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fc" +COUPLE_WITH_HEART_WOMAN_WOMAN_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fd" +COUPLE_WITH_HEART_WOMAN_WOMAN_DARK_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fe" +COUPLE_WITH_HEART_WOMAN_WOMAN_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3ff" +COUPLE_WITH_HEART_PERSON_PERSON_LIGHT_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fc" +COUPLE_WITH_HEART_PERSON_PERSON_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f9d1\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fd" +COUPLE_WITH_HEART_PERSON_PERSON_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f9d1\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fe" +COUPLE_WITH_HEART_PERSON_PERSON_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f9d1\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3ff" +COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_LIGHT_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fb" +COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f9d1\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fd" +COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f9d1\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fe" +COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f9d1\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3ff" +COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fb" +COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fc" +COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f9d1\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fe" +COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f9d1\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3ff" +COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fb" +COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fc" +COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f9d1\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fd" +COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_DARK_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f9d1\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3ff" +COUPLE_WITH_HEART_PERSON_PERSON_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fb" +COUPLE_WITH_HEART_PERSON_PERSON_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fc" +COUPLE_WITH_HEART_PERSON_PERSON_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f9d1\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fd" +COUPLE_WITH_HEART_PERSON_PERSON_DARK_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f9d1\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fe" +KISS_MAN_MAN_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb" +KISS_MAN_MAN_LIGHT_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc" +KISS_MAN_MAN_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd" +KISS_MAN_MAN_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe" +KISS_MAN_MAN_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff" +KISS_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb" +KISS_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc" +KISS_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd" +KISS_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe" +KISS_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff" +KISS_MAN_MAN_MEDIUM_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb" +KISS_MAN_MAN_MEDIUM_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc" +KISS_MAN_MAN_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd" +KISS_MAN_MAN_MEDIUM_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe" +KISS_MAN_MAN_MEDIUM_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff" +KISS_MAN_MAN_MEDIUM_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb" +KISS_MAN_MAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc" +KISS_MAN_MAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd" +KISS_MAN_MAN_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe" +KISS_MAN_MAN_MEDIUM_DARK_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff" +KISS_MAN_MAN_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb" +KISS_MAN_MAN_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc" +KISS_MAN_MAN_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd" +KISS_MAN_MAN_DARK_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe" +KISS_MAN_MAN_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff" +KISS_WOMAN_MAN_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb" +KISS_WOMAN_MAN_LIGHT_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc" +KISS_WOMAN_MAN_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd" +KISS_WOMAN_MAN_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe" +KISS_WOMAN_MAN_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff" +KISS_WOMAN_WOMAN_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fb" +KISS_WOMAN_WOMAN_LIGHT_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fc" +KISS_WOMAN_WOMAN_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fd" +KISS_WOMAN_WOMAN_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fe" +KISS_WOMAN_WOMAN_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3ff" +KISS_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb" +KISS_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc" +KISS_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd" +KISS_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe" +KISS_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff" +KISS_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fb" +KISS_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fc" +KISS_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fd" +KISS_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fe" +KISS_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3ff" +KISS_WOMAN_MAN_MEDIUM_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb" +KISS_WOMAN_MAN_MEDIUM_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc" +KISS_WOMAN_MAN_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd" +KISS_WOMAN_MAN_MEDIUM_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe" +KISS_WOMAN_MAN_MEDIUM_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff" +KISS_WOMAN_WOMAN_MEDIUM_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fb" +KISS_WOMAN_WOMAN_MEDIUM_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fc" +KISS_WOMAN_WOMAN_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fd" +KISS_WOMAN_WOMAN_MEDIUM_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fe" +KISS_WOMAN_WOMAN_MEDIUM_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3ff" +KISS_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb" +KISS_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc" +KISS_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd" +KISS_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe" +KISS_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff" +KISS_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fb" +KISS_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fc" +KISS_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fd" +KISS_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fe" +KISS_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3ff" +KISS_WOMAN_MAN_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb" +KISS_WOMAN_MAN_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc" +KISS_WOMAN_MAN_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd" +KISS_WOMAN_MAN_DARK_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe" +KISS_WOMAN_MAN_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff" +KISS_WOMAN_WOMAN_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fb" +KISS_WOMAN_WOMAN_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fc" +KISS_WOMAN_WOMAN_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fd" +KISS_WOMAN_WOMAN_DARK_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fe" +KISS_WOMAN_WOMAN_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3ff" +KISS_PERSON_PERSON_LIGHT_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fc" +KISS_PERSON_PERSON_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f9d1\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fd" +KISS_PERSON_PERSON_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f9d1\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fe" +KISS_PERSON_PERSON_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f9d1\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3ff" +KISS_PERSON_PERSON_MEDIUM_LIGHT_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fb" +KISS_PERSON_PERSON_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f9d1\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fd" +KISS_PERSON_PERSON_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f9d1\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fe" +KISS_PERSON_PERSON_MEDIUM_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f9d1\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3ff" +KISS_PERSON_PERSON_MEDIUM_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fb" +KISS_PERSON_PERSON_MEDIUM_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fc" +KISS_PERSON_PERSON_MEDIUM_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f9d1\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fe" +KISS_PERSON_PERSON_MEDIUM_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f9d1\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3ff" +KISS_PERSON_PERSON_MEDIUM_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fb" +KISS_PERSON_PERSON_MEDIUM_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fc" +KISS_PERSON_PERSON_MEDIUM_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f9d1\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fd" +KISS_PERSON_PERSON_MEDIUM_DARK_SKIN_TONE_DARK_SKIN_TONE = \ + "\U0001f9d1\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3ff" +KISS_PERSON_PERSON_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fb" +KISS_PERSON_PERSON_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \ + "\U0001f9d1\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fc" +KISS_PERSON_PERSON_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \ + "\U0001f9d1\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fd" +KISS_PERSON_PERSON_DARK_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \ + "\U0001f9d1\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fe" +TAG_TILDE = "\U000e007e" TAG_PERCENT_SIGN = "\U000e0025" -TAG_DIGIT_SEVEN = "\U000e0037" -TAG_LATIN_SMALL_LETTER_W = "\U000e0077" -TAG_DIGIT_ONE = "\U000e0031" -TAG_LATIN_CAPITAL_LETTER_A = "\U000e0041" +ZERO_WIDTH_JOINER = "\u200d" +TAG_LATIN_SMALL_LETTER_E = "\U000e0065" +TAG_LATIN_CAPITAL_LETTER_I = "\U000e0049" +TAG_LATIN_SMALL_LETTER_N = "\U000e006e" +TAG_LATIN_CAPITAL_LETTER_S = "\U000e0053" +TAG_RIGHT_CURLY_BRACKET = "\U000e007d" +TAG_DIGIT_FIVE = "\U000e0035" +TAG_LATIN_CAPITAL_LETTER_V = "\U000e0056" TAG_LATIN_CAPITAL_LETTER_Z = "\U000e005a" TAG_LATIN_CAPITAL_LETTER_M = "\U000e004d" -TAG_QUESTION_MARK = "\U000e003f" TAG_LATIN_CAPITAL_LETTER_W = "\U000e0057" TAG_LATIN_SMALL_LETTER_D = "\U000e0064" +TAG_LATIN_CAPITAL_LETTER_F = "\U000e0046" +TAG_LATIN_SMALL_LETTER_U = "\U000e0075" +TAG_LATIN_SMALL_LETTER_B = "\U000e0062" +VARIATION_SELECTOR_16 = "\ufe0f" +TAG_FULL_STOP = "\U000e002e" +DIGIT_FOUR = "4\ufe0f" +TAG_LATIN_SMALL_LETTER_F = "\U000e0066" +DIGIT_ONE = "1\ufe0f" +TAG_LATIN_CAPITAL_LETTER_B = "\U000e0042" +TAG_DIGIT_SEVEN = "\U000e0037" +TAG_DIGIT_ONE = "\U000e0031" +TAG_COMMERCIAL_AT = "\U000e0040" +TAG_LATIN_SMALL_LETTER_Z = "\U000e007a" TAG_LATIN_SMALL_LETTER_K = "\U000e006b" TAG_LATIN_SMALL_LETTER_V = "\U000e0076" -DIGIT_ZERO = "0\ufe0f" -TAG_DIGIT_SIX = "\U000e0036" -TAG_LATIN_SMALL_LETTER_C = "\U000e0063" +TAG_LATIN_CAPITAL_LETTER_C = "\U000e0043" TAG_LATIN_SMALL_LETTER_X = "\U000e0078" -TAG_LATIN_SMALL_LETTER_U = "\U000e0075" -TAG_DIGIT_NINE = "\U000e0039" -DIGIT_FOUR = "4\ufe0f" -DIGIT_EIGHT = "8\ufe0f" -TAG_RIGHT_CURLY_BRACKET = "\U000e007d" -TAG_LATIN_CAPITAL_LETTER_S = "\U000e0053" -TAG_DIGIT_FIVE = "\U000e0035" -TAG_LATIN_CAPITAL_LETTER_V = "\U000e0056" -TAG_LATIN_SMALL_LETTER_L = "\U000e006c" +TAG_LATIN_SMALL_LETTER_C = "\U000e0063" TAG_SOLIDUS = "\U000e002f" -TAG_LATIN_SMALL_LETTER_B = "\U000e0062" -TAG_SEMICOLON = "\U000e003b" -TAG_LESS_THAN_SIGN = "\U000e003c" -TAG_DIGIT_TWO = "\U000e0032" +TAG_COMMA = "\U000e002c" +TAG_LATIN_CAPITAL_LETTER_P = "\U000e0050" TAG_LATIN_SMALL_LETTER_M = "\U000e006d" -TAG_LATIN_CAPITAL_LETTER_N = "\U000e004e" +TAG_LATIN_CAPITAL_LETTER_J = "\U000e004a" TAG_LATIN_SMALL_LETTER_P = "\U000e0070" TAG_LATIN_SMALL_LETTER_I = "\U000e0069" +TAG_COLON = "\U000e003a" TAG_LATIN_SMALL_LETTER_A = "\U000e0061" TAG_NUMBER_SIGN = "\U000e0023" -TAG_LATIN_CAPITAL_LETTER_K = "\U000e004b" -TAG_LATIN_CAPITAL_LETTER_J = "\U000e004a" +TAG_LATIN_CAPITAL_LETTER_X = "\U000e0058" TAG_LATIN_CAPITAL_LETTER_T = "\U000e0054" -TAG_LATIN_SMALL_LETTER_Y = "\U000e0079" -TAG_QUOTATION_MARK = "\U000e0022" -TAG_TILDE = "\U000e007e" -TAG_FULL_STOP = "\U000e002e" -TAG_LATIN_SMALL_LETTER_S = "\U000e0073" -TAG_LATIN_SMALL_LETTER_F = "\U000e0066" -TAG_LATIN_SMALL_LETTER_O = "\U000e006f" -TAG_DIGIT_ZERO = "\U000e0030" -DIGIT_FIVE = "5\ufe0f" -DIGIT_ONE = "1\ufe0f" -TAG_LATIN_CAPITAL_LETTER_B = "\U000e0042" -DIGIT_NINE = "9\ufe0f" -TAG_LATIN_CAPITAL_LETTER_F = "\U000e0046" +NUMBER_SIGN = "#\ufe0f" +TAG_DOLLAR_SIGN = "\U000e0024" TAG_LATIN_CAPITAL_LETTER_Y = "\U000e0059" -TAG_GREATER_THAN_SIGN = "\U000e003e" -TAG_HYPHEN_MINUS = "\U000e002d" -TAG_LEFT_CURLY_BRACKET = "\U000e007b" -TAG_DIGIT_THREE = "\U000e0033" -TAG_EQUALS_SIGN = "\U000e003d" +TAG_LATIN_CAPITAL_LETTER_E = "\U000e0045" +TAG_LATIN_SMALL_LETTER_O = "\U000e006f" TAG_DIGIT_FOUR = "\U000e0034" -TAG_LATIN_SMALL_LETTER_N = "\U000e006e" -TAG_DOLLAR_SIGN = "\U000e0024" +TAG_HYPHEN_MINUS = "\U000e002d" TAG_RIGHT_PARENTHESIS = "\U000e0029" -TAG_LATIN_CAPITAL_LETTER_Q = "\U000e0051" TAG_CIRCUMFLEX_ACCENT = "\U000e005e" -DIGIT_SEVEN = "7\ufe0f" -VARIATION_SELECTOR_16 = "\ufe0f" -NUMBER_SIGN = "#\ufe0f" -DIGIT_TWO = "2\ufe0f" -TAG_GRAVE_ACCENT = "\U000e0060" -TAG_COLON = "\U000e003a" +TAG_LATIN_CAPITAL_LETTER_Q = "\U000e0051" TAG_REVERSE_SOLIDUS = "\U000e005c" -TAG_COMMERCIAL_AT = "\U000e0040" -TAG_EXCLAMATION_MARK = "\U000e0021" -TAG_APOSTROPHE = "\U000e0027" -TAG_LATIN_SMALL_LETTER_Z = "\U000e007a" -TAG_LATIN_CAPITAL_LETTER_X = "\U000e0058" TAG_LATIN_CAPITAL_LETTER_R = "\U000e0052" -COMBINING_ENCLOSING_KEYCAP = "\u20e3" -ZERO_WIDTH_JOINER = "\u200d" -TAG_LATIN_SMALL_LETTER_G = "\U000e0067" -TAG_LATIN_CAPITAL_LETTER_U = "\U000e0055" -TAG_LATIN_CAPITAL_LETTER_D = "\U000e0044" -TAG_LATIN_SMALL_LETTER_R = "\U000e0072" -TAG_LATIN_CAPITAL_LETTER_P = "\U000e0050" -TAG_LATIN_SMALL_LETTER_H = "\U000e0068" +TAG_QUOTATION_MARK = "\U000e0022" +TAG_DIGIT_NINE = "\U000e0039" CANCEL_TAG = "\U000e007f" -TAG_AMPERSAND = "\U000e0026" -TAG_LEFT_PARENTHESIS = "\U000e0028" -TAG_LATIN_SMALL_LETTER_Q = "\U000e0071" -TAG_LATIN_CAPITAL_LETTER_C = "\U000e0043" TAG_ASTERISK = "\U000e002a" -TAG_LATIN_SMALL_LETTER_T = "\U000e0074" -TAG_LATIN_CAPITAL_LETTER_L = "\U000e004c" -TAG_COMMA = "\U000e002c" +TAG_LEFT_PARENTHESIS = "\U000e0028" TAG_RIGHT_SQUARE_BRACKET = "\U000e005d" -DIGIT_THREE = "3\ufe0f" -TAG_LATIN_CAPITAL_LETTER_H = "\U000e0048" -TAG_LATIN_SMALL_LETTER_J = "\U000e006a" -TAG_SPACE = "\U000e0020" +DIGIT_SIX = "6\ufe0f" +DIGIT_FIVE = "5\ufe0f" +ASTERISK = "*\ufe0f" +TAG_LATIN_CAPITAL_LETTER_G = "\U000e0047" +DIGIT_ZERO = "0\ufe0f" +TAG_VERTICAL_LINE = "\U000e007c" TAG_PLUS_SIGN = "\U000e002b" TAG_LEFT_SQUARE_BRACKET = "\U000e005b" -ASTERISK = "*\ufe0f" -DIGIT_SIX = "6\ufe0f" TAG_DIGIT_EIGHT = "\U000e0038" -TAG_LATIN_CAPITAL_LETTER_E = "\U000e0045" -TAG_LATIN_CAPITAL_LETTER_G = "\U000e0047" -TAG_LATIN_CAPITAL_LETTER_I = "\U000e0049" +TAG_SPACE = "\U000e0020" +TAG_LATIN_SMALL_LETTER_J = "\U000e006a" +TAG_LATIN_CAPITAL_LETTER_H = "\U000e0048" +DIGIT_THREE = "3\ufe0f" +TAG_LATIN_CAPITAL_LETTER_L = "\U000e004c" +TAG_LATIN_CAPITAL_LETTER_D = "\U000e0044" +TAG_LATIN_CAPITAL_LETTER_U = "\U000e0055" +TAG_LESS_THAN_SIGN = "\U000e003c" +TAG_EXCLAMATION_MARK = "\U000e0021" +TAG_APOSTROPHE = "\U000e0027" +TAG_GREATER_THAN_SIGN = "\U000e003e" +TAG_LATIN_SMALL_LETTER_T = "\U000e0074" +DIGIT_NINE = "9\ufe0f" +TAG_LATIN_SMALL_LETTER_S = "\U000e0073" +TAG_LATIN_SMALL_LETTER_Q = "\U000e0071" +DIGIT_TWO = "2\ufe0f" +TAG_AMPERSAND = "\U000e0026" +COMBINING_ENCLOSING_KEYCAP = "\u20e3" +TAG_LATIN_SMALL_LETTER_H = "\U000e0068" +TAG_LATIN_SMALL_LETTER_R = "\U000e0072" +TAG_SEMICOLON = "\U000e003b" +TAG_LATIN_CAPITAL_LETTER_O = "\U000e004f" +TAG_LOW_LINE = "\U000e005f" +DIGIT_SEVEN = "7\ufe0f" +TAG_GRAVE_ACCENT = "\U000e0060" +TAG_LATIN_SMALL_LETTER_W = "\U000e0077" +TAG_EQUALS_SIGN = "\U000e003d" +TAG_LATIN_SMALL_LETTER_G = "\U000e0067" +TAG_LATIN_CAPITAL_LETTER_A = "\U000e0041" +TAG_DIGIT_THREE = "\U000e0033" +TAG_LEFT_CURLY_BRACKET = "\U000e007b" +TAG_QUESTION_MARK = "\U000e003f" +TAG_LATIN_SMALL_LETTER_L = "\U000e006c" +TAG_DIGIT_SIX = "\U000e0036" +TAG_LATIN_CAPITAL_LETTER_N = "\U000e004e" +TAG_DIGIT_TWO = "\U000e0032" +TAG_LATIN_CAPITAL_LETTER_K = "\U000e004b" +TAG_DIGIT_ZERO = "\U000e0030" +DIGIT_EIGHT = "8\ufe0f" +TAG_LATIN_SMALL_LETTER_Y = "\U000e0079" From dadb4b4eb68a1af967611adbd08fdc2db599e06e Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 17 Oct 2020 17:07:32 +0200 Subject: [PATCH 0341/1185] Do not recalculate the md5 sum in case of chunk re-uploads --- pyrogram/methods/advanced/save_file.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/methods/advanced/save_file.py b/pyrogram/methods/advanced/save_file.py index e97279515b..a74c388c17 100644 --- a/pyrogram/methods/advanced/save_file.py +++ b/pyrogram/methods/advanced/save_file.py @@ -154,7 +154,7 @@ async def worker(session): chunk = fp.read(part_size) if not chunk: - if not is_big: + if not is_big and not is_missing_part: md5_sum = "".join([hex(i)[2:].zfill(2) for i in md5_sum.digest()]) break @@ -177,7 +177,7 @@ async def worker(session): if is_missing_part: return - if not is_big: + if not is_big and not is_missing_part: md5_sum.update(chunk) file_part += 1 From 00dd4bc403b2240e67e3c1711db364acaa7a644c Mon Sep 17 00:00:00 2001 From: GodSaveTheDoge <51802433+GodSaveTheDoge@users.noreply.github.com> Date: Sun, 18 Oct 2020 17:54:03 +0200 Subject: [PATCH 0342/1185] Make Message.new_chat_photo downloadable (#508) Fixes #364 --- pyrogram/methods/messages/download_media.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/methods/messages/download_media.py b/pyrogram/methods/messages/download_media.py index cf63c74caa..c6919809e1 100644 --- a/pyrogram/methods/messages/download_media.py +++ b/pyrogram/methods/messages/download_media.py @@ -134,7 +134,7 @@ def progress(current, total): app.download_media(message, progress=progress) """ error_message = "This message doesn't contain any downloadable media" - available_media = ("audio", "document", "photo", "sticker", "animation", "video", "voice", "video_note") + available_media = ("audio", "document", "photo", "sticker", "animation", "video", "voice", "video_note", "new_chat_photo") media_file_name = None file_size = None From d41f8a66f0e5d51b9e888e1bedea20e736be290b Mon Sep 17 00:00:00 2001 From: KILR <65610641+KILR007@users.noreply.github.com> Date: Sun, 18 Oct 2020 21:29:36 +0530 Subject: [PATCH 0343/1185] Fix small typo (#502) * Fix Typo * No extra new line * Update mime.types Co-authored-by: Dan <14043624+delivrance@users.noreply.github.com> --- pyrogram/mime.types | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/mime.types b/pyrogram/mime.types index 50ec065d74..bb8f6fdbbe 100644 --- a/pyrogram/mime.types +++ b/pyrogram/mime.types @@ -1,6 +1,6 @@ # This file maps Internet media types to unique file extension(s). # Although created for httpd, this file is used by many software systems -# and has been placed in the public domain for unlimited redisribution. +# and has been placed in the public domain for unlimited redistribution. # # The table below contains both registered and (common) unregistered types. # A type that has no unique extension can be ignored -- they are listed @@ -1855,4 +1855,4 @@ video/x-smv smv x-conference/x-cooltalk ice # Telegram animated stickers -application/x-tgsticker tgs \ No newline at end of file +application/x-tgsticker tgs From 87f20a1ac2667e6fb778d0387107dc2b4224cf62 Mon Sep 17 00:00:00 2001 From: Mahesh19 Date: Sun, 18 Oct 2020 21:30:17 +0530 Subject: [PATCH 0344/1185] Update examples for send_media_group.py (#481) InputMediaPhoto and InputMediaVideo are under pyrogram.types --- pyrogram/methods/messages/send_media_group.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/methods/messages/send_media_group.py b/pyrogram/methods/messages/send_media_group.py index e35984b868..fc13fb49ac 100644 --- a/pyrogram/methods/messages/send_media_group.py +++ b/pyrogram/methods/messages/send_media_group.py @@ -62,7 +62,7 @@ async def send_media_group( Example: .. code-block:: python - from pyrogram import InputMediaPhoto, InputMediaVideo + from pyrogram.types import InputMediaPhoto, InputMediaVideo app.send_media_group( "me", From 332468d271896e2c81b1a283b6a0f14fc1a666c1 Mon Sep 17 00:00:00 2001 From: "M. Smits" Date: Sun, 18 Oct 2020 18:37:41 +0200 Subject: [PATCH 0345/1185] Enhance filters.me to allow it intercept own anonymous messages (#501) * Check message.outgoing 'True' in me_filter. * After reinterpretation of the ticket, check if user or outgiong. Closes #499 --- pyrogram/filters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/filters.py b/pyrogram/filters.py index 63628a60ad..5c58743fad 100644 --- a/pyrogram/filters.py +++ b/pyrogram/filters.py @@ -163,7 +163,7 @@ async def all_filter(_, __, ___): # region me_filter async def me_filter(_, __, m: Message): - return bool(m.from_user and m.from_user.is_self) + return bool(m.from_user and m.from_user.is_self or m.outgoing) me = create(me_filter) From 31f9772917bc41ced7d5486842f6b41b6489a3b8 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 30 Oct 2020 19:50:29 +0100 Subject: [PATCH 0346/1185] Update API schema to Layer 120 --- compiler/api/source/main_api.tl | 111 +++++++++++++++++++++----------- 1 file changed, 73 insertions(+), 38 deletions(-) diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl index 9b50b802b7..56f18284d0 100644 --- a/compiler/api/source/main_api.tl +++ b/compiler/api/source/main_api.tl @@ -47,7 +47,7 @@ inputMediaPhotoExternal#e5bbfe1a flags:# url:string ttl_seconds:flags.0?int = In inputMediaDocumentExternal#fb52dc99 flags:# url:string ttl_seconds:flags.0?int = InputMedia; inputMediaGame#d33f43f3 id:InputGame = InputMedia; inputMediaInvoice#f4e096c3 flags:# title:string description:string photo:flags.0?InputWebDocument invoice:Invoice payload:bytes provider:string provider_data:DataJSON start_param:string = InputMedia; -inputMediaGeoLive#ce4e82fd flags:# stopped:flags.0?true geo_point:InputGeoPoint period:flags.1?int = InputMedia; +inputMediaGeoLive#971fa843 flags:# stopped:flags.0?true geo_point:InputGeoPoint heading:flags.2?int period:flags.1?int proximity_notification_radius:flags.3?int = InputMedia; inputMediaPoll#f94e5f1 flags:# poll:Poll correct_answers:flags.0?Vector solution:flags.1?string solution_entities:flags.1?Vector = InputMedia; inputMediaDice#e66fbf7b emoticon:string = InputMedia; @@ -56,7 +56,7 @@ inputChatUploadedPhoto#c642724e flags:# file:flags.0?InputFile video:flags.1?Inp inputChatPhoto#8953ad37 id:InputPhoto = InputChatPhoto; inputGeoPointEmpty#e4c123d6 = InputGeoPoint; -inputGeoPoint#f3b7acc9 lat:double long:double = InputGeoPoint; +inputGeoPoint#48222faf flags:# lat:double long:double accuracy_radius:flags.0?int = InputGeoPoint; inputPhotoEmpty#1cd7bf0d = InputPhoto; inputPhoto#3bb3b94a id:long access_hash:long file_reference:bytes = InputPhoto; @@ -106,7 +106,7 @@ channel#d31a961e flags:# creator:flags.0?true left:flags.2?true broadcast:flags. channelForbidden#289da732 flags:# broadcast:flags.5?true megagroup:flags.8?true id:int access_hash:long title:string until_date:flags.16?int = Chat; chatFull#1b7c9db3 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:int about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int = ChatFull; -channelFull#f0e6672a flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true id:int about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?int migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?int location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int = ChatFull; +channelFull#f0e6672a flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true id:int about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?int migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?int location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int = ChatFull; chatParticipant#c8d7493e user_id:int inviter_id:int date:int = ChatParticipant; chatParticipantCreator#da13538a user_id:int = ChatParticipant; @@ -119,8 +119,8 @@ chatPhotoEmpty#37c1011c = ChatPhoto; chatPhoto#d20b9f3c flags:# has_video:flags.0?true photo_small:FileLocation photo_big:FileLocation dc_id:int = ChatPhoto; messageEmpty#83e5de54 id:int = Message; -message#452c0e65 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true id:int from_id:flags.8?int to_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to_msg_id:flags.3?int date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector views:flags.10?int edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long restriction_reason:flags.22?Vector = Message; -messageService#9e19a1f6 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true legacy:flags.19?true id:int from_id:flags.8?int to_id:Peer reply_to_msg_id:flags.3?int date:int action:MessageAction = Message; +message#58ae39c9 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true id:int from_id:flags.8?Peer peer_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long restriction_reason:flags.22?Vector = Message; +messageService#286fa604 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true legacy:flags.19?true id:int from_id:flags.8?Peer peer_id:Peer reply_to:flags.3?MessageReplyHeader date:int action:MessageAction = Message; messageMediaEmpty#3ded6320 = MessageMedia; messageMediaPhoto#695150d7 flags:# photo:flags.0?Photo ttl_seconds:flags.2?int = MessageMedia; @@ -132,7 +132,7 @@ messageMediaWebPage#a32dd600 webpage:WebPage = MessageMedia; messageMediaVenue#2ec0533f geo:GeoPoint title:string address:string provider:string venue_id:string venue_type:string = MessageMedia; messageMediaGame#fdb19008 game:Game = MessageMedia; messageMediaInvoice#84551347 flags:# shipping_address_requested:flags.1?true test:flags.3?true title:string description:string photo:flags.0?WebDocument receipt_msg_id:flags.2?int currency:string total_amount:long start_param:string = MessageMedia; -messageMediaGeoLive#7c3c2609 geo:GeoPoint period:int = MessageMedia; +messageMediaGeoLive#b940c666 flags:# geo:GeoPoint heading:flags.0?int period:int proximity_notification_radius:flags.1?int = MessageMedia; messageMediaPoll#4bd6e798 poll:Poll results:PollResults = MessageMedia; messageMediaDice#3f7ee58b value:int emoticon:string = MessageMedia; @@ -159,6 +159,7 @@ messageActionBotAllowed#abe9affe domain:string = MessageAction; messageActionSecureValuesSentMe#1b287353 values:Vector credentials:SecureCredentialsEncrypted = MessageAction; messageActionSecureValuesSent#d95c6154 types:Vector = MessageAction; messageActionContactSignUp#f3f25f76 = MessageAction; +messageActionGeoProximityReached#98e0d697 from_id:Peer to_id:Peer distance:int = MessageAction; dialog#2c171f72 flags:# pinned:flags.2?true unread_mark:flags.3?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int = Dialog; dialogFolder#71bd134c flags:# pinned:flags.2?true folder:Folder peer:Peer top_message:int unread_muted_peers_count:int unread_unmuted_peers_count:int unread_muted_messages_count:int unread_unmuted_messages_count:int = Dialog; @@ -170,9 +171,10 @@ photoSizeEmpty#e17e23c type:string = PhotoSize; photoSize#77bfb61b type:string location:FileLocation w:int h:int size:int = PhotoSize; photoCachedSize#e9a734fa type:string location:FileLocation w:int h:int bytes:bytes = PhotoSize; photoStrippedSize#e0b0bc2e type:string bytes:bytes = PhotoSize; +photoSizeProgressive#5aa86a51 type:string location:FileLocation w:int h:int sizes:Vector = PhotoSize; geoPointEmpty#1117dd5f = GeoPoint; -geoPoint#296f104 long:double lat:double access_hash:long = GeoPoint; +geoPoint#b2a2f663 flags:# long:double lat:double access_hash:long accuracy_radius:flags.0?int = GeoPoint; auth.sentCode#5e002502 flags:# type:auth.SentCodeType phone_code_hash:string next_type:flags.1?auth.CodeType timeout:flags.2?int = auth.SentCode; @@ -209,8 +211,6 @@ contact#f911c994 user_id:int mutual:Bool = Contact; importedContact#d0028438 user_id:int client_id:long = ImportedContact; -contactBlocked#561bc879 user_id:int date:int = ContactBlocked; - contactStatus#d3680c61 user_id:int status:UserStatus = ContactStatus; contacts.contactsNotModified#b74ba9d2 = contacts.Contacts; @@ -218,16 +218,16 @@ contacts.contacts#eae87e42 contacts:Vector saved_count:int users:Vector contacts.importedContacts#77d01c3b imported:Vector popular_invites:Vector retry_contacts:Vector users:Vector = contacts.ImportedContacts; -contacts.blocked#1c138d15 blocked:Vector users:Vector = contacts.Blocked; -contacts.blockedSlice#900802a1 count:int blocked:Vector users:Vector = contacts.Blocked; +contacts.blocked#ade1591 blocked:Vector chats:Vector users:Vector = contacts.Blocked; +contacts.blockedSlice#e1664194 count:int blocked:Vector chats:Vector users:Vector = contacts.Blocked; messages.dialogs#15ba6c40 dialogs:Vector messages:Vector chats:Vector users:Vector = messages.Dialogs; messages.dialogsSlice#71e094f3 count:int dialogs:Vector messages:Vector chats:Vector users:Vector = messages.Dialogs; messages.dialogsNotModified#f0e3e596 count:int = messages.Dialogs; messages.messages#8c718e87 messages:Vector chats:Vector users:Vector = messages.Messages; -messages.messagesSlice#c8edce1e flags:# inexact:flags.1?true count:int next_rate:flags.0?int messages:Vector chats:Vector users:Vector = messages.Messages; -messages.channelMessages#99262e37 flags:# inexact:flags.1?true pts:int count:int messages:Vector chats:Vector users:Vector = messages.Messages; +messages.messagesSlice#3a54685e flags:# inexact:flags.1?true count:int next_rate:flags.0?int offset_id_offset:flags.2?int messages:Vector chats:Vector users:Vector = messages.Messages; +messages.channelMessages#64479808 flags:# inexact:flags.1?true pts:int count:int offset_id_offset:flags.2?int messages:Vector chats:Vector users:Vector = messages.Messages; messages.messagesNotModified#74535f21 count:int = messages.Messages; messages.chats#64ff9fd5 chats:Vector = messages.Chats; @@ -253,6 +253,7 @@ inputMessagesFilterRoundVideo#b549da53 = MessagesFilter; inputMessagesFilterMyMentions#c1f8e69a = MessagesFilter; inputMessagesFilterGeo#e7026d0d = MessagesFilter; inputMessagesFilterContacts#e062db83 = MessagesFilter; +inputMessagesFilterPinned#1bb00451 = MessagesFilter; updateNewMessage#1f2b0afd message:Message pts:int pts_count:int = Update; updateMessageID#4e90bfd6 id:int random_id:long = Update; @@ -270,7 +271,6 @@ updateEncryptedMessagesRead#38fe25b7 chat_id:int max_date:int date:int = Update; updateChatParticipantAdd#ea4b0e5c chat_id:int user_id:int inviter_id:int date:int version:int = Update; updateChatParticipantDelete#6e5f8c22 chat_id:int user_id:int version:int = Update; updateDcOptions#8e5e9873 dc_options:Vector = Update; -updateUserBlocked#80ece81a user_id:int blocked:Bool = Update; updateNotifySettings#bec268ef peer:NotifyPeer notify_settings:PeerNotifySettings = Update; updateServiceNotification#ebe46819 flags:# popup:flags.0?true inbox_date:flags.1?int type:string message:string media:MessageMedia entities:Vector = Update; updatePrivacy#ee3b272a key:PrivacyKey rules:Vector = Update; @@ -293,7 +293,6 @@ updateSavedGifs#9375341e = Update; updateBotInlineQuery#54826690 flags:# query_id:long user_id:int query:string geo:flags.0?GeoPoint offset:string = Update; updateBotInlineSend#e48f964 flags:# user_id:int query:string geo:flags.0?GeoPoint id:string msg_id:flags.1?InputBotInlineMessageID = Update; updateEditChannelMessage#1b3f4df7 message:Message pts:int pts_count:int = Update; -updateChannelPinnedMessage#98592475 channel_id:int id:int = Update; updateBotCallbackQuery#e73547e1 flags:# query_id:long user_id:int peer:Peer msg_id:int chat_instance:long data:flags.0?bytes game_short_name:flags.1?string = Update; updateEditMessage#e40370a3 message:Message pts:int pts_count:int = Update; updateInlineBotCallbackQuery#f9d27a5a flags:# query_id:long user_id:int msg_id:InputBotInlineMessageID chat_instance:long data:flags.0?bytes game_short_name:flags.1?string = Update; @@ -318,8 +317,6 @@ updateChannelReadMessagesContents#89893b45 channel_id:int messages:Vector = updateContactsReset#7084a7be = Update; updateChannelAvailableMessages#70db6837 channel_id:int available_min_id:int = Update; updateDialogUnreadMark#e16459c3 flags:# unread:flags.0?true peer:DialogPeer = Update; -updateUserPinnedMessage#4c43da18 user_id:int id:int = Update; -updateChatPinnedMessage#e10db349 chat_id:int id:int version:int = Update; updateMessagePoll#aca1657b flags:# poll_id:long poll:flags.0?Poll results:PollResults = Update; updateChatDefaultBannedRights#54c01850 peer:Peer default_banned_rights:ChatBannedRights version:int = Update; updateFolderPeers#19360dc0 folder_peers:Vector pts:int pts_count:int = Update; @@ -336,6 +333,13 @@ updateDialogFilterOrder#a5d72105 order:Vector = Update; updateDialogFilters#3504914f = Update; updatePhoneCallSignalingData#2661bf09 phone_call_id:long data:bytes = Update; updateChannelParticipant#65d2b464 flags:# channel_id:int date:int user_id:int prev_participant:flags.0?ChannelParticipant new_participant:flags.1?ChannelParticipant qts:int = Update; +updateChannelMessageForwards#6e8a84df channel_id:int id:int forwards:int = Update; +updateReadChannelDiscussionInbox#1cc7de54 flags:# channel_id:int top_msg_id:int read_max_id:int broadcast_id:flags.0?int broadcast_post:flags.0?int = Update; +updateReadChannelDiscussionOutbox#4638a26c channel_id:int top_msg_id:int read_max_id:int = Update; +updatePeerBlocked#246a4b22 peer_id:Peer blocked:Bool = Update; +updateChannelUserTyping#ff2abe9f flags:# channel_id:int top_msg_id:flags.0?int user_id:int action:SendMessageAction = Update; +updatePinnedMessages#ed85eab5 flags:# pinned:flags.0?true peer:Peer messages:Vector pts:int pts_count:int = Update; +updatePinnedChannelMessages#8588878b flags:# pinned:flags.0?true channel_id:int messages:Vector pts:int pts_count:int = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -345,8 +349,8 @@ updates.differenceSlice#a8fb1981 new_messages:Vector new_encrypted_mess updates.differenceTooLong#4afe8f6d pts:int = updates.Difference; updatesTooLong#e317af7e = Updates; -updateShortMessage#914fbf11 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true id:int user_id:int message:string pts:int pts_count:int date:int fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to_msg_id:flags.3?int entities:flags.7?Vector = Updates; -updateShortChatMessage#16812688 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true id:int from_id:int chat_id:int message:string pts:int pts_count:int date:int fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to_msg_id:flags.3?int entities:flags.7?Vector = Updates; +updateShortMessage#2296d2c8 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true id:int user_id:int message:string pts:int pts_count:int date:int fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to:flags.3?MessageReplyHeader entities:flags.7?Vector = Updates; +updateShortChatMessage#402d5dbb flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true id:int from_id:int chat_id:int message:string pts:int pts_count:int date:int fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to:flags.3?MessageReplyHeader entities:flags.7?Vector = Updates; updateShort#78d4dec1 update:Update date:int = Updates; updatesCombined#725b04c3 updates:Vector users:Vector chats:Vector date:int seq_start:int seq:int = Updates; updates#74ae4240 updates:Vector users:Vector chats:Vector date:int seq:int = Updates; @@ -525,7 +529,7 @@ botInfo#98e81d3a user_id:int description:string commands:Vector = Bo keyboardButton#a2fa4880 text:string = KeyboardButton; keyboardButtonUrl#258aff05 text:string url:string = KeyboardButton; -keyboardButtonCallback#683a5e46 text:string data:bytes = KeyboardButton; +keyboardButtonCallback#35bbdb6b flags:# requires_password:flags.0?true text:string data:bytes = KeyboardButton; keyboardButtonRequestPhone#b16a6c29 text:string = KeyboardButton; keyboardButtonRequestGeoLocation#fc796b3f text:string = KeyboardButton; keyboardButtonSwitchInline#568a748 flags:# same_peer:flags.0?true text:string query:string = KeyboardButton; @@ -579,9 +583,10 @@ channelMessagesFilter#cd77d957 flags:# exclude_new_messages:flags.1?true ranges: channelParticipant#15ebac1d user_id:int date:int = ChannelParticipant; channelParticipantSelf#a3289a6d user_id:int inviter_id:int date:int = ChannelParticipant; -channelParticipantCreator#808d15a4 flags:# user_id:int rank:flags.0?string = ChannelParticipant; +channelParticipantCreator#447dca4b flags:# user_id:int admin_rights:ChatAdminRights rank:flags.0?string = ChannelParticipant; channelParticipantAdmin#ccbebbaf flags:# can_edit:flags.0?true self:flags.1?true user_id:int inviter_id:flags.1?int promoted_by:int date:int admin_rights:ChatAdminRights rank:flags.2?string = ChannelParticipant; channelParticipantBanned#1c0facaf flags:# left:flags.0?true user_id:int kicked_by:int date:int banned_rights:ChatBannedRights = ChannelParticipant; +channelParticipantLeft#c3c6796b user_id:int = ChannelParticipant; channelParticipantsRecent#de3f3c79 = ChannelParticipantsFilter; channelParticipantsAdmins#b4608969 = ChannelParticipantsFilter; @@ -590,6 +595,7 @@ channelParticipantsBots#b0d1865b = ChannelParticipantsFilter; channelParticipantsBanned#1427a5e1 q:string = ChannelParticipantsFilter; channelParticipantsSearch#656ac4b q:string = ChannelParticipantsFilter; channelParticipantsContacts#bb6ae88d q:string = ChannelParticipantsFilter; +channelParticipantsMentions#e04b5ceb flags:# q:flags.0?string top_msg_id:flags.1?int = ChannelParticipantsFilter; channels.channelParticipants#f56ee2a8 count:int participants:Vector users:Vector = channels.ChannelParticipants; channels.channelParticipantsNotModified#f0173fe9 = channels.ChannelParticipants; @@ -603,7 +609,7 @@ messages.savedGifs#2e0709a5 hash:int gifs:Vector = messages.SavedGifs; inputBotInlineMessageMediaAuto#3380c786 flags:# message:string entities:flags.1?Vector reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; inputBotInlineMessageText#3dcd7a87 flags:# no_webpage:flags.0?true message:string entities:flags.1?Vector reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; -inputBotInlineMessageMediaGeo#c1b15d65 flags:# geo_point:InputGeoPoint period:int reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; +inputBotInlineMessageMediaGeo#96929a85 flags:# geo_point:InputGeoPoint heading:flags.0?int period:flags.1?int proximity_notification_radius:flags.3?int reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; inputBotInlineMessageMediaVenue#417bbf11 flags:# geo_point:InputGeoPoint title:string address:string provider:string venue_id:string venue_type:string reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; inputBotInlineMessageMediaContact#a6edbffd flags:# phone_number:string first_name:string last_name:string vcard:string reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; inputBotInlineMessageGame#4b425864 flags:# reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; @@ -615,7 +621,7 @@ inputBotInlineResultGame#4fa417f2 id:string short_name:string send_message:Input botInlineMessageMediaAuto#764cf810 flags:# message:string entities:flags.1?Vector reply_markup:flags.2?ReplyMarkup = BotInlineMessage; botInlineMessageText#8c7f65e2 flags:# no_webpage:flags.0?true message:string entities:flags.1?Vector reply_markup:flags.2?ReplyMarkup = BotInlineMessage; -botInlineMessageMediaGeo#b722de65 flags:# geo:GeoPoint period:int reply_markup:flags.2?ReplyMarkup = BotInlineMessage; +botInlineMessageMediaGeo#51846fd flags:# geo:GeoPoint heading:flags.0?int period:flags.1?int proximity_notification_radius:flags.3?int reply_markup:flags.2?ReplyMarkup = BotInlineMessage; botInlineMessageMediaVenue#8a86659c flags:# geo:GeoPoint title:string address:string provider:string venue_id:string venue_type:string reply_markup:flags.2?ReplyMarkup = BotInlineMessage; botInlineMessageMediaContact#18d1cdc2 flags:# phone_number:string first_name:string last_name:string vcard:string reply_markup:flags.2?ReplyMarkup = BotInlineMessage; @@ -626,7 +632,7 @@ messages.botResults#947ca848 flags:# gallery:flags.0?true query_id:long next_off exportedMessageLink#5dab1af4 link:string html:string = ExportedMessageLink; -messageFwdHeader#353a686b flags:# from_id:flags.0?int from_name:flags.5?string date:int channel_id:flags.1?int channel_post:flags.2?int post_author:flags.3?string saved_from_peer:flags.4?Peer saved_from_msg_id:flags.4?int psa_type:flags.6?string = MessageFwdHeader; +messageFwdHeader#5f777dce flags:# from_id:flags.0?Peer from_name:flags.5?string date:int channel_post:flags.2?int post_author:flags.3?string saved_from_peer:flags.4?Peer saved_from_msg_id:flags.4?int psa_type:flags.6?string = MessageFwdHeader; auth.codeTypeSms#72a3158c = auth.CodeType; auth.codeTypeCall#741cd3e3 = auth.CodeType; @@ -1007,7 +1013,7 @@ chatOnlines#f041e250 onlines:int = ChatOnlines; statsURL#47a971e0 url:string = StatsURL; -chatAdminRights#5fb224d5 flags:# change_info:flags.0?true post_messages:flags.1?true edit_messages:flags.2?true delete_messages:flags.3?true ban_users:flags.4?true invite_users:flags.5?true pin_messages:flags.7?true add_admins:flags.9?true = ChatAdminRights; +chatAdminRights#5fb224d5 flags:# change_info:flags.0?true post_messages:flags.1?true edit_messages:flags.2?true delete_messages:flags.3?true ban_users:flags.4?true invite_users:flags.5?true pin_messages:flags.7?true add_admins:flags.9?true anonymous:flags.10?true = ChatAdminRights; chatBannedRights#9f120418 flags:# view_messages:flags.0?true send_messages:flags.1?true send_media:flags.2?true send_stickers:flags.3?true send_gifs:flags.4?true send_games:flags.5?true send_inline:flags.6?true embed_links:flags.7?true send_polls:flags.8?true change_info:flags.10?true invite_users:flags.15?true pin_messages:flags.17?true until_date:int = ChatBannedRights; @@ -1128,6 +1134,27 @@ stats.megagroupStats#ef7ff916 period:StatsDateRangeDays members:StatsAbsValueAnd globalPrivacySettings#bea2f424 flags:# archive_and_mute_new_noncontact_peers:flags.0?Bool = GlobalPrivacySettings; +help.countryCode#4203c5ef flags:# country_code:string prefixes:flags.0?Vector patterns:flags.1?Vector = help.CountryCode; + +help.country#c3878e23 flags:# hidden:flags.0?true iso2:string default_name:string name:flags.1?string country_codes:Vector = help.Country; + +help.countriesListNotModified#93cc1f32 = help.CountriesList; +help.countriesList#87d0759e countries:Vector hash:int = help.CountriesList; + +messageViews#455b853d flags:# views:flags.0?int forwards:flags.1?int replies:flags.2?MessageReplies = MessageViews; + +messages.messageViews#b6c4f543 views:Vector chats:Vector users:Vector = messages.MessageViews; + +messages.discussionMessage#f5dd8f9d flags:# messages:Vector max_id:flags.0?int read_inbox_max_id:flags.1?int read_outbox_max_id:flags.2?int chats:Vector users:Vector = messages.DiscussionMessage; + +messageReplyHeader#a6d57763 flags:# reply_to_msg_id:int reply_to_peer_id:flags.0?Peer reply_to_top_id:flags.1?int = MessageReplyHeader; + +messageReplies#4128faac flags:# comments:flags.0?true replies:int replies_pts:int recent_repliers:flags.1?Vector channel_id:flags.0?int max_id:flags.2?int read_max_id:flags.3?int = MessageReplies; + +peerBlocked#e8fd8014 peer_id:Peer date:int = PeerBlocked; + +stats.messageStats#8999f295 views_graph:StatsGraph = stats.MessageStats; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -1235,8 +1262,8 @@ contacts.getContacts#c023849f hash:int = contacts.Contacts; contacts.importContacts#2c800be5 contacts:Vector = contacts.ImportedContacts; contacts.deleteContacts#96a0e00 id:Vector = Updates; contacts.deleteByPhones#1013fd9e phones:Vector = Bool; -contacts.block#332b49fc id:InputUser = Bool; -contacts.unblock#e54100bd id:InputUser = Bool; +contacts.block#68cc1411 id:InputPeer = Bool; +contacts.unblock#bea65d50 id:InputPeer = Bool; contacts.getBlocked#f57c350f offset:int limit:int = contacts.Blocked; contacts.search#11f812d8 q:string limit:int = contacts.Found; contacts.resolveUsername#f93ccba3 username:string = contacts.ResolvedPeer; @@ -1248,19 +1275,20 @@ contacts.toggleTopPeers#8514bdda enabled:Bool = Bool; contacts.addContact#e8f463d0 flags:# add_phone_privacy_exception:flags.0?true id:InputUser first_name:string last_name:string phone:string = Updates; contacts.acceptContact#f831a20f id:InputUser = Updates; contacts.getLocated#d348bc44 flags:# background:flags.1?true geo_point:InputGeoPoint self_expires:flags.0?int = Updates; +contacts.blockFromReplies#29a8962c flags:# delete_message:flags.0?true delete_history:flags.1?true report_spam:flags.2?true msg_id:int = Updates; messages.getMessages#63c66506 id:Vector = messages.Messages; messages.getDialogs#a0ee3b73 flags:# exclude_pinned:flags.0?true folder_id:flags.1?int offset_date:int offset_id:int offset_peer:InputPeer limit:int hash:int = messages.Dialogs; messages.getHistory#dcbb8260 peer:InputPeer offset_id:int offset_date:int add_offset:int limit:int max_id:int min_id:int hash:int = messages.Messages; -messages.search#8614ef68 flags:# peer:InputPeer q:string from_id:flags.0?InputUser filter:MessagesFilter min_date:int max_date:int offset_id:int add_offset:int limit:int max_id:int min_id:int hash:int = messages.Messages; +messages.search#c352eec flags:# peer:InputPeer q:string from_id:flags.0?InputPeer top_msg_id:flags.1?int filter:MessagesFilter min_date:int max_date:int offset_id:int add_offset:int limit:int max_id:int min_id:int hash:int = messages.Messages; messages.readHistory#e306d3a peer:InputPeer max_id:int = messages.AffectedMessages; messages.deleteHistory#1c015b09 flags:# just_clear:flags.0?true revoke:flags.1?true peer:InputPeer max_id:int = messages.AffectedHistory; messages.deleteMessages#e58e95d2 flags:# revoke:flags.0?true id:Vector = messages.AffectedMessages; messages.receivedMessages#5a954c0 max_id:int = Vector; -messages.setTyping#a3825e50 peer:InputPeer action:SendMessageAction = Bool; +messages.setTyping#58943ee2 flags:# peer:InputPeer top_msg_id:flags.0?int action:SendMessageAction = Bool; messages.sendMessage#520c3870 flags:# no_webpage:flags.1?true silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int = Updates; messages.sendMedia#3491eba9 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int media:InputMedia message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int = Updates; -messages.forwardMessages#d9fee60e flags:# silent:flags.5?true background:flags.6?true with_my_score:flags.8?true grouped:flags.9?true from_peer:InputPeer id:Vector random_id:Vector to_peer:InputPeer schedule_date:flags.10?int = Updates; +messages.forwardMessages#d9fee60e flags:# silent:flags.5?true background:flags.6?true with_my_score:flags.8?true from_peer:InputPeer id:Vector random_id:Vector to_peer:InputPeer schedule_date:flags.10?int = Updates; messages.reportSpam#cf1592db peer:InputPeer = Bool; messages.getPeerSettings#3672e09c peer:InputPeer = PeerSettings; messages.report#bd82b658 peer:InputPeer id:Vector reason:ReportReason = Bool; @@ -1277,8 +1305,8 @@ messages.acceptEncryption#3dbc0415 peer:InputEncryptedChat g_b:bytes key_fingerp messages.discardEncryption#edd923c5 chat_id:int = Bool; messages.setEncryptedTyping#791451ed peer:InputEncryptedChat typing:Bool = Bool; messages.readEncryptedHistory#7f4b690a peer:InputEncryptedChat max_date:int = Bool; -messages.sendEncrypted#a9776773 peer:InputEncryptedChat random_id:long data:bytes = messages.SentEncryptedMessage; -messages.sendEncryptedFile#9a901b66 peer:InputEncryptedChat random_id:long data:bytes file:InputEncryptedFile = messages.SentEncryptedMessage; +messages.sendEncrypted#44fa7a15 flags:# silent:flags.0?true peer:InputEncryptedChat random_id:long data:bytes = messages.SentEncryptedMessage; +messages.sendEncryptedFile#5559481d flags:# silent:flags.0?true peer:InputEncryptedChat random_id:long data:bytes file:InputEncryptedFile = messages.SentEncryptedMessage; messages.sendEncryptedService#32d439a4 peer:InputEncryptedChat random_id:long data:bytes = messages.SentEncryptedMessage; messages.receivedQueue#55a5bb66 max_qts:int = Vector; messages.reportEncryptedSpam#4b0c8c0f peer:InputEncryptedChat = Bool; @@ -1293,10 +1321,10 @@ messages.getStickerSet#2619a90e stickerset:InputStickerSet = messages.StickerSet messages.installStickerSet#c78fe460 stickerset:InputStickerSet archived:Bool = messages.StickerSetInstallResult; messages.uninstallStickerSet#f96e55de stickerset:InputStickerSet = Bool; messages.startBot#e6df7378 bot:InputUser peer:InputPeer random_id:long start_param:string = Updates; -messages.getMessagesViews#c4c8a55d peer:InputPeer id:Vector increment:Bool = Vector; +messages.getMessagesViews#5784d3e1 peer:InputPeer id:Vector increment:Bool = messages.MessageViews; messages.editChatAdmin#a9e69f2e chat_id:int user_id:InputUser is_admin:Bool = Bool; messages.migrateChat#15a3b8e3 chat_id:int = Updates; -messages.searchGlobal#bf7225a4 flags:# folder_id:flags.0?int q:string offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; +messages.searchGlobal#4bc6589a flags:# folder_id:flags.0?int q:string filter:MessagesFilter min_date:int max_date:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; messages.reorderStickerSets#78337739 flags:# masks:flags.0?true order:Vector = Bool; messages.getDocumentByHash#338e2464 sha256:bytes size:int mime_type:string = Document; messages.getSavedGifs#83bf3d52 hash:int = messages.SavedGifs; @@ -1307,7 +1335,7 @@ messages.sendInlineBotResult#220815b0 flags:# silent:flags.5?true background:fla messages.getMessageEditData#fda68d36 peer:InputPeer id:int = messages.MessageEditData; messages.editMessage#48f71778 flags:# no_webpage:flags.1?true peer:InputPeer id:int message:flags.11?string media:flags.14?InputMedia reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.15?int = Updates; messages.editInlineBotMessage#83557dba flags:# no_webpage:flags.1?true id:InputBotInlineMessageID message:flags.11?string media:flags.14?InputMedia reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector = Bool; -messages.getBotCallbackAnswer#810a9fec flags:# game:flags.1?true peer:InputPeer msg_id:int data:flags.0?bytes = messages.BotCallbackAnswer; +messages.getBotCallbackAnswer#9342ca07 flags:# game:flags.1?true peer:InputPeer msg_id:int data:flags.0?bytes password:flags.2?InputCheckPasswordSRP = messages.BotCallbackAnswer; messages.setBotCallbackAnswer#d58f130a flags:# alert:flags.1?true query_id:long message:flags.0?string url:flags.2?string cache_time:int = Bool; messages.getPeerDialogs#e470bcfd peers:Vector = messages.PeerDialogs; messages.saveDraft#bc39e14b flags:# no_webpage:flags.1?true reply_to_msg_id:flags.0?int peer:InputPeer message:string entities:flags.3?Vector = Bool; @@ -1346,7 +1374,7 @@ messages.getSplitRanges#1cff7e08 = Vector; messages.markDialogUnread#c286d98f flags:# unread:flags.0?true peer:InputDialogPeer = Bool; messages.getDialogUnreadMarks#22e24e22 = Vector; messages.clearAllDrafts#7e58ee9c = Bool; -messages.updatePinnedMessage#d2aaf7ec flags:# silent:flags.0?true peer:InputPeer id:int = Updates; +messages.updatePinnedMessage#d2aaf7ec flags:# silent:flags.0?true unpin:flags.1?true pm_oneside:flags.2?true peer:InputPeer id:int = Updates; messages.sendVote#10ea6184 peer:InputPeer msg_id:int options:Vector = Updates; messages.getPollResults#73bb643b peer:InputPeer msg_id:int = Updates; messages.getOnlines#6e2be050 peer:InputPeer = ChatOnlines; @@ -1372,6 +1400,10 @@ messages.getSuggestedDialogFilters#a29cd42c = Vector; messages.updateDialogFilter#1ad4a04a flags:# id:int filter:flags.0?DialogFilter = Bool; messages.updateDialogFiltersOrder#c563c1e4 order:Vector = Bool; messages.getOldFeaturedStickers#5fe7025b offset:int limit:int hash:int = messages.FeaturedStickers; +messages.getReplies#24b581ba peer:InputPeer msg_id:int offset_id:int offset_date:int add_offset:int limit:int max_id:int min_id:int hash:int = messages.Messages; +messages.getDiscussionMessage#446972fd peer:InputPeer msg_id:int = messages.DiscussionMessage; +messages.readDiscussion#f731a9f4 peer:InputPeer msg_id:int read_max_id:int = Bool; +messages.unpinAllMessages#f025bc8b peer:InputPeer = messages.AffectedHistory; updates.getState#edd4882a = updates.State; updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference; @@ -1412,6 +1444,7 @@ help.editUserInfo#66b91b70 user_id:InputUser message:string entities:Vector = messages.AffectedMessages; @@ -1432,7 +1465,7 @@ channels.joinChannel#24b524c5 channel:InputChannel = Updates; channels.leaveChannel#f836aa95 channel:InputChannel = Updates; channels.inviteToChannel#199f3a6c channel:InputChannel users:Vector = Updates; channels.deleteChannel#c0111fe3 channel:InputChannel = Updates; -channels.exportMessageLink#ceb77163 channel:InputChannel id:int grouped:Bool = ExportedMessageLink; +channels.exportMessageLink#e63fadeb flags:# grouped:flags.0?true thread:flags.1?true channel:InputChannel id:int = ExportedMessageLink; channels.toggleSignatures#1f69b606 channel:InputChannel enabled:Bool = Updates; channels.getAdminedPublicChannels#f8b036af flags:# by_location:flags.0?true check_limit:flags.1?true = messages.Chats; channels.editBanned#72796912 channel:InputChannel user_id:InputUser banned_rights:ChatBannedRights = Updates; @@ -1489,5 +1522,7 @@ folders.deleteFolder#1c295881 folder_id:int = Updates; stats.getBroadcastStats#ab42441a flags:# dark:flags.0?true channel:InputChannel = stats.BroadcastStats; stats.loadAsyncGraph#621d5fa0 flags:# token:string x:flags.0?long = StatsGraph; stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel = stats.MegagroupStats; +stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; +stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats; -// LAYER 117 \ No newline at end of file +// LAYER 120 \ No newline at end of file From 832f1f6d53c3196f3994e8376a8a9a19d74ec188 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 31 Oct 2020 16:47:32 +0100 Subject: [PATCH 0347/1185] Update pin/unpin_chat_message and Message.pin/unpin --- pyrogram/methods/chats/pin_chat_message.py | 12 +++++-- pyrogram/methods/chats/unpin_chat_message.py | 11 ++++-- pyrogram/types/messages_and_media/message.py | 37 ++++++++++++++++++-- 3 files changed, 52 insertions(+), 8 deletions(-) diff --git a/pyrogram/methods/chats/pin_chat_message.py b/pyrogram/methods/chats/pin_chat_message.py index a357aab9ec..39ddd75e1d 100644 --- a/pyrogram/methods/chats/pin_chat_message.py +++ b/pyrogram/methods/chats/pin_chat_message.py @@ -27,7 +27,8 @@ async def pin_chat_message( self, chat_id: Union[int, str], message_id: int, - disable_notification: bool = None + disable_notification: bool = False, + both_sides: bool = False, ) -> bool: """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 @@ -40,10 +41,14 @@ async def pin_chat_message( message_id (``int``): Identifier of a message to pin. - disable_notification (``bool``): + disable_notification (``bool``, *optional*): Pass True, if it is not necessary to send a notification to all chat members about the new pinned message. Notifications are always disabled in channels. + both_sides (``bool``, *optional*): + Pass True to pin the message for both sides (you and recipient). + Applicable to private chats only. Defaults to False. + Returns: ``bool``: True on success. @@ -60,7 +65,8 @@ async def pin_chat_message( raw.functions.messages.UpdatePinnedMessage( peer=await self.resolve_peer(chat_id), id=message_id, - silent=disable_notification or None + silent=disable_notification or None, + pm_oneside=not both_sides or None ) ) diff --git a/pyrogram/methods/chats/unpin_chat_message.py b/pyrogram/methods/chats/unpin_chat_message.py index 3ee7814dbd..52e533eddb 100644 --- a/pyrogram/methods/chats/unpin_chat_message.py +++ b/pyrogram/methods/chats/unpin_chat_message.py @@ -25,7 +25,8 @@ class UnpinChatMessage(Scaffold): async def unpin_chat_message( self, - chat_id: Union[int, str] + chat_id: Union[int, str], + message_id: int ) -> bool: """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 @@ -35,18 +36,22 @@ async def unpin_chat_message( chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. + message_id (``int``): + Identifier of a message to unpin. + Returns: ``bool``: True on success. Example: .. code-block:: python - app.unpin_chat_message(chat_id) + app.unpin_chat_message(chat_id, message_id) """ await self.send( raw.functions.messages.UpdatePinnedMessage( peer=await self.resolve_peer(chat_id), - id=0 + id=message_id, + unpin=True ) ) diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index df6a5a9551..c964fc3d9b 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -3117,7 +3117,7 @@ async def vote( options=option ) - async def pin(self, disable_notification: bool = None) -> bool: + async def pin(self, disable_notification: bool = False, both_sides: bool = False) -> bool: """Bound method *pin* of :obj:`~pyrogram.types.Message`. Use as a shortcut for: @@ -3139,6 +3139,10 @@ async def pin(self, disable_notification: bool = None) -> bool: Pass True, if it is not necessary to send a notification to all chat members about the new pinned message. Notifications are always disabled in channels. + both_sides (``bool``, *optional*): + Pass True to pin the message for both sides (you and recipient). + Applicable to private chats only. Defaults to False. + Returns: True on success. @@ -3148,5 +3152,34 @@ async def pin(self, disable_notification: bool = None) -> bool: return await self._client.pin_chat_message( chat_id=self.chat.id, message_id=self.message_id, - disable_notification=disable_notification + disable_notification=disable_notification, + both_sides=both_sides + ) + + async def unpin(self) -> bool: + """Bound method *unpin* of :obj:`~pyrogram.types.Message`. + + Use as a shortcut for: + + .. code-block:: python + + client.unpin_chat_message( + chat_id=message.chat.id, + message_id=message_id + ) + + Example: + .. code-block:: python + + message.unpin() + + Returns: + True on success. + + Raises: + RPCError: In case of a Telegram RPC error. + """ + return await self._client.pin_chat_message( + chat_id=self.chat.id, + message_id=self.message_id ) From fe16dc8043796b0ec6eb7917d6865042fb956581 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 31 Oct 2020 16:47:55 +0100 Subject: [PATCH 0348/1185] Allow sending audio playlists --- pyrogram/methods/messages/send_media_group.py | 49 ++++++++++++++++++- 1 file changed, 48 insertions(+), 1 deletion(-) diff --git a/pyrogram/methods/messages/send_media_group.py b/pyrogram/methods/messages/send_media_group.py index e35984b868..677679f795 100644 --- a/pyrogram/methods/messages/send_media_group.py +++ b/pyrogram/methods/messages/send_media_group.py @@ -34,7 +34,7 @@ class SendMediaGroup(Scaffold): async def send_media_group( self, chat_id: Union[int, str], - media: List[Union["types.InputMediaPhoto", "types.InputMediaVideo"]], + media: List[Union["types.InputMediaPhoto", "types.InputMediaVideo", "types.InputMediaAudio"]], disable_notification: bool = None, reply_to_message_id: int = None ) -> List["types.Message"]: @@ -152,6 +152,53 @@ async def send_media_group( ) ) + media = raw.types.InputMediaDocument( + id=raw.types.InputDocument( + id=media.document.id, + access_hash=media.document.access_hash, + file_reference=media.document.file_reference + ) + ) + else: + media = utils.get_input_media_from_file_id(i.media, i.file_ref, 4) + elif isinstance(i, types.InputMediaAudio): + if os.path.isfile(i.media): + media = await self.send( + raw.functions.messages.UploadMedia( + peer=await self.resolve_peer(chat_id), + media=raw.types.InputMediaUploadedDocument( + mime_type=self.guess_mime_type(i.media) or "audio/mpeg", + file=await self.save_file(i.media), + thumb=await self.save_file(i.thumb), + attributes=[ + raw.types.DocumentAttributeAudio( + duration=i.duration, + performer=i.performer, + title=i.title + ), + raw.types.DocumentAttributeFilename(file_name=os.path.basename(i.media)) + ] + ) + ) + ) + + media = raw.types.InputMediaDocument( + id=raw.types.InputDocument( + id=media.document.id, + access_hash=media.document.access_hash, + file_reference=media.document.file_reference + ) + ) + elif re.match("^https?://", i.media): + media = await self.send( + raw.functions.messages.UploadMedia( + peer=await self.resolve_peer(chat_id), + media=raw.types.InputMediaDocumentExternal( + url=i.media + ) + ) + ) + media = raw.types.InputMediaDocument( id=raw.types.InputDocument( id=media.document.id, From 028e6ed04a039c338734e305b9e16ae7858b3785 Mon Sep 17 00:00:00 2001 From: GodSaveTheDoge <51802433+GodSaveTheDoge@users.noreply.github.com> Date: Sat, 31 Oct 2020 17:06:29 +0100 Subject: [PATCH 0349/1185] Make objects pickable by removing the _client attribute (#526) --- pyrogram/types/object.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pyrogram/types/object.py b/pyrogram/types/object.py index a447a0edcf..c1d9248cc5 100644 --- a/pyrogram/types/object.py +++ b/pyrogram/types/object.py @@ -95,3 +95,8 @@ def __getitem__(self, item): def __setitem__(self, key, value): setattr(self, key, value) + + def __getstate__(self): + new_dict = self.__dict__.copy() + new_dict.pop("_client", None) + return new_dict From 62999772c2afde75c0fd0916e2ade951552dd6bd Mon Sep 17 00:00:00 2001 From: NoamDev <37066741+NoamDev@users.noreply.github.com> Date: Sat, 31 Oct 2020 18:08:29 +0200 Subject: [PATCH 0350/1185] Fix wrong comparison with a non-raw type (#525) chatex.send now returns raw.types.Chat instead of types.Chat --- pyrogram/methods/chats/get_chat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/methods/chats/get_chat.py b/pyrogram/methods/chats/get_chat.py index 4e9a5428fa..cd8f5c9d98 100644 --- a/pyrogram/methods/chats/get_chat.py +++ b/pyrogram/methods/chats/get_chat.py @@ -67,7 +67,7 @@ async def get_chat( await self.fetch_peers([r.chat]) - if isinstance(r.chat, types.Chat): + if isinstance(r.chat, raw.types.Chat): chat_id = -r.chat.id if isinstance(r.chat, raw.types.Channel): From 61df1957a09342ab0864ad556314925f26390682 Mon Sep 17 00:00:00 2001 From: Victor Ajibade <43748536+ViaxCo@users.noreply.github.com> Date: Sat, 31 Oct 2020 17:09:34 +0100 Subject: [PATCH 0351/1185] Spelling correction in docs (#524) A spelling correction was made under the "Using add_handler()" section. --- docs/source/start/updates.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/start/updates.rst b/docs/source/start/updates.rst index a13d7fd771..27dd0316b0 100644 --- a/docs/source/start/updates.rst +++ b/docs/source/start/updates.rst @@ -72,7 +72,7 @@ Using add_handler() ^^^^^^^^^^^^^^^^^^^ The :meth:`~pyrogram.Client.add_handler` method takes any handler instance that wraps around your defined callback -function and registers it in your Client. It us useful in case you want to programmatically add handlers (or in case, +function and registers it in your Client. It is useful in case you want to programmatically add handlers (or in case, for some reason, you don't like to use decorators). .. code-block:: python From 740bcd145a215a1b1640844702b3d29f84c94268 Mon Sep 17 00:00:00 2001 From: Shrimadhav U K Date: Sat, 31 Oct 2020 21:44:32 +0530 Subject: [PATCH 0352/1185] Update creator ChatMember with admin permissions (#523) --- pyrogram/types/user_and_chats/chat_member.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/pyrogram/types/user_and_chats/chat_member.py b/pyrogram/types/user_and_chats/chat_member.py index 69ab4955f6..749e70659f 100644 --- a/pyrogram/types/user_and_chats/chat_member.py +++ b/pyrogram/types/user_and_chats/chat_member.py @@ -217,6 +217,26 @@ def _parse(client, member, users) -> "ChatMember": status="creator", client=client ) + + if isinstance(member, raw.types.ChannelParticipantCreator): + permissions = member.admin_rights + return ChatMember( + user=user, + status="creator", + title=getattr(member, "rank", None), + joined_date=member.date, + invited_by=invited_by, + can_be_edited=member.can_edit, + can_change_info=permissions.change_info, + can_post_messages=permissions.post_messages, + can_edit_messages=permissions.edit_messages, + can_delete_messages=permissions.delete_messages, + can_restrict_members=permissions.ban_users, + can_invite_users=permissions.invite_users, + can_pin_messages=permissions.pin_messages, + can_promote_members=permissions.add_admins, + client=client + ) if isinstance(member, raw.types.ChatParticipantAdmin): return ChatMember( From 18639002994515ad8e1a931aabc3e29ae6bfbd63 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 31 Oct 2020 17:15:47 +0100 Subject: [PATCH 0353/1185] Fix of a small mess up --- pyrogram/types/user_and_chats/chat_member.py | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/pyrogram/types/user_and_chats/chat_member.py b/pyrogram/types/user_and_chats/chat_member.py index 749e70659f..69ab4955f6 100644 --- a/pyrogram/types/user_and_chats/chat_member.py +++ b/pyrogram/types/user_and_chats/chat_member.py @@ -217,26 +217,6 @@ def _parse(client, member, users) -> "ChatMember": status="creator", client=client ) - - if isinstance(member, raw.types.ChannelParticipantCreator): - permissions = member.admin_rights - return ChatMember( - user=user, - status="creator", - title=getattr(member, "rank", None), - joined_date=member.date, - invited_by=invited_by, - can_be_edited=member.can_edit, - can_change_info=permissions.change_info, - can_post_messages=permissions.post_messages, - can_edit_messages=permissions.edit_messages, - can_delete_messages=permissions.delete_messages, - can_restrict_members=permissions.ban_users, - can_invite_users=permissions.invite_users, - can_pin_messages=permissions.pin_messages, - can_promote_members=permissions.add_admins, - client=client - ) if isinstance(member, raw.types.ChatParticipantAdmin): return ChatMember( From a2b8658153dced645b34b5d447fb9e7a2d8a7ffb Mon Sep 17 00:00:00 2001 From: Shrimadhav U K Date: Sat, 31 Oct 2020 21:56:10 +0530 Subject: [PATCH 0354/1185] Add missing parameters in Message.reply_poll (#507) --- pyrogram/types/messages_and_media/message.py | 29 ++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index c964fc3d9b..0b5f31cc1e 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -1801,8 +1801,13 @@ async def reply_poll( question: str, options: List[str], quote: bool = None, + is_anonymous: bool = True, + allows_multiple_answers: bool = None, + type: str = "regular", + correct_option_id: int = None, disable_notification: bool = None, reply_to_message_id: int = None, + schedule_date: int = None, reply_markup: Union[ "types.InlineKeyboardMarkup", "types.ReplyKeyboardMarkup", @@ -1838,6 +1843,22 @@ async def reply_poll( If ``True``, the message will be sent as a reply to this message. If *reply_to_message_id* is passed, this parameter will be ignored. Defaults to ``True`` in group chats and ``False`` in private chats. + + is_anonymous (``bool``, *optional*): + True, if the poll needs to be anonymous. + Defaults to True. + + type (``str``, *optional*): + Poll type, "quiz" or "regular". + Defaults to "regular" + + allows_multiple_answers (``bool``, *optional*): + True, if the poll allows multiple answers, ignored for polls in quiz mode. + Defaults to False + + correct_option_id (``int``, *optional*): + 0-based identifier of the correct answer option (the index of the correct option) + Required for polls in quiz mode. disable_notification (``bool``, *optional*): Sends the message silently. @@ -1845,6 +1866,9 @@ async def reply_poll( reply_to_message_id (``int``, *optional*): If the message is a reply, ID of the original message. + + schedule_date (``int``, *optional*): + Date when the message will be automatically sent. Unix time. reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, @@ -1866,8 +1890,13 @@ async def reply_poll( chat_id=self.chat.id, question=question, options=options, + is_anonymous=is_anonymous, + allows_multiple_answers=allows_multiple_answers, + type=type, + correct_option_id=correct_option_id, disable_notification=disable_notification, reply_to_message_id=reply_to_message_id, + schedule_date=schedule_date, reply_markup=reply_markup ) From c139d78b34f9a827b4bb62e80ae6bc900c508a8b Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 31 Oct 2020 19:29:39 +0100 Subject: [PATCH 0355/1185] Fix wrong imports in examples --- pyrogram/methods/bots/answer_inline_query.py | 2 +- pyrogram/methods/chats/restrict_chat_member.py | 2 +- pyrogram/methods/chats/set_chat_permissions.py | 2 +- pyrogram/methods/contacts/add_contacts.py | 2 +- pyrogram/methods/messages/edit_inline_media.py | 2 +- pyrogram/methods/messages/edit_inline_reply_markup.py | 2 +- pyrogram/methods/messages/edit_message_media.py | 2 +- pyrogram/methods/messages/edit_message_reply_markup.py | 2 +- pyrogram/methods/messages/send_message.py | 2 +- pyrogram/methods/utilities/add_handler.py | 3 ++- pyrogram/methods/utilities/remove_handler.py | 3 ++- 11 files changed, 13 insertions(+), 11 deletions(-) diff --git a/pyrogram/methods/bots/answer_inline_query.py b/pyrogram/methods/bots/answer_inline_query.py index e84491ee3a..1915db589f 100644 --- a/pyrogram/methods/bots/answer_inline_query.py +++ b/pyrogram/methods/bots/answer_inline_query.py @@ -84,7 +84,7 @@ async def answer_inline_query( Example: .. code-block:: python - from pyrogram import InlineQueryResultArticle, InputTextMessageContent + from pyrogram.types import InlineQueryResultArticle, InputTextMessageContent app.answer_inline_query( inline_query_id, diff --git a/pyrogram/methods/chats/restrict_chat_member.py b/pyrogram/methods/chats/restrict_chat_member.py index af760feb74..60f7132a91 100644 --- a/pyrogram/methods/chats/restrict_chat_member.py +++ b/pyrogram/methods/chats/restrict_chat_member.py @@ -60,7 +60,7 @@ async def restrict_chat_member( from time import time - from pyrogram import ChatPermissions + from pyrogram.types import ChatPermissions # Completely restrict chat member (mute) forever app.restrict_chat_member(chat_id, user_id, ChatPermissions()) diff --git a/pyrogram/methods/chats/set_chat_permissions.py b/pyrogram/methods/chats/set_chat_permissions.py index 028f0eb167..aab7697bed 100644 --- a/pyrogram/methods/chats/set_chat_permissions.py +++ b/pyrogram/methods/chats/set_chat_permissions.py @@ -47,7 +47,7 @@ async def set_chat_permissions( Example: .. code-block:: python - from pyrogram import ChatPermissions + from pyrogram.types import ChatPermissions # Completely restrict chat app.set_chat_permissions(chat_id, ChatPermissions()) diff --git a/pyrogram/methods/contacts/add_contacts.py b/pyrogram/methods/contacts/add_contacts.py index b786991cc7..aabdf064cb 100644 --- a/pyrogram/methods/contacts/add_contacts.py +++ b/pyrogram/methods/contacts/add_contacts.py @@ -40,7 +40,7 @@ async def add_contacts( Example: .. code-block:: python - from pyrogram import InputPhoneContact + from pyrogram.types import InputPhoneContact app.add_contacts([ InputPhoneContact("39123456789", "Foo"), diff --git a/pyrogram/methods/messages/edit_inline_media.py b/pyrogram/methods/messages/edit_inline_media.py index f607462d34..2e7147e288 100644 --- a/pyrogram/methods/messages/edit_inline_media.py +++ b/pyrogram/methods/messages/edit_inline_media.py @@ -54,7 +54,7 @@ async def edit_inline_media( Example: .. code-block:: python - from pyrogram import InputMediaPhoto, InputMediaVideo, InputMediaAudio + from pyrogram.types import InputMediaPhoto, InputMediaVideo, InputMediaAudio # Bots only diff --git a/pyrogram/methods/messages/edit_inline_reply_markup.py b/pyrogram/methods/messages/edit_inline_reply_markup.py index 8213c86e47..6696b1a761 100644 --- a/pyrogram/methods/messages/edit_inline_reply_markup.py +++ b/pyrogram/methods/messages/edit_inline_reply_markup.py @@ -44,7 +44,7 @@ async def edit_inline_reply_markup( Example: .. code-block:: python - from pyrogram import InlineKeyboardMarkup, InlineKeyboardButton + from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton # Bots only app.edit_inline_reply_markup( diff --git a/pyrogram/methods/messages/edit_message_media.py b/pyrogram/methods/messages/edit_message_media.py index 5733c5bda6..bef439d653 100644 --- a/pyrogram/methods/messages/edit_message_media.py +++ b/pyrogram/methods/messages/edit_message_media.py @@ -65,7 +65,7 @@ async def edit_message_media( Example: .. code-block:: python - from pyrogram import InputMediaPhoto, InputMediaVideo, InputMediaAudio + from pyrogram.types import InputMediaPhoto, InputMediaVideo, InputMediaAudio # Replace the current media with a local photo app.edit_message_media(chat_id, message_id, InputMediaPhoto("new_photo.jpg")) diff --git a/pyrogram/methods/messages/edit_message_reply_markup.py b/pyrogram/methods/messages/edit_message_reply_markup.py index 43d870adcc..ae5d04102b 100644 --- a/pyrogram/methods/messages/edit_message_reply_markup.py +++ b/pyrogram/methods/messages/edit_message_reply_markup.py @@ -50,7 +50,7 @@ async def edit_message_reply_markup( Example: .. code-block:: python - from pyrogram import InlineKeyboardMarkup, InlineKeyboardButton + from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton # Bots only app.edit_message_reply_markup( diff --git a/pyrogram/methods/messages/send_message.py b/pyrogram/methods/messages/send_message.py index e2ccc853cf..9326473119 100644 --- a/pyrogram/methods/messages/send_message.py +++ b/pyrogram/methods/messages/send_message.py @@ -98,7 +98,7 @@ async def send_message( # For bots only, send messages with keyboards attached ## - from pyrogram import ( + from pyrogram.types import ( ReplyKeyboardMarkup, InlineKeyboardMarkup, InlineKeyboardButton) # Send a normal keyboard diff --git a/pyrogram/methods/utilities/add_handler.py b/pyrogram/methods/utilities/add_handler.py index f8d993cddb..3c7440f340 100644 --- a/pyrogram/methods/utilities/add_handler.py +++ b/pyrogram/methods/utilities/add_handler.py @@ -44,7 +44,8 @@ def add_handler(self, handler: "Handler", group: int = 0): .. code-block:: python :emphasize-lines: 8 - from pyrogram import Client, MessageHandler + from pyrogram import Client + from pyrogram.handlers import MessageHandler def dump(client, message): print(message) diff --git a/pyrogram/methods/utilities/remove_handler.py b/pyrogram/methods/utilities/remove_handler.py index 5a4de8c14f..67709cb5ff 100644 --- a/pyrogram/methods/utilities/remove_handler.py +++ b/pyrogram/methods/utilities/remove_handler.py @@ -39,7 +39,8 @@ def remove_handler(self, handler: "Handler", group: int = 0): .. code-block:: python :emphasize-lines: 11 - from pyrogram import Client, MessageHandler + from pyrogram import Client + from pyrogram.handlers import MessageHandler def dump(client, message): print(message) From e2806acbdec82dd93015323427d7f22760d0e97e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=AE=AE=E0=AE=A9=E0=AF=8B=E0=AE=9C=E0=AF=8D=E0=AE=95?= =?UTF-8?q?=E0=AF=81=E0=AE=AE=E0=AE=BE=E0=AE=B0=E0=AF=8D=20=E0=AE=AA?= =?UTF-8?q?=E0=AE=B4=E0=AE=A9=E0=AE=BF=E0=AE=9A=E0=AF=8D=E0=AE=9A=E0=AE=BE?= =?UTF-8?q?=E0=AE=AE=E0=AE=BF?= Date: Sun, 1 Nov 2020 07:49:24 +0530 Subject: [PATCH 0356/1185] Update faq.rst --- docs/source/faq.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/faq.rst b/docs/source/faq.rst index 5141b593d8..5f608c879c 100644 --- a/docs/source/faq.rst +++ b/docs/source/faq.rst @@ -36,7 +36,7 @@ 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 +Pyrogram was first released on December 12, 2017. The actual work on the framework began roughly three months prior to the initial public release on `GitHub`_. .. _GitHub: https://github.com/pyrogram/pyrogram From 6027ee867f32204ed9941150cbb6288c9798116f Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 6 Nov 2020 10:55:51 +0100 Subject: [PATCH 0357/1185] Fix broken Chat parsing for user/bot chats --- pyrogram/types/user_and_chats/chat.py | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/pyrogram/types/user_and_chats/chat.py b/pyrogram/types/user_and_chats/chat.py index 5576e6ad49..3afbb47e12 100644 --- a/pyrogram/types/user_and_chats/chat.py +++ b/pyrogram/types/user_and_chats/chat.py @@ -225,14 +225,7 @@ def _parse_channel_chat(client, channel: raw.types.Channel) -> "Chat": @staticmethod def _parse(client, message: raw.types.Message or raw.types.MessageService, users: dict, chats: dict) -> "Chat": if isinstance(message.peer_id, raw.types.PeerUser): - return Chat._parse_user_chat( - client, - users[ - message.peer_id.user_id - if message.out - else utils.get_raw_peer_id(message.from_id) - ] - ) + return Chat._parse_user_chat(client, users[message.peer_id.user_id]) if isinstance(message.peer_id, raw.types.PeerChat): return Chat._parse_chat_chat(client, chats[message.peer_id.chat_id]) From c7e4e55607d973bcb0231bfda6aae5d588c7b062 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 8 Nov 2020 13:01:23 +0100 Subject: [PATCH 0358/1185] Add "bio" attribute in Chat objects --- pyrogram/types/user_and_chats/chat.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/pyrogram/types/user_and_chats/chat.py b/pyrogram/types/user_and_chats/chat.py index 3afbb47e12..0445cf0f7c 100644 --- a/pyrogram/types/user_and_chats/chat.py +++ b/pyrogram/types/user_and_chats/chat.py @@ -66,8 +66,12 @@ class Chat(Object): photo (:obj:`~pyrogram.types.ChatPhoto`, *optional*): Chat photo. Suitable for downloads only. + bio (``str``, *optional*): + Bio of the other party in a private chat. + Returned only in :meth:`~pyrogram.Client.get_chat`. + description (``str``, *optional*): - Bio, for private chats and bots or description for groups, supergroups and channels. + Description, for groups, supergroups and channel chats. Returned only in :meth:`~pyrogram.Client.get_chat`. dc_id (``int``, *optional*): @@ -126,6 +130,7 @@ def __init__( first_name: str = None, last_name: str = None, photo: "types.ChatPhoto" = None, + bio: str = None, description: str = None, dc_id: int = None, invite_link: str = None, @@ -152,6 +157,7 @@ def __init__( self.first_name = first_name self.last_name = last_name self.photo = photo + self.bio = bio self.description = description self.dc_id = dc_id self.invite_link = invite_link @@ -245,7 +251,7 @@ def _parse_dialog(client, peer, users: dict, chats: dict): async def _parse_full(client, chat_full: raw.types.messages.ChatFull or raw.types.UserFull) -> "Chat": if isinstance(chat_full, raw.types.UserFull): parsed_chat = Chat._parse_user_chat(client, chat_full.user) - parsed_chat.description = chat_full.about + parsed_chat.bio = chat_full.about if chat_full.pinned_msg_id: parsed_chat.pinned_message = await client.get_messages( From ebf2d683860af897f3c08e616286a1a3f89bd4c3 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 8 Nov 2020 13:21:40 +0100 Subject: [PATCH 0359/1185] Add new method unpin_all_chat_messages --- compiler/docs/compiler.py | 1 + pyrogram/methods/chats/__init__.py | 4 +- .../methods/chats/unpin_all_chat_messages.py | 53 +++++++++++++++++++ pyrogram/methods/chats/unpin_chat_message.py | 5 +- 4 files changed, 60 insertions(+), 3 deletions(-) create mode 100644 pyrogram/methods/chats/unpin_all_chat_messages.py diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index fdb4972dab..479ee2b845 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -197,6 +197,7 @@ def get_title_list(s: str) -> list: set_chat_permissions pin_chat_message unpin_chat_message + unpin_all_chat_messages get_chat get_chat_member get_chat_members diff --git a/pyrogram/methods/chats/__init__.py b/pyrogram/methods/chats/__init__.py index b55d1be7ce..8bd785c30e 100644 --- a/pyrogram/methods/chats/__init__.py +++ b/pyrogram/methods/chats/__init__.py @@ -49,6 +49,7 @@ from .set_slow_mode import SetSlowMode from .unarchive_chats import UnarchiveChats from .unban_chat_member import UnbanChatMember +from .unpin_all_chat_messages import UnpinAllChatMessages from .unpin_chat_message import UnpinChatMessage from .update_chat_username import UpdateChatUsername @@ -88,6 +89,7 @@ class Chats( GetNearbyChats, SetAdministratorTitle, SetSlowMode, - DeleteUserHistory + DeleteUserHistory, + UnpinAllChatMessages ): pass diff --git a/pyrogram/methods/chats/unpin_all_chat_messages.py b/pyrogram/methods/chats/unpin_all_chat_messages.py new file mode 100644 index 0000000000..a6f325a4bb --- /dev/null +++ b/pyrogram/methods/chats/unpin_all_chat_messages.py @@ -0,0 +1,53 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan +# +# 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 Union + +from pyrogram import raw +from pyrogram.scaffold import Scaffold + + +class UnpinAllChatMessages(Scaffold): + async def unpin_all_chat_messages( + self, + chat_id: Union[int, str], + ) -> bool: + """Use this method to clear the list of pinned messages in a chat. + If the chat is not a private chat, the bot must be an administrator in the chat for this to work and must have + the 'can_pin_messages' admin right in a supergroup or 'can_edit_messages' admin right in a channel. + + Parameters: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + + Returns: + ``bool``: True on success. + + Example: + .. code-block:: python + + # Unpin all chat messages + app.unpin_all_chat_messages(chat_id) + """ + await self.send( + raw.functions.messages.UnpinAllMessages( + peer=await self.resolve_peer(chat_id) + ) + ) + + return True diff --git a/pyrogram/methods/chats/unpin_chat_message.py b/pyrogram/methods/chats/unpin_chat_message.py index 52e533eddb..79a4a4c2f3 100644 --- a/pyrogram/methods/chats/unpin_chat_message.py +++ b/pyrogram/methods/chats/unpin_chat_message.py @@ -26,7 +26,7 @@ class UnpinChatMessage(Scaffold): async def unpin_chat_message( self, chat_id: Union[int, str], - message_id: int + message_id: int = 0 ) -> bool: """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 @@ -36,8 +36,9 @@ async def unpin_chat_message( chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. - message_id (``int``): + message_id (``int``, *optional*): Identifier of a message to unpin. + If not specified, the most recent pinned message (by sending date) will be unpinned. Returns: ``bool``: True on success. From fa24439ee30daeed474ba3e3c374987ec6076d18 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 8 Nov 2020 13:39:43 +0100 Subject: [PATCH 0360/1185] Add support for InputMediaDocument in media groups --- pyrogram/methods/messages/send_document.py | 3 -- pyrogram/methods/messages/send_media_group.py | 53 +++++++++++++++++-- 2 files changed, 50 insertions(+), 6 deletions(-) diff --git a/pyrogram/methods/messages/send_document.py b/pyrogram/methods/messages/send_document.py index 0ee814a587..f7ba28de46 100644 --- a/pyrogram/methods/messages/send_document.py +++ b/pyrogram/methods/messages/send_document.py @@ -152,9 +152,6 @@ def progress(current, total): """ file = None - # if isinstance(document, PurePath): - # document = str(document) - try: if isinstance(document, str): if os.path.isfile(document): diff --git a/pyrogram/methods/messages/send_media_group.py b/pyrogram/methods/messages/send_media_group.py index e30dc738bc..400b7575a2 100644 --- a/pyrogram/methods/messages/send_media_group.py +++ b/pyrogram/methods/messages/send_media_group.py @@ -34,7 +34,12 @@ class SendMediaGroup(Scaffold): async def send_media_group( self, chat_id: Union[int, str], - media: List[Union["types.InputMediaPhoto", "types.InputMediaVideo", "types.InputMediaAudio"]], + media: List[Union[ + "types.InputMediaPhoto", + "types.InputMediaVideo", + "types.InputMediaAudio", + "types.InputMediaDocument" + ]], disable_notification: bool = None, reply_to_message_id: int = None ) -> List["types.Message"]: @@ -46,7 +51,7 @@ async def send_media_group( For your personal cloud (Saved Messages) you can simply use "me" or "self". For a contact that exists in your Telegram address book you can use his phone number (str). - media (List of :obj:`~pyrogram.types.InputMediaPhoto` and :obj:`~pyrogram.types.InputMediaVideo`): + media (List of :obj:`~pyrogram.types.InputMediaPhoto`, :obj:`~pyrogram.types.InputMediaVideo`, :obj:`~pyrogram.types.InputMediaAudio` and :obj:`~pyrogram.types.InputMediaDocument`): A list describing photos and videos to be sent, must include 2–10 items. disable_notification (``bool``, *optional*): @@ -207,7 +212,49 @@ async def send_media_group( ) ) else: - media = utils.get_input_media_from_file_id(i.media, i.file_ref, 4) + media = utils.get_input_media_from_file_id(i.media, i.file_ref, 9) + elif isinstance(i, types.InputMediaDocument): + if os.path.isfile(i.media): + media = await self.send( + raw.functions.messages.UploadMedia( + peer=await self.resolve_peer(chat_id), + media=raw.types.InputMediaUploadedDocument( + mime_type=self.guess_mime_type(i.media) or "application/zip", + file=await self.save_file(i.media), + thumb=await self.save_file(i.thumb), + attributes=[ + raw.types.DocumentAttributeFilename(file_name=os.path.basename(i.media)) + ] + ) + ) + ) + + media = raw.types.InputMediaDocument( + id=raw.types.InputDocument( + id=media.document.id, + access_hash=media.document.access_hash, + file_reference=media.document.file_reference + ) + ) + elif re.match("^https?://", i.media): + media = await self.send( + raw.functions.messages.UploadMedia( + peer=await self.resolve_peer(chat_id), + media=raw.types.InputMediaDocumentExternal( + url=i.media + ) + ) + ) + + media = raw.types.InputMediaDocument( + id=raw.types.InputDocument( + id=media.document.id, + access_hash=media.document.access_hash, + file_reference=media.document.file_reference + ) + ) + else: + media = utils.get_input_media_from_file_id(i.media, i.file_ref, 5) multi_media.append( raw.types.InputSingleMedia( From abffef5d944d16d6d5571c7d77c3adb2488798fa Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 8 Nov 2020 13:43:49 +0100 Subject: [PATCH 0361/1185] Add support for anonymous messages Add Message.sender_chat attribute --- pyrogram/types/messages_and_media/message.py | 30 +++++++++++++++----- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index 0b5f31cc1e..1cf053093c 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -62,15 +62,21 @@ class Message(Object, Update): message_id (``int``): Unique message identifier inside this chat. + from_user (:obj:`~pyrogram.types.User`, *optional*): + Sender, empty for messages sent to channels. + + sender_chat (:obj:`~pyrogram.types.Chat`, *optional*): + Sender of the message, sent on behalf of a chat. + The channel itself for channel messages. + The supergroup itself for messages from anonymous group administrators. + The linked channel for messages automatically forwarded to the discussion group. + date (``int``, *optional*): Date the message was sent in Unix time. chat (:obj:`~pyrogram.types.Chat`, *optional*): Conversation the message belongs to. - from_user (:obj:`~pyrogram.types.User`, *optional*): - Sender, empty for messages sent to channels. - forward_from (:obj:`~pyrogram.types.User`, *optional*): For forwarded messages, sender of the original message. @@ -272,9 +278,10 @@ def __init__( *, client: "pyrogram.Client" = None, message_id: int, + from_user: "types.User" = None, + sender_chat: "types.Chat" = None, date: int = None, chat: "types.Chat" = None, - from_user: "types.User" = None, forward_from: "types.User" = None, forward_sender_name: str = None, forward_from_chat: "types.Chat" = None, @@ -337,9 +344,10 @@ def __init__( super().__init__(client) self.message_id = message_id + self.from_user = from_user + self.sender_chat = sender_chat self.date = date self.chat = chat - self.from_user = from_user self.forward_from = forward_from self.forward_sender_name = forward_sender_name self.forward_from_chat = forward_from_chat @@ -440,11 +448,15 @@ async def _parse( elif isinstance(action, raw.types.MessageActionChatEditPhoto): new_chat_photo = types.Photo._parse(client, action.photo) + from_user = types.User._parse(client, users.get(utils.get_raw_peer_id(message.from_id), None)) + sender_chat = types.Chat._parse(client, message, users, chats) if not from_user else None + parsed_message = Message( message_id=message.id, date=message.date, chat=types.Chat._parse(client, message, users, chats), - from_user=types.User._parse(client, users.get(utils.get_raw_peer_id(message.from_id), None)), + from_user=from_user, + sender_chat=sender_chat, service=True, new_chat_members=new_chat_members, left_chat_member=left_chat_member, @@ -608,11 +620,15 @@ async def _parse( else: reply_markup = None + from_user = types.User._parse(client, users.get(utils.get_raw_peer_id(message.from_id), None)) + sender_chat = types.Chat._parse(client, message, users, chats) if not from_user else None + parsed_message = Message( message_id=message.id, date=message.date, chat=types.Chat._parse(client, message, users, chats), - from_user=types.User._parse(client, users.get(utils.get_raw_peer_id(message.from_id), None)), + from_user=from_user, + sender_chat=sender_chat, text=( Str(message.message).init(entities) or None if media is None or web_page is not None From e71bb87a2dfa15eebec59030d9451cc180374bdb Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 8 Nov 2020 13:49:44 +0100 Subject: [PATCH 0362/1185] Add is_anonymous attribute to ChatMember --- pyrogram/types/user_and_chats/chat_member.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pyrogram/types/user_and_chats/chat_member.py b/pyrogram/types/user_and_chats/chat_member.py index 69ab4955f6..7f195d4ccb 100644 --- a/pyrogram/types/user_and_chats/chat_member.py +++ b/pyrogram/types/user_and_chats/chat_member.py @@ -58,6 +58,10 @@ class ChatMember(Object): is_member (``bool``, *optional*): Restricted only. True, if the user is a member of the chat at the moment of the request. + is_anonymous (``bool``, *optional*): + True, if the user's presence in the chat is hidden. + Owner and administrators only. + can_be_edited (``bool``, *optional*): Administrators only. True, if you are allowed to edit administrator privileges of the user. @@ -138,6 +142,7 @@ def __init__( promoted_by: "types.User" = None, restricted_by: "types.User" = None, is_member: bool = None, + is_anonymous: bool = None, # Admin permissions can_be_edited: bool = None, @@ -171,6 +176,7 @@ def __init__( self.promoted_by = promoted_by self.restricted_by = restricted_by self.is_member = is_member + self.is_anonymous = is_anonymous self.can_be_edited = can_be_edited self.can_post_messages = can_post_messages @@ -243,6 +249,7 @@ def _parse(client, member, users) -> "ChatMember": can_invite_users=permissions.invite_users, can_pin_messages=permissions.pin_messages, can_promote_members=permissions.add_admins, + is_anonymous=permissions.anonymous, client=client ) @@ -265,6 +272,7 @@ def _parse(client, member, users) -> "ChatMember": can_invite_users=permissions.invite_users, can_pin_messages=permissions.pin_messages, can_promote_members=permissions.add_admins, + is_anonymous=permissions.anonymous, client=client ) From f832df14b4231ae1f971243e1e69433a1d5a6e36 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 8 Nov 2020 13:52:15 +0100 Subject: [PATCH 0363/1185] Add parameter is_anonymous to the method promote_chat_member --- pyrogram/methods/chats/promote_chat_member.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pyrogram/methods/chats/promote_chat_member.py b/pyrogram/methods/chats/promote_chat_member.py index eb49bce05b..15e0837530 100644 --- a/pyrogram/methods/chats/promote_chat_member.py +++ b/pyrogram/methods/chats/promote_chat_member.py @@ -27,6 +27,7 @@ async def promote_chat_member( self, chat_id: Union[int, str], user_id: Union[int, str], + is_anonymous: bool = False, can_change_info: bool = True, can_post_messages: bool = False, can_edit_messages: bool = False, @@ -49,6 +50,9 @@ async def promote_chat_member( Unique identifier (int) or username (str) of the target user. For a contact that exists in your Telegram address book you can use his phone number (str). + is_anonymous (``bool``, *optional*): + Pass True, if the administrator's presence in the chat is hidden. + can_change_info (``bool``, *optional*): Pass True, if the administrator can change chat title, photo and other settings. @@ -89,6 +93,7 @@ async def promote_chat_member( channel=await self.resolve_peer(chat_id), user_id=await self.resolve_peer(user_id), admin_rights=raw.types.ChatAdminRights( + anonymous=is_anonymous or None, change_info=can_change_info or None, post_messages=can_post_messages or None, edit_messages=can_edit_messages or None, From 418ad164a04f64ab44630952f8d7782971167f03 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 8 Nov 2020 13:54:25 +0100 Subject: [PATCH 0364/1185] Update Message.author_signature docs --- pyrogram/types/messages_and_media/message.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index 1cf053093c..4d70206e26 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -124,7 +124,8 @@ class Message(Object, Update): The unique identifier of a media message group this message belongs to. author_signature (``str``, *optional*): - Signature of the post author for messages in channels. + Signature of the post author for messages in channels, or the custom title of an anonymous group + administrator. text (``str``, *optional*): For text messages, the actual UTF-8 text of the message, 0-4096 characters. From 80f8010d50b17f7cb68b0ad5b2f5ba184293a451 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 10 Nov 2020 18:43:47 +0100 Subject: [PATCH 0365/1185] Add support for pinned messages in search_messages --- pyrogram/methods/messages/search_messages.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/pyrogram/methods/messages/search_messages.py b/pyrogram/methods/messages/search_messages.py index 8744fe8118..9389c9df9f 100644 --- a/pyrogram/methods/messages/search_messages.py +++ b/pyrogram/methods/messages/search_messages.py @@ -41,6 +41,7 @@ class Filters: MENTION = raw.types.InputMessagesFilterMyMentions() LOCATION = raw.types.InputMessagesFilterGeo() CONTACT = raw.types.InputMessagesFilterContacts() + PINNED = raw.types.InputMessagesFilterPinned() POSSIBLE_VALUES = list(map(lambda x: x.lower(), filter(lambda x: not x.startswith("__"), Filters.__dict__.keys()))) @@ -135,6 +136,7 @@ async def search_messages( - ``"mention"``: Search for messages containing mentions to yourself. - ``"location"``: Search for location messages. - ``"contact"``: Search for contact messages. + - ``"pinned"``: Search for pinned messages. limit (``int``, *optional*): Limits the number of messages to be retrieved. @@ -153,8 +155,12 @@ async def search_messages( for message in app.search_messages("pyrogramchat", query="dan", limit=333): print(message.text) - # Search for photos sent by @haskell in @pyrogramchat - for message in app.search_messages("pyrogramchat", "", filter="photo" limit=333, from_user="haskell"): + # Search for pinned messages in @pyrogramchat + for message in app.search_messages("pyrogramchat", filter="pinned"): + print(message.text) + + # Search for messages containing "hi" sent by @haskell in @pyrogramchat + for message in app.search_messages("pyrogramchat", "hi", from_user="haskell"): print(message.text) """ current = 0 From e4405db50dc5c247e1fef0e3440e02c8ff4903a7 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 10 Nov 2020 18:54:52 +0100 Subject: [PATCH 0366/1185] Fix senders wrongly being in sender_chat instead of from_user --- pyrogram/types/messages_and_media/message.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index 4d70206e26..48b2e3199e 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -449,7 +449,8 @@ async def _parse( elif isinstance(action, raw.types.MessageActionChatEditPhoto): new_chat_photo = types.Photo._parse(client, action.photo) - from_user = types.User._parse(client, users.get(utils.get_raw_peer_id(message.from_id), None)) + user = utils.get_raw_peer_id(message.from_id) or utils.get_raw_peer_id(message.peer_id) + from_user = types.User._parse(client, users.get(user, None)) sender_chat = types.Chat._parse(client, message, users, chats) if not from_user else None parsed_message = Message( @@ -621,7 +622,8 @@ async def _parse( else: reply_markup = None - from_user = types.User._parse(client, users.get(utils.get_raw_peer_id(message.from_id), None)) + user = utils.get_raw_peer_id(message.from_id) or utils.get_raw_peer_id(message.peer_id) + from_user = types.User._parse(client, users.get(user, None)) sender_chat = types.Chat._parse(client, message, users, chats) if not from_user else None parsed_message = Message( From f3b96e889ff179eb169b8f42386fa682db7f6a71 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 13 Nov 2020 17:49:27 +0100 Subject: [PATCH 0367/1185] Add extra errors --- compiler/error/source/303_SEE_OTHER.tsv | 1 + compiler/error/source/400_BAD_REQUEST.tsv | 212 +++++++++++++++--- compiler/error/source/401_UNAUTHORIZED.tsv | 2 +- compiler/error/source/403_FORBIDDEN.tsv | 19 +- compiler/error/source/406_NOT_ACCEPTABLE.tsv | 11 +- compiler/error/source/420_FLOOD.tsv | 3 +- .../source/500_INTERNAL_SERVER_ERROR.tsv | 27 ++- 7 files changed, 228 insertions(+), 47 deletions(-) diff --git a/compiler/error/source/303_SEE_OTHER.tsv b/compiler/error/source/303_SEE_OTHER.tsv index da3ac18d97..39e18d268b 100644 --- a/compiler/error/source/303_SEE_OTHER.tsv +++ b/compiler/error/source/303_SEE_OTHER.tsv @@ -3,3 +3,4 @@ FILE_MIGRATE_X The file to be accessed is currently stored in DC{x} PHONE_MIGRATE_X The phone number a user is trying to use for authorization is associated with DC{x} NETWORK_MIGRATE_X The source IP address is associated with DC{x} (for registration) USER_MIGRATE_X The user whose identity is being used to execute queries is associated with DC{x} (for registration) +STATS_MIGRATE_X The statistics of the group/channel are stored in DC{x} \ No newline at end of file diff --git a/compiler/error/source/400_BAD_REQUEST.tsv b/compiler/error/source/400_BAD_REQUEST.tsv index fadbbb4971..056ed21757 100644 --- a/compiler/error/source/400_BAD_REQUEST.tsv +++ b/compiler/error/source/400_BAD_REQUEST.tsv @@ -2,8 +2,8 @@ id message FIRSTNAME_INVALID The first name is invalid LASTNAME_INVALID The last name is invalid PHONE_NUMBER_INVALID The phone number is invalid -PHONE_CODE_HASH_EMPTY phone_code_hash is missing -PHONE_CODE_EMPTY phone_code is missing +PHONE_CODE_HASH_EMPTY The phone code hash is missing +PHONE_CODE_EMPTY The phone code is missing PHONE_CODE_EXPIRED The confirmation code has expired PHONE_CODE_INVALID The confirmation code is invalid API_ID_INVALID The api_id/api_hash combination is invalid @@ -20,33 +20,33 @@ PHOTO_INVALID_DIMENSIONS The photo dimensions are invalid FIELD_NAME_INVALID The field with the name FIELD_NAME is invalid FIELD_NAME_EMPTY The field with the name FIELD_NAME is missing MSG_WAIT_FAILED A waiting call returned an error -PEER_ID_INVALID The id/access_hash combination is invalid -MESSAGE_EMPTY The message sent is empty +PEER_ID_INVALID The peer id being used is invalid or not known yet. Make sure you meet the peer before interacting with it +MESSAGE_EMPTY The message sent is empty or contains invalid characters ENCRYPTED_MESSAGE_INVALID The special binding message (bind_auth_key_inner) contains invalid data -INPUT_METHOD_INVALID The method called is invalid -PASSWORD_HASH_INVALID Two-step verification password is invalid +INPUT_METHOD_INVALID The method invoked is invalid in the current schema +PASSWORD_HASH_INVALID The two-step verification password is invalid USERNAME_NOT_OCCUPIED The username is not occupied by anyone USERNAME_INVALID The username is invalid MESSAGE_ID_INVALID The message id is invalid MESSAGE_NOT_MODIFIED The message was not modified because you tried to edit it using the same content ENTITY_MENTION_USER_INVALID The mentioned entity is not an user MESSAGE_TOO_LONG The message text is over 4096 characters -ACCESS_TOKEN_EXPIRED The bot token is invalid +ACCESS_TOKEN_EXPIRED The bot token has expired BOT_METHOD_INVALID The method can't be used by bots QUERY_TOO_SHORT The query is too short -SEARCH_QUERY_EMPTY The query is empty -CHAT_ID_INVALID The chat id is invalid +SEARCH_QUERY_EMPTY The search query is empty +CHAT_ID_INVALID The chat id being used is invalid or not known yet. Make sure you see the chat before interacting with it DATE_EMPTY The date argument is empty -PERSISTENT_TIMESTAMP_EMPTY The pts is empty +PERSISTENT_TIMESTAMP_EMPTY The pts argument is empty CDN_METHOD_INVALID The method can't be used on CDN DCs VOLUME_LOC_NOT_FOUND The volume location can't be found FILE_ID_INVALID The file id is invalid -LOCATION_INVALID The file address is invalid +LOCATION_INVALID The file location is invalid CHAT_ADMIN_REQUIRED The method requires chat admin privileges -PHONE_NUMBER_BANNED The phone number is banned -ABOUT_TOO_LONG The about text is too long -MULTI_MEDIA_TOO_LONG The album contains more than 10 items -USERNAME_OCCUPIED The username is already in use +PHONE_NUMBER_BANNED The phone number is banned from Telegram and cannot be used +ABOUT_TOO_LONG The provided about/bio text is too long +MULTI_MEDIA_TOO_LONG The album/media group contains too many items +USERNAME_OCCUPIED The username is already in use by someone else BOT_INLINE_DISABLED The inline feature of the bot is disabled INLINE_RESULT_EXPIRED The inline bot query expired INVITE_HASH_INVALID The invite link hash is invalid @@ -61,25 +61,25 @@ EMAIL_INVALID The email provided is invalid USER_IS_BOT A bot cannot send messages to other bots or to itself WEBPAGE_CURL_FAILED Telegram server could not fetch the provided URL STICKERSET_INVALID The requested sticker set is invalid -PEER_FLOOD The method can't be used because your account is limited +PEER_FLOOD The method can't be used because your account is currently limited MEDIA_CAPTION_TOO_LONG The media caption is longer than 1024 characters USER_NOT_MUTUAL_CONTACT The user is not a mutual contact USER_CHANNELS_TOO_MUCH The user is already in too many channels or supergroups -API_ID_PUBLISHED_FLOOD You are using an API key that is limited on the server side +API_ID_PUBLISHED_FLOOD You are using an API key that is limited on the server side because it was published somewhere USER_NOT_PARTICIPANT The user is not a member of this chat CHANNEL_PRIVATE The channel/supergroup is not accessible -MESSAGE_IDS_EMPTY The requested message doesn't exist +MESSAGE_IDS_EMPTY The requested message doesn't exist or you provided no message id WEBPAGE_MEDIA_EMPTY The URL doesn't contain any valid media QUERY_ID_INVALID The callback query id is invalid -MEDIA_EMPTY The media is invalid +MEDIA_EMPTY The media you tried to send is invalid USER_IS_BLOCKED The user blocked you YOU_BLOCKED_USER You blocked this user ADMINS_TOO_MUCH The chat has too many administrators BOTS_TOO_MUCH The chat has too many bots -USER_ADMIN_INVALID The action requires admin privileges -INPUT_USER_DEACTIVATED The target user has been deactivated +USER_ADMIN_INVALID The action requires admin privileges. Probably you tried to edit admin privileges on someone you don't have rights to +INPUT_USER_DEACTIVATED The target user has been deleted/deactivated PASSWORD_RECOVERY_NA The password recovery e-mail is not available -PASSWORD_EMPTY The password entered is empty +PASSWORD_EMPTY The password provided is empty PHONE_NUMBER_FLOOD This number has tried to login too many times TAKEOUT_INVALID The takeout id is invalid TAKEOUT_REQUIRED The method must be invoked inside a takeout session @@ -88,18 +88,18 @@ MEDIA_INVALID The media is invalid BOT_SCORE_NOT_MODIFIED The bot score was not modified USER_BOT_REQUIRED The method can be used by bots only IMAGE_PROCESS_FAILED The server failed to process your image -USERNAME_NOT_MODIFIED The username was not modified +USERNAME_NOT_MODIFIED The username was not modified because you tried to edit it using the same one CALL_ALREADY_ACCEPTED The call is already accepted CALL_ALREADY_DECLINED The call is already declined PHOTO_EXT_INVALID The photo extension is invalid EXTERNAL_URL_INVALID The external media URL is invalid -CHAT_NOT_MODIFIED The chat settings were not modified +CHAT_NOT_MODIFIED The chat settings (title, permissions, photo, etc..) were not modified because you tried to edit them using the same content 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 -USER_BANNED_IN_CHANNEL You are limited, check @SpamBot for details -MESSAGE_EDIT_TIME_EXPIRED You can no longer edit this message +USER_BANNED_IN_CHANNEL You are limited from sending messages in supergroups/channels, check @SpamBot for details +MESSAGE_EDIT_TIME_EXPIRED You can no longer edit this message because too much time has passed FOLDER_ID_INVALID The folder id is invalid MEGAGROUP_PREHISTORY_HIDDEN The action failed because the supergroup has the pre-history hidden CHAT_LINK_EXISTS The action failed because the supergroup is linked to a channel @@ -125,10 +125,10 @@ WEBDOCUMENT_URL_INVALID The web document URL is invalid WEBDOCUMENT_MIME_INVALID The web document mime type is invalid BUTTON_URL_INVALID The button url is invalid AUTH_BYTES_INVALID The authorization bytes are invalid -USER_ID_INVALID The user ID is invalid -CHANNELS_TOO_MUCH You have joined too many channels or supergroups +USER_ID_INVALID The user id being used is invalid or not known yet. Make sure you meet the user before interacting with it +CHANNELS_TOO_MUCH You have joined too many channels or supergroups, leave some and try again ADMIN_RANK_INVALID The custom administrator title is invalid or is longer than 16 characters -ADMIN_RANK_EMOJI_NOT_ALLOWED Emojis are not allowed in custom administrator titles +ADMIN_RANK_EMOJI_NOT_ALLOWED Emoji are not allowed in custom administrator titles FILE_REFERENCE_EMPTY The file reference is empty FILE_REFERENCE_INVALID The file reference is invalid REPLY_MARKUP_TOO_LONG The reply markup is too long @@ -140,10 +140,160 @@ QUIZ_CORRECT_ANSWERS_TOO_MUCH The quiz contains too many correct answers OPTIONS_TOO_MUCH The poll options are too many POLL_ANSWERS_INVALID The poll answers are invalid POLL_QUESTION_INVALID The poll question is invalid -FRESH_CHANGE_ADMINS_FORBIDDEN Recently logged-in users cannot change admins +FRESH_CHANGE_ADMINS_FORBIDDEN You can't change administrator settings in this chat because your session was logged-in recently BROADCAST_PUBLIC_VOTERS_FORBIDDEN Polls with public voters cannot be sent in channels INPUT_FILTER_INVALID The filter is invalid for this query EMOTICON_EMPTY The emoticon parameter is empty EMOTICON_INVALID The emoticon parameter is invalid VIDEO_FILE_INVALID The video file is invalid -PRIVACY_TOO_LONG Your privacy exception list has exceeded the maximum capacity \ No newline at end of file +PRIVACY_TOO_LONG Your privacy exception list has exceeded the maximum capacity +ALBUM_PHOTOS_TOO_MANY Too many photos were included in the album +AUDIO_TITLE_EMPTY The title attribute of the audio is empty +AUTH_TOKEN_ALREADY_ACCEPTED The authorization token was already used +AUTH_TOKEN_EXPIRED The provided authorization token has expired and the updated QR-code must be re-scanned +AUTH_TOKEN_INVALID An invalid authorization token was provided +AUTOARCHIVE_NOT_AVAILABLE This feature is not yet enabled for your account due to it not receiving too many private messages from strangers +BANK_CARD_NUMBER_INVALID The credit card number is invalid +BANNED_RIGHTS_INVALID You provided a set of restrictions that is invalid +BOT_CHANNELS_NA Bots can't edit admin privileges +BOT_COMMAND_DESCRIPTION_INVALID The command description was empty, too long or had invalid characters +BOT_DOMAIN_INVALID The domain used for the auth button does not match the one configured in @BotFather +BOT_GAMES_DISABLED Bot games cannot be used in this type of chat +BOT_GROUPS_BLOCKED This bot can't be added to groups +BOT_INVALID This is not a valid bot +BOT_MISSING This method can only be run by a bot +BOT_PAYMENTS_DISABLED This method can only be run by a bot +BOT_POLLS_DISABLED Sending polls by bots has been disabled +BOT_RESPONSE_TIMEOUT The bot did not answer to the callback query in time +BROADCAST_REQUIRED The request can only be used with a channel +BUTTON_TYPE_INVALID The type of one of the buttons you provided is invalid +CALL_PEER_INVALID The provided call peer object is invalid +CALL_PROTOCOL_FLAGS_INVALID Call protocol flags invalid +CHANNELS_ADMIN_PUBLIC_TOO_MUCH You are an administrator of too many public channels +CHANNEL_BANNED The channel is banned +CHAT_ABOUT_NOT_MODIFIED The chat about text was not modified because you tried to edit it using the same content +CHAT_ABOUT_TOO_LONG The chat about text is too long +CHAT_ID_EMPTY The provided chat id is empty +CHAT_INVALID The chat is invalid +CHAT_RESTRICTED The chat is restricted and cannot be used +CHAT_SEND_INLINE_FORBIDDEN You cannot use inline bots to send messages in this chat +CHAT_TITLE_EMPTY The chat title is empty +CODE_EMPTY The provided code is empty +CODE_HASH_INVALID The provided code hash invalid +CODE_INVALID The provided code is invalid (i.e. from email) +CONNECTION_API_ID_INVALID The provided API id is invalid +CONNECTION_DEVICE_MODEL_EMPTY The device model is empty +CONNECTION_LANG_PACK_INVALID The specified language pack is not valid +CONNECTION_LAYER_INVALID The connection layer is invalid. Missing InvokeWithLayer-InitConnection call +CONNECTION_NOT_INITED The connection was not initialized +CONNECTION_SYSTEM_EMPTY The connection to the system is empty +CONNECTION_SYSTEM_LANG_CODE_EMPTY The system language code is empty +CONTACT_ID_INVALID The provided contact id is invalid +CONTACT_NAME_EMPTY The provided contact name is empty +DATA_INVALID The encrypted data is invalid +DATA_JSON_INVALID The provided JSON data is invalid +DH_G_A_INVALID The g_a parameter invalid +DOCUMENT_INVALID The document is invalid +EMAIL_HASH_EXPIRED The email hash expired and cannot be used to verify it +EMAIL_UNCONFIRMED_X Email unconfirmed, the length of the code must be {x} +EMOTICON_STICKERPACK_MISSING The emoticon sticker pack you are trying to obtain is missing +ENCRYPTION_ALREADY_ACCEPTED The secret chat is already accepted +ENCRYPTION_ALREADY_DECLINED The secret chat is already declined +ENCRYPTION_DECLINED The secret chat was declined +ENCRYPTION_ID_INVALID The provided secret chat id is invalid +ENTITIES_TOO_LONG The entity provided contains data that is too long, or you passed too many entities to this message +ERROR_TEXT_EMPTY The provided error message is empty +EXPORT_CARD_INVALID The provided card is invalid +FILE_PART_LENGTH_INVALID The length of a file part is invalid +FILE_REFERENCE_EXPIRED The file reference has expired and is no longer valid or it belongs to self-destructing media and cannot be resent +FOLDER_ID_EMPTY The folder you tried to delete was already empty +GAME_BOT_INVALID You cannot send that game with the current bot +GIF_ID_INVALID The provided gif/animation id is invalid +GRAPH_OUTDATED_RELOAD The graph data is outdated +GROUPED_MEDIA_INVALID The album contains invalid media +HASH_INVALID The provided hash is invalid +INPUT_CONSTRUCTOR_INVALID The provided constructor is invalid +INPUT_FETCH_ERROR An error occurred while deserializing TL parameters +INPUT_FETCH_FAIL Failed deserializing TL payload +INPUT_LAYER_INVALID The provided layer is invalid +INPUT_REQUEST_TOO_LONG The input request is too long +INVITE_HASH_EMPTY The invite hash is empty +LANG_PACK_INVALID The provided language pack is invalid +MAX_QTS_INVALID The provided QTS is invalid +MEDIA_NEW_INVALID The new media to edit the message with is invalid +MEDIA_PREV_INVALID The previous media cannot be edited with anything else +MEGAGROUP_REQUIRED The request can only be used with a supergroup +METHOD_INVALID The API method is invalid and cannot be used +MSG_ID_INVALID The message ID used in the peer was invalid +NEW_SALT_INVALID The new salt is invalid +NEW_SETTINGS_INVALID The new settings are invalid +OFFSET_PEER_ID_INVALID The provided offset peer is invalid +OPTION_INVALID The option specified is invalid and does not exist in the target poll +PACK_SHORT_NAME_INVALID Invalid sticker pack name. It must begin with a letter, can't contain consecutive underscores and must end in "_by_". +PACK_SHORT_NAME_OCCUPIED A sticker pack with this name already exists +PARTICIPANTS_TOO_FEW The chat doesn't have enough participants +PARTICIPANT_VERSION_OUTDATED The other participant is using an outdated Telegram app version +PASSWORD_MISSING The account is missing the two-step verification password +PASSWORD_REQUIRED The two-step verification password is required for this method +PASSWORD_TOO_FRESH_X The two-step verification password was added recently and you are required to wait {x} seconds +PAYMENT_PROVIDER_INVALID The payment provider was not recognised or its token was invalid +PEER_ID_NOT_SUPPORTED The provided peer id is not supported +PERSISTENT_TIMESTAMP_INVALID The persistent timestamp is invalid +PHONE_NUMBER_APP_SIGNUP_FORBIDDEN You can't sign up using this app +PHONE_PASSWORD_PROTECTED The phone is password protected +PHOTO_CROP_SIZE_SMALL The photo is too small +PHOTO_ID_INVALID The photo id is invalid +PHOTO_INVALID The photo is invalid +PHOTO_SAVE_FILE_INVALID The photo you tried to send cannot be saved by Telegram. A reason may be that it exceeds 10 MB. Try resizing it locally +PIN_RESTRICTED You can't pin messages in private chats with other people +POLL_OPTION_DUPLICATE A duplicate option was sent in the same poll +POLL_OPTION_INVALID A poll option used invalid data (the data may be too long) +POLL_UNSUPPORTED This layer does not support polls in the invoked method +PRIVACY_KEY_INVALID The privacy key is invalid +PRIVACY_VALUE_INVALID The privacy value is invalid +QUERY_ID_EMPTY The query ID is empty +RANDOM_ID_INVALID The provided random ID is invalid +RANDOM_LENGTH_INVALID The random length is invalid +RANGES_INVALID Invalid range provided +REACTION_EMPTY The reaction provided is empty +REACTION_INVALID Invalid reaction provided (only emoji are allowed) +REPLY_MARKUP_GAME_EMPTY The provided reply markup for the game is empty +REPLY_MARKUP_INVALID The provided reply markup is invalid +RESULT_ID_INVALID The given result cannot be used to send the selection to the bot +RSA_DECRYPT_FAILED Internal RSA decryption failed +SCHEDULE_BOT_NOT_ALLOWED Bots are not allowed to schedule messages +SCHEDULE_DATE_TOO_LATE The date you tried to schedule is too far in the future (more than one year) +SCHEDULE_STATUS_PRIVATE You cannot schedule a message until the person comes online if their privacy does not show this information +SCHEDULE_TOO_MUCH You cannot schedule more than 100 messages in this chat +SEND_MESSAGE_MEDIA_INVALID The message media is invalid +SEND_MESSAGE_TYPE_INVALID The message type is invalid +SESSION_TOO_FRESH_X You can't do this action because the current session was logged-in recently +SHA256_HASH_INVALID The provided SHA256 hash is invalid +SHORTNAME_OCCUPY_FAILED An error occurred when trying to register the short-name used for the sticker pack. Try a different name +START_PARAM_EMPTY The start parameter is empty +STICKERS_EMPTY The sticker provided is empty +STICKER_DOCUMENT_INVALID The sticker document is invalid +STICKER_EMOJI_INVALID The sticker emoji is invalid +STICKER_FILE_INVALID The sticker file is invalid +STICKER_ID_INVALID The provided sticker id is invalid +STICKER_INVALID The provided sticker is invalid +STICKER_PNG_DIMENSIONS The sticker png dimensions are invalid +STICKER_PNG_NOPNG Stickers must be png files but the provided image was not a png +TEMP_AUTH_KEY_EMPTY The temporary auth key provided is empty +THEME_MIME_INVALID You cannot create this theme because the mime-type is invalid +TMP_PASSWORD_DISABLED The temporary password is disabled +TOKEN_INVALID The provided token is invalid +TTL_DAYS_INVALID The provided TTL days is invalid +TYPES_EMPTY The types parameter is empty +UNTIL_DATE_INVALID That date parameter is invalid +URL_INVALID The URL provided is invalid +USER_BLOCKED The user is blocked +USER_BOT Bots in channels can only be administrators, not members. +USER_BOT_INVALID This method can only be used by a bot +USER_CREATOR You can't leave this channel because you're its creator +USER_INVALID The provided user is invalid +USER_KICKED This user was kicked from this chat +VIDEO_CONTENT_TYPE_INVALID The video content type is invalid (i.e.: not streamable) +WALLPAPER_FILE_INVALID The provided file cannot be used as a wallpaper +WALLPAPER_INVALID The input wallpaper was not valid +WC_CONVERT_URL_INVALID WC convert URL invalid \ No newline at end of file diff --git a/compiler/error/source/401_UNAUTHORIZED.tsv b/compiler/error/source/401_UNAUTHORIZED.tsv index e5cd387401..120535e0e9 100644 --- a/compiler/error/source/401_UNAUTHORIZED.tsv +++ b/compiler/error/source/401_UNAUTHORIZED.tsv @@ -7,4 +7,4 @@ SESSION_REVOKED The authorization has been invalidated, because of the user term SESSION_EXPIRED The authorization has expired ACTIVE_USER_REQUIRED The method is only available to already activated users AUTH_KEY_PERM_EMPTY The method is unavailable for temporary authorization key, not bound to permanent -SESSION_PASSWORD_NEEDED Two-step verification password required +SESSION_PASSWORD_NEEDED The two-step verification is enabled and a password is required \ No newline at end of file diff --git a/compiler/error/source/403_FORBIDDEN.tsv b/compiler/error/source/403_FORBIDDEN.tsv index dd1e98fad0..04d18e8d60 100644 --- a/compiler/error/source/403_FORBIDDEN.tsv +++ b/compiler/error/source/403_FORBIDDEN.tsv @@ -1,7 +1,20 @@ 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) +RIGHT_FORBIDDEN You don't have enough rights for this action, or you tried to set one or more admin rights that can't be applied to this kind of chat (channel or 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 +MESSAGE_DELETE_FORBIDDEN You don't have rights to delete messages in this chat, most likely because you are not the author of them 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 +MESSAGE_AUTHOR_REQUIRED You are not the author of this message +BROADCAST_FORBIDDEN The request can't be used in channels +CHANNEL_PUBLIC_GROUP_NA The channel/supergroup is not available +CHAT_FORBIDDEN You cannot write in this chat +CHAT_SEND_GIFS_FORBIDDEN You can't send animations in this chat +CHAT_SEND_STICKERS_FORBIDDEN You can't send stickers in this chat +INLINE_BOT_REQUIRED The action must be performed through an inline bot callback +SENSITIVE_CHANGE_FORBIDDEN Your sensitive content settings can't be changed at this time +USER_BOT_INVALID This method can only be called by a bot +USER_CHANNELS_TOO_MUCH One of the users you tried to add is already in too many channels/supergroups +USER_IS_BLOCKED The user is blocked +USER_NOT_MUTUAL_CONTACT The provided user is not a mutual contact +USER_PRIVACY_RESTRICTED The user's privacy settings is preventing you to perform this action +USER_RESTRICTED You are limited/restricted. You can't perform this action. \ No newline at end of file diff --git a/compiler/error/source/406_NOT_ACCEPTABLE.tsv b/compiler/error/source/406_NOT_ACCEPTABLE.tsv index 1c8e564766..e53c7c7436 100644 --- a/compiler/error/source/406_NOT_ACCEPTABLE.tsv +++ b/compiler/error/source/406_NOT_ACCEPTABLE.tsv @@ -1,4 +1,9 @@ id message -AUTH_KEY_DUPLICATED Authorization error - you must delete your session file and log in again with your phone number -FILEREF_UPGRADE_NEEDED The file reference has expired - you must obtain the original media message -STICKERSET_INVALID The sticker set is invalid \ No newline at end of file +AUTH_KEY_DUPLICATED The same authorization key (session file) was used in more than one place simultaneously. You must delete your session file and log in again with your phone number or bot token +FILEREF_UPGRADE_NEEDED The file reference has expired and you must use a refreshed one by obtaining the original media message +STICKERSET_INVALID The sticker set is invalid +FRESH_CHANGE_PHONE_FORBIDDEN You can't change your phone number because your session was logged-in recently +FRESH_RESET_AUTHORISATION_FORBIDDEN You can't terminate other authorized sessions because the current was logged-in recently +PHONE_NUMBER_INVALID The phone number is invalid +PHONE_PASSWORD_FLOOD You have tried to log-in too many times +STICKERSET_OWNER_ANONYMOUS This sticker set can't be used as the group's sticker set because it was created by one of its anonymous admins \ No newline at end of file diff --git a/compiler/error/source/420_FLOOD.tsv b/compiler/error/source/420_FLOOD.tsv index c381e75224..f0eb7b6109 100644 --- a/compiler/error/source/420_FLOOD.tsv +++ b/compiler/error/source/420_FLOOD.tsv @@ -1,4 +1,5 @@ id message FLOOD_WAIT_X A wait of {x} seconds is required TAKEOUT_INIT_DELAY_X You have to confirm the data export request using one of your mobile devices or wait {x} seconds -SLOWMODE_WAIT_X A wait of {x} seconds is required to send messages in this chat. \ No newline at end of file +SLOWMODE_WAIT_X A wait of {x} seconds is required to send messages in this chat. +FLOOD_TEST_PHONE_WAIT_X A wait of {x} seconds is required in the test servers \ No newline at end of file diff --git a/compiler/error/source/500_INTERNAL_SERVER_ERROR.tsv b/compiler/error/source/500_INTERNAL_SERVER_ERROR.tsv index a2fd91cb01..cb5c50e4cc 100644 --- a/compiler/error/source/500_INTERNAL_SERVER_ERROR.tsv +++ b/compiler/error/source/500_INTERNAL_SERVER_ERROR.tsv @@ -2,13 +2,24 @@ id message AUTH_RESTART User authorization has restarted RPC_CALL_FAIL Telegram is having internal problems. Please try again later 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 -WORKER_BUSY_TOO_LONG_RETRY Telegram is having internal problems. Please try again later -INTERDC_X_CALL_ERROR Telegram is having internal problems at DC{x}. Please try again later -INTERDC_X_CALL_RICH_ERROR Telegram is having internal problems at DC{x}. Please try again later +PERSISTENT_TIMESTAMP_OUTDATED The persistent timestamp is outdated due to Telegram having internal problems. Please try again later +HISTORY_GET_FAILED The chat history couldn't be retrieved due to Telegram having internal problems. Please try again later +REG_ID_GENERATE_FAILED The registration id failed to generate due to Telegram having internal problems. Please try again later +RANDOM_ID_DUPLICATE You provided a random ID that was already used +WORKER_BUSY_TOO_LONG_RETRY Server workers are too busy right now due to Telegram having internal problems. Please try again later +INTERDC_X_CALL_ERROR An error occurred while Telegram was intercommunicating with DC{x}. Please try again later +INTERDC_X_CALL_RICH_ERROR A rich error occurred while Telegram was intercommunicating with DC{x}. Please try again later FOLDER_DEAC_AUTOFIX_ALL Telegram is having internal problems. Please try again later MSGID_DECREASE_RETRY Telegram is having internal problems. Please try again later -MEMBER_OCCUPY_PRIMARY_LOC_FAILED Telegram is having internal problems. Please try again later \ No newline at end of file +MEMBER_OCCUPY_PRIMARY_LOC_FAILED Telegram is having internal problems. Please try again later +CALL_OCCUPY_FAILED The call failed because the user is already making another call +ENCRYPTION_OCCUPY_FAILED Internal server error while accepting secret chat +MEMBER_NO_LOCATION Couldn't find the member's location due to Telegram having internal problems. Please try again later +MT_SEND_QUEUE_TOO_LONG The MTProto send queue has grown too much due to Telegram having internal problems. Please try again later +NEED_CHAT_INVALID The provided chat is invalid +NEED_MEMBER_INVALID The provided member is invalid or does not exist +PARTICIPANT_CALL_FAILED Failure while making call due to Telegram having internal problems. Please try again later +PTS_CHANGE_EMPTY No PTS change +STORAGE_CHECK_FAILED Server storage check failed due to Telegram having internal problems. Please try again later +STORE_INVALID_SCALAR_TYPE Telegram is having internal problems. Please try again later +UNKNOWN_METHOD The method you tried to call cannot be called on non-CDN DCs \ No newline at end of file From 6a8741124d64d3c5cda6e07c47993b9729ca8abb Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 13 Nov 2020 17:58:10 +0100 Subject: [PATCH 0368/1185] Sort errors, add sort.py script for future sorts --- compiler/error/sort.py | 35 +++ compiler/error/source/303_SEE_OTHER.tsv | 6 +- compiler/error/source/400_BAD_REQUEST.tsv | 292 +++++++++--------- compiler/error/source/401_UNAUTHORIZED.tsv | 14 +- compiler/error/source/403_FORBIDDEN.tsv | 12 +- compiler/error/source/406_NOT_ACCEPTABLE.tsv | 2 +- compiler/error/source/420_FLOOD.tsv | 4 +- .../source/500_INTERNAL_SERVER_ERROR.tsv | 24 +- 8 files changed, 212 insertions(+), 177 deletions(-) create mode 100644 compiler/error/sort.py diff --git a/compiler/error/sort.py b/compiler/error/sort.py new file mode 100644 index 0000000000..de25b9b192 --- /dev/null +++ b/compiler/error/sort.py @@ -0,0 +1,35 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan +# +# 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 csv +from pathlib import Path + +for p in Path("source").glob("*.tsv"): + with open(p) as f: + reader = csv.reader(f, delimiter="\t") + dct = {k: v for k, v in reader if k != "id"} + keys = sorted(dct) + + with open(p, "w") as f: + f.write("id\tmessage\n") + + for i, item in enumerate(keys, start=1): + f.write(f"{item}\t{dct[item]}") + + if i != len(keys): + f.write("\n") diff --git a/compiler/error/source/303_SEE_OTHER.tsv b/compiler/error/source/303_SEE_OTHER.tsv index 39e18d268b..62c1409df2 100644 --- a/compiler/error/source/303_SEE_OTHER.tsv +++ b/compiler/error/source/303_SEE_OTHER.tsv @@ -1,6 +1,6 @@ id message FILE_MIGRATE_X The file to be accessed is currently stored in DC{x} -PHONE_MIGRATE_X The phone number a user is trying to use for authorization is associated with DC{x} NETWORK_MIGRATE_X The source IP address is associated with DC{x} (for registration) -USER_MIGRATE_X The user whose identity is being used to execute queries is associated with DC{x} (for registration) -STATS_MIGRATE_X The statistics of the group/channel are stored in DC{x} \ No newline at end of file +PHONE_MIGRATE_X The phone number a user is trying to use for authorization is associated with DC{x} +STATS_MIGRATE_X The statistics of the group/channel are stored in DC{x} +USER_MIGRATE_X The user whose identity is being used to execute queries is associated with DC{x} (for registration) \ No newline at end of file diff --git a/compiler/error/source/400_BAD_REQUEST.tsv b/compiler/error/source/400_BAD_REQUEST.tsv index 056ed21757..50e2dec2f1 100644 --- a/compiler/error/source/400_BAD_REQUEST.tsv +++ b/compiler/error/source/400_BAD_REQUEST.tsv @@ -1,180 +1,60 @@ id message -FIRSTNAME_INVALID The first name is invalid -LASTNAME_INVALID The last name is invalid -PHONE_NUMBER_INVALID The phone number is invalid -PHONE_CODE_HASH_EMPTY The phone code hash is missing -PHONE_CODE_EMPTY The phone code is missing -PHONE_CODE_EXPIRED The confirmation code has expired -PHONE_CODE_INVALID The confirmation code is invalid -API_ID_INVALID The api_id/api_hash combination is invalid -PHONE_NUMBER_OCCUPIED The phone number is already in use -PHONE_NUMBER_UNOCCUPIED The phone number is not yet being used -USERS_TOO_FEW Not enough users (to create a chat, for example) -USERS_TOO_MUCH The maximum number of users has been exceeded (to create a chat, for example) -TYPE_CONSTRUCTOR_INVALID The type constructor is invalid -FILE_PART_INVALID The file part number is invalid. The value is not between 0 and 3999 -FILE_PARTS_INVALID Invalid number of parts. The value is not between 1 and 4000 -FILE_PART_X_MISSING Part {x} of the file is missing from storage -MD5_CHECKSUM_INVALID The file's checksum did not match the md5_checksum parameter -PHOTO_INVALID_DIMENSIONS The photo dimensions are invalid -FIELD_NAME_INVALID The field with the name FIELD_NAME is invalid -FIELD_NAME_EMPTY The field with the name FIELD_NAME is missing -MSG_WAIT_FAILED A waiting call returned an error -PEER_ID_INVALID The peer id being used is invalid or not known yet. Make sure you meet the peer before interacting with it -MESSAGE_EMPTY The message sent is empty or contains invalid characters -ENCRYPTED_MESSAGE_INVALID The special binding message (bind_auth_key_inner) contains invalid data -INPUT_METHOD_INVALID The method invoked is invalid in the current schema -PASSWORD_HASH_INVALID The two-step verification password is invalid -USERNAME_NOT_OCCUPIED The username is not occupied by anyone -USERNAME_INVALID The username is invalid -MESSAGE_ID_INVALID The message id is invalid -MESSAGE_NOT_MODIFIED The message was not modified because you tried to edit it using the same content -ENTITY_MENTION_USER_INVALID The mentioned entity is not an user -MESSAGE_TOO_LONG The message text is over 4096 characters -ACCESS_TOKEN_EXPIRED The bot token has expired -BOT_METHOD_INVALID The method can't be used by bots -QUERY_TOO_SHORT The query is too short -SEARCH_QUERY_EMPTY The search query is empty -CHAT_ID_INVALID The chat id being used is invalid or not known yet. Make sure you see the chat before interacting with it -DATE_EMPTY The date argument is empty -PERSISTENT_TIMESTAMP_EMPTY The pts argument is empty -CDN_METHOD_INVALID The method can't be used on CDN DCs -VOLUME_LOC_NOT_FOUND The volume location can't be found -FILE_ID_INVALID The file id is invalid -LOCATION_INVALID The file location is invalid -CHAT_ADMIN_REQUIRED The method requires chat admin privileges -PHONE_NUMBER_BANNED The phone number is banned from Telegram and cannot be used ABOUT_TOO_LONG The provided about/bio text is too long -MULTI_MEDIA_TOO_LONG The album/media group contains too many items -USERNAME_OCCUPIED The username is already in use by someone else -BOT_INLINE_DISABLED The inline feature of the bot is disabled -INLINE_RESULT_EXPIRED The inline bot query expired -INVITE_HASH_INVALID The invite link hash is invalid -USER_ALREADY_PARTICIPANT The user is already a participant of this chat -TTL_MEDIA_INVALID The media does not support self-destruction -MAX_ID_INVALID The max_id parameter is invalid -CHANNEL_INVALID The channel parameter is invalid -DC_ID_INVALID The dc_id parameter is invalid -LIMIT_INVALID The limit parameter is invalid -OFFSET_INVALID The offset parameter is invalid -EMAIL_INVALID The email provided is invalid -USER_IS_BOT A bot cannot send messages to other bots or to itself -WEBPAGE_CURL_FAILED Telegram server could not fetch the provided URL -STICKERSET_INVALID The requested sticker set is invalid -PEER_FLOOD The method can't be used because your account is currently limited -MEDIA_CAPTION_TOO_LONG The media caption is longer than 1024 characters -USER_NOT_MUTUAL_CONTACT The user is not a mutual contact -USER_CHANNELS_TOO_MUCH The user is already in too many channels or supergroups -API_ID_PUBLISHED_FLOOD You are using an API key that is limited on the server side because it was published somewhere -USER_NOT_PARTICIPANT The user is not a member of this chat -CHANNEL_PRIVATE The channel/supergroup is not accessible -MESSAGE_IDS_EMPTY The requested message doesn't exist or you provided no message id -WEBPAGE_MEDIA_EMPTY The URL doesn't contain any valid media -QUERY_ID_INVALID The callback query id is invalid -MEDIA_EMPTY The media you tried to send is invalid -USER_IS_BLOCKED The user blocked you -YOU_BLOCKED_USER You blocked this user -ADMINS_TOO_MUCH The chat has too many administrators -BOTS_TOO_MUCH The chat has too many bots -USER_ADMIN_INVALID The action requires admin privileges. Probably you tried to edit admin privileges on someone you don't have rights to -INPUT_USER_DEACTIVATED The target user has been deleted/deactivated -PASSWORD_RECOVERY_NA The password recovery e-mail is not available -PASSWORD_EMPTY The password provided is empty -PHONE_NUMBER_FLOOD This number has tried to login too many times -TAKEOUT_INVALID The takeout id is invalid -TAKEOUT_REQUIRED The method must be invoked inside a takeout session -MESSAGE_POLL_CLOSED You can't interact with a closed poll -MEDIA_INVALID The media is invalid -BOT_SCORE_NOT_MODIFIED The bot score was not modified -USER_BOT_REQUIRED The method can be used by bots only -IMAGE_PROCESS_FAILED The server failed to process your image -USERNAME_NOT_MODIFIED The username was not modified because you tried to edit it using the same one -CALL_ALREADY_ACCEPTED The call is already accepted -CALL_ALREADY_DECLINED The call is already declined -PHOTO_EXT_INVALID The photo extension is invalid -EXTERNAL_URL_INVALID The external media URL is invalid -CHAT_NOT_MODIFIED The chat settings (title, permissions, photo, etc..) were not modified because you tried to edit them using the same content -RESULTS_TOO_MUCH The result contains too many items -RESULT_ID_DUPLICATE The result contains items with duplicated identifiers +ACCESS_TOKEN_EXPIRED The bot token has expired ACCESS_TOKEN_INVALID The bot access token is invalid -INVITE_HASH_EXPIRED The chat invite link is no longer valid -USER_BANNED_IN_CHANNEL You are limited from sending messages in supergroups/channels, check @SpamBot for details -MESSAGE_EDIT_TIME_EXPIRED You can no longer edit this message because too much time has passed -FOLDER_ID_INVALID The folder id is invalid -MEGAGROUP_PREHISTORY_HIDDEN The action failed because the supergroup has the pre-history hidden -CHAT_LINK_EXISTS The action failed because the supergroup is linked to a channel -LINK_NOT_MODIFIED The chat link was not modified because you tried to link to the same target -BROADCAST_ID_INVALID The channel is invalid -MEGAGROUP_ID_INVALID The supergroup is invalid -BUTTON_DATA_INVALID The button callback data contains invalid data or exceeds 64 bytes -START_PARAM_INVALID The start parameter is invalid -ARTICLE_TITLE_EMPTY The article title is empty -FILE_PART_TOO_BIG The size limit (512 KB) for the content of the file part has been exceeded -FILE_PART_EMPTY The file part sent is empty -FILE_PART_SIZE_INVALID 512 KB cannot be evenly divided by part_size -FILE_PART_SIZE_CHANGED The part size is different from the size of one of the previous parts in the same file -FILE_MIGRATE_X The file is in Data Center No. {x} -RESULT_TYPE_INVALID The result type is invalid -PHOTO_THUMB_URL_EMPTY The photo thumb URL is empty -PHOTO_THUMB_URL_INVALID The photo thumb URL is invalid -PHOTO_CONTENT_URL_EMPTY The photo content URL is empty -PHOTO_CONTENT_TYPE_INVALID The photo content type is invalid -WEBDOCUMENT_INVALID The web document is invalid -WEBDOCUMENT_URL_EMPTY The web document URL is empty -WEBDOCUMENT_URL_INVALID The web document URL is invalid -WEBDOCUMENT_MIME_INVALID The web document mime type is invalid -BUTTON_URL_INVALID The button url is invalid -AUTH_BYTES_INVALID The authorization bytes are invalid -USER_ID_INVALID The user id being used is invalid or not known yet. Make sure you meet the user before interacting with it -CHANNELS_TOO_MUCH You have joined too many channels or supergroups, leave some and try again -ADMIN_RANK_INVALID The custom administrator title is invalid or is longer than 16 characters +ADMINS_TOO_MUCH The chat has too many administrators ADMIN_RANK_EMOJI_NOT_ALLOWED Emoji are not allowed in custom administrator titles -FILE_REFERENCE_EMPTY The file reference is empty -FILE_REFERENCE_INVALID The file reference is invalid -REPLY_MARKUP_TOO_LONG The reply markup is too long -SECONDS_INVALID The seconds interval is invalid, for slow mode try with 0 (off), 10, 30, 60 (1m), 300 (5m), 900 (15m) or 3600 (1h) -QUIZ_MULTIPLE_INVALID A quiz can't have multiple answers -QUIZ_CORRECT_ANSWERS_EMPTY The correct answers of the quiz are empty -QUIZ_CORRECT_ANSWER_INVALID The correct answers of the quiz are invalid -QUIZ_CORRECT_ANSWERS_TOO_MUCH The quiz contains too many correct answers -OPTIONS_TOO_MUCH The poll options are too many -POLL_ANSWERS_INVALID The poll answers are invalid -POLL_QUESTION_INVALID The poll question is invalid -FRESH_CHANGE_ADMINS_FORBIDDEN You can't change administrator settings in this chat because your session was logged-in recently -BROADCAST_PUBLIC_VOTERS_FORBIDDEN Polls with public voters cannot be sent in channels -INPUT_FILTER_INVALID The filter is invalid for this query -EMOTICON_EMPTY The emoticon parameter is empty -EMOTICON_INVALID The emoticon parameter is invalid -VIDEO_FILE_INVALID The video file is invalid -PRIVACY_TOO_LONG Your privacy exception list has exceeded the maximum capacity +ADMIN_RANK_INVALID The custom administrator title is invalid or is longer than 16 characters ALBUM_PHOTOS_TOO_MANY Too many photos were included in the album +API_ID_INVALID The api_id/api_hash combination is invalid +API_ID_PUBLISHED_FLOOD You are using an API key that is limited on the server side because it was published somewhere +ARTICLE_TITLE_EMPTY The article title is empty AUDIO_TITLE_EMPTY The title attribute of the audio is empty +AUTH_BYTES_INVALID The authorization bytes are invalid AUTH_TOKEN_ALREADY_ACCEPTED The authorization token was already used AUTH_TOKEN_EXPIRED The provided authorization token has expired and the updated QR-code must be re-scanned AUTH_TOKEN_INVALID An invalid authorization token was provided AUTOARCHIVE_NOT_AVAILABLE This feature is not yet enabled for your account due to it not receiving too many private messages from strangers BANK_CARD_NUMBER_INVALID The credit card number is invalid BANNED_RIGHTS_INVALID You provided a set of restrictions that is invalid +BOTS_TOO_MUCH The chat has too many bots BOT_CHANNELS_NA Bots can't edit admin privileges BOT_COMMAND_DESCRIPTION_INVALID The command description was empty, too long or had invalid characters BOT_DOMAIN_INVALID The domain used for the auth button does not match the one configured in @BotFather BOT_GAMES_DISABLED Bot games cannot be used in this type of chat BOT_GROUPS_BLOCKED This bot can't be added to groups +BOT_INLINE_DISABLED The inline feature of the bot is disabled BOT_INVALID This is not a valid bot +BOT_METHOD_INVALID The method can't be used by bots BOT_MISSING This method can only be run by a bot BOT_PAYMENTS_DISABLED This method can only be run by a bot BOT_POLLS_DISABLED Sending polls by bots has been disabled BOT_RESPONSE_TIMEOUT The bot did not answer to the callback query in time +BOT_SCORE_NOT_MODIFIED The bot score was not modified +BROADCAST_ID_INVALID The channel is invalid +BROADCAST_PUBLIC_VOTERS_FORBIDDEN Polls with public voters cannot be sent in channels BROADCAST_REQUIRED The request can only be used with a channel +BUTTON_DATA_INVALID The button callback data contains invalid data or exceeds 64 bytes BUTTON_TYPE_INVALID The type of one of the buttons you provided is invalid +BUTTON_URL_INVALID The button url is invalid +CALL_ALREADY_ACCEPTED The call is already accepted +CALL_ALREADY_DECLINED The call is already declined CALL_PEER_INVALID The provided call peer object is invalid CALL_PROTOCOL_FLAGS_INVALID Call protocol flags invalid +CDN_METHOD_INVALID The method can't be used on CDN DCs CHANNELS_ADMIN_PUBLIC_TOO_MUCH You are an administrator of too many public channels +CHANNELS_TOO_MUCH You have joined too many channels or supergroups, leave some and try again CHANNEL_BANNED The channel is banned +CHANNEL_INVALID The channel parameter is invalid +CHANNEL_PRIVATE The channel/supergroup is not accessible CHAT_ABOUT_NOT_MODIFIED The chat about text was not modified because you tried to edit it using the same content CHAT_ABOUT_TOO_LONG The chat about text is too long +CHAT_ADMIN_REQUIRED The method requires chat admin privileges CHAT_ID_EMPTY The provided chat id is empty +CHAT_ID_INVALID The chat id being used is invalid or not known yet. Make sure you see the chat before interacting with it CHAT_INVALID The chat is invalid +CHAT_LINK_EXISTS The action failed because the supergroup is linked to a channel +CHAT_NOT_MODIFIED The chat settings (title, permissions, photo, etc..) were not modified because you tried to edit them using the same content CHAT_RESTRICTED The chat is restricted and cannot be used CHAT_SEND_INLINE_FORBIDDEN You cannot use inline bots to send messages in this chat CHAT_TITLE_EMPTY The chat title is empty @@ -192,66 +72,149 @@ CONTACT_ID_INVALID The provided contact id is invalid CONTACT_NAME_EMPTY The provided contact name is empty DATA_INVALID The encrypted data is invalid DATA_JSON_INVALID The provided JSON data is invalid +DATE_EMPTY The date argument is empty +DC_ID_INVALID The dc_id parameter is invalid DH_G_A_INVALID The g_a parameter invalid DOCUMENT_INVALID The document is invalid EMAIL_HASH_EXPIRED The email hash expired and cannot be used to verify it +EMAIL_INVALID The email provided is invalid EMAIL_UNCONFIRMED_X Email unconfirmed, the length of the code must be {x} +EMOTICON_EMPTY The emoticon parameter is empty +EMOTICON_INVALID The emoticon parameter is invalid EMOTICON_STICKERPACK_MISSING The emoticon sticker pack you are trying to obtain is missing +ENCRYPTED_MESSAGE_INVALID The special binding message (bind_auth_key_inner) contains invalid data ENCRYPTION_ALREADY_ACCEPTED The secret chat is already accepted ENCRYPTION_ALREADY_DECLINED The secret chat is already declined ENCRYPTION_DECLINED The secret chat was declined ENCRYPTION_ID_INVALID The provided secret chat id is invalid ENTITIES_TOO_LONG The entity provided contains data that is too long, or you passed too many entities to this message +ENTITY_MENTION_USER_INVALID The mentioned entity is not an user ERROR_TEXT_EMPTY The provided error message is empty EXPORT_CARD_INVALID The provided card is invalid +EXTERNAL_URL_INVALID The external media URL is invalid +FIELD_NAME_EMPTY The field with the name FIELD_NAME is missing +FIELD_NAME_INVALID The field with the name FIELD_NAME is invalid +FILE_ID_INVALID The file id is invalid +FILE_MIGRATE_X The file is in Data Center No. {x} +FILE_PARTS_INVALID Invalid number of parts. The value is not between 1 and 4000 +FILE_PART_EMPTY The file part sent is empty +FILE_PART_INVALID The file part number is invalid. The value is not between 0 and 3999 FILE_PART_LENGTH_INVALID The length of a file part is invalid +FILE_PART_SIZE_CHANGED The part size is different from the size of one of the previous parts in the same file +FILE_PART_SIZE_INVALID 512 KB cannot be evenly divided by part_size +FILE_PART_TOO_BIG The size limit (512 KB) for the content of the file part has been exceeded +FILE_PART_X_MISSING Part {x} of the file is missing from storage +FILE_REFERENCE_EMPTY The file reference is empty FILE_REFERENCE_EXPIRED The file reference has expired and is no longer valid or it belongs to self-destructing media and cannot be resent +FILE_REFERENCE_INVALID The file reference is invalid +FIRSTNAME_INVALID The first name is invalid FOLDER_ID_EMPTY The folder you tried to delete was already empty +FOLDER_ID_INVALID The folder id is invalid +FRESH_CHANGE_ADMINS_FORBIDDEN You can't change administrator settings in this chat because your session was logged-in recently GAME_BOT_INVALID You cannot send that game with the current bot GIF_ID_INVALID The provided gif/animation id is invalid GRAPH_OUTDATED_RELOAD The graph data is outdated GROUPED_MEDIA_INVALID The album contains invalid media HASH_INVALID The provided hash is invalid +IMAGE_PROCESS_FAILED The server failed to process your image +INLINE_RESULT_EXPIRED The inline bot query expired INPUT_CONSTRUCTOR_INVALID The provided constructor is invalid INPUT_FETCH_ERROR An error occurred while deserializing TL parameters INPUT_FETCH_FAIL Failed deserializing TL payload +INPUT_FILTER_INVALID The filter is invalid for this query INPUT_LAYER_INVALID The provided layer is invalid +INPUT_METHOD_INVALID The method invoked is invalid in the current schema INPUT_REQUEST_TOO_LONG The input request is too long +INPUT_USER_DEACTIVATED The target user has been deleted/deactivated INVITE_HASH_EMPTY The invite hash is empty +INVITE_HASH_EXPIRED The chat invite link is no longer valid +INVITE_HASH_INVALID The invite link hash is invalid LANG_PACK_INVALID The provided language pack is invalid +LASTNAME_INVALID The last name is invalid +LIMIT_INVALID The limit parameter is invalid +LINK_NOT_MODIFIED The chat link was not modified because you tried to link to the same target +LOCATION_INVALID The file location is invalid +MAX_ID_INVALID The max_id parameter is invalid MAX_QTS_INVALID The provided QTS is invalid +MD5_CHECKSUM_INVALID The file's checksum did not match the md5_checksum parameter +MEDIA_CAPTION_TOO_LONG The media caption is longer than 1024 characters +MEDIA_EMPTY The media you tried to send is invalid +MEDIA_INVALID The media is invalid MEDIA_NEW_INVALID The new media to edit the message with is invalid MEDIA_PREV_INVALID The previous media cannot be edited with anything else +MEGAGROUP_ID_INVALID The supergroup is invalid +MEGAGROUP_PREHISTORY_HIDDEN The action failed because the supergroup has the pre-history hidden MEGAGROUP_REQUIRED The request can only be used with a supergroup +MESSAGE_EDIT_TIME_EXPIRED You can no longer edit this message because too much time has passed +MESSAGE_EMPTY The message sent is empty or contains invalid characters +MESSAGE_IDS_EMPTY The requested message doesn't exist or you provided no message id +MESSAGE_ID_INVALID The message id is invalid +MESSAGE_NOT_MODIFIED The message was not modified because you tried to edit it using the same content +MESSAGE_POLL_CLOSED You can't interact with a closed poll +MESSAGE_TOO_LONG The message text is over 4096 characters METHOD_INVALID The API method is invalid and cannot be used MSG_ID_INVALID The message ID used in the peer was invalid +MSG_WAIT_FAILED A waiting call returned an error +MULTI_MEDIA_TOO_LONG The album/media group contains too many items NEW_SALT_INVALID The new salt is invalid NEW_SETTINGS_INVALID The new settings are invalid +OFFSET_INVALID The offset parameter is invalid OFFSET_PEER_ID_INVALID The provided offset peer is invalid +OPTIONS_TOO_MUCH The poll options are too many OPTION_INVALID The option specified is invalid and does not exist in the target poll PACK_SHORT_NAME_INVALID Invalid sticker pack name. It must begin with a letter, can't contain consecutive underscores and must end in "_by_". PACK_SHORT_NAME_OCCUPIED A sticker pack with this name already exists PARTICIPANTS_TOO_FEW The chat doesn't have enough participants PARTICIPANT_VERSION_OUTDATED The other participant is using an outdated Telegram app version +PASSWORD_EMPTY The password provided is empty +PASSWORD_HASH_INVALID The two-step verification password is invalid PASSWORD_MISSING The account is missing the two-step verification password +PASSWORD_RECOVERY_NA The password recovery e-mail is not available PASSWORD_REQUIRED The two-step verification password is required for this method PASSWORD_TOO_FRESH_X The two-step verification password was added recently and you are required to wait {x} seconds PAYMENT_PROVIDER_INVALID The payment provider was not recognised or its token was invalid +PEER_FLOOD The method can't be used because your account is currently limited +PEER_ID_INVALID The peer id being used is invalid or not known yet. Make sure you meet the peer before interacting with it PEER_ID_NOT_SUPPORTED The provided peer id is not supported +PERSISTENT_TIMESTAMP_EMPTY The pts argument is empty PERSISTENT_TIMESTAMP_INVALID The persistent timestamp is invalid +PHONE_CODE_EMPTY The phone code is missing +PHONE_CODE_EXPIRED The confirmation code has expired +PHONE_CODE_HASH_EMPTY The phone code hash is missing +PHONE_CODE_INVALID The confirmation code is invalid PHONE_NUMBER_APP_SIGNUP_FORBIDDEN You can't sign up using this app +PHONE_NUMBER_BANNED The phone number is banned from Telegram and cannot be used +PHONE_NUMBER_FLOOD This number has tried to login too many times +PHONE_NUMBER_INVALID The phone number is invalid +PHONE_NUMBER_OCCUPIED The phone number is already in use +PHONE_NUMBER_UNOCCUPIED The phone number is not yet being used PHONE_PASSWORD_PROTECTED The phone is password protected +PHOTO_CONTENT_TYPE_INVALID The photo content type is invalid +PHOTO_CONTENT_URL_EMPTY The photo content URL is empty PHOTO_CROP_SIZE_SMALL The photo is too small +PHOTO_EXT_INVALID The photo extension is invalid PHOTO_ID_INVALID The photo id is invalid PHOTO_INVALID The photo is invalid +PHOTO_INVALID_DIMENSIONS The photo dimensions are invalid PHOTO_SAVE_FILE_INVALID The photo you tried to send cannot be saved by Telegram. A reason may be that it exceeds 10 MB. Try resizing it locally +PHOTO_THUMB_URL_EMPTY The photo thumb URL is empty +PHOTO_THUMB_URL_INVALID The photo thumb URL is invalid PIN_RESTRICTED You can't pin messages in private chats with other people +POLL_ANSWERS_INVALID The poll answers are invalid POLL_OPTION_DUPLICATE A duplicate option was sent in the same poll POLL_OPTION_INVALID A poll option used invalid data (the data may be too long) +POLL_QUESTION_INVALID The poll question is invalid POLL_UNSUPPORTED This layer does not support polls in the invoked method PRIVACY_KEY_INVALID The privacy key is invalid +PRIVACY_TOO_LONG Your privacy exception list has exceeded the maximum capacity PRIVACY_VALUE_INVALID The privacy value is invalid QUERY_ID_EMPTY The query ID is empty +QUERY_ID_INVALID The callback query id is invalid +QUERY_TOO_SHORT The query is too short +QUIZ_CORRECT_ANSWERS_EMPTY The correct answers of the quiz are empty +QUIZ_CORRECT_ANSWERS_TOO_MUCH The quiz contains too many correct answers +QUIZ_CORRECT_ANSWER_INVALID The correct answers of the quiz are invalid +QUIZ_MULTIPLE_INVALID A quiz can't have multiple answers RANDOM_ID_INVALID The provided random ID is invalid RANDOM_LENGTH_INVALID The random length is invalid RANGES_INVALID Invalid range provided @@ -259,18 +222,26 @@ REACTION_EMPTY The reaction provided is empty REACTION_INVALID Invalid reaction provided (only emoji are allowed) REPLY_MARKUP_GAME_EMPTY The provided reply markup for the game is empty REPLY_MARKUP_INVALID The provided reply markup is invalid +REPLY_MARKUP_TOO_LONG The reply markup is too long +RESULTS_TOO_MUCH The result contains too many items +RESULT_ID_DUPLICATE The result contains items with duplicated identifiers RESULT_ID_INVALID The given result cannot be used to send the selection to the bot +RESULT_TYPE_INVALID The result type is invalid RSA_DECRYPT_FAILED Internal RSA decryption failed SCHEDULE_BOT_NOT_ALLOWED Bots are not allowed to schedule messages SCHEDULE_DATE_TOO_LATE The date you tried to schedule is too far in the future (more than one year) SCHEDULE_STATUS_PRIVATE You cannot schedule a message until the person comes online if their privacy does not show this information SCHEDULE_TOO_MUCH You cannot schedule more than 100 messages in this chat +SEARCH_QUERY_EMPTY The search query is empty +SECONDS_INVALID The seconds interval is invalid, for slow mode try with 0 (off), 10, 30, 60 (1m), 300 (5m), 900 (15m) or 3600 (1h) SEND_MESSAGE_MEDIA_INVALID The message media is invalid SEND_MESSAGE_TYPE_INVALID The message type is invalid SESSION_TOO_FRESH_X You can't do this action because the current session was logged-in recently SHA256_HASH_INVALID The provided SHA256 hash is invalid SHORTNAME_OCCUPY_FAILED An error occurred when trying to register the short-name used for the sticker pack. Try a different name START_PARAM_EMPTY The start parameter is empty +START_PARAM_INVALID The start parameter is invalid +STICKERSET_INVALID The requested sticker set is invalid STICKERS_EMPTY The sticker provided is empty STICKER_DOCUMENT_INVALID The sticker document is invalid STICKER_EMOJI_INVALID The sticker emoji is invalid @@ -279,21 +250,50 @@ STICKER_ID_INVALID The provided sticker id is invalid STICKER_INVALID The provided sticker is invalid STICKER_PNG_DIMENSIONS The sticker png dimensions are invalid STICKER_PNG_NOPNG Stickers must be png files but the provided image was not a png +TAKEOUT_INVALID The takeout id is invalid +TAKEOUT_REQUIRED The method must be invoked inside a takeout session TEMP_AUTH_KEY_EMPTY The temporary auth key provided is empty THEME_MIME_INVALID You cannot create this theme because the mime-type is invalid TMP_PASSWORD_DISABLED The temporary password is disabled TOKEN_INVALID The provided token is invalid TTL_DAYS_INVALID The provided TTL days is invalid +TTL_MEDIA_INVALID The media does not support self-destruction TYPES_EMPTY The types parameter is empty +TYPE_CONSTRUCTOR_INVALID The type constructor is invalid UNTIL_DATE_INVALID That date parameter is invalid URL_INVALID The URL provided is invalid +USERNAME_INVALID The username is invalid +USERNAME_NOT_MODIFIED The username was not modified because you tried to edit it using the same one +USERNAME_NOT_OCCUPIED The username is not occupied by anyone +USERNAME_OCCUPIED The username is already in use by someone else +USERS_TOO_FEW Not enough users (to create a chat, for example) +USERS_TOO_MUCH The maximum number of users has been exceeded (to create a chat, for example) +USER_ADMIN_INVALID The action requires admin privileges. Probably you tried to edit admin privileges on someone you don't have rights to +USER_ALREADY_PARTICIPANT The user is already a participant of this chat +USER_BANNED_IN_CHANNEL You are limited from sending messages in supergroups/channels, check @SpamBot for details USER_BLOCKED The user is blocked USER_BOT Bots in channels can only be administrators, not members. USER_BOT_INVALID This method can only be used by a bot +USER_BOT_REQUIRED The method can be used by bots only +USER_CHANNELS_TOO_MUCH The user is already in too many channels or supergroups USER_CREATOR You can't leave this channel because you're its creator +USER_ID_INVALID The user id being used is invalid or not known yet. Make sure you meet the user before interacting with it USER_INVALID The provided user is invalid +USER_IS_BLOCKED The user blocked you +USER_IS_BOT A bot cannot send messages to other bots or to itself USER_KICKED This user was kicked from this chat +USER_NOT_MUTUAL_CONTACT The user is not a mutual contact +USER_NOT_PARTICIPANT The user is not a member of this chat VIDEO_CONTENT_TYPE_INVALID The video content type is invalid (i.e.: not streamable) +VIDEO_FILE_INVALID The video file is invalid +VOLUME_LOC_NOT_FOUND The volume location can't be found WALLPAPER_FILE_INVALID The provided file cannot be used as a wallpaper WALLPAPER_INVALID The input wallpaper was not valid -WC_CONVERT_URL_INVALID WC convert URL invalid \ No newline at end of file +WC_CONVERT_URL_INVALID WC convert URL invalid +WEBDOCUMENT_INVALID The web document is invalid +WEBDOCUMENT_MIME_INVALID The web document mime type is invalid +WEBDOCUMENT_URL_EMPTY The web document URL is empty +WEBDOCUMENT_URL_INVALID The web document URL is invalid +WEBPAGE_CURL_FAILED Telegram server could not fetch the provided URL +WEBPAGE_MEDIA_EMPTY The URL doesn't contain any valid media +YOU_BLOCKED_USER You blocked this user \ No newline at end of file diff --git a/compiler/error/source/401_UNAUTHORIZED.tsv b/compiler/error/source/401_UNAUTHORIZED.tsv index 120535e0e9..1bb07b89ce 100644 --- a/compiler/error/source/401_UNAUTHORIZED.tsv +++ b/compiler/error/source/401_UNAUTHORIZED.tsv @@ -1,10 +1,10 @@ id message -AUTH_KEY_UNREGISTERED The key is not registered in the system -AUTH_KEY_INVALID The key is invalid -USER_DEACTIVATED The user has been deleted/deactivated -USER_DEACTIVATED_BAN The user has been deleted/deactivated -SESSION_REVOKED The authorization has been invalidated, because of the user terminating all sessions -SESSION_EXPIRED The authorization has expired ACTIVE_USER_REQUIRED The method is only available to already activated users +AUTH_KEY_INVALID The key is invalid AUTH_KEY_PERM_EMPTY The method is unavailable for temporary authorization key, not bound to permanent -SESSION_PASSWORD_NEEDED The two-step verification is enabled and a password is required \ No newline at end of file +AUTH_KEY_UNREGISTERED The key is not registered in the system +SESSION_EXPIRED The authorization has expired +SESSION_PASSWORD_NEEDED The two-step verification is enabled and a password is required +SESSION_REVOKED The authorization has been invalidated, because of the user terminating all sessions +USER_DEACTIVATED The user has been deleted/deactivated +USER_DEACTIVATED_BAN The user has been deleted/deactivated \ No newline at end of file diff --git a/compiler/error/source/403_FORBIDDEN.tsv b/compiler/error/source/403_FORBIDDEN.tsv index 04d18e8d60..25a1e0d0f0 100644 --- a/compiler/error/source/403_FORBIDDEN.tsv +++ b/compiler/error/source/403_FORBIDDEN.tsv @@ -1,16 +1,16 @@ id message -CHAT_WRITE_FORBIDDEN You don't have rights to send messages in this chat -RIGHT_FORBIDDEN You don't have enough rights for this action, or you tried to set one or more admin rights that can't be applied to this kind of chat (channel or 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, most likely because you are not the author of them -CHAT_SEND_MEDIA_FORBIDDEN You can't send media messages in this chat -MESSAGE_AUTHOR_REQUIRED You are not the author of this message BROADCAST_FORBIDDEN The request can't be used in channels CHANNEL_PUBLIC_GROUP_NA The channel/supergroup is not available +CHAT_ADMIN_INVITE_REQUIRED You don't have rights to invite other users CHAT_FORBIDDEN You cannot write in this chat CHAT_SEND_GIFS_FORBIDDEN You can't send animations in this chat +CHAT_SEND_MEDIA_FORBIDDEN You can't send media messages in this chat CHAT_SEND_STICKERS_FORBIDDEN You can't send stickers in this chat +CHAT_WRITE_FORBIDDEN You don't have rights to send messages in this chat INLINE_BOT_REQUIRED The action must be performed through an inline bot callback +MESSAGE_AUTHOR_REQUIRED You are not the author of this message +MESSAGE_DELETE_FORBIDDEN You don't have rights to delete messages in this chat, most likely because you are not the author of them +RIGHT_FORBIDDEN You don't have enough rights for this action, or you tried to set one or more admin rights that can't be applied to this kind of chat (channel or supergroup) SENSITIVE_CHANGE_FORBIDDEN Your sensitive content settings can't be changed at this time USER_BOT_INVALID This method can only be called by a bot USER_CHANNELS_TOO_MUCH One of the users you tried to add is already in too many channels/supergroups diff --git a/compiler/error/source/406_NOT_ACCEPTABLE.tsv b/compiler/error/source/406_NOT_ACCEPTABLE.tsv index e53c7c7436..4190e0709a 100644 --- a/compiler/error/source/406_NOT_ACCEPTABLE.tsv +++ b/compiler/error/source/406_NOT_ACCEPTABLE.tsv @@ -1,9 +1,9 @@ id message AUTH_KEY_DUPLICATED The same authorization key (session file) was used in more than one place simultaneously. You must delete your session file and log in again with your phone number or bot token FILEREF_UPGRADE_NEEDED The file reference has expired and you must use a refreshed one by obtaining the original media message -STICKERSET_INVALID The sticker set is invalid FRESH_CHANGE_PHONE_FORBIDDEN You can't change your phone number because your session was logged-in recently FRESH_RESET_AUTHORISATION_FORBIDDEN You can't terminate other authorized sessions because the current was logged-in recently PHONE_NUMBER_INVALID The phone number is invalid PHONE_PASSWORD_FLOOD You have tried to log-in too many times +STICKERSET_INVALID The sticker set is invalid STICKERSET_OWNER_ANONYMOUS This sticker set can't be used as the group's sticker set because it was created by one of its anonymous admins \ No newline at end of file diff --git a/compiler/error/source/420_FLOOD.tsv b/compiler/error/source/420_FLOOD.tsv index f0eb7b6109..a8a573351e 100644 --- a/compiler/error/source/420_FLOOD.tsv +++ b/compiler/error/source/420_FLOOD.tsv @@ -1,5 +1,5 @@ id message +FLOOD_TEST_PHONE_WAIT_X A wait of {x} seconds is required in the test servers FLOOD_WAIT_X A wait of {x} seconds is required -TAKEOUT_INIT_DELAY_X You have to confirm the data export request using one of your mobile devices or wait {x} seconds SLOWMODE_WAIT_X A wait of {x} seconds is required to send messages in this chat. -FLOOD_TEST_PHONE_WAIT_X A wait of {x} seconds is required in the test servers \ No newline at end of file +TAKEOUT_INIT_DELAY_X You have to confirm the data export request using one of your mobile devices or wait {x} seconds \ No newline at end of file diff --git a/compiler/error/source/500_INTERNAL_SERVER_ERROR.tsv b/compiler/error/source/500_INTERNAL_SERVER_ERROR.tsv index cb5c50e4cc..75fc1fcf9a 100644 --- a/compiler/error/source/500_INTERNAL_SERVER_ERROR.tsv +++ b/compiler/error/source/500_INTERNAL_SERVER_ERROR.tsv @@ -1,25 +1,25 @@ id message AUTH_RESTART User authorization has restarted -RPC_CALL_FAIL Telegram is having internal problems. Please try again later -RPC_MCGET_FAIL Telegram is having internal problems. Please try again later -PERSISTENT_TIMESTAMP_OUTDATED The persistent timestamp is outdated due to Telegram having internal problems. Please try again later +CALL_OCCUPY_FAILED The call failed because the user is already making another call +ENCRYPTION_OCCUPY_FAILED Internal server error while accepting secret chat +FOLDER_DEAC_AUTOFIX_ALL Telegram is having internal problems. Please try again later HISTORY_GET_FAILED The chat history couldn't be retrieved due to Telegram having internal problems. Please try again later -REG_ID_GENERATE_FAILED The registration id failed to generate due to Telegram having internal problems. Please try again later -RANDOM_ID_DUPLICATE You provided a random ID that was already used -WORKER_BUSY_TOO_LONG_RETRY Server workers are too busy right now due to Telegram having internal problems. Please try again later INTERDC_X_CALL_ERROR An error occurred while Telegram was intercommunicating with DC{x}. Please try again later INTERDC_X_CALL_RICH_ERROR A rich error occurred while Telegram was intercommunicating with DC{x}. Please try again later -FOLDER_DEAC_AUTOFIX_ALL Telegram is having internal problems. Please try again later -MSGID_DECREASE_RETRY Telegram is having internal problems. Please try again later -MEMBER_OCCUPY_PRIMARY_LOC_FAILED Telegram is having internal problems. Please try again later -CALL_OCCUPY_FAILED The call failed because the user is already making another call -ENCRYPTION_OCCUPY_FAILED Internal server error while accepting secret chat MEMBER_NO_LOCATION Couldn't find the member's location due to Telegram having internal problems. Please try again later +MEMBER_OCCUPY_PRIMARY_LOC_FAILED Telegram is having internal problems. Please try again later +MSGID_DECREASE_RETRY Telegram is having internal problems. Please try again later MT_SEND_QUEUE_TOO_LONG The MTProto send queue has grown too much due to Telegram having internal problems. Please try again later NEED_CHAT_INVALID The provided chat is invalid NEED_MEMBER_INVALID The provided member is invalid or does not exist PARTICIPANT_CALL_FAILED Failure while making call due to Telegram having internal problems. Please try again later +PERSISTENT_TIMESTAMP_OUTDATED The persistent timestamp is outdated due to Telegram having internal problems. Please try again later PTS_CHANGE_EMPTY No PTS change +RANDOM_ID_DUPLICATE You provided a random ID that was already used +REG_ID_GENERATE_FAILED The registration id failed to generate due to Telegram having internal problems. Please try again later +RPC_CALL_FAIL Telegram is having internal problems. Please try again later +RPC_MCGET_FAIL Telegram is having internal problems. Please try again later STORAGE_CHECK_FAILED Server storage check failed due to Telegram having internal problems. Please try again later STORE_INVALID_SCALAR_TYPE Telegram is having internal problems. Please try again later -UNKNOWN_METHOD The method you tried to call cannot be called on non-CDN DCs \ No newline at end of file +UNKNOWN_METHOD The method you tried to call cannot be called on non-CDN DCs +WORKER_BUSY_TOO_LONG_RETRY Server workers are too busy right now due to Telegram having internal problems. Please try again later \ No newline at end of file From 1b42fb7f778379e350506d238d3046ee120a200a Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 13 Nov 2020 17:59:41 +0100 Subject: [PATCH 0369/1185] Rename "error" package to "errors" --- compiler/{error => errors}/__init__.py | 0 compiler/{error => errors}/compiler.py | 0 compiler/{error => errors}/sort.py | 0 compiler/{error => errors}/source/303_SEE_OTHER.tsv | 0 compiler/{error => errors}/source/400_BAD_REQUEST.tsv | 0 compiler/{error => errors}/source/401_UNAUTHORIZED.tsv | 0 compiler/{error => errors}/source/403_FORBIDDEN.tsv | 0 compiler/{error => errors}/source/406_NOT_ACCEPTABLE.tsv | 0 compiler/{error => errors}/source/420_FLOOD.tsv | 0 compiler/{error => errors}/source/500_INTERNAL_SERVER_ERROR.tsv | 0 compiler/{error => errors}/template/class.txt | 0 compiler/{error => errors}/template/sub_class.txt | 0 setup.py | 2 +- 13 files changed, 1 insertion(+), 1 deletion(-) rename compiler/{error => errors}/__init__.py (100%) rename compiler/{error => errors}/compiler.py (100%) rename compiler/{error => errors}/sort.py (100%) rename compiler/{error => errors}/source/303_SEE_OTHER.tsv (100%) rename compiler/{error => errors}/source/400_BAD_REQUEST.tsv (100%) rename compiler/{error => errors}/source/401_UNAUTHORIZED.tsv (100%) rename compiler/{error => errors}/source/403_FORBIDDEN.tsv (100%) rename compiler/{error => errors}/source/406_NOT_ACCEPTABLE.tsv (100%) rename compiler/{error => errors}/source/420_FLOOD.tsv (100%) rename compiler/{error => errors}/source/500_INTERNAL_SERVER_ERROR.tsv (100%) rename compiler/{error => errors}/template/class.txt (100%) rename compiler/{error => errors}/template/sub_class.txt (100%) diff --git a/compiler/error/__init__.py b/compiler/errors/__init__.py similarity index 100% rename from compiler/error/__init__.py rename to compiler/errors/__init__.py diff --git a/compiler/error/compiler.py b/compiler/errors/compiler.py similarity index 100% rename from compiler/error/compiler.py rename to compiler/errors/compiler.py diff --git a/compiler/error/sort.py b/compiler/errors/sort.py similarity index 100% rename from compiler/error/sort.py rename to compiler/errors/sort.py diff --git a/compiler/error/source/303_SEE_OTHER.tsv b/compiler/errors/source/303_SEE_OTHER.tsv similarity index 100% rename from compiler/error/source/303_SEE_OTHER.tsv rename to compiler/errors/source/303_SEE_OTHER.tsv diff --git a/compiler/error/source/400_BAD_REQUEST.tsv b/compiler/errors/source/400_BAD_REQUEST.tsv similarity index 100% rename from compiler/error/source/400_BAD_REQUEST.tsv rename to compiler/errors/source/400_BAD_REQUEST.tsv diff --git a/compiler/error/source/401_UNAUTHORIZED.tsv b/compiler/errors/source/401_UNAUTHORIZED.tsv similarity index 100% rename from compiler/error/source/401_UNAUTHORIZED.tsv rename to compiler/errors/source/401_UNAUTHORIZED.tsv diff --git a/compiler/error/source/403_FORBIDDEN.tsv b/compiler/errors/source/403_FORBIDDEN.tsv similarity index 100% rename from compiler/error/source/403_FORBIDDEN.tsv rename to compiler/errors/source/403_FORBIDDEN.tsv diff --git a/compiler/error/source/406_NOT_ACCEPTABLE.tsv b/compiler/errors/source/406_NOT_ACCEPTABLE.tsv similarity index 100% rename from compiler/error/source/406_NOT_ACCEPTABLE.tsv rename to compiler/errors/source/406_NOT_ACCEPTABLE.tsv diff --git a/compiler/error/source/420_FLOOD.tsv b/compiler/errors/source/420_FLOOD.tsv similarity index 100% rename from compiler/error/source/420_FLOOD.tsv rename to compiler/errors/source/420_FLOOD.tsv diff --git a/compiler/error/source/500_INTERNAL_SERVER_ERROR.tsv b/compiler/errors/source/500_INTERNAL_SERVER_ERROR.tsv similarity index 100% rename from compiler/error/source/500_INTERNAL_SERVER_ERROR.tsv rename to compiler/errors/source/500_INTERNAL_SERVER_ERROR.tsv diff --git a/compiler/error/template/class.txt b/compiler/errors/template/class.txt similarity index 100% rename from compiler/error/template/class.txt rename to compiler/errors/template/class.txt diff --git a/compiler/error/template/sub_class.txt b/compiler/errors/template/sub_class.txt similarity index 100% rename from compiler/error/template/sub_class.txt rename to compiler/errors/template/sub_class.txt diff --git a/setup.py b/setup.py index 28942b6b36..c26ee2ddf5 100644 --- a/setup.py +++ b/setup.py @@ -25,7 +25,7 @@ from compiler.api import compiler as api_compiler from compiler.docs import compiler as docs_compiler -from compiler.error import compiler as error_compiler +from compiler.errors import compiler as error_compiler with open("requirements.txt", encoding="utf-8") as r: requires = [i.strip() for i in r] From 301a13edd2294f8bb4654d31a3d071cb9d8903c5 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 20 Nov 2020 01:22:01 +0100 Subject: [PATCH 0370/1185] Add file_id.py A module to deal with Telegram file ids --- pyrogram/file_id.py | 478 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 478 insertions(+) create mode 100644 pyrogram/file_id.py diff --git a/pyrogram/file_id.py b/pyrogram/file_id.py new file mode 100644 index 0000000000..b64de42397 --- /dev/null +++ b/pyrogram/file_id.py @@ -0,0 +1,478 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan +# +# 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 logging +import struct +from enum import IntEnum +from io import BytesIO + +from pyrogram.raw.core import Bytes, String + +log = logging.getLogger(__name__) + + +def b64_encode(s: bytes) -> str: + """Encode bytes into a URL-safe Base64 string without padding + + Parameters: + s (``bytes``): + Bytes to encode + + Returns: + ``str``: The encoded bytes + """ + return base64.urlsafe_b64encode(s).decode().strip("=") + + +def b64_decode(s: str) -> bytes: + """Decode a URL-safe Base64 string without padding to bytes + + Parameters: + s (``str``): + String to decode + + Returns: + ``bytes``: The decoded string + """ + return base64.urlsafe_b64decode(s + "=" * (-len(s) % 4)) + + +def rle_encode(s: bytes) -> bytes: + """Zero-value RLE encoder + + Parameters: + s (``bytes``): + Bytes to encode + + Returns: + ``bytes``: The encoded bytes + """ + r = b"" + n = 0 + + for b in s: + if b == 0: + n += 1 + else: + if n > 0: + r += bytes([0, n]) + n = 0 + + r += bytes([b]) + + if n > 0: + r += bytes([0, n]) + + return r + + +def rle_decode(s: bytes) -> bytes: + """Zero-value RLE decoder + + Parameters: + s (``bytes``): + Bytes to encode + + Returns: + ``bytes``: The encoded bytes + """ + r = b"" + i = 0 + + while i < len(s): + if s[i] != 0: + r += bytes([s[i]]) + else: + r += b"\x00" * s[i + 1] + i += 1 + + i += 1 + + return r + + +class FileType(IntEnum): + """Known file types""" + THUMBNAIL = 0 + CHAT_PHOTO = 1 # ProfilePhoto + PHOTO = 2 + VOICE = 3 # VoiceNote + VIDEO = 4 + DOCUMENT = 5 + ENCRYPTED = 6 + TEMP = 7 + STICKER = 8 + AUDIO = 9 + ANIMATION = 10 + ENCRYPTED_THUMBNAIL = 11 + WALLPAPER = 12 + VIDEO_NOTE = 13 + SECURE_RAW = 14 + SECURE = 15 + BACKGROUND = 16 + DOCUMENT_AS_FILE = 17 + + +class ThumbnailSource(IntEnum): + """Known thumbnail sources""" + LEGACY = 0 + THUMBNAIL = 1 + CHAT_PHOTO_SMALL = 2 # DialogPhotoSmall + CHAT_PHOTO_BIG = 3 # DialogPhotoBig + STICKER_SET_THUMBNAIL = 4 + + +# Photo-like file ids are longer and contain extra info, the rest are all documents +PHOTO_TYPES = {FileType.THUMBNAIL, FileType.CHAT_PHOTO, FileType.PHOTO, FileType.WALLPAPER, + FileType.ENCRYPTED_THUMBNAIL} +DOCUMENT_TYPES = set(FileType) - PHOTO_TYPES + +# Since the file type values are small enough to fit them in few bits, Telegram thought it would be a good idea to +# encode extra information about web url and file reference existence as flag inside the 4 bytes allocated for the field +WEB_LOCATION_FLAG = 1 << 24 +FILE_REFERENCE_FLAG = 1 << 25 + + +class FileId: + MAJOR = 4 + MINOR = 30 + + def __init__( + self, *, + major: int = MAJOR, + minor: int = MINOR, + file_type: FileType, + dc_id: int, + file_reference: bytes = None, + url: str = None, + media_id: int = None, + access_hash: int = None, + volume_id: int = None, + thumbnail_source: ThumbnailSource = None, + thumbnail_file_type: str = None, + thumbnail_size: str = None, + secret: int = None, + local_id: str = None, + chat_id: int = None, + chat_access_hash: int = None, + sticker_set_id: int = None, + sticker_set_access_hash: int = None + ): + self.major = major + self.minor = minor + self.file_type = file_type + self.dc_id = dc_id + self.file_reference = file_reference + self.url = url + self.media_id = media_id + self.access_hash = access_hash + self.volume_id = volume_id + self.thumbnail_source = thumbnail_source + self.thumbnail_file_type = thumbnail_file_type + self.thumbnail_size = thumbnail_size + self.secret = secret + self.local_id = local_id + self.chat_id = chat_id + self.chat_access_hash = chat_access_hash + self.sticker_set_id = sticker_set_id + self.sticker_set_access_hash = sticker_set_access_hash + + @staticmethod + def decode(file_id: str): + decoded = rle_decode(b64_decode(file_id)) + + # region read version + # File id versioning. Major versions lower than 4 don't have a minor version + major = decoded[-1] + + if major < 4: + minor = 0 + buffer = BytesIO(decoded[:-1]) + else: + minor = decoded[-2] + buffer = BytesIO(decoded[:-2]) + # endregion + + file_type, dc_id = struct.unpack("= 4: + buffer.write(struct.pack(" Date: Fri, 20 Nov 2020 01:22:37 +0100 Subject: [PATCH 0371/1185] Add file_id.py tests --- tests/__init__.py | 17 +++++ tests/test_file_id.py | 157 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 174 insertions(+) create mode 100644 tests/__init__.py create mode 100644 tests/test_file_id.py diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000000..00a6c16df9 --- /dev/null +++ b/tests/__init__.py @@ -0,0 +1,17 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan +# +# 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 . diff --git a/tests/test_file_id.py b/tests/test_file_id.py new file mode 100644 index 0000000000..3ec029d341 --- /dev/null +++ b/tests/test_file_id.py @@ -0,0 +1,157 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan +# +# 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 pyrogram.file_id import FileId, FileUniqueId, FileType, FileUniqueType + + +def check(file_id: str, expected_file_type: FileType): + decoded = FileId.decode(file_id) + + assert decoded.file_type == expected_file_type + assert decoded.encode() == file_id + + +def check_unique(file_unique_id: str, expected_file_unique_type: FileUniqueType): + decoded = FileUniqueId.decode(file_unique_id) + + assert decoded.file_unique_type == expected_file_unique_type + assert decoded.encode() == file_unique_id + + +def test_audio(): + audio = "CQACAgIAAx0CAAGgr9AAAgmQX7b4XPBstC1fFUuJBooHTHFd7HMAAgUAA4GkuUnVOGG5P196yR4E" + audio_unique = "AgADBQADgaS5SQ" + audio_thumb = "AAMCAgADHQIAAaCv0AACCZBftvhc8Gy0LV8VS4kGigdMcV3scwACBQADgaS5SdU4Ybk_X3rJIH3qihAAAwEAB20AA_OeAQABHgQ" + audio_thumb_unique = "AQADIH3qihAAA_OeAQAB" + + check(audio, FileType.AUDIO) + check_unique(audio_unique, FileUniqueType.DOCUMENT) + check(audio_thumb, FileType.THUMBNAIL) + check_unique(audio_thumb_unique, FileUniqueType.PHOTO) + + +def test_video(): + video = "BAACAgIAAx0CAAGgr9AAAgmRX7b4Xv9f-4BK5VR_5ppIOF6UIp0AAgYAA4GkuUmhnZz2xC37wR4E" + video_unique = "AgADBgADgaS5SQ" + video_thumb = "AAMCAgADHQIAAaCv0AACCZFftvhe_1_7gErlVH_mmkg4XpQinQACBgADgaS5SaGdnPbELfvBIH3qihAAAwEAB20AA_WeAQABHgQ" + video_thumb_unique = "AQADIH3qihAAA_WeAQAB" + + check(video, FileType.VIDEO) + check_unique(video_unique, FileUniqueType.DOCUMENT) + check(video_thumb, FileType.THUMBNAIL) + check_unique(video_thumb_unique, FileUniqueType.PHOTO) + + +def test_document(): + document = "BQACAgIAAx0CAAGgr9AAAgmPX7b4UxbjNoFEO_L0I4s6wrXNJA8AAgQAA4GkuUm9FFvIaOhXWR4E" + document_unique = "AgADBAADgaS5SQ" + document_thumb = "AAMCAgADHQIAAaCv0AACCY9ftvhTFuM2gUQ78vQjizrCtc0kDwACBAADgaS5Sb0UW8ho6FdZIH3qihAAAwEAB3MAA_GeAQABHgQ" + document_thumb_unique = "AQADIH3qihAAA_GeAQAB" + + check(document, FileType.DOCUMENT) + check_unique(document_unique, FileUniqueType.DOCUMENT) + check(document_thumb, FileType.THUMBNAIL) + check_unique(document_thumb_unique, FileUniqueType.PHOTO) + + +def test_animation(): + animation = "CgACAgIAAx0CAAGgr9AAAgmSX7b4Y2g8_QW2XFd49iUmRnHOyG8AAgcAA4GkuUnry9gWDzF_5R4E" + animation_unique = "AgADBwADgaS5SQ" + + check(animation, FileType.ANIMATION) + check_unique(animation_unique, FileUniqueType.DOCUMENT) + + +def test_voice(): + voice = "AwACAgIAAx0CAAGgr9AAAgmUX7b4c1KQyHVwzffxC2EnSYWsMAQAAgkAA4GkuUlsZUZ4_I97AR4E" + voice_unique = "AgADCQADgaS5SQ" + + check(voice, FileType.VOICE) + check_unique(voice_unique, FileUniqueType.DOCUMENT) + + +def test_video_note(): + video_note = "DQACAgIAAx0CAAGgr9AAAgmVX7b53qrRzCEO13BaLQJaYuFbdlwAAgoAA4GkuUmlqIzDy_PCsx4E" + video_note_unique = "AgADCgADgaS5SQ" + video_note_thumb = "AAMCAgADHQIAAaCv0AACCZVftvneqtHMIQ7XcFotAlpi4Vt2XAACCgADgaS5SaWojMPL88KzIH3qihAAAwEAB20AA_meAQABHgQ" + video_note_thumb_unique = "AQADIH3qihAAA_meAQAB" + + check(video_note, FileType.VIDEO_NOTE) + check_unique(video_note_unique, FileUniqueType.DOCUMENT) + check(video_note_thumb, FileType.THUMBNAIL) + check_unique(video_note_thumb_unique, FileUniqueType.PHOTO) + + +def test_sticker(): + sticker = "CAACAgEAAx0CAAGgr9AAAgmWX7b6uFeLlhXEgYrM8pIbGaQKRQ0AAswBAALjeAQAAbeooNv_tb6-HgQ" + sticker_unique = "AgADzAEAAuN4BAAB" + sticker_thumb = "AAMCAQADHQIAAaCv0AACCZZftvq4V4uWFcSBiszykhsZpApFDQACzAEAAuN4BAABt6ig2_-1vr5gWNkpAAQBAAdtAAM0BQACHgQ" + sticker_thumb_unique = "AQADYFjZKQAENAUAAg" + + check(sticker, FileType.STICKER) + check_unique(sticker_unique, FileUniqueType.DOCUMENT) + check(sticker_thumb, FileType.THUMBNAIL) + check_unique(sticker_thumb_unique, FileUniqueType.PHOTO) + + +def test_photo(): + photo_small = "AgACAgIAAx0CAAGgr9AAAgmZX7b7IPLRl8NcV3EJkzHwI1gwT-oAAq2nMRuBpLlJPJY-URZfhTkgfeqKEAADAQADAgADbQADAZ8BAAEeBA" + photo_small_unique = "AQADIH3qihAAAwGfAQAB" + photo_medium = "AgACAgIAAx0CAAGgr9AAAgmZX7b7IPLRl8NcV3EJkzHwI1gwT-oAAq2nMRuBpLlJPJY-URZfhTkgfeqKEAADAQADAgADeAADAp8BAAEeBA" + photo_medium_unique = "AQADIH3qihAAAwKfAQAB" + photo_big = "AgACAgIAAx0CAAGgr9AAAgmZX7b7IPLRl8NcV3EJkzHwI1gwT-oAAq2nMRuBpLlJPJY-URZfhTkgfeqKEAADAQADAgADeQAD_54BAAEeBA" + photo_big_unique = "AQADIH3qihAAA_-eAQAB" + + check(photo_small, FileType.PHOTO) + check_unique(photo_small_unique, FileUniqueType.PHOTO) + check(photo_medium, FileType.PHOTO) + check_unique(photo_medium_unique, FileUniqueType.PHOTO) + check(photo_big, FileType.PHOTO) + check_unique(photo_big_unique, FileUniqueType.PHOTO) + + +def test_chat_photo(): + user_photo_small = "AQADAgADrKcxGylBBQAJIH3qihAAAwIAAylBBQAF7bDHYwABnc983KcAAh4E" + user_photo_small_unique = "AQADIH3qihAAA9ynAAI" + user_photo_big = "AQADAgADrKcxGylBBQAJIH3qihAAAwMAAylBBQAF7bDHYwABnc983qcAAh4E" + user_photo_big_unique = "AQADIH3qihAAA96nAAI" + + chat_photo_small = "AQADAgATIH3qihAAAwIAA3t3-P______AAjhngEAAR4E" + chat_photo_small_unique = "AQADIH3qihAAA-GeAQAB" + chat_photo_big = "AQADAgATIH3qihAAAwMAA3t3-P______AAjjngEAAR4E" + chat_photo_big_unique = "AQADIH3qihAAA-OeAQAB" + + channel_photo_small = "AQADAgATIH3qihAAAwIAA-fFwCoX____MvARg8nvpc3RpwACHgQ" + channel_photo_small_unique = "AQADIH3qihAAA9GnAAI" + channel_photo_big = "AQADAgATIH3qihAAAwMAA-fFwCoX____MvARg8nvpc3TpwACHgQ" + channel_photo_big_unique = "AQADIH3qihAAA9OnAAI" + + check(user_photo_small, FileType.CHAT_PHOTO) + check_unique(user_photo_small_unique, FileUniqueType.PHOTO) + check(user_photo_big, FileType.CHAT_PHOTO) + check_unique(user_photo_big_unique, FileUniqueType.PHOTO) + + check(chat_photo_small, FileType.CHAT_PHOTO) + check_unique(chat_photo_small_unique, FileUniqueType.PHOTO) + check(chat_photo_big, FileType.CHAT_PHOTO) + check_unique(chat_photo_big_unique, FileUniqueType.PHOTO) + + check(channel_photo_small, FileType.CHAT_PHOTO) + check_unique(channel_photo_small_unique, FileUniqueType.PHOTO) + check(channel_photo_big, FileType.CHAT_PHOTO) + check_unique(channel_photo_big_unique, FileUniqueType.PHOTO) From 91f3a16d08204945a13b12c71c5ff72168d350e2 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 20 Nov 2020 01:23:16 +0100 Subject: [PATCH 0372/1185] Make use of tox for tests --- dev-requirements.txt | 3 +++ tox.ini | 4 ++++ 2 files changed, 7 insertions(+) create mode 100644 dev-requirements.txt create mode 100644 tox.ini diff --git a/dev-requirements.txt b/dev-requirements.txt new file mode 100644 index 0000000000..ed89aca2e4 --- /dev/null +++ b/dev-requirements.txt @@ -0,0 +1,3 @@ +-r requirements.txt + +pytest \ No newline at end of file diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000000..af7893a13b --- /dev/null +++ b/tox.ini @@ -0,0 +1,4 @@ +[testenv] +deps = -rdev-requirements.txt +commands = pytest {posargs} +skip_install = true \ No newline at end of file From 6e7c0309b564a1b6581c49e7dce8dc8a5f02fe63 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 20 Nov 2020 01:24:42 +0100 Subject: [PATCH 0373/1185] Add GitHub workflow actions --- .github/workflows/python.yml | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 .github/workflows/python.yml diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml new file mode 100644 index 0000000000..6241be274e --- /dev/null +++ b/.github/workflows/python.yml @@ -0,0 +1,25 @@ +name: Pyrogram + +on: [push] + +jobs: + build: + + runs-on: ubuntu-latest + strategy: + matrix: + python-version: [3.6, 3.7, 3.8, 3.9] + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install tox + - name: Run tests + run: | + tox \ No newline at end of file From bbd2a23c2d06376e3c419642441db20e7712840d Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 20 Nov 2020 01:48:13 +0100 Subject: [PATCH 0374/1185] Fix missing API when running actions --- .github/workflows/python.yml | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 6241be274e..3aabee8762 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -1,6 +1,6 @@ name: Pyrogram -on: [push] +on: [ push ] jobs: build: @@ -8,18 +8,25 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [3.6, 3.7, 3.8, 3.9] + python-version: [ 3.6, 3.7, 3.8, 3.9 ] steps: - - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install tox - - name: Run tests - run: | - tox \ No newline at end of file + - uses: actions/checkout@v2 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install tox + + - name: Generate API + run: | + python setup.py generate --api + + - name: Run tests + run: | + tox \ No newline at end of file From 76985faa118835e1b8ee2e5fb8ce51631d90ffd4 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 20 Nov 2020 01:53:19 +0100 Subject: [PATCH 0375/1185] Evaluate debug raw data only when actually needed #541 --- pyrogram/session/session.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index 3aecaab3af..490eea54f4 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -243,7 +243,10 @@ async def handle_packet(self, packet): else [data] ) - log.debug(f"Received:\n{data}") + # Call log.debug twice because calling it once by appending "data" to the previous string (i.e. f"Kind: {data}") + # will cause "data" to be evaluated as string every time instead of only when debug is actually enabled. + log.debug("Received:") + log.debug(data) for msg in messages: if msg.seq_no == 0: @@ -367,7 +370,10 @@ async def _send(self, data: TLObject, wait_response: bool = True, timeout: float if wait_response: self.results[msg_id] = Result() - log.debug(f"Sent:\n{message}") + # Call log.debug twice because calling it once by appending "data" to the previous string (i.e. f"Kind: {data}") + # will cause "data" to be evaluated as string every time instead of only when debug is actually enabled. + log.debug(f"Sent:") + log.debug(message) if len(message) <= self.EXECUTOR_SIZE_THRESHOLD: payload = mtproto.pack( From bf7bd45f1c7cce20c0a8d7183aa2abae0170f7d2 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 20 Nov 2020 02:04:24 +0100 Subject: [PATCH 0376/1185] Fix compiled errors destination path --- compiler/errors/compiler.py | 2 +- setup.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/errors/compiler.py b/compiler/errors/compiler.py index 222fdfcabc..d8f556a05f 100644 --- a/compiler/errors/compiler.py +++ b/compiler/errors/compiler.py @@ -21,7 +21,7 @@ import re import shutil -HOME = "compiler/error" +HOME = "compiler/errors" DEST = "pyrogram/errors/exceptions" NOTICE_PATH = "NOTICE" diff --git a/setup.py b/setup.py index c26ee2ddf5..4f67b52cf7 100644 --- a/setup.py +++ b/setup.py @@ -25,7 +25,7 @@ from compiler.api import compiler as api_compiler from compiler.docs import compiler as docs_compiler -from compiler.errors import compiler as error_compiler +from compiler.errors import compiler as errors_compiler with open("requirements.txt", encoding="utf-8") as r: requires = [i.strip() for i in r] @@ -119,7 +119,7 @@ def finalize_options(self): def run(self): if self.api: - error_compiler.start() + errors_compiler.start() api_compiler.start() if self.docs: @@ -128,7 +128,7 @@ def run(self): if len(argv) > 1 and argv[1] in ["bdist_wheel", "install", "develop"]: api_compiler.start() - error_compiler.start() + errors_compiler.start() setup( name="Pyrogram", From 63b3a61ae4d3bc4341a66edc9a1d8e9073637a60 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 20 Nov 2020 02:07:29 +0100 Subject: [PATCH 0377/1185] Add macOS and Windows environments for tests --- .github/workflows/python.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 3aabee8762..7a4b996e7b 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -5,9 +5,10 @@ on: [ push ] jobs: build: - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} strategy: matrix: + os: [ ubuntu-latest, macos-latest, windows-latest ] python-version: [ 3.6, 3.7, 3.8, 3.9 ] steps: From 2f127451fb7ccd7b93c9d32056cebe5f9cd7f670 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 20 Nov 2020 03:27:54 +0100 Subject: [PATCH 0378/1185] Add github.com/delivrance to FUNDING.yml --- .github/FUNDING.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 3437aeae85..f34f615a8c 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,2 +1,2 @@ -# github: delivrance +github: delivrance custom: https://docs.pyrogram.org/support-pyrogram From a2d7da1f591e135dd5a8948109c3cb1af1ec374f Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 24 Nov 2020 16:11:13 +0100 Subject: [PATCH 0379/1185] Fix Illegal quoting --- compiler/errors/source/400_BAD_REQUEST.tsv | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/errors/source/400_BAD_REQUEST.tsv b/compiler/errors/source/400_BAD_REQUEST.tsv index 50e2dec2f1..c72985bf51 100644 --- a/compiler/errors/source/400_BAD_REQUEST.tsv +++ b/compiler/errors/source/400_BAD_REQUEST.tsv @@ -162,7 +162,7 @@ OFFSET_INVALID The offset parameter is invalid OFFSET_PEER_ID_INVALID The provided offset peer is invalid OPTIONS_TOO_MUCH The poll options are too many OPTION_INVALID The option specified is invalid and does not exist in the target poll -PACK_SHORT_NAME_INVALID Invalid sticker pack name. It must begin with a letter, can't contain consecutive underscores and must end in "_by_". +PACK_SHORT_NAME_INVALID Invalid sticker pack name. It must begin with a letter, can't contain consecutive underscores and must end in 'by_'. PACK_SHORT_NAME_OCCUPIED A sticker pack with this name already exists PARTICIPANTS_TOO_FEW The chat doesn't have enough participants PARTICIPANT_VERSION_OUTDATED The other participant is using an outdated Telegram app version @@ -296,4 +296,4 @@ WEBDOCUMENT_URL_EMPTY The web document URL is empty WEBDOCUMENT_URL_INVALID The web document URL is invalid WEBPAGE_CURL_FAILED Telegram server could not fetch the provided URL WEBPAGE_MEDIA_EMPTY The URL doesn't contain any valid media -YOU_BLOCKED_USER You blocked this user \ No newline at end of file +YOU_BLOCKED_USER You blocked this user From fa820328c3edfe0ea22aa9ff56a52cde6a04198b Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 27 Nov 2020 14:01:13 +0100 Subject: [PATCH 0380/1185] Filter None values out from stringified file ids --- pyrogram/file_id.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/file_id.py b/pyrogram/file_id.py index b64de42397..0fa5e3d066 100644 --- a/pyrogram/file_id.py +++ b/pyrogram/file_id.py @@ -395,7 +395,7 @@ def encode(self, *, major: int = None, minor: int = None): return b64_encode(rle_encode(buffer.getvalue())) def __str__(self): - return str(self.__dict__) + return str({k: v for k, v in self.__dict__.items() if v is not None}) class FileUniqueType(IntEnum): @@ -475,4 +475,4 @@ def encode(self): return b64_encode(rle_encode(string)) def __str__(self): - return str(self.__dict__) + return str({k: v for k, v in self.__dict__.items() if v is not None}) From 786235f3163af5f9eb9ae3a1bac6a9f3c538ff62 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 27 Nov 2020 14:12:49 +0100 Subject: [PATCH 0381/1185] Fix version being taken from class fields instead of instance fields --- pyrogram/file_id.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/file_id.py b/pyrogram/file_id.py index 0fa5e3d066..8bc6d5f9dd 100644 --- a/pyrogram/file_id.py +++ b/pyrogram/file_id.py @@ -335,8 +335,8 @@ def decode(file_id: str): ) def encode(self, *, major: int = None, minor: int = None): - major = major if major is not None else self.MAJOR - minor = minor if minor is not None else self.MINOR + major = major if major is not None else self.major + minor = minor if minor is not None else self.minor buffer = BytesIO() From 3f351d5875e1401013dcd4e15679f94e950ca09f Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 27 Nov 2020 14:13:34 +0100 Subject: [PATCH 0382/1185] Improve test coverage --- tests/test_file_id.py | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/tests/test_file_id.py b/tests/test_file_id.py index 3ec029d341..98265d1eb5 100644 --- a/tests/test_file_id.py +++ b/tests/test_file_id.py @@ -16,6 +16,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import pytest + from pyrogram.file_id import FileId, FileUniqueId, FileType, FileUniqueType @@ -155,3 +157,38 @@ def test_chat_photo(): check_unique(channel_photo_small_unique, FileUniqueType.PHOTO) check(channel_photo_big, FileType.CHAT_PHOTO) check_unique(channel_photo_big_unique, FileUniqueType.PHOTO) + + +def test_old_file_id(): + old = "BQADBAADQNKSZqjl5DcROGn_eu5JtgAEAgAEAg" + check(old, FileType.DOCUMENT) + + +def test_unknown_file_type(): + unknown = "RQACAgIAAx0CAAGgr9AAAgmPX7b4UxbjNoFEO_L0I4s6wrXNJA8AAgQAA4GkuUm9FFvIaOhXWR4E" + + with pytest.raises(ValueError, match=r"Unknown file_type \d+ of file_id \w+"): + check(unknown, FileType.DOCUMENT) + + +def test_unknown_thumbnail_source(): + unknown = "AAMCAgADHQIAAaCv0AACCY9ftvhTFuM2gUQ78vQjizrCtc0kDwACBAADgaS5Sb0UW8ho6FdZIH3qihAAA6QBAAIeBA" + + with pytest.raises(ValueError, match=r"Unknown thumbnail_source \d+ of file_id \w+"): + check(unknown, FileType.THUMBNAIL) + + +def test_stringify_file_id(): + file_id = "BQACAgIAAx0CAAGgr9AAAgmPX7b4UxbjNoFEO_L0I4s6wrXNJA8AAgQAA4GkuUm9FFvIaOhXWR4E" + string = "{'major': 4, 'minor': 30, 'file_type': , 'dc_id': 2, " \ + "'file_reference': b'\\x02\\x00\\xa0\\xaf\\xd0\\x00\\x00\\t\\x8f_\\xb6\\xf8S\\x16\\xe36\\x81D;\\xf2\\xf4#\\x8b:\\xc2\\xb5\\xcd$\\x0f', " \ + "'media_id': 5312458109417947140, 'access_hash': 6437869729085068477}" + + assert str(FileId.decode(file_id)) == string + + +def test_stringify_file_unique_id(): + file_unique_id = "AgADBAADgaS5SQ" + string = "{'file_unique_type': , 'media_id': 5312458109417947140}" + + assert str(FileUniqueId.decode(file_unique_id)) == string From ce729fa1e339db299314789553cac7c24d59806b Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 27 Nov 2020 19:04:34 +0100 Subject: [PATCH 0383/1185] Fix docs errors path --- docs/source/api/errors/bad-request.rst | 2 +- docs/source/api/errors/flood.rst | 2 +- docs/source/api/errors/forbidden.rst | 2 +- docs/source/api/errors/internal-server-error.rst | 2 +- docs/source/api/errors/not-acceptable.rst | 2 +- docs/source/api/errors/see-other.rst | 2 +- docs/source/api/errors/unauthorized.rst | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/source/api/errors/bad-request.rst b/docs/source/api/errors/bad-request.rst index 130cc57404..ab13fdabb2 100644 --- a/docs/source/api/errors/bad-request.rst +++ b/docs/source/api/errors/bad-request.rst @@ -2,6 +2,6 @@ ---------------- .. csv-table:: - :file: ../../../../compiler/error/source/400_BAD_REQUEST.tsv + :file: ../../../../compiler/errors/source/400_BAD_REQUEST.tsv :delim: tab :header-rows: 1 diff --git a/docs/source/api/errors/flood.rst b/docs/source/api/errors/flood.rst index eae3359170..e01e012274 100644 --- a/docs/source/api/errors/flood.rst +++ b/docs/source/api/errors/flood.rst @@ -2,6 +2,6 @@ ----------- .. csv-table:: - :file: ../../../../compiler/error/source/420_FLOOD.tsv + :file: ../../../../compiler/errors/source/420_FLOOD.tsv :delim: tab :header-rows: 1 \ No newline at end of file diff --git a/docs/source/api/errors/forbidden.rst b/docs/source/api/errors/forbidden.rst index 280adf40cc..c2a8dcb80b 100644 --- a/docs/source/api/errors/forbidden.rst +++ b/docs/source/api/errors/forbidden.rst @@ -2,6 +2,6 @@ --------------- .. csv-table:: - :file: ../../../../compiler/error/source/403_FORBIDDEN.tsv + :file: ../../../../compiler/errors/source/403_FORBIDDEN.tsv :delim: tab :header-rows: 1 \ No newline at end of file diff --git a/docs/source/api/errors/internal-server-error.rst b/docs/source/api/errors/internal-server-error.rst index bbc9177a6c..996d77eba5 100644 --- a/docs/source/api/errors/internal-server-error.rst +++ b/docs/source/api/errors/internal-server-error.rst @@ -2,6 +2,6 @@ ------------------------- .. csv-table:: - :file: ../../../../compiler/error/source/500_INTERNAL_SERVER_ERROR.tsv + :file: ../../../../compiler/errors/source/500_INTERNAL_SERVER_ERROR.tsv :delim: tab :header-rows: 1 \ No newline at end of file diff --git a/docs/source/api/errors/not-acceptable.rst b/docs/source/api/errors/not-acceptable.rst index cfe0383e73..79cfa8bcaf 100644 --- a/docs/source/api/errors/not-acceptable.rst +++ b/docs/source/api/errors/not-acceptable.rst @@ -2,6 +2,6 @@ ------------------- .. csv-table:: - :file: ../../../../compiler/error/source/406_NOT_ACCEPTABLE.tsv + :file: ../../../../compiler/errors/source/406_NOT_ACCEPTABLE.tsv :delim: tab :header-rows: 1 \ No newline at end of file diff --git a/docs/source/api/errors/see-other.rst b/docs/source/api/errors/see-other.rst index f2cde9cf5e..629b955764 100644 --- a/docs/source/api/errors/see-other.rst +++ b/docs/source/api/errors/see-other.rst @@ -2,6 +2,6 @@ -------------- .. csv-table:: - :file: ../../../../compiler/error/source/303_SEE_OTHER.tsv + :file: ../../../../compiler/errors/source/303_SEE_OTHER.tsv :delim: tab :header-rows: 1 \ No newline at end of file diff --git a/docs/source/api/errors/unauthorized.rst b/docs/source/api/errors/unauthorized.rst index 4f2b24a5cb..c56abeecc3 100644 --- a/docs/source/api/errors/unauthorized.rst +++ b/docs/source/api/errors/unauthorized.rst @@ -2,6 +2,6 @@ ------------------ .. csv-table:: - :file: ../../../../compiler/error/source/401_UNAUTHORIZED.tsv + :file: ../../../../compiler/errors/source/401_UNAUTHORIZED.tsv :delim: tab :header-rows: 1 From 30664b26d53bcf6ed10a6ddf79cf797b50e5afcd Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 27 Nov 2020 19:05:58 +0100 Subject: [PATCH 0384/1185] Remove "schedule" from docs --- docs/source/topics/scheduling.rst | 58 ++++++++----------------------- 1 file changed, 15 insertions(+), 43 deletions(-) diff --git a/docs/source/topics/scheduling.rst b/docs/source/topics/scheduling.rst index 0776d601ed..f96e7b07f2 100644 --- a/docs/source/topics/scheduling.rst +++ b/docs/source/topics/scheduling.rst @@ -4,9 +4,8 @@ Scheduling Tasks Scheduling tasks means executing one or more functions periodically at pre-defined intervals or after a delay. This is useful, for example, to send recurring messages to specific chats or users. -Since there's no built-in task scheduler in Pyrogram, this page will only show examples on how to integrate Pyrogram -with the main Python schedule libraries such as ``schedule`` and ``apscheduler``. For more detailed information, you can -visit and learn from each library documentation. +This page will show examples on how to integrate Pyrogram with ``apscheduler`` in both asynchronous and +non-asynchronous contexts. For more detailed information, you can visit and learn from the library documentation. .. contents:: Contents :backlinks: none @@ -15,79 +14,52 @@ visit and learn from each library documentation. ----- -Using ``schedule`` ------------------- - -- Install with ``pip3 install schedule`` -- Documentation: https://schedule.readthedocs.io - -.. code-block:: python - - import time - - import schedule - - from pyrogram import Client - - app = Client("my_account") - - - def job(): - app.send_message("me", "Hi!") - - - schedule.every(3).seconds.do(job) - - with app: - while True: - schedule.run_pending() - time.sleep(1) - - - Using ``apscheduler`` --------------------- - Install with ``pip3 install apscheduler`` - Documentation: https://apscheduler.readthedocs.io +Asynchronously +^^^^^^^^^^^^^^ + .. code-block:: python - from apscheduler.schedulers.background import BackgroundScheduler + from apscheduler.schedulers.asyncio import AsyncIOScheduler from pyrogram import Client app = Client("my_account") - def job(): - app.send_message("me", "Hi!") + async def job(): + await app.send_message("me", "Hi!") - scheduler = BackgroundScheduler() + scheduler = AsyncIOScheduler() scheduler.add_job(job, "interval", seconds=3) scheduler.start() app.run() -``apscheduler`` does also support async code, here's an example: +Non-Asynchronously +^^^^^^^^^^^^^^^^^^ .. code-block:: python - from apscheduler.schedulers.asyncio import AsyncIOScheduler + from apscheduler.schedulers.background import BackgroundScheduler from pyrogram import Client app = Client("my_account") - async def job(): - await app.send_message("me", "Hi!") + def job(): + app.send_message("me", "Hi!") - scheduler = AsyncIOScheduler() + scheduler = BackgroundScheduler() scheduler.add_job(job, "interval", seconds=3) scheduler.start() app.run() - From 8fbe45ddbaee279819a587f2b9eb0e1e21019a0c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 27 Nov 2020 19:06:25 +0100 Subject: [PATCH 0385/1185] Docs improvements --- docs/Makefile | 2 +- docs/source/index.rst | 2 +- pyrogram/methods/chats/iter_chat_members.py | 9 +++++++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/Makefile b/docs/Makefile index cabe3ecb33..ceb7494c14 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -20,4 +20,4 @@ help: @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) lhtml: # live html - sphinx-autobuild -H $(shell ipconfig getifaddr en3) -b html "$(SOURCEDIR)" "$(BUILDDIR)/html" $(SPHINXOPTS) + sphinx-autobuild --host $(shell ifconfig | grep "inet " | grep -v 127.0.0.1 | cut -d\ -f2) -b html "$(SOURCEDIR)" "$(BUILDDIR)/html" $(SPHINXOPTS) diff --git a/docs/source/index.rst b/docs/source/index.rst index 18c69412b8..c3727360df 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -54,7 +54,7 @@ following them in order using the :guilabel:`Next` button at the end of each pag list of the most relevant pages for a quick access. .. admonition :: Cloud Credits - :class: attention + :class: tip If you need a cloud server to host your applications, we recommend using **Hetzner Cloud**. Sign up with `this link `_ to get €20 in cloud credits and help support Pyrogram as diff --git a/pyrogram/methods/chats/iter_chat_members.py b/pyrogram/methods/chats/iter_chat_members.py index 59def3c1da..782548729d 100644 --- a/pyrogram/methods/chats/iter_chat_members.py +++ b/pyrogram/methods/chats/iter_chat_members.py @@ -57,11 +57,11 @@ async def iter_chat_members( limit (``int``, *optional*): Limits the number of members to be retrieved. - By default, no limit is applied and all members are returned. + By default, no limit is applied and all members are returned [1]_. query (``str``, *optional*): Query string to filter members based on their display names and usernames. - Defaults to "" (empty string). + Defaults to "" (empty string) [2]_. filter (``str``, *optional*): Filter used to select the kind of members you want to retrieve. Only applicable for supergroups @@ -74,6 +74,11 @@ async def iter_chat_members( *"administrators"* - chat administrators only. Defaults to *"recent"*. + .. [1] Server limit: on supergroups, you can get up to 10,000 members for a single query and up to 200 members + on channels. + + .. [2] A query string is applicable only for *"all"*, *"kicked"* and *"restricted"* filters only. + Returns: ``Generator``: A generator yielding :obj:`~pyrogram.types.ChatMember` objects. From b85096c4ceb7a3d224ffc3c0c1cbffec55929765 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 27 Nov 2020 21:14:37 +0100 Subject: [PATCH 0386/1185] Fix thumbnail_file_type type (str -> FileType) --- pyrogram/file_id.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/file_id.py b/pyrogram/file_id.py index 8bc6d5f9dd..1980aa1fcb 100644 --- a/pyrogram/file_id.py +++ b/pyrogram/file_id.py @@ -165,10 +165,10 @@ def __init__( access_hash: int = None, volume_id: int = None, thumbnail_source: ThumbnailSource = None, - thumbnail_file_type: str = None, + thumbnail_file_type: FileType = None, thumbnail_size: str = None, secret: int = None, - local_id: str = None, + local_id: int = None, chat_id: int = None, chat_access_hash: int = None, sticker_set_id: int = None, From e0c3578fbb2bfb9913eb35d52a337f8e9c5e2ae5 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 27 Nov 2020 22:02:52 +0100 Subject: [PATCH 0387/1185] Set default file_reference to b"" instead of None --- pyrogram/file_id.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/file_id.py b/pyrogram/file_id.py index 1980aa1fcb..8bca0eddcd 100644 --- a/pyrogram/file_id.py +++ b/pyrogram/file_id.py @@ -159,7 +159,7 @@ def __init__( minor: int = MINOR, file_type: FileType, dc_id: int, - file_reference: bytes = None, + file_reference: bytes = b"", url: str = None, media_id: int = None, access_hash: int = None, @@ -239,7 +239,7 @@ def decode(file_id: str): access_hash=access_hash ) - file_reference = Bytes.read(buffer) if has_file_reference else None + file_reference = Bytes.read(buffer) if has_file_reference else b"" media_id, access_hash = struct.unpack(" Date: Fri, 27 Nov 2020 22:09:17 +0100 Subject: [PATCH 0388/1185] Add support for the new Bot API fields: file_id, file_unique_id Remove file_ref from Pyrogram's API --- pyrogram/client.py | 89 ++++------ pyrogram/methods/chats/set_chat_photo.py | 22 +-- pyrogram/methods/messages/download_media.py | 164 ++++-------------- .../methods/messages/edit_inline_media.py | 11 +- .../methods/messages/edit_message_media.py | 11 +- pyrogram/methods/messages/send_animation.py | 12 +- pyrogram/methods/messages/send_audio.py | 8 +- .../methods/messages/send_cached_media.py | 7 +- pyrogram/methods/messages/send_document.py | 8 +- pyrogram/methods/messages/send_media_group.py | 9 +- pyrogram/methods/messages/send_photo.py | 8 +- pyrogram/methods/messages/send_sticker.py | 8 +- pyrogram/methods/messages/send_video.py | 8 +- pyrogram/methods/messages/send_video_note.py | 8 +- pyrogram/methods/messages/send_voice.py | 8 +- .../methods/users/delete_profile_photos.py | 3 +- pyrogram/types/input_media/input_media.py | 3 +- .../input_media/input_media_animation.py | 7 +- .../types/input_media/input_media_audio.py | 7 +- .../types/input_media/input_media_document.py | 7 +- .../types/input_media/input_media_photo.py | 7 +- .../types/input_media/input_media_video.py | 7 +- .../types/messages_and_media/animation.py | 35 ++-- pyrogram/types/messages_and_media/audio.py | 59 +++---- pyrogram/types/messages_and_media/document.py | 35 ++-- pyrogram/types/messages_and_media/message.py | 66 +------ pyrogram/types/messages_and_media/photo.py | 52 ++++-- pyrogram/types/messages_and_media/sticker.py | 35 ++-- .../types/messages_and_media/thumbnail.py | 42 +++-- pyrogram/types/messages_and_media/video.py | 53 +++--- .../types/messages_and_media/video_note.py | 35 ++-- pyrogram/types/messages_and_media/voice.py | 36 ++-- pyrogram/types/user_and_chats/chat_photo.py | 84 +++++---- pyrogram/utils.py | 115 +++--------- 34 files changed, 400 insertions(+), 669 deletions(-) diff --git a/pyrogram/client.py b/pyrogram/client.py index 28c65c16a0..df06e94c7f 100644 --- a/pyrogram/client.py +++ b/pyrogram/client.py @@ -47,6 +47,7 @@ from pyrogram.types import User, TermsOfService from pyrogram.utils import ainput from .dispatcher import Dispatcher +from .file_id import FileId, FileType, ThumbnailSource from .scaffold import Scaffold log = logging.getLogger(__name__) @@ -493,22 +494,11 @@ async def handle_download(self, packet): final_file_path = "" try: - data, directory, file_name, progress, progress_args = packet + file_id, directory, file_name, file_size, progress, progress_args = packet temp_file_path = await self.get_file( - media_type=data.media_type, - dc_id=data.dc_id, - document_id=data.document_id, - access_hash=data.access_hash, - thumb_size=data.thumb_size, - peer_id=data.peer_id, - peer_type=data.peer_type, - peer_access_hash=data.peer_access_hash, - volume_id=data.volume_id, - local_id=data.local_id, - file_ref=data.file_ref, - file_size=data.file_size, - is_big=data.is_big, + file_id=file_id, + file_size=file_size, progress=progress, progress_args=progress_args ) @@ -817,22 +807,13 @@ def load_plugins(self): async def get_file( self, - media_type: int, - dc_id: int, - document_id: int, - access_hash: int, - thumb_size: str, - peer_id: int, - peer_type: str, - peer_access_hash: int, - volume_id: int, - local_id: int, - file_ref: str, + file_id: FileId, file_size: int, - is_big: bool, progress: callable, progress_args: tuple = () ) -> str: + dc_id = file_id.dc_id + async with self.media_sessions_lock: session = self.media_sessions.get(dc_id, None) @@ -874,49 +855,43 @@ async def get_file( self.media_sessions[dc_id] = session - file_ref = utils.decode_file_ref(file_ref) + file_type = file_id.file_type - if media_type == 1: - if peer_type == "user": + if file_type == FileType.CHAT_PHOTO: + if file_id.chat_id > 0: peer = raw.types.InputPeerUser( - user_id=peer_id, - access_hash=peer_access_hash - ) - elif peer_type == "chat": - peer = raw.types.InputPeerChat( - chat_id=peer_id + user_id=file_id.chat_id, + access_hash=file_id.chat_access_hash ) else: - peer = raw.types.InputPeerChannel( - channel_id=peer_id, - access_hash=peer_access_hash - ) + if file_id.chat_access_hash == 0: + peer = raw.types.InputPeerChat( + chat_id=file_id.chat_id + ) + else: + peer = raw.types.InputPeerChannel( + channel_id=file_id.chat_id, + access_hash=file_id.chat_access_hash + ) location = raw.types.InputPeerPhotoFileLocation( peer=peer, - volume_id=volume_id, - local_id=local_id, - big=is_big or None + volume_id=file_id.volume_id, + local_id=file_id.local_id, + big=file_id.thumbnail_source == ThumbnailSource.CHAT_PHOTO_BIG ) - elif media_type in (0, 2): + elif file_type in (FileType.THUMBNAIL, FileType.PHOTO): location = raw.types.InputPhotoFileLocation( - id=document_id, - access_hash=access_hash, - file_reference=file_ref, - thumb_size=thumb_size - ) - elif media_type == 14: - location = raw.types.InputDocumentFileLocation( - id=document_id, - access_hash=access_hash, - file_reference=file_ref, - thumb_size=thumb_size + id=file_id.media_id, + access_hash=file_id.access_hash, + file_reference=file_id.file_reference, + thumb_size=file_id.thumbnail_size ) else: location = raw.types.InputDocumentFileLocation( - id=document_id, - access_hash=access_hash, - file_reference=file_ref, + id=file_id.media_id, + access_hash=file_id.access_hash, + file_reference=file_id.file_reference, thumb_size="" ) diff --git a/pyrogram/methods/chats/set_chat_photo.py b/pyrogram/methods/chats/set_chat_photo.py index 8ccb123cd3..0474fd18b8 100644 --- a/pyrogram/methods/chats/set_chat_photo.py +++ b/pyrogram/methods/chats/set_chat_photo.py @@ -22,6 +22,7 @@ from pyrogram import raw from pyrogram import utils from pyrogram.scaffold import Scaffold +from pyrogram.file_id import FileType class SetChatPhoto(Scaffold): @@ -30,8 +31,7 @@ async def set_chat_photo( chat_id: Union[int, str], *, photo: Union[str, BinaryIO] = None, - video: Union[str, BinaryIO] = None, - file_ref: str = None + video: Union[str, BinaryIO] = None ) -> bool: """Set a new chat photo or video (H.264/MPEG-4 AVC video, max 5 seconds). @@ -45,19 +45,15 @@ async def set_chat_photo( Unique identifier (int) or username (str) of the target chat. photo (``str`` | ``BinaryIO``, *optional*): - New chat photo. You can pass a :obj:`~pyrogram.types.Photo` file_id (in pair with a valid file_ref), a - file path to upload a new photo from your local machine or a binary file-like object with its attribute + New chat photo. You can pass a :obj:`~pyrogram.types.Photo` file_id, a file path to upload a new photo + from your local machine or a binary file-like object with its attribute ".name" set for in-memory uploads. video (``str`` | ``BinaryIO``, *optional*): - New chat video. You can pass a :obj:`~pyrogram.types.Video` file_id (in pair with a valid file_ref), a - file path to upload a new video from your local machine or a binary file-like object with its attribute + New chat video. You can pass a :obj:`~pyrogram.types.Video` file_id, a file path to upload a new video + from your local machine or a binary file-like object with its attribute ".name" set for in-memory uploads. - file_ref (``str``, *optional*): - A valid file reference obtained by a recently fetched media message. - To be used in combination with a file_id in case a file reference is needed. - Returns: ``bool``: True on success. @@ -71,14 +67,14 @@ async def set_chat_photo( app.set_chat_photo(chat_id, photo="photo.jpg") # Set chat photo using an exiting Photo file_id - app.set_chat_photo(chat_id, photo=photo.file_id, file_ref=photo.file_ref) + app.set_chat_photo(chat_id, photo=photo.file_id) # Set chat video using a local file app.set_chat_photo(chat_id, video="video.mp4") # Set chat photo using an exiting Video file_id - app.set_chat_photo(chat_id, video=video.file_id, file_ref=video.file_ref) + app.set_chat_photo(chat_id, video=video.file_id) """ peer = await self.resolve_peer(chat_id) @@ -89,7 +85,7 @@ async def set_chat_photo( video=await self.save_file(video) ) else: - photo = utils.get_input_media_from_file_id(photo, file_ref, 2) + photo = utils.get_input_media_from_file_id(photo, FileType.PHOTO) photo = raw.types.InputChatPhoto(id=photo.id) else: photo = raw.types.InputChatUploadedPhoto( diff --git a/pyrogram/methods/messages/download_media.py b/pyrogram/methods/messages/download_media.py index c6919809e1..c2d4d0b481 100644 --- a/pyrogram/methods/messages/download_media.py +++ b/pyrogram/methods/messages/download_media.py @@ -17,51 +17,22 @@ # along with Pyrogram. If not, see . import asyncio -import binascii import os -import struct import time from datetime import datetime from typing import Union from pyrogram import types -from pyrogram import utils -from pyrogram.errors import FileIdInvalid +from pyrogram.file_id import FileId, FileType, PHOTO_TYPES from pyrogram.scaffold import Scaffold DEFAULT_DOWNLOAD_DIR = "downloads/" -class FileData: - def __init__( - self, *, media_type: int = None, dc_id: int = None, document_id: int = None, access_hash: int = None, - thumb_size: str = None, peer_id: int = None, peer_type: str = None, peer_access_hash: int = None, - volume_id: int = None, local_id: int = None, is_big: bool = None, file_size: int = None, mime_type: str = None, - file_name: str = None, date: int = None, file_ref: str = None - ): - self.media_type = media_type - self.dc_id = dc_id - self.document_id = document_id - self.access_hash = access_hash - self.thumb_size = thumb_size - self.peer_id = peer_id - self.peer_type = peer_type - self.peer_access_hash = peer_access_hash - self.volume_id = volume_id - self.local_id = local_id - self.is_big = is_big - self.file_size = file_size - self.mime_type = mime_type - self.file_name = file_name - self.date = date - self.file_ref = file_ref - - class DownloadMedia(Scaffold): async def download_media( self, - message: Union["types.Message", str], - file_ref: str = None, + media: Union["types.Message", str], file_name: str = DEFAULT_DOWNLOAD_DIR, block: bool = True, progress: callable = None, @@ -70,13 +41,9 @@ async def download_media( """Download the media from a message. Parameters: - message (:obj:`~pyrogram.types.Message` | ``str``): - Pass a Message containing the media, the media itself (message.audio, message.video, ...) or - the file id as string. - - file_ref (``str``, *optional*): - A valid file reference obtained by a recently fetched media message. - To be used in combination with a file id in case a file reference is needed. + media (:obj:`~pyrogram.types.Message` | ``str``): + Pass a Message containing the media, the media itself (message.audio, message.video, ...) or a file id + as string. file_name (``str``, *optional*): A custom *file_name* to be used instead of the one provided by Telegram. @@ -133,136 +100,63 @@ def progress(current, total): app.download_media(message, progress=progress) """ - error_message = "This message doesn't contain any downloadable media" - available_media = ("audio", "document", "photo", "sticker", "animation", "video", "voice", "video_note", "new_chat_photo") + available_media = ("audio", "document", "photo", "sticker", "animation", "video", "voice", "video_note", + "new_chat_photo") - media_file_name = None - file_size = None - mime_type = None - date = None - - if isinstance(message, types.Message): + if isinstance(media, types.Message): for kind in available_media: - media = getattr(message, kind, None) + media = getattr(media, kind, None) if media is not None: break else: - raise ValueError(error_message) - else: - media = message + raise ValueError("This message doesn't contain any downloadable media") if isinstance(media, str): file_id_str = media else: file_id_str = media.file_id - media_file_name = getattr(media, "file_name", "") - file_size = getattr(media, "file_size", None) - mime_type = getattr(media, "mime_type", None) - date = getattr(media, "date", None) - file_ref = getattr(media, "file_ref", None) - - data = FileData( - file_name=media_file_name, - file_size=file_size, - mime_type=mime_type, - date=date, - file_ref=file_ref - ) - - def get_existing_attributes() -> dict: - return dict(filter(lambda x: x[1] is not None, data.__dict__.items())) - - try: - decoded = utils.decode_file_id(file_id_str) - media_type = decoded[0] - - if media_type == 1: - unpacked = struct.unpack(". -from struct import pack from typing import List import pyrogram from pyrogram import raw from pyrogram import types -from pyrogram.utils import encode_file_id, encode_file_ref +from pyrogram.file_id import FileId, FileType, FileUniqueId, FileUniqueType from ..object import Object @@ -31,10 +30,11 @@ class Animation(Object): Parameters: file_id (``str``): - Unique identifier for this file. + Identifier for this file, which can be used to download or reuse the file. - file_ref (``str``): - Up to date file reference. + file_unique_id (``str``): + Unique identifier for this file, which is supposed to be the same over time and for different accounts. + Can't be used to download or reuse the file. width (``int``): Animation width as defined by sender. @@ -66,7 +66,7 @@ def __init__( *, client: "pyrogram.Client" = None, file_id: str, - file_ref: str, + file_unique_id: str, width: int, height: int, duration: int, @@ -79,7 +79,7 @@ def __init__( super().__init__(client) self.file_id = file_id - self.file_ref = file_ref + self.file_unique_id = file_unique_id self.file_name = file_name self.mime_type = mime_type self.file_size = file_size @@ -97,16 +97,17 @@ def _parse( file_name: str ) -> "Animation": return Animation( - file_id=encode_file_id( - pack( - ". -from struct import pack from typing import List import pyrogram from pyrogram import raw from pyrogram import types -from pyrogram.utils import encode_file_id, encode_file_ref +from pyrogram.file_id import FileId, FileType, FileUniqueId, FileUniqueType from ..object import Object @@ -31,14 +30,21 @@ class Audio(Object): Parameters: file_id (``str``): - Unique identifier for this file. + Identifier for this file, which can be used to download or reuse the file. - file_ref (``str``): - Up to date file reference. + file_unique_id (``str``): + Unique identifier for this file, which is supposed to be the same over time and for different accounts. + Can't be used to download or reuse the file. duration (``int``): Duration of the audio in seconds as defined by sender. + performer (``str``, *optional*): + Performer of the audio as defined by sender or by audio tags. + + title (``str``, *optional*): + Title of the audio as defined by sender or by audio tags. + file_name (``str``, *optional*): Audio file name. @@ -49,13 +55,7 @@ class Audio(Object): File size. date (``int``, *optional*): - Date the audio was sent in Unix time. - - performer (``str``, *optional*): - Performer of the audio as defined by sender or by audio tags. - - title (``str``, *optional*): - Title of the audio as defined by sender or by audio tags. + Date the audio was originally sent, in Unix time. thumbs (List of :obj:`~pyrogram.types.Thumbnail`, *optional*): Thumbnails of the music file album cover. @@ -66,27 +66,27 @@ def __init__( *, client: "pyrogram.Client" = None, file_id: str, - file_ref: str, + file_unique_id: str, duration: int, + performer: str = None, + title: str = None, file_name: str = None, mime_type: str = None, file_size: int = None, date: int = None, - performer: str = None, - title: str = None, thumbs: List["types.Thumbnail"] = None ): super().__init__(client) self.file_id = file_id - self.file_ref = file_ref + self.file_unique_id = file_unique_id + self.duration = duration + self.performer = performer + self.title = title self.file_name = file_name self.mime_type = mime_type self.file_size = file_size self.date = date - self.duration = duration - self.performer = performer - self.title = title self.thumbs = thumbs @staticmethod @@ -97,16 +97,17 @@ def _parse( file_name: str ) -> "Audio": return Audio( - file_id=encode_file_id( - pack( - ". -from struct import pack from typing import List import pyrogram from pyrogram import raw from pyrogram import types -from pyrogram.utils import encode_file_id, encode_file_ref +from pyrogram.file_id import FileId, FileType, FileUniqueId, FileUniqueType from ..object import Object @@ -31,10 +30,11 @@ class Document(Object): Parameters: file_id (``str``): - Unique file identifier. + Identifier for this file, which can be used to download or reuse the file. - file_ref (``str``): - Up to date file reference. + file_unique_id (``str``): + Unique identifier for this file, which is supposed to be the same over time and for different accounts. + Can't be used to download or reuse the file. file_name (``str``, *optional*): Original filename as defined by sender. @@ -57,7 +57,7 @@ def __init__( *, client: "pyrogram.Client" = None, file_id: str, - file_ref: str, + file_unique_id: str, file_name: str = None, mime_type: str = None, file_size: int = None, @@ -67,7 +67,7 @@ def __init__( super().__init__(client) self.file_id = file_id - self.file_ref = file_ref + self.file_unique_id = file_unique_id self.file_name = file_name self.mime_type = mime_type self.file_size = file_size @@ -77,16 +77,17 @@ def __init__( @staticmethod def _parse(client, document: "raw.types.Document", file_name: str) -> "Document": return Document( - file_id=encode_file_id( - pack( - ". -from struct import pack from typing import List import pyrogram from pyrogram import raw from pyrogram import types -from pyrogram.utils import encode_file_id, encode_file_ref +from pyrogram.file_id import FileId, FileType, FileUniqueId, FileUniqueType, ThumbnailSource from ..object import Object @@ -31,10 +30,11 @@ class Photo(Object): Parameters: file_id (``str``): - Unique identifier for this photo. + Identifier for this file, which can be used to download or reuse the file. - file_ref (``str``): - Up to date file reference. + file_unique_id (``str``): + Unique identifier for this file, which is supposed to be the same over time and for different accounts. + Can't be used to download or reuse the file. width (``int``): Photo width. @@ -60,7 +60,7 @@ def __init__( *, client: "pyrogram.Client" = None, file_id: str, - file_ref: str, + file_unique_id: str, width: int, height: int, file_size: int, @@ -71,7 +71,7 @@ def __init__( super().__init__(client) self.file_id = file_id - self.file_ref = file_ref + self.file_unique_id = file_unique_id self.width = width self.height = height self.file_size = file_size @@ -82,18 +82,36 @@ def __init__( @staticmethod def _parse(client, photo: "raw.types.Photo", ttl_seconds: int = None) -> "Photo": if isinstance(photo, raw.types.Photo): - big = list(filter(lambda p: isinstance(p, raw.types.PhotoSize), photo.sizes))[-1] + big = photo.sizes[-1] + + if isinstance(big, raw.types.PhotoSizeProgressive): + big = raw.types.PhotoSize( + type=big.type, + location=big.location, + w=big.w, + h=big.h, + size=big.sizes[-1] + ) return Photo( - file_id=encode_file_id( - pack( - ". -from struct import pack from typing import List from async_lru import alru_cache @@ -25,7 +24,7 @@ from pyrogram import raw from pyrogram import types from pyrogram.errors import StickersetInvalid -from pyrogram.utils import encode_file_id, encode_file_ref +from pyrogram.file_id import FileId, FileType, FileUniqueId, FileUniqueType from ..object import Object @@ -34,10 +33,11 @@ class Sticker(Object): Parameters: file_id (``str``): - Unique identifier for this file. + Identifier for this file, which can be used to download or reuse the file. - file_ref (``str``): - Up to date file reference. + file_unique_id (``str``): + Unique identifier for this file, which is supposed to be the same over time and for different accounts. + Can't be used to download or reuse the file. width (``int``): Sticker width. @@ -77,7 +77,7 @@ def __init__( *, client: "pyrogram.Client" = None, file_id: str, - file_ref: str, + file_unique_id: str, width: int, height: int, is_animated: bool, @@ -92,7 +92,7 @@ def __init__( super().__init__(client) self.file_id = file_id - self.file_ref = file_ref + self.file_unique_id = file_unique_id self.file_name = file_name self.mime_type = mime_type self.file_size = file_size @@ -137,16 +137,17 @@ async def _parse( set_name = None return Sticker( - file_id=encode_file_id( - pack( - ". -from struct import pack from typing import Union, List import pyrogram from pyrogram import raw from pyrogram import types -from pyrogram.utils import encode_file_id +from pyrogram.file_id import FileId, FileType, FileUniqueId, FileUniqueType, ThumbnailSource from ..object import Object @@ -31,7 +30,11 @@ class Thumbnail(Object): Parameters: file_id (``str``): - Unique identifier for this file. + Identifier for this file, which can be used to download or reuse the file. + + file_unique_id (``str``): + Unique identifier for this file, which is supposed to be the same over time and for different accounts. + Can't be used to download or reuse the file. width (``int``): Photo width. @@ -48,6 +51,7 @@ def __init__( *, client: "pyrogram.Client" = None, file_id: str, + file_unique_id: str, width: int, height: int, file_size: int @@ -55,6 +59,7 @@ def __init__( super().__init__(client) self.file_id = file_id + self.file_unique_id = file_unique_id self.width = width self.height = height self.file_size = file_size @@ -66,10 +71,8 @@ def _parse( ) -> Union[List[Union["types.StrippedThumbnail", "Thumbnail"]], None]: if isinstance(media, raw.types.Photo): raw_thumbnails = media.sizes[:-1] - media_type = 2 elif isinstance(media, raw.types.Document): raw_thumbnails = media.thumbs - media_type = 14 if not raw_thumbnails: return None @@ -78,6 +81,9 @@ def _parse( thumbnails = [] + file_type = FileType.PHOTO if isinstance(media, raw.types.Photo) else FileType.THUMBNAIL + thumbnail_file_type = file_type + for thumbnail in raw_thumbnails: # TODO: Enable this # if isinstance(thumbnail, types.PhotoStrippedSize): @@ -85,14 +91,24 @@ def _parse( if isinstance(thumbnail, raw.types.PhotoSize): thumbnails.append( Thumbnail( - file_id=encode_file_id( - pack( - ". -from struct import pack from typing import List import pyrogram from pyrogram import raw from pyrogram import types -from pyrogram.utils import encode_file_id, encode_file_ref +from pyrogram.file_id import FileId, FileType, FileUniqueId, FileUniqueType from ..object import Object @@ -31,10 +30,11 @@ class Video(Object): Parameters: file_id (``str``): - Unique identifier for this file. + Identifier for this file, which can be used to download or reuse the file. - file_ref (``str``): - Up to date file reference. + file_unique_id (``str``): + Unique identifier for this file, which is supposed to be the same over time and for different accounts. + Can't be used to download or reuse the file. width (``int``): Video width as defined by sender. @@ -51,18 +51,18 @@ class Video(Object): mime_type (``str``, *optional*): Mime type of a file as defined by sender. - supports_streaming (``bool``, *optional*): - True, if the video was uploaded with streaming support. - file_size (``int``, *optional*): File size. - date (``int``, *optional*): - Date the video was sent in Unix time. + supports_streaming (``bool``, *optional*): + True, if the video was uploaded with streaming support. ttl_seconds (``int``. *optional*): Time-to-live seconds, for secret photos. + date (``int``, *optional*): + Date the video was sent in Unix time. + thumbs (List of :obj:`~pyrogram.types.Thumbnail`, *optional*): Video thumbnails. """ @@ -72,31 +72,31 @@ def __init__( *, client: "pyrogram.Client" = None, file_id: str, - file_ref: str, + file_unique_id: str, width: int, height: int, duration: int, file_name: str = None, mime_type: str = None, - supports_streaming: bool = None, file_size: int = None, - date: int = None, + supports_streaming: bool = None, ttl_seconds: int = None, + date: int = None, thumbs: List["types.Thumbnail"] = None ): super().__init__(client) self.file_id = file_id - self.file_ref = file_ref + self.file_unique_id = file_unique_id self.width = width self.height = height self.duration = duration self.file_name = file_name self.mime_type = mime_type - self.supports_streaming = supports_streaming self.file_size = file_size - self.date = date + self.supports_streaming = supports_streaming self.ttl_seconds = ttl_seconds + self.date = date self.thumbs = thumbs @staticmethod @@ -108,16 +108,17 @@ def _parse( ttl_seconds: int = None ) -> "Video": return Video( - file_id=encode_file_id( - pack( - ". -from struct import pack from typing import List import pyrogram from pyrogram import raw from pyrogram import types -from pyrogram.utils import encode_file_id, encode_file_ref +from pyrogram.file_id import FileId, FileType, FileUniqueId, FileUniqueType from ..object import Object @@ -31,10 +30,11 @@ class VideoNote(Object): Parameters: file_id (``str``): - Unique identifier for this file. + Identifier for this file, which can be used to download or reuse the file. - file_ref (``str``): - Up to date file reference. + file_unique_id (``str``): + Unique identifier for this file, which is supposed to be the same over time and for different accounts. + Can't be used to download or reuse the file. length (``int``): Video width and height as defined by sender. @@ -60,7 +60,7 @@ def __init__( *, client: "pyrogram.Client" = None, file_id: str, - file_ref: str, + file_unique_id: str, length: int, duration: int, thumbs: List["types.Thumbnail"] = None, @@ -71,7 +71,7 @@ def __init__( super().__init__(client) self.file_id = file_id - self.file_ref = file_ref + self.file_unique_id = file_unique_id self.mime_type = mime_type self.file_size = file_size self.date = date @@ -86,16 +86,17 @@ def _parse( video_attributes: "raw.types.DocumentAttributeVideo" ) -> "VideoNote": return VideoNote( - file_id=encode_file_id( - pack( - ". -from struct import pack - import pyrogram from pyrogram import raw -from pyrogram.utils import encode_file_id, encode_file_ref +from pyrogram.file_id import FileId, FileType, FileUniqueId, FileUniqueType from ..object import Object @@ -29,10 +27,11 @@ class Voice(Object): Parameters: file_id (``str``): - Unique identifier for this file. + Identifier for this file, which can be used to download or reuse the file. - file_ref (``str``): - Up to date file reference. + file_unique_id (``str``): + Unique identifier for this file, which is supposed to be the same over time and for different accounts. + Can't be used to download or reuse the file. duration (``int``): Duration of the audio in seconds as defined by sender. @@ -55,7 +54,7 @@ def __init__( *, client: "pyrogram.Client" = None, file_id: str, - file_ref: str, + file_unique_id: str, duration: int, waveform: bytes = None, mime_type: str = None, @@ -65,7 +64,7 @@ def __init__( super().__init__(client) self.file_id = file_id - self.file_ref = file_ref + self.file_unique_id = file_unique_id self.duration = duration self.waveform = waveform self.mime_type = mime_type @@ -75,16 +74,17 @@ def __init__( @staticmethod def _parse(client, voice: "raw.types.Document", attributes: "raw.types.DocumentAttributeAudio") -> "Voice": return Voice( - file_id=encode_file_id( - pack( - ". -from struct import pack from typing import Union import pyrogram from pyrogram import raw -from pyrogram.utils import encode_file_id +from pyrogram.file_id import FileId, FileType, FileUniqueId, FileUniqueType, ThumbnailSource from ..object import Object -from ... import utils class ChatPhoto(Object): @@ -34,9 +32,17 @@ class ChatPhoto(Object): File identifier of small (160x160) chat photo. This file_id can be used only for photo download and only for as long as the photo is not changed. + small_photo_unique_id (``str``): + Unique file identifier of small (160x160) chat photo, which is supposed to be the same over time and for + different accounts. Can't be used to download or reuse the file. + big_file_id (``str``): File identifier of big (640x640) chat photo. This file_id can be used only for photo download and only for as long as the photo is not changed. + + big_photo_unique_id (``str``): + Unique file identifier of big (640x640) chat photo, which is supposed to be the same over time and for + different accounts. Can't be used to download or reuse the file. """ def __init__( @@ -44,12 +50,17 @@ def __init__( *, client: "pyrogram.Client" = None, small_file_id: str, - big_file_id: str + small_photo_unique_id: str, + big_file_id: str, + big_photo_unique_id: str + ): super().__init__(client) self.small_file_id = small_file_id + self.small_photo_unique_id = small_photo_unique_id self.big_file_id = big_file_id + self.big_photo_unique_id = big_photo_unique_id @staticmethod def _parse( @@ -61,39 +72,40 @@ def _parse( if not isinstance(chat_photo, (raw.types.UserProfilePhoto, raw.types.ChatPhoto)): return None - if peer_access_hash is None: - return None - - photo_id = getattr(chat_photo, "photo_id", 0) - loc_small = chat_photo.photo_small - loc_big = chat_photo.photo_big - - peer_type = utils.get_peer_type(peer_id) - - if peer_type == "user": - x = 0 - elif peer_type == "chat": - x = -1 - else: - peer_id += 1000727379968 - x = -234 + media_id = chat_photo.photo_id if isinstance(chat_photo, raw.types.UserProfilePhoto) else 0 return ChatPhoto( - small_file_id=encode_file_id( - pack( - " bytes: - s = base64.urlsafe_b64decode(s + "=" * (-len(s) % 4)) - r = b"" - - major = s[-1] - minor = s[-2] if major != 2 else 0 - - assert minor in (0, 22, 24) - - skip = 2 if minor else 1 - - i = 0 - - while i < len(s) - skip: - if s[i] != 0: - r += bytes([s[i]]) - else: - r += b"\x00" * s[i + 1] - i += 1 - - i += 1 - - return r - - -def encode_file_id(s: bytes) -> str: - r = b"" - n = 0 - - for i in s + bytes([22]) + bytes([4]): - if i == 0: - n += 1 - else: - if n: - r += b"\x00" + bytes([n]) - n = 0 - - r += bytes([i]) - - return base64.urlsafe_b64encode(r).decode().rstrip("=") - - -def encode_file_ref(file_ref: bytes) -> str: - return base64.urlsafe_b64encode(file_ref).decode().rstrip("=") - - -def decode_file_ref(file_ref: str) -> bytes: - if file_ref is None: - return b"" - - return base64.urlsafe_b64decode(file_ref + "=" * (-len(file_ref) % 4)) +from pyrogram.file_id import FileId, FileType, PHOTO_TYPES, DOCUMENT_TYPES async def ainput(prompt: str = "", *, hide: bool = False): @@ -102,52 +49,38 @@ def get_offset_date(dialogs): def get_input_media_from_file_id( - file_id_str: str, - file_ref: str = None, - expected_media_type: int = None + file_id: str, + expected_file_type: FileType = None ) -> Union["raw.types.InputMediaPhoto", "raw.types.InputMediaDocument"]: - try: - decoded = decode_file_id(file_id_str) - except Exception: - raise ValueError(f"Failed to decode file_id: {file_id_str}") - else: - media_type = decoded[0] - - if expected_media_type is not None: - if media_type != expected_media_type: - media_type_str = Scaffold.MEDIA_TYPE_ID.get(media_type, None) - expected_media_type_str = Scaffold.MEDIA_TYPE_ID.get(expected_media_type, None) + decoded = FileId.decode(file_id) - raise ValueError(f'Expected: "{expected_media_type_str}", got "{media_type_str}" file_id instead') + file_type = decoded.file_type - if media_type in (0, 1, 14): - raise ValueError(f"This file_id can only be used for download: {file_id_str}") + if expected_file_type is not None and file_type != expected_file_type: + raise ValueError(f'Expected: "{expected_file_type}", got "{file_type}" file_id instead') - if media_type == 2: - unpacked = struct.unpack(" List["types.Message"]: From a0e0cbe477a3c124139085b499ece3e0300f0838 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 27 Nov 2020 22:09:45 +0100 Subject: [PATCH 0389/1185] Improve FAQs --- docs/source/faq.rst | 48 +++++++-------------------------------------- 1 file changed, 7 insertions(+), 41 deletions(-) diff --git a/docs/source/faq.rst b/docs/source/faq.rst index 5f608c879c..6b1e26ff04 100644 --- a/docs/source/faq.rst +++ b/docs/source/faq.rst @@ -111,8 +111,8 @@ Can I use the same file_id across different accounts? No, Telegram doesn't allow this. -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]``. +File ids are personal and bound to a specific account; an attempt in using a foreign file id will result in errors such +as ``[400 MEDIA_EMPTY]``. The only exception are stickers' file ids; you can use them across different accounts without any problem, like this one: ``CAADBAADyg4AAvLQYAEYD4F7vcZ43AI``. @@ -120,17 +120,8 @@ one: ``CAADBAADyg4AAvLQYAEYD4F7vcZ43AI``. Can I use Bot API's file_id values in Pyrogram? ----------------------------------------------- -Definitely! All file ids you might have taken from the Bot API are 100% compatible and re-usable in Pyrogram. - -**However...** - -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 (see `What is a file_ref and why do I need it?`_). - -This change will most likely affect the official :doc:`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. +Yes! All file ids you take or might have taken from the Bot API are 100% compatible and re-usable in Pyrogram. +The opposite is also valid, you can take any file id generated by Pyrogram and re-use in the Bot API. Can I use multiple clients at once on the same account? ------------------------------------------------------- @@ -259,37 +250,12 @@ The error in question is ``[400 PEER_ID_INVALID]``, and could mean several thing - 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 argument you passed is in form of a string; you have to convert it into an integer with ``int(chat_id)``. -- The chat id refers to a user your current session haven't met yet. +- The chat id refers to a user or chat your current session hasn't met yet. About the last point: in order for you to meet a user and thus communicate with them, you should ask yourself how to contact people using official apps. The answer is the same for Pyrogram too and involves normal usages such as searching -for usernames, meeting them in a common group, have their phone contacts saved or getting a message mentioning them, -either a forward or a mention in the message text. - -What is a file_ref and why do I need it? ----------------------------------------- - -.. note:: - - This FAQ is currently applicable to user accounts only. Bot accounts are still doing fine without a file_ref - (even though this can change anytime since it's a Telegram's internal server behaviour). - -Similarly to what happens with users and chats which need to first be encountered in order to interact with them, media -messages also need to be "seen" recently before downloading or re-sending without uploading as new file. - -**What is it meant by "they need to be seen recently"?** - -That means you have to fetch the original media messages prior any action in order to get a valid and up to date value -called file reference (file_ref) which, in pair with a file_id, enables you to interact with the media. This file_ref -value won't last forever (usually 24h, but could expire anytime) and in case of errors you have to get a refreshed -file_ref by re-fetching the original message (fetching forwarded media messages does also work). - -**Ok, but what is a file_ref actually needed for?** - -Nobody knows for sure, but is likely because that's the correct approach for handling tons of files uploaded by users in -Telegram's cloud. Which means, as soon as the media message still exists, a valid file_ref can be obtained, otherwise, -in case there's no more messages referencing a specific media, Telegram is able to free disk space by deleting old -files. +for usernames, meeting them in a common group, having their phone contacts saved, getting a message mentioning them +(either a forward or a mention in the message text) or obtaining the dialogs list. Code hangs when I stop, restart, add/remove_handler --------------------------------------------------- From 9f77fed181807ea5f9a70328b165e2be293b6594 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 28 Nov 2020 14:58:24 +0100 Subject: [PATCH 0390/1185] Add a clearer error in case of bad file ids --- pyrogram/utils.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pyrogram/utils.py b/pyrogram/utils.py index 10fe7ea9ed..e0b79786e3 100644 --- a/pyrogram/utils.py +++ b/pyrogram/utils.py @@ -52,7 +52,11 @@ def get_input_media_from_file_id( file_id: str, expected_file_type: FileType = None ) -> Union["raw.types.InputMediaPhoto", "raw.types.InputMediaDocument"]: - decoded = FileId.decode(file_id) + try: + decoded = FileId.decode(file_id) + except Exception: + raise ValueError(f'Failed to decode "{file_id}". The value does not represent an existing local file, ' + f'HTTP URL, or valid file id.') file_type = decoded.file_type From a6f8827433114754f20535afffd0498104681bc3 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 28 Nov 2020 15:52:47 +0100 Subject: [PATCH 0391/1185] Fix some missing raw API references in docs --- pyrogram/handlers/raw_update_handler.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pyrogram/handlers/raw_update_handler.py b/pyrogram/handlers/raw_update_handler.py index 9376409b3e..ed18fc4bb2 100644 --- a/pyrogram/handlers/raw_update_handler.py +++ b/pyrogram/handlers/raw_update_handler.py @@ -37,8 +37,8 @@ class RawUpdateHandler(Handler): The Client itself, useful when you want to call other API methods inside the update handler. update (``Update``): - The received update, which can be one of the many single Updates listed in the *updates* - field you see in the :obj:`~pyrogram.types.Update` type. + The received update, which can be one of the many single Updates listed in the + :obj:`~pyrogram.raw.base.Update` base type. users (``dict``): Dictionary of all :obj:`~pyrogram.types.User` mentioned in the update. @@ -47,7 +47,7 @@ class RawUpdateHandler(Handler): chats (``dict``): Dictionary of all :obj:`~pyrogram.types.Chat` and - :obj:`Channel ` mentioned in the update. + :obj:`~pyrogram.raw.types.Channel` mentioned in the update. You can access extra info about the chat (such as *title*, *participants_count*, etc...) by using the IDs you find in the *update* argument (e.g.: *chats[1701277281]*). @@ -55,10 +55,10 @@ class RawUpdateHandler(Handler): The following Empty or Forbidden types may exist inside the *users* and *chats* dictionaries. They mean you have been blocked by the user or banned from the group/channel. - - :obj:`UserEmpty ` - - :obj:`ChatEmpty ` - - :obj:`ChatForbidden ` - - :obj:`ChannelForbidden ` + - :obj:`~pyrogram.raw.types.UserEmpty` + - :obj:`~pyrogram.raw.types.ChatEmpty` + - :obj:`~pyrogram.raw.types.ChatForbidden` + - :obj:`~pyrogram.raw.types.ChannelForbidden` """ def __init__(self, callback: callable): From 90cee1ea890171afde47d33c85d493c1bb69c4c7 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 28 Nov 2020 15:58:51 +0100 Subject: [PATCH 0392/1185] Add schedule_date parameter to send_media_group() --- pyrogram/methods/messages/send_media_group.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/pyrogram/methods/messages/send_media_group.py b/pyrogram/methods/messages/send_media_group.py index 61956785c1..21b55d6d01 100644 --- a/pyrogram/methods/messages/send_media_group.py +++ b/pyrogram/methods/messages/send_media_group.py @@ -42,7 +42,8 @@ async def send_media_group( "types.InputMediaDocument" ]], disable_notification: bool = None, - reply_to_message_id: int = None + reply_to_message_id: int = None, + schedule_date: int = None, ) -> List["types.Message"]: """Send a group of photos or videos as an album. @@ -62,6 +63,9 @@ async def send_media_group( reply_to_message_id (``int``, *optional*): If the message is a reply, ID of the original message. + schedule_date (``int``, *optional*): + Date when the message will be automatically sent. Unix time. + Returns: List of :obj:`~pyrogram.types.Message`: On success, a list of the sent messages is returned. @@ -270,7 +274,8 @@ async def send_media_group( peer=await self.resolve_peer(chat_id), multi_media=multi_media, silent=disable_notification or None, - reply_to_msg_id=reply_to_message_id + reply_to_msg_id=reply_to_message_id, + schedule_date=schedule_date ), sleep_threshold=60 ) @@ -279,7 +284,9 @@ async def send_media_group( self, raw.types.messages.Messages( messages=[m.message for m in filter( - lambda u: isinstance(u, (raw.types.UpdateNewMessage, raw.types.UpdateNewChannelMessage)), + lambda u: isinstance(u, (raw.types.UpdateNewMessage, + raw.types.UpdateNewChannelMessage, + raw.types.UpdateNewScheduledMessage)), r.updates )], users=r.users, From 4f197855f4af38f5dc49ce4ff76e062d25227623 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 28 Nov 2020 17:37:39 +0100 Subject: [PATCH 0393/1185] Fix messed up variable names --- pyrogram/methods/messages/download_media.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pyrogram/methods/messages/download_media.py b/pyrogram/methods/messages/download_media.py index c2d4d0b481..db9493f38e 100644 --- a/pyrogram/methods/messages/download_media.py +++ b/pyrogram/methods/messages/download_media.py @@ -32,7 +32,7 @@ class DownloadMedia(Scaffold): async def download_media( self, - media: Union["types.Message", str], + message: Union["types.Message", str], file_name: str = DEFAULT_DOWNLOAD_DIR, block: bool = True, progress: callable = None, @@ -41,7 +41,7 @@ async def download_media( """Download the media from a message. Parameters: - media (:obj:`~pyrogram.types.Message` | ``str``): + message (:obj:`~pyrogram.types.Message` | ``str``): Pass a Message containing the media, the media itself (message.audio, message.video, ...) or a file id as string. @@ -103,14 +103,16 @@ def progress(current, total): available_media = ("audio", "document", "photo", "sticker", "animation", "video", "voice", "video_note", "new_chat_photo") - if isinstance(media, types.Message): + if isinstance(message, types.Message): for kind in available_media: - media = getattr(media, kind, None) + media = getattr(message, kind, None) if media is not None: break else: raise ValueError("This message doesn't contain any downloadable media") + else: + media = message if isinstance(media, str): file_id_str = media From 72db61a416acd77625278bd3f774046e2393c97d Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 28 Nov 2020 17:38:08 +0100 Subject: [PATCH 0394/1185] Fix wrong docs references on pyrogram.filters --- pyrogram/methods/decorators/on_callback_query.py | 2 +- pyrogram/methods/decorators/on_chosen_inline_result.py | 2 +- pyrogram/methods/decorators/on_deleted_messages.py | 2 +- pyrogram/methods/decorators/on_inline_query.py | 2 +- pyrogram/methods/decorators/on_message.py | 2 +- pyrogram/methods/decorators/on_poll.py | 2 +- pyrogram/methods/decorators/on_user_status.py | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pyrogram/methods/decorators/on_callback_query.py b/pyrogram/methods/decorators/on_callback_query.py index f681eee70b..863fe8f795 100644 --- a/pyrogram/methods/decorators/on_callback_query.py +++ b/pyrogram/methods/decorators/on_callback_query.py @@ -35,7 +35,7 @@ def on_callback_query( :obj:`~pyrogram.handlers.CallbackQueryHandler`. Parameters: - filters (:obj:`~pyrogram.Filters`, *optional*): + filters (:obj:`~pyrogram.filters`, *optional*): Pass one or more filters to allow only a subset of callback queries to be passed in your function. diff --git a/pyrogram/methods/decorators/on_chosen_inline_result.py b/pyrogram/methods/decorators/on_chosen_inline_result.py index 3163ccb388..198fdd965c 100644 --- a/pyrogram/methods/decorators/on_chosen_inline_result.py +++ b/pyrogram/methods/decorators/on_chosen_inline_result.py @@ -35,7 +35,7 @@ def on_chosen_inline_result( :obj:`~pyrogram.handlers.ChosenInlineResult`. Parameters: - filters (:obj:`~pyrogram.Filters`, *optional*): + filters (:obj:`~pyrogram.filters`, *optional*): Pass one or more filters to allow only a subset of chosen inline results to be passed in your function. diff --git a/pyrogram/methods/decorators/on_deleted_messages.py b/pyrogram/methods/decorators/on_deleted_messages.py index adb4d87914..90e692da54 100644 --- a/pyrogram/methods/decorators/on_deleted_messages.py +++ b/pyrogram/methods/decorators/on_deleted_messages.py @@ -35,7 +35,7 @@ def on_deleted_messages( :obj:`~pyrogram.handlers.DeletedMessagesHandler`. Parameters: - filters (:obj:`~pyrogram.Filters`, *optional*): + filters (:obj:`~pyrogram.filters`, *optional*): Pass one or more filters to allow only a subset of messages to be passed in your function. diff --git a/pyrogram/methods/decorators/on_inline_query.py b/pyrogram/methods/decorators/on_inline_query.py index fdc287ecfe..bf445d0766 100644 --- a/pyrogram/methods/decorators/on_inline_query.py +++ b/pyrogram/methods/decorators/on_inline_query.py @@ -35,7 +35,7 @@ def on_inline_query( :obj:`~pyrogram.handlers.InlineQueryHandler`. Parameters: - filters (:obj:`~pyrogram.Filters`, *optional*): + filters (:obj:`~pyrogram.filters`, *optional*): Pass one or more filters to allow only a subset of inline queries to be passed in your function. diff --git a/pyrogram/methods/decorators/on_message.py b/pyrogram/methods/decorators/on_message.py index 6316def640..4f467810ad 100644 --- a/pyrogram/methods/decorators/on_message.py +++ b/pyrogram/methods/decorators/on_message.py @@ -35,7 +35,7 @@ def on_message( :obj:`~pyrogram.handlers.MessageHandler`. Parameters: - filters (:obj:`~pyrogram.Filters`, *optional*): + filters (:obj:`~pyrogram.filters`, *optional*): Pass one or more filters to allow only a subset of messages to be passed in your function. diff --git a/pyrogram/methods/decorators/on_poll.py b/pyrogram/methods/decorators/on_poll.py index 7506c21828..71825c0aee 100644 --- a/pyrogram/methods/decorators/on_poll.py +++ b/pyrogram/methods/decorators/on_poll.py @@ -35,7 +35,7 @@ def on_poll( :obj:`~pyrogram.handlers.PollHandler`. Parameters: - filters (:obj:`~pyrogram.Filters`, *optional*): + filters (:obj:`~pyrogram.filters`, *optional*): Pass one or more filters to allow only a subset of polls to be passed in your function. diff --git a/pyrogram/methods/decorators/on_user_status.py b/pyrogram/methods/decorators/on_user_status.py index 3063ad3d48..2b2ee7fea0 100644 --- a/pyrogram/methods/decorators/on_user_status.py +++ b/pyrogram/methods/decorators/on_user_status.py @@ -34,7 +34,7 @@ def on_user_status( :obj:`~pyrogram.handlers.UserStatusHandler`. Parameters: - filters (:obj:`~pyrogram.Filters`, *optional*): + filters (:obj:`~pyrogram.filters`, *optional*): Pass one or more filters to allow only a subset of UserStatus updated to be passed in your function. group (``int``, *optional*): From 384f4eba71519e0da12c23226b0aac10a706bd8d Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 29 Nov 2020 15:48:29 +0100 Subject: [PATCH 0395/1185] Add support for manual text entities. --- .../methods/messages/edit_message_caption.py | 7 +- .../methods/messages/edit_message_text.py | 9 +- pyrogram/methods/messages/send_animation.py | 8 +- pyrogram/methods/messages/send_audio.py | 11 +- pyrogram/methods/messages/send_document.py | 8 +- pyrogram/methods/messages/send_message.py | 10 +- pyrogram/methods/messages/send_photo.py | 8 +- pyrogram/methods/messages/send_video.py | 8 +- pyrogram/methods/messages/send_voice.py | 8 +- pyrogram/types/input_media/input_media.py | 12 +- .../input_media/input_media_animation.py | 9 +- .../types/input_media/input_media_audio.py | 9 +- .../types/input_media/input_media_document.py | 11 +- .../types/input_media/input_media_photo.py | 11 +- .../types/input_media/input_media_video.py | 9 +- pyrogram/types/messages_and_media/message.py | 50 ++++++++ .../messages_and_media/message_entity.py | 116 ++++++++++++++---- pyrogram/utils.py | 25 +++- 18 files changed, 270 insertions(+), 59 deletions(-) diff --git a/pyrogram/methods/messages/edit_message_caption.py b/pyrogram/methods/messages/edit_message_caption.py index 76b6cc483d..a1cceffa56 100644 --- a/pyrogram/methods/messages/edit_message_caption.py +++ b/pyrogram/methods/messages/edit_message_caption.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Union +from typing import Union, List from pyrogram import types from pyrogram.scaffold import Scaffold @@ -29,6 +29,7 @@ async def edit_message_caption( message_id: int, caption: str, parse_mode: Union[str, None] = object, + caption_entities: List["types.MessageEntity"] = None, reply_markup: "types.InlineKeyboardMarkup" = None ) -> "types.Message": """Edit the caption of media messages. @@ -52,6 +53,9 @@ async def edit_message_caption( Pass "html" to enable HTML-style parsing only. Pass None to completely disable style parsing. + caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): + List of special entities that appear in the caption, which can be specified instead of __parse_mode__. + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*): An InlineKeyboardMarkup object. @@ -68,5 +72,6 @@ async def edit_message_caption( message_id=message_id, text=caption, parse_mode=parse_mode, + entities=caption_entities, reply_markup=reply_markup ) diff --git a/pyrogram/methods/messages/edit_message_text.py b/pyrogram/methods/messages/edit_message_text.py index b7d8483054..ece20d0430 100644 --- a/pyrogram/methods/messages/edit_message_text.py +++ b/pyrogram/methods/messages/edit_message_text.py @@ -16,10 +16,11 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Union +from typing import Union, List from pyrogram import raw from pyrogram import types +from pyrogram import utils from pyrogram.scaffold import Scaffold @@ -30,6 +31,7 @@ async def edit_message_text( message_id: int, text: str, parse_mode: Union[str, None] = object, + entities: List["types.MessageEntity"] = None, disable_web_page_preview: bool = None, reply_markup: "types.InlineKeyboardMarkup" = None ) -> "types.Message": @@ -54,6 +56,9 @@ async def edit_message_text( Pass "html" to enable HTML-style parsing only. Pass None to completely disable style parsing. + entities (List of :obj:`~pyrogram.types.MessageEntity`): + List of special entities that appear in message text, which can be specified instead of __parse_mode__. + disable_web_page_preview (``bool``, *optional*): Disables link previews for links in this message. @@ -81,7 +86,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, - **await self.parser.parse(text, parse_mode) + **await utils.parse_text_entities(self, text, parse_mode, entities) ) ) diff --git a/pyrogram/methods/messages/send_animation.py b/pyrogram/methods/messages/send_animation.py index 2abc8a92c8..e705b5c86e 100644 --- a/pyrogram/methods/messages/send_animation.py +++ b/pyrogram/methods/messages/send_animation.py @@ -18,7 +18,7 @@ import os import re -from typing import Union, BinaryIO +from typing import Union, BinaryIO, List from pyrogram import StopTransmission from pyrogram import raw @@ -37,6 +37,7 @@ async def send_animation( caption: str = "", unsave: bool = False, parse_mode: Union[str, None] = object, + caption_entities: List["types.MessageEntity"] = None, duration: int = 0, width: int = 0, height: int = 0, @@ -83,6 +84,9 @@ async def send_animation( Pass "html" to enable HTML-style parsing only. Pass None to completely disable style parsing. + caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): + List of special entities that appear in the caption, which can be specified instead of __parse_mode__. + duration (``int``, *optional*): Duration of sent animation in seconds. @@ -219,7 +223,7 @@ def progress(current, total): random_id=self.rnd_id(), schedule_date=schedule_date, reply_markup=reply_markup.write() if reply_markup else None, - **await self.parser.parse(caption, parse_mode) + **await utils.parse_text_entities(self, caption, parse_mode, caption_entities) ) ) except FilePartMissing as e: diff --git a/pyrogram/methods/messages/send_audio.py b/pyrogram/methods/messages/send_audio.py index ceff4a546c..18fd2bf181 100644 --- a/pyrogram/methods/messages/send_audio.py +++ b/pyrogram/methods/messages/send_audio.py @@ -18,7 +18,7 @@ import os import re -from typing import Union, BinaryIO +from typing import Union, BinaryIO, List from pyrogram import StopTransmission from pyrogram import raw @@ -36,10 +36,12 @@ async def send_audio( audio: Union[str, BinaryIO], caption: str = "", parse_mode: Union[str, None] = object, + caption_entities: List["types.MessageEntity"] = None, duration: int = 0, performer: str = None, title: str = None, - thumb: Union[str, BinaryIO] = None, file_name: str = None, + thumb: Union[str, BinaryIO] = None, + file_name: str = None, disable_notification: bool = None, reply_to_message_id: int = None, schedule_date: int = None, @@ -79,6 +81,9 @@ async def send_audio( Pass "html" to enable HTML-style parsing only. Pass None to completely disable style parsing. + caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): + List of special entities that appear in the caption, which can be specified instead of __parse_mode__. + duration (``int``, *optional*): Duration of the audio in seconds. @@ -213,7 +218,7 @@ def progress(current, total): random_id=self.rnd_id(), schedule_date=schedule_date, reply_markup=reply_markup.write() if reply_markup else None, - **await self.parser.parse(caption, parse_mode) + **await utils.parse_text_entities(self, caption, parse_mode, caption_entities) ) ) except FilePartMissing as e: diff --git a/pyrogram/methods/messages/send_document.py b/pyrogram/methods/messages/send_document.py index d64117d544..20fea5d5a2 100644 --- a/pyrogram/methods/messages/send_document.py +++ b/pyrogram/methods/messages/send_document.py @@ -18,7 +18,7 @@ import os import re -from typing import Union, BinaryIO +from typing import Union, BinaryIO, List from pyrogram import StopTransmission from pyrogram import raw @@ -37,6 +37,7 @@ async def send_document( thumb: Union[str, BinaryIO] = None, caption: str = "", parse_mode: Union[str, None] = object, + caption_entities: List["types.MessageEntity"] = None, file_name: str = None, force_document: bool = None, disable_notification: bool = None, @@ -82,6 +83,9 @@ async def send_document( Pass "html" to enable HTML-style parsing only. Pass None to completely disable style parsing. + caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): + List of special entities that appear in the caption, which can be specified instead of __parse_mode__. + file_name (``str``, *optional*): File name of the document sent. Defaults to file's path basename. @@ -191,7 +195,7 @@ def progress(current, total): random_id=self.rnd_id(), schedule_date=schedule_date, reply_markup=reply_markup.write() if reply_markup else None, - **await self.parser.parse(caption, parse_mode) + **await utils.parse_text_entities(self, caption, parse_mode, caption_entities) ) ) except FilePartMissing as e: diff --git a/pyrogram/methods/messages/send_message.py b/pyrogram/methods/messages/send_message.py index 9326473119..e873b58df7 100644 --- a/pyrogram/methods/messages/send_message.py +++ b/pyrogram/methods/messages/send_message.py @@ -16,9 +16,9 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Union +from typing import Union, List -from pyrogram import raw +from pyrogram import raw, utils from pyrogram import types from pyrogram.scaffold import Scaffold @@ -29,6 +29,7 @@ async def send_message( chat_id: Union[int, str], text: str, parse_mode: Union[str, None] = object, + entities: List["types.MessageEntity"] = None, disable_web_page_preview: bool = None, disable_notification: bool = None, reply_to_message_id: int = None, @@ -58,6 +59,9 @@ async def send_message( Pass "html" to enable HTML-style parsing only. Pass None to completely disable style parsing. + entities (List of :obj:`~pyrogram.types.MessageEntity`): + List of special entities that appear in message text, which can be specified instead of __parse_mode__. + disable_web_page_preview (``bool``, *optional*): Disables link previews for links in this message. @@ -116,7 +120,7 @@ async def send_message( ])) """ - message, entities = (await self.parser.parse(text, parse_mode)).values() + message, entities = (await utils.parse_text_entities(self, text, parse_mode, entities)).values() r = await self.send( raw.functions.messages.SendMessage( diff --git a/pyrogram/methods/messages/send_photo.py b/pyrogram/methods/messages/send_photo.py index 6caca1bf56..0b84a8c0aa 100644 --- a/pyrogram/methods/messages/send_photo.py +++ b/pyrogram/methods/messages/send_photo.py @@ -18,7 +18,7 @@ import os import re -from typing import Union, BinaryIO +from typing import Union, BinaryIO, List import pyrogram from pyrogram import raw @@ -36,6 +36,7 @@ async def send_photo( photo: Union[str, BinaryIO], caption: str = "", parse_mode: Union[str, None] = object, + caption_entities: List["types.MessageEntity"] = None, ttl_seconds: int = None, disable_notification: bool = None, reply_to_message_id: int = None, @@ -74,6 +75,9 @@ async def send_photo( Pass "html" to enable HTML-style parsing only. Pass None to completely disable style parsing. + caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): + List of special entities that appear in the caption, which can be specified instead of __parse_mode__. + ttl_seconds (``int``, *optional*): Self-Destruct Timer. If you set a timer, the photo will self-destruct in *ttl_seconds* @@ -169,7 +173,7 @@ async def send_photo( random_id=self.rnd_id(), schedule_date=schedule_date, reply_markup=reply_markup.write() if reply_markup else None, - **await self.parser.parse(caption, parse_mode) + **await utils.parse_text_entities(self, caption, parse_mode, caption_entities) ) ) except FilePartMissing as e: diff --git a/pyrogram/methods/messages/send_video.py b/pyrogram/methods/messages/send_video.py index cc4e082d1c..18eb5b673a 100644 --- a/pyrogram/methods/messages/send_video.py +++ b/pyrogram/methods/messages/send_video.py @@ -18,7 +18,7 @@ import os import re -from typing import Union, BinaryIO +from typing import Union, BinaryIO, List from pyrogram import StopTransmission from pyrogram import raw @@ -36,6 +36,7 @@ async def send_video( video: Union[str, BinaryIO], caption: str = "", parse_mode: Union[str, None] = object, + caption_entities: List["types.MessageEntity"] = None, duration: int = 0, width: int = 0, height: int = 0, @@ -79,6 +80,9 @@ async def send_video( Pass "html" to enable HTML-style parsing only. Pass None to completely disable style parsing. + caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): + List of special entities that appear in the caption, which can be specified instead of __parse_mode__. + duration (``int``, *optional*): Duration of sent video in seconds. @@ -213,7 +217,7 @@ def progress(current, total): random_id=self.rnd_id(), schedule_date=schedule_date, reply_markup=reply_markup.write() if reply_markup else None, - **await self.parser.parse(caption, parse_mode) + **await utils.parse_text_entities(self, caption, parse_mode, caption_entities) ) ) except FilePartMissing as e: diff --git a/pyrogram/methods/messages/send_voice.py b/pyrogram/methods/messages/send_voice.py index d6dd9bdb05..36bea41ce5 100644 --- a/pyrogram/methods/messages/send_voice.py +++ b/pyrogram/methods/messages/send_voice.py @@ -18,7 +18,7 @@ import os import re -from typing import Union, BinaryIO +from typing import Union, BinaryIO, List from pyrogram import StopTransmission from pyrogram import raw @@ -36,6 +36,7 @@ async def send_voice( voice: Union[str, BinaryIO], caption: str = "", parse_mode: Union[str, None] = object, + caption_entities: List["types.MessageEntity"] = None, duration: int = 0, disable_notification: bool = None, reply_to_message_id: int = None, @@ -74,6 +75,9 @@ async def send_voice( Pass "html" to enable HTML-style parsing only. Pass None to completely disable style parsing. + caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): + List of special entities that appear in the caption, which can be specified instead of __parse_mode__. + duration (``int``, *optional*): Duration of the voice message in seconds. @@ -175,7 +179,7 @@ async def send_voice( random_id=self.rnd_id(), schedule_date=schedule_date, reply_markup=reply_markup.write() if reply_markup else None, - **await self.parser.parse(caption, parse_mode) + **await utils.parse_text_entities(self, caption, parse_mode, caption_entities) ) ) except FilePartMissing as e: diff --git a/pyrogram/types/input_media/input_media.py b/pyrogram/types/input_media/input_media.py index 6c739d87a9..bcbe2e52be 100644 --- a/pyrogram/types/input_media/input_media.py +++ b/pyrogram/types/input_media/input_media.py @@ -16,6 +16,9 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import List + +from ..messages_and_media import MessageEntity from ..object import Object @@ -31,9 +34,16 @@ class InputMedia(Object): - :obj:`~pyrogram.types.InputMediaVideo` """ - def __init__(self, media: str, caption: str, parse_mode: str): + def __init__( + self, + media: str, + caption: str = None, + parse_mode: str = None, + caption_entities: List[MessageEntity] = None + ): super().__init__() self.media = media self.caption = caption self.parse_mode = parse_mode + self.caption_entities = caption_entities diff --git a/pyrogram/types/input_media/input_media_animation.py b/pyrogram/types/input_media/input_media_animation.py index a59f229ff7..bd9ac1b551 100644 --- a/pyrogram/types/input_media/input_media_animation.py +++ b/pyrogram/types/input_media/input_media_animation.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 typing import Union +from typing import Union, List from .input_media import InputMedia +from ..messages_and_media import MessageEntity class InputMediaAnimation(InputMedia): @@ -46,6 +47,9 @@ class InputMediaAnimation(InputMedia): Pass "html" to enable HTML-style parsing only. Pass None to completely disable style parsing. + caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): + List of special entities that appear in the caption, which can be specified instead of __parse_mode__. + width (``int``, *optional*): Animation width. @@ -62,11 +66,12 @@ def __init__( thumb: str = None, caption: str = "", parse_mode: Union[str, None] = object, + caption_entities: List[MessageEntity] = None, width: int = 0, height: int = 0, duration: int = 0 ): - super().__init__(media, caption, parse_mode) + super().__init__(media, caption, parse_mode, caption_entities) self.thumb = thumb self.width = width diff --git a/pyrogram/types/input_media/input_media_audio.py b/pyrogram/types/input_media/input_media_audio.py index 99fa973c2b..be966d5213 100644 --- a/pyrogram/types/input_media/input_media_audio.py +++ b/pyrogram/types/input_media/input_media_audio.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 typing import Union +from typing import Union, List from .input_media import InputMedia +from ..messages_and_media import MessageEntity class InputMediaAudio(InputMedia): @@ -48,6 +49,9 @@ class InputMediaAudio(InputMedia): Pass "html" to enable HTML-style parsing only. Pass None to completely disable style parsing. + caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): + List of special entities that appear in the caption, which can be specified instead of __parse_mode__. + duration (``int``, *optional*): Duration of the audio in seconds @@ -64,11 +68,12 @@ def __init__( thumb: str = None, caption: str = "", parse_mode: Union[str, None] = object, + caption_entities: List[MessageEntity] = None, duration: int = 0, performer: str = "", title: str = "" ): - super().__init__(media, caption, parse_mode) + super().__init__(media, caption, parse_mode, caption_entities) self.thumb = thumb self.duration = duration diff --git a/pyrogram/types/input_media/input_media_document.py b/pyrogram/types/input_media/input_media_document.py index 23deec3278..7d6500d4ac 100644 --- a/pyrogram/types/input_media/input_media_document.py +++ b/pyrogram/types/input_media/input_media_document.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 typing import Union +from typing import Union, List from .input_media import InputMedia +from ..messages_and_media import MessageEntity class InputMediaDocument(InputMedia): @@ -45,6 +46,9 @@ class InputMediaDocument(InputMedia): Pass "markdown" or "md" to enable Markdown-style parsing only. Pass "html" to enable HTML-style parsing only. Pass None to completely disable style parsing. + + caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): + List of special entities that appear in the caption, which can be specified instead of __parse_mode__. """ def __init__( @@ -52,8 +56,9 @@ def __init__( media: str, thumb: str = None, caption: str = "", - parse_mode: Union[str, None] = object + parse_mode: Union[str, None] = object, + caption_entities: List[MessageEntity] = None ): - super().__init__(media, caption, parse_mode) + super().__init__(media, caption, parse_mode, caption_entities) self.thumb = thumb diff --git a/pyrogram/types/input_media/input_media_photo.py b/pyrogram/types/input_media/input_media_photo.py index f87979827e..7b0c97aa8d 100644 --- a/pyrogram/types/input_media/input_media_photo.py +++ b/pyrogram/types/input_media/input_media_photo.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 typing import Union +from typing import Union, List from .input_media import InputMedia +from ..messages_and_media import MessageEntity class InputMediaPhoto(InputMedia): @@ -41,12 +42,16 @@ class InputMediaPhoto(InputMedia): Pass "markdown" or "md" to enable Markdown-style parsing only. Pass "html" to enable HTML-style parsing only. Pass None to completely disable style parsing. + + caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): + List of special entities that appear in the caption, which can be specified instead of __parse_mode__. """ def __init__( self, media: str, caption: str = "", - parse_mode: Union[str, None] = object + parse_mode: Union[str, None] = object, + caption_entities: List[MessageEntity] = None ): - super().__init__(media, caption, parse_mode) + super().__init__(media, caption, parse_mode, caption_entities) diff --git a/pyrogram/types/input_media/input_media_video.py b/pyrogram/types/input_media/input_media_video.py index 3de713e1a3..8a3013bd70 100644 --- a/pyrogram/types/input_media/input_media_video.py +++ b/pyrogram/types/input_media/input_media_video.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 typing import Union +from typing import Union, List from .input_media import InputMedia +from ..messages_and_media import MessageEntity class InputMediaVideo(InputMedia): @@ -48,6 +49,9 @@ class InputMediaVideo(InputMedia): Pass "html" to enable HTML-style parsing only. Pass None to completely disable style parsing. + caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): + List of special entities that appear in the caption, which can be specified instead of __parse_mode__. + width (``int``, *optional*): Video width. @@ -67,12 +71,13 @@ def __init__( thumb: str = None, caption: str = "", parse_mode: Union[str, None] = object, + caption_entities: List[MessageEntity] = None, width: int = 0, height: int = 0, duration: int = 0, supports_streaming: bool = True ): - super().__init__(media, caption, parse_mode) + super().__init__(media, caption, parse_mode, caption_entities) self.thumb = thumb self.width = width diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index 1f85ba8b3e..4992c37d48 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -711,6 +711,7 @@ async def reply_text( text: str, quote: bool = None, parse_mode: Union[str, None] = object, + entities: List["types.MessageEntity"] = None, disable_web_page_preview: bool = None, disable_notification: bool = None, reply_to_message_id: int = None, @@ -749,6 +750,9 @@ async def reply_text( Pass "html" to enable HTML-style parsing only. Pass None to completely disable style parsing. + entities (List of :obj:`~pyrogram.types.MessageEntity`): + List of special entities that appear in message text, which can be specified instead of __parse_mode__. + disable_web_page_preview (``bool``, *optional*): Disables link previews for links in this message. @@ -779,6 +783,7 @@ async def reply_text( chat_id=self.chat.id, text=text, parse_mode=parse_mode, + entities=entities, disable_web_page_preview=disable_web_page_preview, disable_notification=disable_notification, reply_to_message_id=reply_to_message_id, @@ -793,6 +798,7 @@ async def reply_animation( quote: bool = None, caption: str = "", parse_mode: Union[str, None] = object, + caption_entities: List["types.MessageEntity"] = None, duration: int = 0, width: int = 0, height: int = 0, @@ -846,6 +852,9 @@ async def reply_animation( Pass "html" to enable HTML-style parsing only. Pass None to completely disable style parsing. + caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): + List of special entities that appear in the caption, which can be specified instead of __parse_mode__. + duration (``int``, *optional*): Duration of sent animation in seconds. @@ -913,6 +922,7 @@ async def reply_animation( animation=animation, caption=caption, parse_mode=parse_mode, + caption_entities=caption_entities, duration=duration, width=width, height=height, @@ -930,6 +940,7 @@ async def reply_audio( quote: bool = None, caption: str = "", parse_mode: Union[str, None] = object, + caption_entities: List["types.MessageEntity"] = None, duration: int = 0, performer: str = None, title: str = None, @@ -983,6 +994,9 @@ async def reply_audio( Pass "html" to enable HTML-style parsing only. Pass None to completely disable style parsing. + caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): + List of special entities that appear in the caption, which can be specified instead of __parse_mode__. + duration (``int``, *optional*): Duration of the audio in seconds. @@ -1050,6 +1064,7 @@ async def reply_audio( audio=audio, caption=caption, parse_mode=parse_mode, + caption_entities=caption_entities, duration=duration, performer=performer, title=title, @@ -1067,6 +1082,7 @@ async def reply_cached_media( quote: bool = None, caption: str = "", parse_mode: Union[str, None] = object, + caption_entities: List["types.MessageEntity"] = None, disable_notification: bool = None, reply_to_message_id: int = None, reply_markup: Union[ @@ -1112,6 +1128,9 @@ async def reply_cached_media( Pass "html" to enable HTML-style parsing only. Pass None to completely disable style parsing. + caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): + List of special entities that appear in the caption, which can be specified instead of __parse_mode__. + disable_notification (``bool``, *optional*): Sends the message silently. Users will receive a notification with no sound. @@ -1140,6 +1159,7 @@ async def reply_cached_media( file_id=file_id, caption=caption, parse_mode=parse_mode, + caption_entities=caption_entities, disable_notification=disable_notification, reply_to_message_id=reply_to_message_id, reply_markup=reply_markup @@ -1275,6 +1295,7 @@ async def reply_document( thumb: str = None, caption: str = "", parse_mode: Union[str, None] = object, + caption_entities: List["types.MessageEntity"] = None, disable_notification: bool = None, reply_to_message_id: int = None, reply_markup: Union[ @@ -1330,6 +1351,9 @@ async def reply_document( Pass "html" to enable HTML-style parsing only. Pass None to completely disable style parsing. + caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): + List of special entities that appear in the caption, which can be specified instead of __parse_mode__. + disable_notification (``bool``, *optional*): Sends the message silently. Users will receive a notification with no sound. @@ -1383,6 +1407,7 @@ async def reply_document( thumb=thumb, caption=caption, parse_mode=parse_mode, + caption_entities=caption_entities, disable_notification=disable_notification, reply_to_message_id=reply_to_message_id, reply_markup=reply_markup, @@ -1670,6 +1695,7 @@ async def reply_photo( quote: bool = None, caption: str = "", parse_mode: Union[str, None] = object, + caption_entities: List["types.MessageEntity"] = None, ttl_seconds: int = None, disable_notification: bool = None, reply_to_message_id: int = None, @@ -1720,6 +1746,9 @@ async def reply_photo( Pass "html" to enable HTML-style parsing only. Pass None to completely disable style parsing. + caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): + List of special entities that appear in the caption, which can be specified instead of __parse_mode__. + ttl_seconds (``int``, *optional*): Self-Destruct Timer. If you set a timer, the photo will self-destruct in *ttl_seconds* @@ -1777,6 +1806,7 @@ async def reply_photo( photo=photo, caption=caption, parse_mode=parse_mode, + caption_entities=caption_entities, ttl_seconds=ttl_seconds, disable_notification=disable_notification, reply_to_message_id=reply_to_message_id, @@ -2093,6 +2123,7 @@ async def reply_video( quote: bool = None, caption: str = "", parse_mode: Union[str, None] = object, + caption_entities: List["types.MessageEntity"] = None, duration: int = 0, width: int = 0, height: int = 0, @@ -2147,6 +2178,9 @@ async def reply_video( Pass "html" to enable HTML-style parsing only. Pass None to completely disable style parsing. + caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): + List of special entities that appear in the caption, which can be specified instead of __parse_mode__. + duration (``int``, *optional*): Duration of sent video in seconds. @@ -2217,6 +2251,7 @@ async def reply_video( video=video, caption=caption, parse_mode=parse_mode, + caption_entities=caption_entities, duration=duration, width=width, height=height, @@ -2353,6 +2388,7 @@ async def reply_voice( quote: bool = None, caption: str = "", parse_mode: Union[str, None] = object, + caption_entities: List["types.MessageEntity"] = None, duration: int = 0, disable_notification: bool = None, reply_to_message_id: int = None, @@ -2403,6 +2439,9 @@ async def reply_voice( Pass "html" to enable HTML-style parsing only. Pass None to completely disable style parsing. + caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): + List of special entities that appear in the caption, which can be specified instead of __parse_mode__. + duration (``int``, *optional*): Duration of the voice message in seconds. @@ -2458,6 +2497,7 @@ async def reply_voice( voice=voice, caption=caption, parse_mode=parse_mode, + caption_entities=caption_entities, duration=duration, disable_notification=disable_notification, reply_to_message_id=reply_to_message_id, @@ -2470,6 +2510,7 @@ async def edit_text( self, text: str, parse_mode: Union[str, None] = object, + entities: List["types.MessageEntity"] = None, disable_web_page_preview: bool = None, reply_markup: "types.InlineKeyboardMarkup" = None ) -> "Message": @@ -2501,6 +2542,9 @@ async def edit_text( Pass "html" to enable HTML-style parsing only. Pass None to completely disable style parsing. + entities (List of :obj:`~pyrogram.types.MessageEntity`): + List of special entities that appear in message text, which can be specified instead of __parse_mode__. + disable_web_page_preview (``bool``, *optional*): Disables link previews for links in this message. @@ -2518,6 +2562,7 @@ async def edit_text( message_id=self.message_id, text=text, parse_mode=parse_mode, + entities=entities, disable_web_page_preview=disable_web_page_preview, reply_markup=reply_markup ) @@ -2528,6 +2573,7 @@ async def edit_caption( self, caption: str, parse_mode: Union[str, None] = object, + caption_entities: List["types.MessageEntity"] = None, reply_markup: "types.InlineKeyboardMarkup" = None ) -> "Message": """Bound method *edit_caption* of :obj:`~pyrogram.types.Message`. @@ -2558,6 +2604,9 @@ async def edit_caption( Pass "html" to enable HTML-style parsing only. Pass None to completely disable style parsing. + caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): + List of special entities that appear in the caption, which can be specified instead of __parse_mode__. + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*): An InlineKeyboardMarkup object. @@ -2572,6 +2621,7 @@ async def edit_caption( message_id=self.message_id, caption=caption, parse_mode=parse_mode, + caption_entities=caption_entities, reply_markup=reply_markup ) diff --git a/pyrogram/types/messages_and_media/message_entity.py b/pyrogram/types/messages_and_media/message_entity.py index a88a91c859..0dd13d70e5 100644 --- a/pyrogram/types/messages_and_media/message_entity.py +++ b/pyrogram/types/messages_and_media/message_entity.py @@ -16,12 +16,60 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from enum import Enum, auto + import pyrogram from pyrogram import raw from pyrogram import types from ..object import Object +class AutoName(Enum): + def _generate_next_value_(self, *args): + return self.lower() + + +class MessageEntityType(AutoName): + MENTION = auto() + HASHTAG = auto() + CASHTAG = auto() + BOT_COMMAND = auto() + URL = auto() + EMAIL = auto() + PHONE_NUMBER = auto() + BOLD = auto() + ITALIC = auto() + UNDERLINE = auto() + STRIKETHROUGH = auto() + CODE = auto() + PRE = auto() + TEXT_LINK = auto() + TEXT_MENTION = auto() + BLOCKQUOTE = auto() + + +RAW_ENTITIES_TO_TYPE = { + raw.types.MessageEntityMention: MessageEntityType.MENTION, + raw.types.MessageEntityHashtag: MessageEntityType.HASHTAG, + raw.types.MessageEntityCashtag: MessageEntityType.CASHTAG, + raw.types.MessageEntityBotCommand: MessageEntityType.BOT_COMMAND, + raw.types.MessageEntityUrl: MessageEntityType.URL, + raw.types.MessageEntityEmail: MessageEntityType.EMAIL, + raw.types.MessageEntityBold: MessageEntityType.BOLD, + raw.types.MessageEntityItalic: MessageEntityType.ITALIC, + raw.types.MessageEntityCode: MessageEntityType.CODE, + raw.types.MessageEntityPre: MessageEntityType.PRE, + raw.types.MessageEntityUnderline: MessageEntityType.UNDERLINE, + raw.types.MessageEntityStrike: MessageEntityType.STRIKETHROUGH, + raw.types.MessageEntityBlockquote: MessageEntityType.BLOCKQUOTE, + raw.types.MessageEntityTextUrl: MessageEntityType.TEXT_LINK, + raw.types.MessageEntityMentionName: MessageEntityType.TEXT_MENTION, + raw.types.MessageEntityPhone: MessageEntityType.PHONE_NUMBER +} + +TYPE_TO_RAW_ENTITIES = {v.value: k for k, v in RAW_ENTITIES_TO_TYPE.items()} + + class MessageEntity(Object): """One special entity in a text message. For example, hashtags, usernames, URLs, etc. @@ -29,9 +77,12 @@ class MessageEntity(Object): Parameters: type (``str``): Type of the entity. - Can be "mention" (@username), "hashtag", "cashtag", "bot_command", "url", "email", "phone_number", "bold" - (bold text), "italic" (italic text), "code" (monowidth string), "pre" (monowidth block), "text_link" - (for clickable text URLs), "text_mention" (for custom text mentions based on users' identifiers). + Can be "mention" (``@username``), "hashtag" (``#hashtag``), "cashtag" (``$PYRO``), + "bot_command" (``/start@pyrogrambot``), "url" (``https://pyrogram.org``), + "email" (``do-not-reply@pyrogram.org``), "phone_number" (``+1-420-069-1337``), "bold" (**bold text**), + "italic" (*italic text*), "underline" (underlined text), "strikethrough" (strikethrough text), + "code" (monowidth string), "pre" (monowidth block), "text_link" (for clickable text URLs), + "text_mention" (for users without usernames). offset (``int``): Offset in UTF-16 code units to the start of the entity. @@ -44,26 +95,10 @@ class MessageEntity(Object): user (:obj:`~pyrogram.types.User`, *optional*): For "text_mention" only, the mentioned user. - """ - ENTITIES = { - raw.types.MessageEntityMention.ID: "mention", - raw.types.MessageEntityHashtag.ID: "hashtag", - raw.types.MessageEntityCashtag.ID: "cashtag", - raw.types.MessageEntityBotCommand.ID: "bot_command", - raw.types.MessageEntityUrl.ID: "url", - raw.types.MessageEntityEmail.ID: "email", - raw.types.MessageEntityBold.ID: "bold", - raw.types.MessageEntityItalic.ID: "italic", - raw.types.MessageEntityCode.ID: "code", - raw.types.MessageEntityPre.ID: "pre", - raw.types.MessageEntityUnderline.ID: "underline", - raw.types.MessageEntityStrike.ID: "strike", - raw.types.MessageEntityBlockquote.ID: "blockquote", - raw.types.MessageEntityTextUrl.ID: "text_link", - raw.types.MessageEntityMentionName.ID: "text_mention", - raw.types.MessageEntityPhone.ID: "phone_number" - } + language (``str``. *optional*): + For "pre" only, the programming language of the entity text. + """ def __init__( self, @@ -73,7 +108,8 @@ def __init__( offset: int, length: int, url: str = None, - user: "types.User" = None + user: "types.User" = None, + language: str = None ): super().__init__(client) @@ -82,19 +118,49 @@ def __init__( self.length = length self.url = url self.user = user + self.language = language @staticmethod def _parse(client, entity, users: dict) -> "MessageEntity" or None: - type = MessageEntity.ENTITIES.get(entity.ID, None) + type = RAW_ENTITIES_TO_TYPE.get(entity.__class__, None) if type is None: return None return MessageEntity( - type=type, + type=type.value, offset=entity.offset, length=entity.length, url=getattr(entity, "url", None), user=types.User._parse(client, users.get(getattr(entity, "user_id", None), None)), + language=getattr(entity, "language", None), client=client ) + + async def write(self): + args = self.__dict__.copy() + + for arg in ("_client", "type", "user"): + args.pop(arg) + + if self.user: + args["user_id"] = await self._client.resolve_peer(self.user.id) + + if not self.url: + args.pop("url") + + if self.language is None: + args.pop("language") + + try: + entity = TYPE_TO_RAW_ENTITIES[self.type] + + if entity is raw.types.MessageEntityMentionName: + entity = raw.types.InputMessageEntityMentionName + except KeyError as e: + raise ValueError(f"Invalid message entity type {e}") + else: + try: + return entity(**args) + except TypeError as e: + raise TypeError(f"{entity.QUALNAME}'s {e}") diff --git a/pyrogram/utils.py b/pyrogram/utils.py index e0b79786e3..4c4445334f 100644 --- a/pyrogram/utils.py +++ b/pyrogram/utils.py @@ -24,9 +24,9 @@ import struct from concurrent.futures.thread import ThreadPoolExecutor from getpass import getpass -from typing import List -from typing import Union +from typing import Union, List, Dict +import pyrogram from pyrogram import raw from pyrogram import types from pyrogram.file_id import FileId, FileType, PHOTO_TYPES, DOCUMENT_TYPES @@ -294,3 +294,24 @@ def compute_password_check(r: raw.types.account.Password, password: str) -> raw. ) return raw.types.InputCheckPasswordSRP(srp_id=srp_id, A=A_bytes, M1=M1_bytes) + + +async def parse_text_entities( + client: "pyrogram.Client", + text: str, + parse_mode: str, + entities: List["types.MessageEntity"] +) -> Dict[str, raw.base.MessageEntity]: + if entities: + # Inject the client instance because parsing user mentions requires it + for entity in entities: + entity._client = client + + text, entities = text, [await entity.write() for entity in entities] + else: + text, entities = (await client.parser.parse(text, parse_mode)).values() + + return { + "message": text, + "entities": entities + } From c87177e7a655a1958b024064e6fbfdcab53d90e3 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 30 Nov 2020 12:40:26 +0100 Subject: [PATCH 0396/1185] Do not parse PhotoStrippedSize --- pyrogram/types/messages_and_media/photo.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pyrogram/types/messages_and_media/photo.py b/pyrogram/types/messages_and_media/photo.py index 013378016e..801d711884 100644 --- a/pyrogram/types/messages_and_media/photo.py +++ b/pyrogram/types/messages_and_media/photo.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import List +from typing import List, Optional import pyrogram from pyrogram import raw @@ -80,10 +80,13 @@ def __init__( self.thumbs = thumbs @staticmethod - def _parse(client, photo: "raw.types.Photo", ttl_seconds: int = None) -> "Photo": + def _parse(client, photo: "raw.types.Photo", ttl_seconds: int = None) -> Optional["Photo"]: if isinstance(photo, raw.types.Photo): big = photo.sizes[-1] + if isinstance(big, raw.types.PhotoStrippedSize): + return None + if isinstance(big, raw.types.PhotoSizeProgressive): big = raw.types.PhotoSize( type=big.type, From 68158d10538a1696a4f079601f39173bee673c6a Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 30 Nov 2020 12:41:06 +0100 Subject: [PATCH 0397/1185] Add nicer error in case of unknown constructors --- pyrogram/raw/core/tl_object.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/pyrogram/raw/core/tl_object.py b/pyrogram/raw/core/tl_object.py index 48b3ec9134..de596a8bbc 100644 --- a/pyrogram/raw/core/tl_object.py +++ b/pyrogram/raw/core/tl_object.py @@ -30,7 +30,16 @@ class TLObject: @classmethod def read(cls, data: BytesIO, *args: Any) -> Any: - return cast(TLObject, objects[int.from_bytes(data.read(4), "little")]).read(data, *args) + try: + return cast(TLObject, objects[int.from_bytes(data.read(4), "little")]).read(data, *args) + except KeyError as e: + left = data.read() + + left = [left[i:i + 64] for i in range(0, len(left), 64)] + left = [[left[i:i + 8] for i in range(0, len(left), 8)] for left in left] + left = "\n".join(" ".join(x for x in left) for left in left) + + raise ValueError(f"Unknown constructor found: {hex(e.args[0])}\n{left}") def write(self, *args: Any) -> bytes: pass From 601483f210d08326db6be9539eb78e1bd1f768f6 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 30 Nov 2020 12:46:44 +0100 Subject: [PATCH 0398/1185] Fix leftover bytes not being represented in hex --- pyrogram/raw/core/tl_object.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/raw/core/tl_object.py b/pyrogram/raw/core/tl_object.py index de596a8bbc..1d6b503217 100644 --- a/pyrogram/raw/core/tl_object.py +++ b/pyrogram/raw/core/tl_object.py @@ -33,7 +33,7 @@ def read(cls, data: BytesIO, *args: Any) -> Any: try: return cast(TLObject, objects[int.from_bytes(data.read(4), "little")]).read(data, *args) except KeyError as e: - left = data.read() + left = data.read().hex() left = [left[i:i + 64] for i in range(0, len(left), 64)] left = [[left[i:i + 8] for i in range(0, len(left), 8)] for left in left] From b8934ae17c9b47448648f4b387ee44701b87d4ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joscha=20G=C3=B6tzer?= Date: Tue, 1 Dec 2020 21:55:33 +0100 Subject: [PATCH 0399/1185] Don't process MessageEmpty in get_dialogs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Prevent ``` File "C:\Users\INT002327\AppData\Local\pypoetry\Cache\virtualenvs\josxabot-c3BmTbt9-py3.8\lib\site-packages\pyrogram\methods\chats\get_dialogs.py", line 92, in get_dialogs to_id = message.to_id └ pyrogram.raw.types.MessageEmpty(id=2767691) AttributeError: 'MessageEmpty' object has no attribute 'to_id' ``` --- pyrogram/methods/chats/get_dialogs.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyrogram/methods/chats/get_dialogs.py b/pyrogram/methods/chats/get_dialogs.py index b437012b77..c05e6d020d 100644 --- a/pyrogram/methods/chats/get_dialogs.py +++ b/pyrogram/methods/chats/get_dialogs.py @@ -89,6 +89,9 @@ async def get_dialogs( messages = {} for message in r.messages: + if message.empty: + continue + peer_id = message.peer_id if isinstance(peer_id, raw.types.PeerUser): From 8f71527bbdf77507b722f67b425f7ec4032dcd11 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 2 Dec 2020 17:00:42 +0100 Subject: [PATCH 0400/1185] Update errors --- compiler/errors/source/400_BAD_REQUEST.tsv | 44 ++++++++++++++++--- compiler/errors/source/403_FORBIDDEN.tsv | 6 +++ compiler/errors/source/406_NOT_ACCEPTABLE.tsv | 5 ++- .../source/500_INTERNAL_SERVER_ERROR.tsv | 19 +++++++- 4 files changed, 66 insertions(+), 8 deletions(-) diff --git a/compiler/errors/source/400_BAD_REQUEST.tsv b/compiler/errors/source/400_BAD_REQUEST.tsv index c72985bf51..02e6902372 100644 --- a/compiler/errors/source/400_BAD_REQUEST.tsv +++ b/compiler/errors/source/400_BAD_REQUEST.tsv @@ -47,6 +47,7 @@ CHANNELS_TOO_MUCH You have joined too many channels or supergroups, leave some a CHANNEL_BANNED The channel is banned CHANNEL_INVALID The channel parameter is invalid CHANNEL_PRIVATE The channel/supergroup is not accessible +CHANNEL_TOO_LARGE The channel is too large to be deleted; this error is issued when trying to delete channels with more than 1000 members (subject to change) CHAT_ABOUT_NOT_MODIFIED The chat about text was not modified because you tried to edit it using the same content CHAT_ABOUT_TOO_LONG The chat about text is too long CHAT_ADMIN_REQUIRED The method requires chat admin privileges @@ -62,23 +63,29 @@ CODE_EMPTY The provided code is empty CODE_HASH_INVALID The provided code hash invalid CODE_INVALID The provided code is invalid (i.e. from email) CONNECTION_API_ID_INVALID The provided API id is invalid +CONNECTION_APP_VERSION_EMPTY App version is empty CONNECTION_DEVICE_MODEL_EMPTY The device model is empty CONNECTION_LANG_PACK_INVALID The specified language pack is not valid CONNECTION_LAYER_INVALID The connection layer is invalid. Missing InvokeWithLayer-InitConnection call CONNECTION_NOT_INITED The connection was not initialized CONNECTION_SYSTEM_EMPTY The connection to the system is empty CONNECTION_SYSTEM_LANG_CODE_EMPTY The system language code is empty +CONTACT_ADD_MISSING Contact to add is missing CONTACT_ID_INVALID The provided contact id is invalid CONTACT_NAME_EMPTY The provided contact name is empty +CONTACT_REQ_MISSING Missing contact request DATA_INVALID The encrypted data is invalid DATA_JSON_INVALID The provided JSON data is invalid +DATA_TOO_LONG Data too long DATE_EMPTY The date argument is empty DC_ID_INVALID The dc_id parameter is invalid DH_G_A_INVALID The g_a parameter invalid DOCUMENT_INVALID The document is invalid EMAIL_HASH_EXPIRED The email hash expired and cannot be used to verify it EMAIL_INVALID The email provided is invalid -EMAIL_UNCONFIRMED_X Email unconfirmed, the length of the code must be {x} +EMAIL_UNCONFIRMED Email unconfirmed +EMAIL_UNCONFIRMED_X The provided email isn't confirmed, {x} is the length of the verification code that was just sent to the email +EMAIL_VERIFY_EXPIRED The verification email has expired EMOTICON_EMPTY The emoticon parameter is empty EMOTICON_INVALID The emoticon parameter is invalid EMOTICON_STICKERPACK_MISSING The emoticon sticker pack you are trying to obtain is missing @@ -104,15 +111,20 @@ FILE_PART_SIZE_CHANGED The part size is different from the size of one of the pr FILE_PART_SIZE_INVALID 512 KB cannot be evenly divided by part_size FILE_PART_TOO_BIG The size limit (512 KB) for the content of the file part has been exceeded FILE_PART_X_MISSING Part {x} of the file is missing from storage -FILE_REFERENCE_EMPTY The file reference is empty -FILE_REFERENCE_EXPIRED The file reference has expired and is no longer valid or it belongs to self-destructing media and cannot be resent -FILE_REFERENCE_INVALID The file reference is invalid +FILE_REFERENCE_EMPTY The file id contains an empty file reference, you must obtain a valid one by fetching the message from the origin context +FILE_REFERENCE_EXPIRED The file id contains an expired file reference, you must obtain a valid one by fetching the message from the origin context +FILE_REFERENCE_INVALID The file id contains an invalid file reference, you must obtain a valid one by fetching the message from the origin context +FILTER_ID_INVALID The specified filter ID is invalid FIRSTNAME_INVALID The first name is invalid FOLDER_ID_EMPTY The folder you tried to delete was already empty FOLDER_ID_INVALID The folder id is invalid FRESH_CHANGE_ADMINS_FORBIDDEN You can't change administrator settings in this chat because your session was logged-in recently +FROM_MESSAGE_BOT_DISABLED Bots can't use fromMessage min constructors GAME_BOT_INVALID You cannot send that game with the current bot +GEO_POINT_INVALID Invalid geo point provided +GIF_CONTENT_TYPE_INVALID GIF content-type invalid GIF_ID_INVALID The provided gif/animation id is invalid +GRAPH_INVALID_RELOAD Invalid graph token provided, please reload the stats and provide the updated token GRAPH_OUTDATED_RELOAD The graph data is outdated GROUPED_MEDIA_INVALID The album contains invalid media HASH_INVALID The provided hash is invalid @@ -162,8 +174,9 @@ OFFSET_INVALID The offset parameter is invalid OFFSET_PEER_ID_INVALID The provided offset peer is invalid OPTIONS_TOO_MUCH The poll options are too many OPTION_INVALID The option specified is invalid and does not exist in the target poll -PACK_SHORT_NAME_INVALID Invalid sticker pack name. It must begin with a letter, can't contain consecutive underscores and must end in 'by_'. +PACK_SHORT_NAME_INVALID Invalid sticker pack name. It must begin with a letter, can't contain consecutive underscores and must end in '_by_'. PACK_SHORT_NAME_OCCUPIED A sticker pack with this name already exists +PACK_TITLE_INVALID The sticker pack title is invalid PARTICIPANTS_TOO_FEW The chat doesn't have enough participants PARTICIPANT_VERSION_OUTDATED The other participant is using an outdated Telegram app version PASSWORD_EMPTY The password provided is empty @@ -191,20 +204,24 @@ PHONE_NUMBER_UNOCCUPIED The phone number is not yet being used PHONE_PASSWORD_PROTECTED The phone is password protected PHOTO_CONTENT_TYPE_INVALID The photo content type is invalid PHOTO_CONTENT_URL_EMPTY The photo content URL is empty +PHOTO_CROP_FILE_MISSING Photo crop file missing PHOTO_CROP_SIZE_SMALL The photo is too small PHOTO_EXT_INVALID The photo extension is invalid +PHOTO_FILE_MISSING Profile photo file missing PHOTO_ID_INVALID The photo id is invalid PHOTO_INVALID The photo is invalid PHOTO_INVALID_DIMENSIONS The photo dimensions are invalid PHOTO_SAVE_FILE_INVALID The photo you tried to send cannot be saved by Telegram. A reason may be that it exceeds 10 MB. Try resizing it locally PHOTO_THUMB_URL_EMPTY The photo thumb URL is empty PHOTO_THUMB_URL_INVALID The photo thumb URL is invalid +PINNED_DIALOGS_TOO_MUCH Too many pinned dialogs PIN_RESTRICTED You can't pin messages in private chats with other people POLL_ANSWERS_INVALID The poll answers are invalid POLL_OPTION_DUPLICATE A duplicate option was sent in the same poll POLL_OPTION_INVALID A poll option used invalid data (the data may be too long) POLL_QUESTION_INVALID The poll question is invalid POLL_UNSUPPORTED This layer does not support polls in the invoked method +POLL_VOTE_REQUIRED Cast a vote in the poll before calling this method PRIVACY_KEY_INVALID The privacy key is invalid PRIVACY_TOO_LONG Your privacy exception list has exceeded the maximum capacity PRIVACY_VALUE_INVALID The privacy value is invalid @@ -215,20 +232,25 @@ QUIZ_CORRECT_ANSWERS_EMPTY The correct answers of the quiz are empty QUIZ_CORRECT_ANSWERS_TOO_MUCH The quiz contains too many correct answers QUIZ_CORRECT_ANSWER_INVALID The correct answers of the quiz are invalid QUIZ_MULTIPLE_INVALID A quiz can't have multiple answers +RANDOM_ID_EMPTY The random ID is empty RANDOM_ID_INVALID The provided random ID is invalid RANDOM_LENGTH_INVALID The random length is invalid RANGES_INVALID Invalid range provided REACTION_EMPTY The reaction provided is empty REACTION_INVALID Invalid reaction provided (only emoji are allowed) +REPLY_MARKUP_BUY_EMPTY Reply markup for buy button empty REPLY_MARKUP_GAME_EMPTY The provided reply markup for the game is empty REPLY_MARKUP_INVALID The provided reply markup is invalid REPLY_MARKUP_TOO_LONG The reply markup is too long RESULTS_TOO_MUCH The result contains too many items RESULT_ID_DUPLICATE The result contains items with duplicated identifiers +RESULT_ID_EMPTY Result ID empty RESULT_ID_INVALID The given result cannot be used to send the selection to the bot RESULT_TYPE_INVALID The result type is invalid +REVOTE_NOT_ALLOWED You cannot change your vote RSA_DECRYPT_FAILED Internal RSA decryption failed SCHEDULE_BOT_NOT_ALLOWED Bots are not allowed to schedule messages +SCHEDULE_DATE_INVALID Invalid schedule date provided SCHEDULE_DATE_TOO_LATE The date you tried to schedule is too far in the future (more than one year) SCHEDULE_STATUS_PRIVATE You cannot schedule a message until the person comes online if their privacy does not show this information SCHEDULE_TOO_MUCH You cannot schedule more than 100 messages in this chat @@ -237,10 +259,16 @@ SECONDS_INVALID The seconds interval is invalid, for slow mode try with 0 (off), SEND_MESSAGE_MEDIA_INVALID The message media is invalid SEND_MESSAGE_TYPE_INVALID The message type is invalid SESSION_TOO_FRESH_X You can't do this action because the current session was logged-in recently +SETTINGS_INVALID Invalid settings were provided SHA256_HASH_INVALID The provided SHA256 hash is invalid SHORTNAME_OCCUPY_FAILED An error occurred when trying to register the short-name used for the sticker pack. Try a different name +SLOWMODE_MULTI_MSGS_DISABLED Slowmode is enabled, you cannot forward multiple messages to this group +SMS_CODE_CREATE_FAILED An error occurred while creating the SMS code +SRP_ID_INVALID Invalid SRP ID provided +SRP_PASSWORD_CHANGED The password has changed START_PARAM_EMPTY The start parameter is empty START_PARAM_INVALID The start parameter is invalid +START_PARAM_TOO_LONG The start parameter is too long STICKERSET_INVALID The requested sticker set is invalid STICKERS_EMPTY The sticker provided is empty STICKER_DOCUMENT_INVALID The sticker document is invalid @@ -253,6 +281,9 @@ STICKER_PNG_NOPNG Stickers must be png files but the provided image was not a pn TAKEOUT_INVALID The takeout id is invalid TAKEOUT_REQUIRED The method must be invoked inside a takeout session TEMP_AUTH_KEY_EMPTY The temporary auth key provided is empty +THEME_FILE_INVALID Invalid theme file provided +THEME_FORMAT_INVALID Invalid theme format provided +THEME_INVALID Invalid theme provided THEME_MIME_INVALID You cannot create this theme because the mime-type is invalid TMP_PASSWORD_DISABLED The temporary password is disabled TOKEN_INVALID The provided token is invalid @@ -292,8 +323,9 @@ WALLPAPER_INVALID The input wallpaper was not valid WC_CONVERT_URL_INVALID WC convert URL invalid WEBDOCUMENT_INVALID The web document is invalid WEBDOCUMENT_MIME_INVALID The web document mime type is invalid +WEBDOCUMENT_SIZE_TOO_BIG The web document is too big WEBDOCUMENT_URL_EMPTY The web document URL is empty WEBDOCUMENT_URL_INVALID The web document URL is invalid WEBPAGE_CURL_FAILED Telegram server could not fetch the provided URL WEBPAGE_MEDIA_EMPTY The URL doesn't contain any valid media -YOU_BLOCKED_USER You blocked this user +YOU_BLOCKED_USER You blocked this user \ No newline at end of file diff --git a/compiler/errors/source/403_FORBIDDEN.tsv b/compiler/errors/source/403_FORBIDDEN.tsv index 25a1e0d0f0..e560a0aae9 100644 --- a/compiler/errors/source/403_FORBIDDEN.tsv +++ b/compiler/errors/source/403_FORBIDDEN.tsv @@ -2,18 +2,24 @@ id message BROADCAST_FORBIDDEN The request can't be used in channels CHANNEL_PUBLIC_GROUP_NA The channel/supergroup is not available CHAT_ADMIN_INVITE_REQUIRED You don't have rights to invite other users +CHAT_ADMIN_REQUIRED The method requires chat admin privileges CHAT_FORBIDDEN You cannot write in this chat CHAT_SEND_GIFS_FORBIDDEN You can't send animations in this chat +CHAT_SEND_INLINE_FORBIDDEN You cannot use inline bots to send messages in this chat CHAT_SEND_MEDIA_FORBIDDEN You can't send media messages in this chat +CHAT_SEND_POLL_FORBIDDEN You can't send polls in this chat CHAT_SEND_STICKERS_FORBIDDEN You can't send stickers in this chat CHAT_WRITE_FORBIDDEN You don't have rights to send messages in this chat INLINE_BOT_REQUIRED The action must be performed through an inline bot callback MESSAGE_AUTHOR_REQUIRED You are not the author of this message MESSAGE_DELETE_FORBIDDEN You don't have rights to delete messages in this chat, most likely because you are not the author of them +POLL_VOTE_REQUIRED Cast a vote in the poll before calling this method RIGHT_FORBIDDEN You don't have enough rights for this action, or you tried to set one or more admin rights that can't be applied to this kind of chat (channel or supergroup) SENSITIVE_CHANGE_FORBIDDEN Your sensitive content settings can't be changed at this time +TAKEOUT_REQUIRED The method must be invoked inside a takeout session USER_BOT_INVALID This method can only be called by a bot USER_CHANNELS_TOO_MUCH One of the users you tried to add is already in too many channels/supergroups +USER_INVALID The provided user is invalid USER_IS_BLOCKED The user is blocked USER_NOT_MUTUAL_CONTACT The provided user is not a mutual contact USER_PRIVACY_RESTRICTED The user's privacy settings is preventing you to perform this action diff --git a/compiler/errors/source/406_NOT_ACCEPTABLE.tsv b/compiler/errors/source/406_NOT_ACCEPTABLE.tsv index 4190e0709a..c1c5b7efb5 100644 --- a/compiler/errors/source/406_NOT_ACCEPTABLE.tsv +++ b/compiler/errors/source/406_NOT_ACCEPTABLE.tsv @@ -1,9 +1,12 @@ id message AUTH_KEY_DUPLICATED The same authorization key (session file) was used in more than one place simultaneously. You must delete your session file and log in again with your phone number or bot token FILEREF_UPGRADE_NEEDED The file reference has expired and you must use a refreshed one by obtaining the original media message +FRESH_CHANGE_ADMINS_FORBIDDEN You were just elected admin, you can't add or modify other admins yet FRESH_CHANGE_PHONE_FORBIDDEN You can't change your phone number because your session was logged-in recently FRESH_RESET_AUTHORISATION_FORBIDDEN You can't terminate other authorized sessions because the current was logged-in recently PHONE_NUMBER_INVALID The phone number is invalid PHONE_PASSWORD_FLOOD You have tried to log-in too many times STICKERSET_INVALID The sticker set is invalid -STICKERSET_OWNER_ANONYMOUS This sticker set can't be used as the group's sticker set because it was created by one of its anonymous admins \ No newline at end of file +STICKERSET_OWNER_ANONYMOUS This sticker set can't be used as the group's sticker set because it was created by one of its anonymous admins +USERPIC_UPLOAD_REQUIRED You must have a profile picture to publish your geolocation +USER_RESTRICTED You are limited/restricted. You can't perform this action \ No newline at end of file diff --git a/compiler/errors/source/500_INTERNAL_SERVER_ERROR.tsv b/compiler/errors/source/500_INTERNAL_SERVER_ERROR.tsv index 75fc1fcf9a..00a3e28b2d 100644 --- a/compiler/errors/source/500_INTERNAL_SERVER_ERROR.tsv +++ b/compiler/errors/source/500_INTERNAL_SERVER_ERROR.tsv @@ -1,25 +1,42 @@ id message +API_CALL_ERROR API call error due to Telegram having internal problems. Please try again later AUTH_RESTART User authorization has restarted CALL_OCCUPY_FAILED The call failed because the user is already making another call +CHAT_ID_GENERATE_FAILED Failure while generating the chat ID due to Telegram having internal problems. Please try again later +CHAT_OCCUPY_LOC_FAILED An internal error occurred while creating the chat +CHAT_OCCUPY_USERNAME_FAILED Failure to occupy chat username due to Telegram having internal problems. Please try again later +CHP_CALL_FAIL Telegram is having internal problems. Please try again later +ENCRYPTION_OCCUPY_ADMIN_FAILED Failed occupying memory for admin info due to Telegram having internal problems. Please try again later ENCRYPTION_OCCUPY_FAILED Internal server error while accepting secret chat FOLDER_DEAC_AUTOFIX_ALL Telegram is having internal problems. Please try again later +GROUPED_ID_OCCUPY_FAILED Telegram is having internal problems. Please try again later HISTORY_GET_FAILED The chat history couldn't be retrieved due to Telegram having internal problems. Please try again later +IMAGE_ENGINE_DOWN Image engine down due to Telegram having internal problems. Please try again later INTERDC_X_CALL_ERROR An error occurred while Telegram was intercommunicating with DC{x}. Please try again later INTERDC_X_CALL_RICH_ERROR A rich error occurred while Telegram was intercommunicating with DC{x}. Please try again later +MEMBER_FETCH_FAILED Telegram is having internal problems. Please try again later MEMBER_NO_LOCATION Couldn't find the member's location due to Telegram having internal problems. Please try again later MEMBER_OCCUPY_PRIMARY_LOC_FAILED Telegram is having internal problems. Please try again later +MEMBER_OCCUPY_USERNAME_FAILED Failure to occupy member username due to Telegram having internal problems. Please try again later MSGID_DECREASE_RETRY Telegram is having internal problems. Please try again later +MSG_RANGE_UNSYNC Message range unsynchronized due to Telegram having internal problems. Please try again later MT_SEND_QUEUE_TOO_LONG The MTProto send queue has grown too much due to Telegram having internal problems. Please try again later NEED_CHAT_INVALID The provided chat is invalid NEED_MEMBER_INVALID The provided member is invalid or does not exist PARTICIPANT_CALL_FAILED Failure while making call due to Telegram having internal problems. Please try again later PERSISTENT_TIMESTAMP_OUTDATED The persistent timestamp is outdated due to Telegram having internal problems. Please try again later +POSTPONED_TIMEOUT Telegram is having internal problems. Please try again later PTS_CHANGE_EMPTY No PTS change RANDOM_ID_DUPLICATE You provided a random ID that was already used REG_ID_GENERATE_FAILED The registration id failed to generate due to Telegram having internal problems. Please try again later RPC_CALL_FAIL Telegram is having internal problems. Please try again later +RPC_CONNECT_FAILED Telegram is having internal problems. Please try again later RPC_MCGET_FAIL Telegram is having internal problems. Please try again later +SIGN_IN_FAILED Failure while signing in due to Telegram having internal problems. Please try again later STORAGE_CHECK_FAILED Server storage check failed due to Telegram having internal problems. Please try again later STORE_INVALID_SCALAR_TYPE Telegram is having internal problems. Please try again later UNKNOWN_METHOD The method you tried to call cannot be called on non-CDN DCs -WORKER_BUSY_TOO_LONG_RETRY Server workers are too busy right now due to Telegram having internal problems. Please try again later \ No newline at end of file +UPLOAD_NO_VOLUME Telegram is having internal problems. Please try again later +VOLUME_LOC_NOT_FOUND Telegram is having internal problems. Please try again later +WORKER_BUSY_TOO_LONG_RETRY Server workers are too busy right now due to Telegram having internal problems. Please try again later +WP_ID_GENERATE_FAILED Telegram is having internal problems. Please try again later \ No newline at end of file From de0b215f8de0dbdcb51a1307197564037e57ec01 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 2 Dec 2020 17:27:04 +0100 Subject: [PATCH 0401/1185] Add a missing method to docs and move one to the correct space --- compiler/docs/compiler.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index 479ee2b845..446dab1e26 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -207,7 +207,6 @@ def get_title_list(s: str) -> list: iter_dialogs get_dialogs_count update_chat_username - get_common_chats get_nearby_chats archive_chats unarchive_chats @@ -217,6 +216,7 @@ def get_title_list(s: str) -> list: create_supergroup delete_channel delete_supergroup + delete_user_history set_slow_mode """, users=""" @@ -232,6 +232,7 @@ def get_title_list(s: str) -> list: update_profile block_user unblock_user + get_common_chats """, contacts=""" Contacts From ff0fa9cbc3234299e02bd6fbbba6eb1f5d0d24cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Joscha=20G=C3=B6tzer?= Date: Thu, 3 Dec 2020 02:51:03 +0100 Subject: [PATCH 0402/1185] Update get_dialogs.py --- pyrogram/methods/chats/get_dialogs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/methods/chats/get_dialogs.py b/pyrogram/methods/chats/get_dialogs.py index c05e6d020d..487e8f8d5a 100644 --- a/pyrogram/methods/chats/get_dialogs.py +++ b/pyrogram/methods/chats/get_dialogs.py @@ -89,7 +89,7 @@ async def get_dialogs( messages = {} for message in r.messages: - if message.empty: + if isinstance(message, raw.types.MessageEmpty): continue peer_id = message.peer_id From babbe00e5e602b0423514fd9c563be42b498fd61 Mon Sep 17 00:00:00 2001 From: Timothy Redaelli Date: Thu, 3 Dec 2020 12:21:38 +0100 Subject: [PATCH 0403/1185] Add ttl_seconds support for send_video and reply_video --- pyrogram/methods/messages/send_video.py | 14 +++++++++++++- pyrogram/types/messages_and_media/message.py | 7 +++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/pyrogram/methods/messages/send_video.py b/pyrogram/methods/messages/send_video.py index 18eb5b673a..a5b12aee84 100644 --- a/pyrogram/methods/messages/send_video.py +++ b/pyrogram/methods/messages/send_video.py @@ -37,6 +37,7 @@ async def send_video( caption: str = "", parse_mode: Union[str, None] = object, caption_entities: List["types.MessageEntity"] = None, + ttl_seconds: int = None, duration: int = 0, width: int = 0, height: int = 0, @@ -83,6 +84,11 @@ async def send_video( caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): List of special entities that appear in the caption, which can be specified instead of __parse_mode__. + ttl_seconds (``int``, *optional*): + Self-Destruct Timer. + If you set a timer, the video will self-destruct in *ttl_seconds* + seconds after it was viewed. + duration (``int``, *optional*): Duration of sent video in seconds. @@ -155,6 +161,9 @@ async def send_video( # Add caption to the video app.send_video("me", "video.mp4", caption="recording") + # Send self-destructing video + app.send_photo("me", "video.mp4", ttl_seconds=10) + # Keep track of the progress while uploading def progress(current, total): print(f"{current * 100 / total:.1f}%") @@ -171,6 +180,7 @@ def progress(current, total): media = raw.types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(video) or "video/mp4", file=file, + ttl_seconds=ttl_seconds, thumb=thumb, attributes=[ raw.types.DocumentAttributeVideo( @@ -184,7 +194,8 @@ def progress(current, total): ) elif re.match("^https?://", video): media = raw.types.InputMediaDocumentExternal( - url=video + url=video, + ttl_seconds=ttl_seconds ) else: media = utils.get_input_media_from_file_id(video, FileType.VIDEO) @@ -194,6 +205,7 @@ def progress(current, total): media = raw.types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(video.name) or "video/mp4", file=file, + ttl_seconds=ttl_seconds, thumb=thumb, attributes=[ raw.types.DocumentAttributeVideo( diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index 4992c37d48..2e89166664 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -2124,6 +2124,7 @@ async def reply_video( caption: str = "", parse_mode: Union[str, None] = object, caption_entities: List["types.MessageEntity"] = None, + ttl_seconds: int = None, duration: int = 0, width: int = 0, height: int = 0, @@ -2181,6 +2182,11 @@ async def reply_video( caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): List of special entities that appear in the caption, which can be specified instead of __parse_mode__. + ttl_seconds (``int``, *optional*): + Self-Destruct Timer. + If you set a timer, the video will self-destruct in *ttl_seconds* + seconds after it was viewed. + duration (``int``, *optional*): Duration of sent video in seconds. @@ -2252,6 +2258,7 @@ async def reply_video( caption=caption, parse_mode=parse_mode, caption_entities=caption_entities, + ttl_seconds=ttl_seconds, duration=duration, width=width, height=height, From 6f878949e1e08e3526f3299c4279c6ffa935af49 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 5 Dec 2020 01:34:14 +0100 Subject: [PATCH 0404/1185] Add missing caption_entities parameter to send_cached_media --- pyrogram/methods/messages/send_cached_media.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pyrogram/methods/messages/send_cached_media.py b/pyrogram/methods/messages/send_cached_media.py index bc7b2f15f6..09fd96cd2a 100644 --- a/pyrogram/methods/messages/send_cached_media.py +++ b/pyrogram/methods/messages/send_cached_media.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Union +from typing import Union, List from pyrogram import raw from pyrogram import types @@ -31,6 +31,7 @@ async def send_cached_media( file_id: str, caption: str = "", parse_mode: Union[str, None] = object, + caption_entities: List["types.MessageEntity"] = None, disable_notification: bool = None, reply_to_message_id: int = None, schedule_date: int = None, @@ -67,6 +68,9 @@ async def send_cached_media( Pass "html" to enable HTML-style parsing only. Pass None to completely disable style parsing. + caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): + List of special entities that appear in the caption, which can be specified instead of __parse_mode__. + disable_notification (``bool``, *optional*): Sends the message silently. Users will receive a notification with no sound. @@ -99,7 +103,7 @@ async def send_cached_media( random_id=self.rnd_id(), schedule_date=schedule_date, reply_markup=reply_markup.write() if reply_markup else None, - **await self.parser.parse(caption, parse_mode) + **await utils.parse_text_entities(self, caption, parse_mode, caption_entities) ) ) From 7325daf870785888158067bb5f29d54bde94da15 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 5 Dec 2020 01:35:24 +0100 Subject: [PATCH 0405/1185] Add new method: copy_message --- compiler/docs/compiler.py | 1 + pyrogram/methods/messages/__init__.py | 4 +- pyrogram/methods/messages/copy_messages.py | 206 +++++++++++++++++++++ 3 files changed, 210 insertions(+), 1 deletion(-) create mode 100644 pyrogram/methods/messages/copy_messages.py diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index 446dab1e26..8d7efb911b 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -143,6 +143,7 @@ def get_title_list(s: str) -> list: Messages send_message forward_messages + copy_message send_photo send_audio send_document diff --git a/pyrogram/methods/messages/__init__.py b/pyrogram/methods/messages/__init__.py index e78d2bc52c..c6697dbc89 100644 --- a/pyrogram/methods/messages/__init__.py +++ b/pyrogram/methods/messages/__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 .copy_messages import CopyMessage from .delete_messages import DeleteMessages from .download_media import DownloadMedia from .edit_inline_caption import EditInlineCaption @@ -94,6 +95,7 @@ class Messages( EditInlineReplyMarkup, SendDice, SearchMessages, - SearchGlobal + SearchGlobal, + CopyMessage ): pass diff --git a/pyrogram/methods/messages/copy_messages.py b/pyrogram/methods/messages/copy_messages.py new file mode 100644 index 0000000000..5b4ec51780 --- /dev/null +++ b/pyrogram/methods/messages/copy_messages.py @@ -0,0 +1,206 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan +# +# 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 +from functools import partial +from typing import Union, Iterable, List + +from pyrogram import types +from pyrogram.scaffold import Scaffold + +log = logging.getLogger(__name__) + + +class CopyMessage(Scaffold): + async def copy_messages( + self, + chat_id: Union[int, str], + from_chat_id: Union[int, str], + message_id: Union[int, Iterable[int]], + caption: str = None, + parse_mode: Union[str, None] = object, + caption_entities: List["types.MessageEntity"] = None, + disable_notification: bool = None, + reply_to_message_id: int = None, + schedule_date: int = None, + reply_markup: Union[ + "types.InlineKeyboardMarkup", + "types.ReplyKeyboardMarkup", + "types.ReplyKeyboardRemove", + "types.ForceReply" + ] = None + ) -> List["types.Message"]: + """Copy messages of any kind. + + The method is analogous to the method :meth:`~Client.forward_messages`, but the copied message doesn't have a + link to the original message. + + Parameters: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + For your personal cloud (Saved Messages) you can simply use "me" or "self". + For a contact that exists in your Telegram address book you can use his phone number (str). + + from_chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the source chat where the original message was sent. + For your personal cloud (Saved Messages) you can simply use "me" or "self". + For a contact that exists in your Telegram address book you can use his phone number (str). + + message_id (``int``): + Message identifier in the chat specified in *from_chat_id*. + + caption (``string``, *optional*): + New caption for media, 0-1024 characters after entities parsing. + If not specified, the original caption is kept. + Pass "" (empty string) to remove the caption. + + parse_mode (``str``, *optional*): + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" or "md" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. + + caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): + List of special entities that appear in the new caption, which can be specified instead of __parse_mode__. + + disable_notification (``bool``, *optional*): + Sends the message silently. + Users will receive a notification with no sound. + + reply_to_message_id (``int``, *optional*): + If the message is a reply, ID of the original message. + + schedule_date (``int``, *optional*): + Date when the message will be automatically sent. Unix time. + + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): + Additional interface options. An object for an inline keyboard, custom reply keyboard, + instructions to remove reply keyboard or to force a reply from the user. + + Returns: + :obj:`~pyrogram.types.Message`: On success, the copied message is returned. + + Example: + .. code-block:: python + + # Copy a message + app.copy_messages("me", "pyrogram", 20) + + """ + message: types.Message = await self.get_messages(from_chat_id, message_id) + + if message.service: + log.warning(f"Service messages cannot be copied. " + f"chat_id: {message.chat.id}, message_id: {message.message_id}") + elif message.game and not await self.storage.is_bot(): + log.warning(f"Users cannot send messages with Game media type. " + f"chat_id: {message.chat.id}, message_id: {message.message_id}") + elif message.text: + return await self.send_message( + chat_id, + text=message.text, + entities=message.entities, + disable_web_page_preview=not message.web_page, + disable_notification=disable_notification, + schedule_date=schedule_date + ) + elif message.media: + send_media = partial( + self.send_cached_media, + chat_id=chat_id, + disable_notification=disable_notification, + reply_to_message_id=reply_to_message_id, + schedule_date=schedule_date, + reply_markup=reply_markup + ) + + if message.photo: + file_id = message.photo.file_id + elif message.audio: + file_id = message.audio.file_id + elif message.document: + file_id = message.document.file_id + elif message.video: + file_id = message.video.file_id + elif message.animation: + file_id = message.animation.file_id + elif message.voice: + file_id = message.voice.file_id + elif message.sticker: + file_id = message.sticker.file_id + elif message.video_note: + file_id = message.video_note.file_id + elif message.contact: + return await self.send_contact( + chat_id, + phone_number=message.contact.phone_number, + first_name=message.contact.first_name, + last_name=message.contact.last_name, + vcard=message.contact.vcard, + disable_notification=disable_notification, + schedule_date=schedule_date + ) + elif message.location: + return await self.send_location( + chat_id, + latitude=message.location.latitude, + longitude=message.location.longitude, + disable_notification=disable_notification, + schedule_date=schedule_date + ) + elif message.venue: + return await self.send_venue( + chat_id, + latitude=message.venue.location.latitude, + longitude=message.venue.location.longitude, + title=message.venue.title, + address=message.venue.address, + foursquare_id=message.venue.foursquare_id, + foursquare_type=message.venue.foursquare_type, + disable_notification=disable_notification, + schedule_date=schedule_date + ) + elif message.poll: + return await self.send_poll( + chat_id, + question=message.poll.question, + options=[opt.text for opt in message.poll.options], + disable_notification=disable_notification, + schedule_date=schedule_date + ) + elif message.game: + return await self.send_game( + chat_id, + game_short_name=message.game.short_name, + disable_notification=disable_notification + ) + else: + raise ValueError("Unknown media type") + + if message.sticker or message.video_note: # Sticker and VideoNote should have no caption + return await send_media(file_id=file_id) + else: + return await send_media( + file_id=file_id, + caption=caption if caption is not None else message.caption, + parse_mode=parse_mode, + caption_entities=caption_entities or message.caption_entities + ) + else: + raise ValueError("Can't copy this message") From c606f836d402162bb1e835a0a5a89e29244bca40 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 5 Dec 2020 01:36:39 +0100 Subject: [PATCH 0406/1185] Remove as_copy from forward_messages (superseded by copy_message) --- pyrogram/methods/messages/forward_messages.py | 77 ++++------- pyrogram/types/messages_and_media/message.py | 122 +----------------- 2 files changed, 30 insertions(+), 169 deletions(-) diff --git a/pyrogram/methods/messages/forward_messages.py b/pyrogram/methods/messages/forward_messages.py index 5287a4ced9..82b726113d 100644 --- a/pyrogram/methods/messages/forward_messages.py +++ b/pyrogram/methods/messages/forward_messages.py @@ -30,8 +30,6 @@ async def forward_messages( from_chat_id: Union[int, str], message_ids: Union[int, Iterable[int]], disable_notification: bool = None, - as_copy: bool = False, - remove_caption: bool = False, schedule_date: int = None ) -> List["types.Message"]: """Forward messages of any kind. @@ -47,7 +45,7 @@ async def forward_messages( For your personal cloud (Saved Messages) you can simply use "me" or "self". For a contact that exists in your Telegram address book you can use his phone number (str). - message_ids (``iterable``): + message_ids (``int`` | List of ``int``): A list of Message identifiers in the chat specified in *from_chat_id* or a single message id. Iterators and Generators are also accepted. @@ -55,16 +53,6 @@ async def forward_messages( Sends the message silently. Users will receive a notification with no sound. - as_copy (``bool``, *optional*): - Pass True to forward messages without the forward header (i.e.: send a copy of the message content so - that it appears as originally sent by you). - Defaults to False. - - remove_caption (``bool``, *optional*): - If set to True and *as_copy* is enabled as well, media captions are not preserved when copying the - message. Has no effect if *as_copy* is not enabled. - Defaults to False. - schedule_date (``int``, *optional*): Date when the message will be automatically sent. Unix time. @@ -90,50 +78,31 @@ async def forward_messages( is_iterable = not isinstance(message_ids, int) message_ids = list(message_ids) if is_iterable else [message_ids] - if as_copy: - forwarded_messages = [] - - for chunk in [message_ids[i:i + 200] for i in range(0, len(message_ids), 200)]: - messages = await self.get_messages(chat_id=from_chat_id, message_ids=chunk) - - for message in messages: - forwarded_messages.append( - await message.forward( - chat_id, - disable_notification=disable_notification, - as_copy=True, - remove_caption=remove_caption, - schedule_date=schedule_date - ) - ) - - return types.List(forwarded_messages) if is_iterable else forwarded_messages[0] - else: - r = await self.send( - raw.functions.messages.ForwardMessages( - 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], - schedule_date=schedule_date - ) + r = await self.send( + raw.functions.messages.ForwardMessages( + 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], + schedule_date=schedule_date ) + ) - forwarded_messages = [] + forwarded_messages = [] - users = {i.id: i for i in r.users} - chats = {i.id: i for i in r.chats} + users = {i.id: i for i in r.users} + chats = {i.id: i for i in r.chats} - for i in r.updates: - if isinstance(i, (raw.types.UpdateNewMessage, - raw.types.UpdateNewChannelMessage, - raw.types.UpdateNewScheduledMessage)): - forwarded_messages.append( - await types.Message._parse( - self, i.message, - users, chats - ) + for i in r.updates: + if isinstance(i, (raw.types.UpdateNewMessage, + raw.types.UpdateNewChannelMessage, + raw.types.UpdateNewScheduledMessage)): + forwarded_messages.append( + await types.Message._parse( + self, i.message, + users, chats ) + ) - return types.List(forwarded_messages) if is_iterable else forwarded_messages[0] + return types.List(forwarded_messages) if is_iterable else forwarded_messages[0] diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index 4992c37d48..ec0532fd2e 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -17,7 +17,6 @@ # along with Pyrogram. If not, see . import logging -from functools import partial from typing import List, Match, Union, BinaryIO import pyrogram @@ -2706,8 +2705,6 @@ async def forward( self, chat_id: int or str, disable_notification: bool = None, - as_copy: bool = False, - remove_caption: bool = False, schedule_date: int = None ) -> Union["types.Message", List["types.Message"]]: """Bound method *forward* of :obj:`~pyrogram.types.Message`. @@ -2737,15 +2734,6 @@ async def forward( Sends the message silently. Users will receive a notification with no sound. - as_copy (``bool``, *optional*): - Pass True to forward messages without the forward header (i.e.: send a copy of the message content). - Defaults to False. - - remove_caption (``bool``, *optional*): - If set to True and *as_copy* is enabled as well, media captions are not preserved when copying the - message. Has no effect if *as_copy* is not enabled. - Defaults to False. - schedule_date (``int``, *optional*): Date when the message will be automatically sent. Unix time. @@ -2755,109 +2743,13 @@ async def forward( Raises: RPCError: In case of a Telegram RPC error. """ - if as_copy: - if self.service: - log.warning(f"Service messages cannot be copied. " - f"chat_id: {self.chat.id}, message_id: {self.message_id}") - elif self.game and not await self._client.storage.is_bot(): - log.warning(f"Users cannot send messages with Game media type. " - f"chat_id: {self.chat.id}, message_id: {self.message_id}") - elif self.text: - return await self._client.send_message( - chat_id, - text=self.text.html, - parse_mode="html", - disable_web_page_preview=not self.web_page, - disable_notification=disable_notification, - schedule_date=schedule_date - ) - elif self.media: - caption = self.caption.html if self.caption and not remove_caption else "" - - send_media = partial( - self._client.send_cached_media, - chat_id=chat_id, - disable_notification=disable_notification, - schedule_date=schedule_date - ) - - if self.photo: - file_id = self.photo.file_id - elif self.audio: - file_id = self.audio.file_id - elif self.document: - file_id = self.document.file_id - elif self.video: - file_id = self.video.file_id - elif self.animation: - file_id = self.animation.file_id - elif self.voice: - file_id = self.voice.file_id - elif self.sticker: - file_id = self.sticker.file_id - elif self.video_note: - file_id = self.video_note.file_id - elif self.contact: - return await self._client.send_contact( - chat_id, - phone_number=self.contact.phone_number, - first_name=self.contact.first_name, - last_name=self.contact.last_name, - vcard=self.contact.vcard, - disable_notification=disable_notification, - schedule_date=schedule_date - ) - elif self.location: - return await self._client.send_location( - chat_id, - latitude=self.location.latitude, - longitude=self.location.longitude, - disable_notification=disable_notification, - schedule_date=schedule_date - ) - elif self.venue: - return await self._client.send_venue( - chat_id, - latitude=self.venue.location.latitude, - longitude=self.venue.location.longitude, - title=self.venue.title, - address=self.venue.address, - foursquare_id=self.venue.foursquare_id, - foursquare_type=self.venue.foursquare_type, - disable_notification=disable_notification, - schedule_date=schedule_date - ) - elif self.poll: - return await self._client.send_poll( - chat_id, - question=self.poll.question, - options=[opt.text for opt in self.poll.options], - disable_notification=disable_notification, - schedule_date=schedule_date - ) - elif self.game: - return await self._client.send_game( - chat_id, - game_short_name=self.game.short_name, - disable_notification=disable_notification - ) - else: - raise ValueError("Unknown media type") - - if self.sticker or self.video_note: # Sticker and VideoNote should have no caption - return await send_media(file_id=file_id) - else: - return await send_media(file_id=file_id, caption=caption, parse_mode="html") - else: - raise ValueError("Can't copy this message") - else: - 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, - schedule_date=schedule_date - ) + 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, + schedule_date=schedule_date + ) async def delete(self, revoke: bool = True): """Bound method *delete* of :obj:`~pyrogram.types.Message`. From 2f3bcd7ee57bd9eec4884bcfcf9fe79f6a004261 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 5 Dec 2020 01:54:07 +0100 Subject: [PATCH 0407/1185] Add Message.copy bound method --- compiler/docs/compiler.py | 1 + pyrogram/methods/messages/copy_messages.py | 114 ++---------- pyrogram/types/messages_and_media/message.py | 181 +++++++++++++++++++ 3 files changed, 194 insertions(+), 102 deletions(-) diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index 8d7efb911b..7bc7308f30 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -432,6 +432,7 @@ def get_title_list(s: str) -> list: Message.delete Message.download Message.forward + Message.copy Message.pin Message.edit_text Message.edit_caption diff --git a/pyrogram/methods/messages/copy_messages.py b/pyrogram/methods/messages/copy_messages.py index 5b4ec51780..d45c7b45d6 100644 --- a/pyrogram/methods/messages/copy_messages.py +++ b/pyrogram/methods/messages/copy_messages.py @@ -17,8 +17,7 @@ # along with Pyrogram. If not, see . import logging -from functools import partial -from typing import Union, Iterable, List +from typing import Union, List from pyrogram import types from pyrogram.scaffold import Scaffold @@ -31,7 +30,7 @@ async def copy_messages( self, chat_id: Union[int, str], from_chat_id: Union[int, str], - message_id: Union[int, Iterable[int]], + message_id: int, caption: str = None, parse_mode: Union[str, None] = object, caption_entities: List["types.MessageEntity"] = None, @@ -105,102 +104,13 @@ async def copy_messages( """ message: types.Message = await self.get_messages(from_chat_id, message_id) - if message.service: - log.warning(f"Service messages cannot be copied. " - f"chat_id: {message.chat.id}, message_id: {message.message_id}") - elif message.game and not await self.storage.is_bot(): - log.warning(f"Users cannot send messages with Game media type. " - f"chat_id: {message.chat.id}, message_id: {message.message_id}") - elif message.text: - return await self.send_message( - chat_id, - text=message.text, - entities=message.entities, - disable_web_page_preview=not message.web_page, - disable_notification=disable_notification, - schedule_date=schedule_date - ) - elif message.media: - send_media = partial( - self.send_cached_media, - chat_id=chat_id, - disable_notification=disable_notification, - reply_to_message_id=reply_to_message_id, - schedule_date=schedule_date, - reply_markup=reply_markup - ) - - if message.photo: - file_id = message.photo.file_id - elif message.audio: - file_id = message.audio.file_id - elif message.document: - file_id = message.document.file_id - elif message.video: - file_id = message.video.file_id - elif message.animation: - file_id = message.animation.file_id - elif message.voice: - file_id = message.voice.file_id - elif message.sticker: - file_id = message.sticker.file_id - elif message.video_note: - file_id = message.video_note.file_id - elif message.contact: - return await self.send_contact( - chat_id, - phone_number=message.contact.phone_number, - first_name=message.contact.first_name, - last_name=message.contact.last_name, - vcard=message.contact.vcard, - disable_notification=disable_notification, - schedule_date=schedule_date - ) - elif message.location: - return await self.send_location( - chat_id, - latitude=message.location.latitude, - longitude=message.location.longitude, - disable_notification=disable_notification, - schedule_date=schedule_date - ) - elif message.venue: - return await self.send_venue( - chat_id, - latitude=message.venue.location.latitude, - longitude=message.venue.location.longitude, - title=message.venue.title, - address=message.venue.address, - foursquare_id=message.venue.foursquare_id, - foursquare_type=message.venue.foursquare_type, - disable_notification=disable_notification, - schedule_date=schedule_date - ) - elif message.poll: - return await self.send_poll( - chat_id, - question=message.poll.question, - options=[opt.text for opt in message.poll.options], - disable_notification=disable_notification, - schedule_date=schedule_date - ) - elif message.game: - return await self.send_game( - chat_id, - game_short_name=message.game.short_name, - disable_notification=disable_notification - ) - else: - raise ValueError("Unknown media type") - - if message.sticker or message.video_note: # Sticker and VideoNote should have no caption - return await send_media(file_id=file_id) - else: - return await send_media( - file_id=file_id, - caption=caption if caption is not None else message.caption, - parse_mode=parse_mode, - caption_entities=caption_entities or message.caption_entities - ) - else: - raise ValueError("Can't copy this message") + return await message.copy( + chat_id=chat_id, + caption=caption, + parse_mode=parse_mode, + caption_entities=caption_entities, + disable_notification=disable_notification, + reply_to_message_id=reply_to_message_id, + schedule_date=schedule_date, + reply_markup=reply_markup + ) diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index ec0532fd2e..e15ef7d3dd 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -17,6 +17,7 @@ # along with Pyrogram. If not, see . import logging +from functools import partial from typing import List, Match, Union, BinaryIO import pyrogram @@ -2751,6 +2752,186 @@ async def forward( schedule_date=schedule_date ) + async def copy( + self, + chat_id: Union[int, str], + caption: str = None, + parse_mode: Union[str, None] = object, + caption_entities: List["types.MessageEntity"] = None, + disable_notification: bool = None, + reply_to_message_id: int = None, + schedule_date: int = None, + reply_markup: Union[ + "types.InlineKeyboardMarkup", + "types.ReplyKeyboardMarkup", + "types.ReplyKeyboardRemove", + "types.ForceReply" + ] = None + ) -> Union["types.Message", List["types.Message"]]: + """Bound method *copy* of :obj:`~pyrogram.types.Message`. + + Use as a shortcut for: + + .. code-block:: python + + client.copy_message( + chat_id=chat_id, + from_chat_id=message.chat.id, + message_ids=message.message_id + ) + + Example: + .. code-block:: python + + message.copy(chat_id) + + Parameters: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + For your personal cloud (Saved Messages) you can simply use "me" or "self". + For a contact that exists in your Telegram address book you can use his phone number (str). + + caption (``string``, *optional*): + New caption for media, 0-1024 characters after entities parsing. + If not specified, the original caption is kept. + Pass "" (empty string) to remove the caption. + + parse_mode (``str``, *optional*): + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" or "md" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. + + caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): + List of special entities that appear in the new caption, which can be specified instead of __parse_mode__. + + disable_notification (``bool``, *optional*): + Sends the message silently. + Users will receive a notification with no sound. + + reply_to_message_id (``int``, *optional*): + If the message is a reply, ID of the original message. + + schedule_date (``int``, *optional*): + Date when the message will be automatically sent. Unix time. + + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): + Additional interface options. An object for an inline keyboard, custom reply keyboard, + instructions to remove reply keyboard or to force a reply from the user. + + Returns: + :obj:`~pyrogram.types.Message`: On success, the copied message is returned. + + Raises: + RPCError: In case of a Telegram RPC error. + """ + if self.service: + log.warning(f"Service messages cannot be copied. " + f"chat_id: {self.chat.id}, message_id: {self.message_id}") + elif self.game and not await self._client.storage.is_bot(): + log.warning(f"Users cannot send messages with Game media type. " + f"chat_id: {self.chat.id}, message_id: {self.message_id}") + elif self.text: + return await self._client.send_message( + chat_id, + text=self.text, + entities=self.entities, + disable_web_page_preview=not self.web_page, + disable_notification=disable_notification, + reply_to_message_id=reply_to_message_id, + schedule_date=schedule_date, + reply_markup=reply_markup + ) + elif self.media: + send_media = partial( + self._client.send_cached_media, + chat_id=chat_id, + disable_notification=disable_notification, + reply_to_message_id=reply_to_message_id, + schedule_date=schedule_date, + reply_markup=reply_markup + ) + + if self.photo: + file_id = self.photo.file_id + elif self.audio: + file_id = self.audio.file_id + elif self.document: + file_id = self.document.file_id + elif self.video: + file_id = self.video.file_id + elif self.animation: + file_id = self.animation.file_id + elif self.voice: + file_id = self.voice.file_id + elif self.sticker: + file_id = self.sticker.file_id + elif self.video_note: + file_id = self.video_note.file_id + elif self.contact: + return await self._client.send_contact( + chat_id, + phone_number=self.contact.phone_number, + first_name=self.contact.first_name, + last_name=self.contact.last_name, + vcard=self.contact.vcard, + disable_notification=disable_notification, + schedule_date=schedule_date + ) + elif self.location: + return await self._client.send_location( + chat_id, + latitude=self.location.latitude, + longitude=self.location.longitude, + disable_notification=disable_notification, + schedule_date=schedule_date + ) + elif self.venue: + return await self._client.send_venue( + chat_id, + latitude=self.venue.location.latitude, + longitude=self.venue.location.longitude, + title=self.venue.title, + address=self.venue.address, + foursquare_id=self.venue.foursquare_id, + foursquare_type=self.venue.foursquare_type, + disable_notification=disable_notification, + schedule_date=schedule_date + ) + elif self.poll: + return await self._client.send_poll( + chat_id, + question=self.poll.question, + options=[opt.text for opt in self.poll.options], + disable_notification=disable_notification, + schedule_date=schedule_date + ) + elif self.game: + return await self._client.send_game( + chat_id, + game_short_name=self.game.short_name, + disable_notification=disable_notification + ) + else: + raise ValueError("Unknown media type") + + if self.sticker or self.video_note: # Sticker and VideoNote should have no caption + return await send_media(file_id=file_id) + else: + if caption is None: + caption = self.caption + caption_entities = self.caption_entities + + return await send_media( + file_id=file_id, + caption=caption, + parse_mode=parse_mode, + caption_entities=caption_entities + ) + else: + raise ValueError("Can't copy this message") + async def delete(self, revoke: bool = True): """Bound method *delete* of :obj:`~pyrogram.types.Message`. From 76b9f97681069275ddc3bd7ad74b7ef9c5c2c2a2 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 5 Dec 2020 17:00:00 +0100 Subject: [PATCH 0408/1185] Fix little typo --- pyrogram/methods/messages/__init__.py | 2 +- pyrogram/methods/messages/{copy_messages.py => copy_message.py} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename pyrogram/methods/messages/{copy_messages.py => copy_message.py} (99%) diff --git a/pyrogram/methods/messages/__init__.py b/pyrogram/methods/messages/__init__.py index c6697dbc89..4c9319a707 100644 --- a/pyrogram/methods/messages/__init__.py +++ b/pyrogram/methods/messages/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from .copy_messages import CopyMessage +from .copy_message import CopyMessage from .delete_messages import DeleteMessages from .download_media import DownloadMedia from .edit_inline_caption import EditInlineCaption diff --git a/pyrogram/methods/messages/copy_messages.py b/pyrogram/methods/messages/copy_message.py similarity index 99% rename from pyrogram/methods/messages/copy_messages.py rename to pyrogram/methods/messages/copy_message.py index d45c7b45d6..3c9498c379 100644 --- a/pyrogram/methods/messages/copy_messages.py +++ b/pyrogram/methods/messages/copy_message.py @@ -26,7 +26,7 @@ class CopyMessage(Scaffold): - async def copy_messages( + async def copy_message( self, chat_id: Union[int, str], from_chat_id: Union[int, str], From d2023c39aeab9e77aff5abcb7ba12ca9df868977 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 7 Dec 2020 17:22:10 +0100 Subject: [PATCH 0409/1185] Fix copied messages reporting "None" in case of empty captions --- pyrogram/types/messages_and_media/message.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index e15ef7d3dd..518604a270 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -2920,7 +2920,7 @@ async def copy( return await send_media(file_id=file_id) else: if caption is None: - caption = self.caption + caption = self.caption or "" caption_entities = self.caption_entities return await send_media( From 3c8f41b1d2270fca41e6115b215a539faf515950 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 7 Dec 2020 19:11:26 +0100 Subject: [PATCH 0410/1185] Add more verbose error in case of network issues --- 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 3a2126e99a..b06024947e 100644 --- a/pyrogram/connection/connection.py +++ b/pyrogram/connection/connection.py @@ -54,7 +54,7 @@ async def connect(self): log.info("Connecting...") await self.protocol.connect(self.address) except OSError as e: - log.warning(e) # TODO: Remove + log.warning(f"Unable to connect due to network issues: {e}") self.protocol.close() await asyncio.sleep(1) else: From 521e403f9220f53c8e19500c36fb4b4a3bafe577 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 7 Dec 2020 19:15:46 +0100 Subject: [PATCH 0411/1185] Fix peer ids decoding from file ids --- pyrogram/client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/client.py b/pyrogram/client.py index df06e94c7f..6638b3b433 100644 --- a/pyrogram/client.py +++ b/pyrogram/client.py @@ -866,11 +866,11 @@ async def get_file( else: if file_id.chat_access_hash == 0: peer = raw.types.InputPeerChat( - chat_id=file_id.chat_id + chat_id=-file_id.chat_id ) else: peer = raw.types.InputPeerChannel( - channel_id=file_id.chat_id, + channel_id=utils.get_channel_id(file_id.chat_id), access_hash=file_id.chat_access_hash ) From 844e53a70e696f74560e57bad3af236efaac75db Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 7 Dec 2020 19:16:46 +0100 Subject: [PATCH 0412/1185] Move crypto calls to threads in case of big enough chunks --- pyrogram/__init__.py | 6 +++ pyrogram/connection/transport/tcp/tcp.py | 1 + .../transport/tcp/tcp_abridged_o.py | 15 ++---- pyrogram/session/session.py | 53 +++++-------------- pyrogram/utils.py | 8 +++ 5 files changed, 34 insertions(+), 49 deletions(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 39b83df745..0d0669e7e3 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -20,6 +20,8 @@ __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)" __copyright__ = "Copyright (C) 2017-2020 Dan " +from concurrent.futures.thread import ThreadPoolExecutor + class StopTransmission(StopAsyncIteration): pass @@ -41,3 +43,7 @@ class ContinuePropagation(StopAsyncIteration): # Save the main thread loop for future references main_event_loop = asyncio.get_event_loop() + +CRYPTO_EXECUTOR_SIZE_THRESHOLD = 512 + +crypto_executor = ThreadPoolExecutor(2, thread_name_prefix="CryptoWorker") diff --git a/pyrogram/connection/transport/tcp/tcp.py b/pyrogram/connection/transport/tcp/tcp.py index b2ff539184..acc248b7aa 100644 --- a/pyrogram/connection/transport/tcp/tcp.py +++ b/pyrogram/connection/transport/tcp/tcp.py @@ -45,6 +45,7 @@ def __init__(self, ipv6: bool, proxy: dict): self.writer = None # type: asyncio.StreamWriter self.lock = asyncio.Lock() + self.loop = asyncio.get_event_loop() if proxy.get("enabled", False): hostname = proxy.get("hostname", None) diff --git a/pyrogram/connection/transport/tcp/tcp_abridged_o.py b/pyrogram/connection/transport/tcp/tcp_abridged_o.py index c7b241593f..04644a634d 100644 --- a/pyrogram/connection/transport/tcp/tcp_abridged_o.py +++ b/pyrogram/connection/transport/tcp/tcp_abridged_o.py @@ -19,6 +19,7 @@ import logging import os +from pyrogram import utils from pyrogram.crypto import aes from .tcp import TCP @@ -55,16 +56,10 @@ async def connect(self, address: tuple): async def send(self, data: bytes, *args): length = len(data) // 4 + data = (bytes([length]) if length <= 126 else b"\x7f" + length.to_bytes(3, "little")) + data + payload = await utils.maybe_run_in_executor(aes.ctr256_encrypt, data, len(data), self.loop, *self.encrypt) - await super().send( - aes.ctr256_encrypt( - (bytes([length]) - if length <= 126 - else b"\x7f" + length.to_bytes(3, "little")) - + data, - *self.encrypt - ) - ) + await super().send(payload) async def recv(self, length: int = 0) -> bytes or None: length = await super().recv(1) @@ -87,4 +82,4 @@ async def recv(self, length: int = 0) -> bytes or None: if data is None: return None - return aes.ctr256_decrypt(data, *self.decrypt) + return await utils.maybe_run_in_executor(aes.ctr256_decrypt, data, len(data), self.loop, *self.decrypt) diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index 490eea54f4..d46751d4ef 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -19,13 +19,12 @@ import asyncio import logging import os -from concurrent.futures.thread import ThreadPoolExecutor from datetime import datetime, timedelta from hashlib import sha1 from io import BytesIO import pyrogram -from pyrogram import __copyright__, __license__, __version__ +from pyrogram import __copyright__, __license__, __version__, utils from pyrogram import raw from pyrogram.connection import Connection from pyrogram.crypto import mtproto @@ -51,7 +50,6 @@ class Session: MAX_RETRIES = 5 ACKS_THRESHOLD = 8 PING_INTERVAL = 5 - EXECUTOR_SIZE_THRESHOLD = 512 notice_displayed = False @@ -69,8 +67,6 @@ class Session: 64: "[64] invalid container" } - executor = ThreadPoolExecutor(2, thread_name_prefix="CryptoWorker") - def __init__( self, client: "pyrogram.Client", @@ -220,22 +216,12 @@ async def restart(self): await self.start() async def handle_packet(self, packet): - if len(packet) <= self.EXECUTOR_SIZE_THRESHOLD: - data = mtproto.unpack( - BytesIO(packet), - self.session_id, - self.auth_key, - self.auth_key_id - ) - else: - data = await self.loop.run_in_executor( - self.executor, - mtproto.unpack, - BytesIO(packet), - self.session_id, - self.auth_key, - self.auth_key_id - ) + data = await utils.maybe_run_in_executor( + mtproto.unpack, BytesIO(packet), len(packet), self.loop, + self.session_id, + self.auth_key, + self.auth_key_id + ) messages = ( data.body.messages @@ -375,24 +361,13 @@ async def _send(self, data: TLObject, wait_response: bool = True, timeout: float log.debug(f"Sent:") log.debug(message) - if len(message) <= self.EXECUTOR_SIZE_THRESHOLD: - payload = mtproto.pack( - message, - self.current_salt.salt, - self.session_id, - self.auth_key, - self.auth_key_id - ) - else: - payload = await self.loop.run_in_executor( - self.executor, - mtproto.pack, - message, - self.current_salt.salt, - self.session_id, - self.auth_key, - self.auth_key_id - ) + payload = await utils.maybe_run_in_executor( + mtproto.pack, message, len(message), self.loop, + self.current_salt.salt, + self.session_id, + self.auth_key, + self.auth_key_id + ) try: await self.connection.send(payload) diff --git a/pyrogram/utils.py b/pyrogram/utils.py index 4c4445334f..8b6242ebf7 100644 --- a/pyrogram/utils.py +++ b/pyrogram/utils.py @@ -315,3 +315,11 @@ async def parse_text_entities( "message": text, "entities": entities } + + +async def maybe_run_in_executor(func, data, length, loop, *args): + return ( + func(data, *args) + if length <= pyrogram.CRYPTO_EXECUTOR_SIZE_THRESHOLD + else await loop.run_in_executor(pyrogram.crypto_executor, func, data, *args) + ) From 5cbe03e89e18017d0015da302706bcfb42d5db0c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 8 Dec 2020 22:25:11 +0100 Subject: [PATCH 0413/1185] Update API schema to Layer 122 --- compiler/api/source/main_api.tl | 51 +++++++++++++++++++++++++++++---- 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl index 56f18284d0..963dd1df7b 100644 --- a/compiler/api/source/main_api.tl +++ b/compiler/api/source/main_api.tl @@ -102,11 +102,11 @@ userStatusLastMonth#77ebc742 = UserStatus; chatEmpty#9ba2d800 id:int = Chat; chat#3bda1bde flags:# creator:flags.0?true kicked:flags.1?true left:flags.2?true deactivated:flags.5?true id:int title:string photo:ChatPhoto participants_count:int date:int version:int migrated_to:flags.6?InputChannel admin_rights:flags.14?ChatAdminRights default_banned_rights:flags.18?ChatBannedRights = Chat; chatForbidden#7328bdb id:int title:string = Chat; -channel#d31a961e flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true id:int access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int version:int restriction_reason:flags.9?Vector admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int = Chat; +channel#d31a961e flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true id:int access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int version:int restriction_reason:flags.9?Vector admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int = Chat; channelForbidden#289da732 flags:# broadcast:flags.5?true megagroup:flags.8?true id:int access_hash:long title:string until_date:flags.16?int = Chat; chatFull#1b7c9db3 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:int about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int = ChatFull; -channelFull#f0e6672a flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true id:int about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?int migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?int location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int = ChatFull; +channelFull#ef3a6acd flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true id:int about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?int migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?int location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall = ChatFull; chatParticipant#c8d7493e user_id:int inviter_id:int date:int = ChatParticipant; chatParticipantCreator#da13538a user_id:int = ChatParticipant; @@ -160,6 +160,8 @@ messageActionSecureValuesSentMe#1b287353 values:Vector credentials: messageActionSecureValuesSent#d95c6154 types:Vector = MessageAction; messageActionContactSignUp#f3f25f76 = MessageAction; messageActionGeoProximityReached#98e0d697 from_id:Peer to_id:Peer distance:int = MessageAction; +messageActionGroupCall#7a0d7f42 flags:# call:InputGroupCall duration:flags.0?int = MessageAction; +messageActionInviteToGroupCall#76b9f11a call:InputGroupCall users:Vector = MessageAction; dialog#2c171f72 flags:# pinned:flags.2?true unread_mark:flags.3?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int = Dialog; dialogFolder#71bd134c flags:# pinned:flags.2?true folder:Folder peer:Peer top_message:int unread_muted_peers_count:int unread_unmuted_peers_count:int unread_muted_messages_count:int unread_unmuted_messages_count:int = Dialog; @@ -172,6 +174,7 @@ photoSize#77bfb61b type:string location:FileLocation w:int h:int size:int = Phot photoCachedSize#e9a734fa type:string location:FileLocation w:int h:int bytes:bytes = PhotoSize; photoStrippedSize#e0b0bc2e type:string bytes:bytes = PhotoSize; photoSizeProgressive#5aa86a51 type:string location:FileLocation w:int h:int sizes:Vector = PhotoSize; +photoPathSize#d8214d41 type:string bytes:bytes = PhotoSize; geoPointEmpty#1117dd5f = GeoPoint; geoPoint#b2a2f663 flags:# long:double lat:double access_hash:long accuracy_radius:flags.0?int = GeoPoint; @@ -290,7 +293,7 @@ updateNewStickerSet#688a30aa stickerset:messages.StickerSet = Update; updateStickerSetsOrder#bb2d201 flags:# masks:flags.0?true order:Vector = Update; updateStickerSets#43ae3dec = Update; updateSavedGifs#9375341e = Update; -updateBotInlineQuery#54826690 flags:# query_id:long user_id:int query:string geo:flags.0?GeoPoint offset:string = Update; +updateBotInlineQuery#3f2038db flags:# query_id:long user_id:int query:string geo:flags.0?GeoPoint peer_type:flags.1?InlineQueryPeerType offset:string = Update; updateBotInlineSend#e48f964 flags:# user_id:int query:string geo:flags.0?GeoPoint id:string msg_id:flags.1?InputBotInlineMessageID = Update; updateEditChannelMessage#1b3f4df7 message:Message pts:int pts_count:int = Update; updateBotCallbackQuery#e73547e1 flags:# query_id:long user_id:int peer:Peer msg_id:int chat_instance:long data:flags.0?bytes game_short_name:flags.1?string = Update; @@ -340,6 +343,8 @@ updatePeerBlocked#246a4b22 peer_id:Peer blocked:Bool = Update; updateChannelUserTyping#ff2abe9f flags:# channel_id:int top_msg_id:flags.0?int user_id:int action:SendMessageAction = Update; updatePinnedMessages#ed85eab5 flags:# pinned:flags.0?true peer:Peer messages:Vector pts:int pts_count:int = Update; updatePinnedChannelMessages#8588878b flags:# pinned:flags.0?true channel_id:int messages:Vector pts:int pts_count:int = Update; +updateGroupCallParticipants#f2ebdb4e call:InputGroupCall participants:Vector version:int = Update; +updateGroupCall#5724806e channel_id:int call:GroupCall = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -426,6 +431,7 @@ sendMessageChooseContactAction#628cbc6f = SendMessageAction; sendMessageGamePlayAction#dd6a8f48 = SendMessageAction; sendMessageRecordRoundAction#88f27fbc = SendMessageAction; sendMessageUploadRoundAction#243e1c66 progress:int = SendMessageAction; +speakingInGroupCallAction#d92c2285 = SendMessageAction; contacts.found#b3134d9d my_results:Vector results:Vector chats:Vector users:Vector = contacts.Found; @@ -851,12 +857,17 @@ channelAdminLogEventActionStopPoll#8f079643 message:Message = ChannelAdminLogEve channelAdminLogEventActionChangeLinkedChat#a26f881b prev_value:int new_value:int = ChannelAdminLogEventAction; channelAdminLogEventActionChangeLocation#e6b76ae prev_value:ChannelLocation new_value:ChannelLocation = ChannelAdminLogEventAction; channelAdminLogEventActionToggleSlowMode#53909779 prev_value:int new_value:int = ChannelAdminLogEventAction; +channelAdminLogEventActionStartGroupCall#23209745 call:InputGroupCall = ChannelAdminLogEventAction; +channelAdminLogEventActionDiscardGroupCall#db9f9140 call:InputGroupCall = ChannelAdminLogEventAction; +channelAdminLogEventActionParticipantMute#f92424d2 participant:GroupCallParticipant = ChannelAdminLogEventAction; +channelAdminLogEventActionParticipantUnmute#e64429c0 participant:GroupCallParticipant = ChannelAdminLogEventAction; +channelAdminLogEventActionToggleGroupCallSetting#56d6a247 join_muted:Bool = ChannelAdminLogEventAction; channelAdminLogEvent#3b5a3e40 id:long date:int user_id:int action:ChannelAdminLogEventAction = ChannelAdminLogEvent; channels.adminLogResults#ed8af74d events:Vector chats:Vector users:Vector = channels.AdminLogResults; -channelAdminLogEventsFilter#ea107ae4 flags:# join:flags.0?true leave:flags.1?true invite:flags.2?true ban:flags.3?true unban:flags.4?true kick:flags.5?true unkick:flags.6?true promote:flags.7?true demote:flags.8?true info:flags.9?true settings:flags.10?true pinned:flags.11?true edit:flags.12?true delete:flags.13?true = ChannelAdminLogEventsFilter; +channelAdminLogEventsFilter#ea107ae4 flags:# join:flags.0?true leave:flags.1?true invite:flags.2?true ban:flags.3?true unban:flags.4?true kick:flags.5?true unkick:flags.6?true promote:flags.7?true demote:flags.8?true info:flags.9?true settings:flags.10?true pinned:flags.11?true edit:flags.12?true delete:flags.13?true group_call:flags.14?true = ChannelAdminLogEventsFilter; popularContact#5ce14175 client_id:long importers:int = PopularContact; @@ -880,6 +891,7 @@ account.webAuthorizations#ed56c9fc authorizations:Vector users inputMessageID#a676a322 id:int = InputMessage; inputMessageReplyTo#bad88395 id:int = InputMessage; inputMessagePinned#86872538 = InputMessage; +inputMessageCallbackQuery#acfa1a7e id:int query_id:long = InputMessage; inputDialogPeer#fcaafeb7 peer:InputPeer = InputDialogPeer; inputDialogPeerFolder#64600527 folder_id:int = InputDialogPeer; @@ -1013,7 +1025,7 @@ chatOnlines#f041e250 onlines:int = ChatOnlines; statsURL#47a971e0 url:string = StatsURL; -chatAdminRights#5fb224d5 flags:# change_info:flags.0?true post_messages:flags.1?true edit_messages:flags.2?true delete_messages:flags.3?true ban_users:flags.4?true invite_users:flags.5?true pin_messages:flags.7?true add_admins:flags.9?true anonymous:flags.10?true = ChatAdminRights; +chatAdminRights#5fb224d5 flags:# change_info:flags.0?true post_messages:flags.1?true edit_messages:flags.2?true delete_messages:flags.3?true ban_users:flags.4?true invite_users:flags.5?true pin_messages:flags.7?true add_admins:flags.9?true anonymous:flags.10?true manage_call:flags.11?true = ChatAdminRights; chatBannedRights#9f120418 flags:# view_messages:flags.0?true send_messages:flags.1?true send_media:flags.2?true send_stickers:flags.3?true send_gifs:flags.4?true send_games:flags.5?true send_inline:flags.6?true embed_links:flags.7?true send_polls:flags.8?true change_info:flags.10?true invite_users:flags.15?true pin_messages:flags.17?true until_date:int = ChatBannedRights; @@ -1155,6 +1167,23 @@ peerBlocked#e8fd8014 peer_id:Peer date:int = PeerBlocked; stats.messageStats#8999f295 views_graph:StatsGraph = stats.MessageStats; +groupCallDiscarded#7780bcb4 id:long access_hash:long duration:int = GroupCall; +groupCall#55903081 flags:# join_muted:flags.1?true can_change_join_muted:flags.2?true id:long access_hash:long participants_count:int params:flags.0?DataJSON version:int = GroupCall; + +inputGroupCall#d8aa840f id:long access_hash:long = InputGroupCall; + +groupCallParticipant#56b087c9 flags:# muted:flags.0?true left:flags.1?true can_self_unmute:flags.2?true user_id:int date:int active_date:flags.3?int source:int = GroupCallParticipant; + +phone.groupCall#66ab0bfc call:GroupCall participants:Vector participants_next_offset:string users:Vector = phone.GroupCall; + +phone.groupParticipants#9cfeb92d count:int participants:Vector next_offset:string users:Vector version:int = phone.GroupParticipants; + +inlineQueryPeerTypeSameBotPM#3081ed9d = InlineQueryPeerType; +inlineQueryPeerTypePM#833c0fac = InlineQueryPeerType; +inlineQueryPeerTypeChat#d766c50a = InlineQueryPeerType; +inlineQueryPeerTypeMegagroup#5ec4be43 = InlineQueryPeerType; +inlineQueryPeerTypeBroadcast#6334ee9a = InlineQueryPeerType; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -1509,6 +1538,16 @@ phone.discardCall#b2cbc1c0 flags:# video:flags.0?true peer:InputPhoneCall durati phone.setCallRating#59ead627 flags:# user_initiative:flags.0?true peer:InputPhoneCall rating:int comment:string = Updates; phone.saveCallDebug#277add7e peer:InputPhoneCall debug:DataJSON = Bool; phone.sendSignalingData#ff7a9383 peer:InputPhoneCall data:bytes = Bool; +phone.createGroupCall#e428fa02 channel:InputChannel random_id:int = Updates; +phone.joinGroupCall#5f9c8e62 flags:# muted:flags.0?true call:InputGroupCall params:DataJSON = Updates; +phone.leaveGroupCall#500377f9 call:InputGroupCall source:int = Updates; +phone.editGroupCallMember#63146ae4 flags:# muted:flags.0?true call:InputGroupCall user_id:InputUser = Updates; +phone.inviteToGroupCall#7b393160 call:InputGroupCall users:Vector = Updates; +phone.discardGroupCall#7a777135 call:InputGroupCall = Updates; +phone.toggleGroupCallSettings#74bbb43d flags:# call:InputGroupCall join_muted:flags.0?Bool = Updates; +phone.getGroupCall#c7cb017 call:InputGroupCall = phone.GroupCall; +phone.getGroupParticipants#c9f1d285 call:InputGroupCall ids:Vector sources:Vector offset:string limit:int = phone.GroupParticipants; +phone.checkGroupCall#b74a7bea call:InputGroupCall source:int = Bool; langpack.getLangPack#f2f2330a lang_pack:string lang_code:string = LangPackDifference; langpack.getStrings#efea3803 lang_pack:string lang_code:string keys:Vector = Vector; @@ -1525,4 +1564,4 @@ stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats; -// LAYER 120 \ No newline at end of file +// LAYER 122 \ No newline at end of file From a30e49fe795224b8ed23a833a12d14019bea7941 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 8 Dec 2020 22:25:42 +0100 Subject: [PATCH 0414/1185] Ignore PhotoPathSize for now --- pyrogram/types/messages_and_media/photo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/types/messages_and_media/photo.py b/pyrogram/types/messages_and_media/photo.py index 801d711884..27a6c40991 100644 --- a/pyrogram/types/messages_and_media/photo.py +++ b/pyrogram/types/messages_and_media/photo.py @@ -84,7 +84,7 @@ def _parse(client, photo: "raw.types.Photo", ttl_seconds: int = None) -> Optiona if isinstance(photo, raw.types.Photo): big = photo.sizes[-1] - if isinstance(big, raw.types.PhotoStrippedSize): + if isinstance(big, (raw.types.PhotoStrippedSize, raw.types.PhotoPathSize)): return None if isinstance(big, raw.types.PhotoSizeProgressive): From 9891eb77a18f8482fcda56fec1778f01b29dd607 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 8 Dec 2020 22:26:43 +0100 Subject: [PATCH 0415/1185] Bump beta version --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 0d0669e7e3..2fd3a168e4 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "1.0.7" +__version__ = "1.1.0b1" __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)" __copyright__ = "Copyright (C) 2017-2020 Dan " From e7667d851e5aea3c8b0c116b617a9335d3221234 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 8 Dec 2020 22:30:37 +0100 Subject: [PATCH 0416/1185] Add "speaking" chat action --- pyrogram/methods/messages/send_chat_action.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pyrogram/methods/messages/send_chat_action.py b/pyrogram/methods/messages/send_chat_action.py index c9c5d5692d..10d0da4897 100644 --- a/pyrogram/methods/messages/send_chat_action.py +++ b/pyrogram/methods/messages/send_chat_action.py @@ -36,6 +36,7 @@ class ChatAction: UPLOAD_VIDEO_NOTE = raw.types.SendMessageUploadRoundAction PLAYING = raw.types.SendMessageGamePlayAction CHOOSE_CONTACT = raw.types.SendMessageChooseContactAction + SPEAKING = raw.types.SpeakingInGroupCallAction CANCEL = raw.types.SendMessageCancelAction @@ -57,8 +58,8 @@ async def send_chat_action(self, chat_id: Union[int, str], action: str) -> bool: text messages, *"upload_photo"* for photos, *"record_video"* or *"upload_video"* for videos, *"record_audio"* or *"upload_audio"* for audio files, *"upload_document"* for general files, *"find_location"* for location data, *"record_video_note"* or *"upload_video_note"* for video notes, - *"choose_contact"* for contacts, *"playing"* for games or *"cancel"* to cancel any chat action currently - displayed. + *"choose_contact"* for contacts, *"playing"* for games, *"speaking"* for speaking in group calls or + *"cancel"* to cancel any chat action currently displayed. Returns: ``bool``: On success, True is returned. From 8b3e1ce9cc9b9cb1912561f469788775b8279d67 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 12 Dec 2020 16:46:40 +0100 Subject: [PATCH 0417/1185] Make generating new salts independent of the system clock Closes #553 --- pyrogram/session/session.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index d46751d4ef..2fa8c8096b 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -19,6 +19,7 @@ import asyncio import logging import os +import time from datetime import datetime, timedelta from hashlib import sha1 from io import BytesIO @@ -303,17 +304,15 @@ async def next_salt_worker(self): log.info("NextSaltTask started") while True: - now = datetime.now() + now = datetime.fromtimestamp(time.perf_counter() - MsgId.reference_clock + MsgId.server_time) # Seconds to wait until middle-overlap, which is # 15 minutes before/after the current/next salt end/start time valid_until = datetime.fromtimestamp(self.current_salt.valid_until) dt = (valid_until - now).total_seconds() - 900 - log.info("Next salt in {:.0f}m {:.0f}s ({})".format( - dt // 60, dt % 60, - now + timedelta(seconds=dt) - )) + minutes, seconds = divmod(int(dt), 60) + log.info(f"Next salt in {minutes:.0f}m {seconds:.0f}s (at {now + timedelta(seconds=dt)})") try: await asyncio.wait_for(self.next_salt_task_event.wait(), dt) From 04cf4e68e3339f12e3c3a44649cced690727c04d Mon Sep 17 00:00:00 2001 From: ColinShark Date: Sat, 12 Dec 2020 16:56:26 +0100 Subject: [PATCH 0418/1185] Add mark_chat_unread() method (#322) * Add mark_chat_unread() method * Add bound method for mark_chat_unread * Update mark_chat_unread.py * Update chat.py Apply Dans suggested changes * Update mark_chat_unread.py * Update chat.py * Update compiler.py Co-authored-by: Dan <14043624+delivrance@users.noreply.github.com> --- compiler/docs/compiler.py | 2 + pyrogram/client/methods/chats/__init__.py | 4 +- .../client/methods/chats/mark_chat_unread.py | 45 +++++++++++++++++++ 3 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 pyrogram/client/methods/chats/mark_chat_unread.py diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index 8afd99f841..8115d0ef87 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -216,6 +216,7 @@ def get_title_list(s: str) -> list: delete_channel delete_supergroup set_slow_mode + mark_chat_unread """, users=""" Users @@ -459,6 +460,7 @@ def get_title_list(s: str) -> list: Chat.add_members Chat.join Chat.leave + Chat.mark_unread """, user=""" User diff --git a/pyrogram/client/methods/chats/__init__.py b/pyrogram/client/methods/chats/__init__.py index 6e4a9228cf..8926191d39 100644 --- a/pyrogram/client/methods/chats/__init__.py +++ b/pyrogram/client/methods/chats/__init__.py @@ -50,6 +50,7 @@ from .unban_chat_member import UnbanChatMember from .unpin_chat_message import UnpinChatMessage from .update_chat_username import UpdateChatUsername +from .mark_chat_unread import MarkChatUnread class Chats( @@ -86,6 +87,7 @@ class Chats( DeleteSupergroup, GetNearbyChats, SetAdministratorTitle, - SetSlowMode + SetSlowMode, + MarkChatUnread ): pass diff --git a/pyrogram/client/methods/chats/mark_chat_unread.py b/pyrogram/client/methods/chats/mark_chat_unread.py new file mode 100644 index 0000000000..7d8558dad5 --- /dev/null +++ b/pyrogram/client/methods/chats/mark_chat_unread.py @@ -0,0 +1,45 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan +# +# 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 Union + +from pyrogram.raw import functions +from pyrogram.scaffold import Scaffold + + +class MarkChatUnread(Scaffold): + async def mark_chat_unread( + self, + chat_id: Union[int, str], + ) -> bool: + """Mark a chat as unread. + + Parameters: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + + Returns: + ``bool``: On success, True is returned. + """ + + return await self.send( + functions.messages.MarkDialogUnread( + peer=await self.resolve_peer(chat_id), + unread=True + ) + ) From db3b262b8cb7473f7ca11c05bfc3bb6552ce6ce9 Mon Sep 17 00:00:00 2001 From: Shrimadhav U K Date: Sat, 12 Dec 2020 21:27:51 +0530 Subject: [PATCH 0419/1185] Typo on_chosen_inline_query -> on_chosen_inline_result (#559) --- pyrogram/handlers/chosen_inline_result_handler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/handlers/chosen_inline_result_handler.py b/pyrogram/handlers/chosen_inline_result_handler.py index 428b153dd6..0e4df6f966 100644 --- a/pyrogram/handlers/chosen_inline_result_handler.py +++ b/pyrogram/handlers/chosen_inline_result_handler.py @@ -24,7 +24,7 @@ class ChosenInlineResultHandler(Handler): It is intended to be used with :meth:`~pyrogram.Client.add_handler` For a nicer way to register this handler, have a look at the - :meth:`~pyrogram.Client.on_chosen_inline_query` decorator. + :meth:`~pyrogram.Client.on_chosen_inline_result` decorator. Parameters: callback (``callable``): From 503327c864e46c915d32760db9cdfeee04eeb73c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 14 Dec 2020 17:33:05 +0100 Subject: [PATCH 0420/1185] Update API schema (Layer 122 patch) --- compiler/api/source/main_api.tl | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl index 963dd1df7b..b894fd525a 100644 --- a/compiler/api/source/main_api.tl +++ b/compiler/api/source/main_api.tl @@ -100,12 +100,12 @@ userStatusLastWeek#7bf09fc = UserStatus; userStatusLastMonth#77ebc742 = UserStatus; chatEmpty#9ba2d800 id:int = Chat; -chat#3bda1bde flags:# creator:flags.0?true kicked:flags.1?true left:flags.2?true deactivated:flags.5?true id:int title:string photo:ChatPhoto participants_count:int date:int version:int migrated_to:flags.6?InputChannel admin_rights:flags.14?ChatAdminRights default_banned_rights:flags.18?ChatBannedRights = Chat; +chat#3bda1bde flags:# creator:flags.0?true kicked:flags.1?true left:flags.2?true deactivated:flags.5?true call_active:flags.23?true call_not_empty:flags.24?true id:int title:string photo:ChatPhoto participants_count:int date:int version:int migrated_to:flags.6?InputChannel admin_rights:flags.14?ChatAdminRights default_banned_rights:flags.18?ChatBannedRights = Chat; chatForbidden#7328bdb id:int title:string = Chat; channel#d31a961e flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true id:int access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int version:int restriction_reason:flags.9?Vector admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int = Chat; channelForbidden#289da732 flags:# broadcast:flags.5?true megagroup:flags.8?true id:int access_hash:long title:string until_date:flags.16?int = Chat; -chatFull#1b7c9db3 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:int about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int = ChatFull; +chatFull#dc8c181 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:int about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall = ChatFull; channelFull#ef3a6acd flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true id:int about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?int migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?int location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall = ChatFull; chatParticipant#c8d7493e user_id:int inviter_id:int date:int = ChatParticipant; @@ -343,8 +343,9 @@ updatePeerBlocked#246a4b22 peer_id:Peer blocked:Bool = Update; updateChannelUserTyping#ff2abe9f flags:# channel_id:int top_msg_id:flags.0?int user_id:int action:SendMessageAction = Update; updatePinnedMessages#ed85eab5 flags:# pinned:flags.0?true peer:Peer messages:Vector pts:int pts_count:int = Update; updatePinnedChannelMessages#8588878b flags:# pinned:flags.0?true channel_id:int messages:Vector pts:int pts_count:int = Update; +updateChat#1330a196 chat_id:int = Update; updateGroupCallParticipants#f2ebdb4e call:InputGroupCall participants:Vector version:int = Update; -updateGroupCall#5724806e channel_id:int call:GroupCall = Update; +updateGroupCall#a45eb99b chat_id:int call:GroupCall = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -525,7 +526,7 @@ inputStickerSetShortName#861cc8a0 short_name:string = InputStickerSet; inputStickerSetAnimatedEmoji#28703c8 = InputStickerSet; inputStickerSetDice#e67f520e emoticon:string = InputStickerSet; -stickerSet#eeb46f27 flags:# archived:flags.1?true official:flags.2?true masks:flags.3?true animated:flags.5?true installed_date:flags.0?int id:long access_hash:long title:string short_name:string thumb:flags.4?PhotoSize thumb_dc_id:flags.4?int count:int hash:int = StickerSet; +stickerSet#40e237a8 flags:# archived:flags.1?true official:flags.2?true masks:flags.3?true animated:flags.5?true installed_date:flags.0?int id:long access_hash:long title:string short_name:string thumbs:flags.4?Vector thumb_dc_id:flags.4?int count:int hash:int = StickerSet; messages.stickerSet#b60a24a6 set:StickerSet packs:Vector documents:Vector = messages.StickerSet; @@ -1172,7 +1173,7 @@ groupCall#55903081 flags:# join_muted:flags.1?true can_change_join_muted:flags.2 inputGroupCall#d8aa840f id:long access_hash:long = InputGroupCall; -groupCallParticipant#56b087c9 flags:# muted:flags.0?true left:flags.1?true can_self_unmute:flags.2?true user_id:int date:int active_date:flags.3?int source:int = GroupCallParticipant; +groupCallParticipant#56b087c9 flags:# muted:flags.0?true left:flags.1?true can_self_unmute:flags.2?true just_joined:flags.4?true user_id:int date:int active_date:flags.3?int source:int = GroupCallParticipant; phone.groupCall#66ab0bfc call:GroupCall participants:Vector participants_next_offset:string users:Vector = phone.GroupCall; @@ -1538,7 +1539,7 @@ phone.discardCall#b2cbc1c0 flags:# video:flags.0?true peer:InputPhoneCall durati phone.setCallRating#59ead627 flags:# user_initiative:flags.0?true peer:InputPhoneCall rating:int comment:string = Updates; phone.saveCallDebug#277add7e peer:InputPhoneCall debug:DataJSON = Bool; phone.sendSignalingData#ff7a9383 peer:InputPhoneCall data:bytes = Bool; -phone.createGroupCall#e428fa02 channel:InputChannel random_id:int = Updates; +phone.createGroupCall#bd3dabe0 peer:InputPeer random_id:int = Updates; phone.joinGroupCall#5f9c8e62 flags:# muted:flags.0?true call:InputGroupCall params:DataJSON = Updates; phone.leaveGroupCall#500377f9 call:InputGroupCall source:int = Updates; phone.editGroupCallMember#63146ae4 flags:# muted:flags.0?true call:InputGroupCall user_id:InputUser = Updates; From e1dac6c0e241c6332024aa0d3c6b9a7e84ae67d2 Mon Sep 17 00:00:00 2001 From: Legenda24 <48826466+Legenda24@users.noreply.github.com> Date: Sun, 20 Dec 2020 21:02:54 +0500 Subject: [PATCH 0421/1185] Add new method get_media_group (#550) * Update __init__.py Support for GetMediaGroup * Create get_media_group.py Added new method - get_media_group * Update get_media_group.py Add pyro stuff * Update get_media_group.py * Update compiler.py Co-authored-by: Dan <14043624+delivrance@users.noreply.github.com> --- compiler/docs/compiler.py | 1 + pyrogram/methods/messages/__init__.py | 2 + pyrogram/methods/messages/get_media_group.py | 59 ++++++++++++++++++++ 3 files changed, 62 insertions(+) create mode 100644 pyrogram/methods/messages/get_media_group.py diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index 7bc7308f30..b1c9b93212 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -168,6 +168,7 @@ def get_title_list(s: str) -> list: send_chat_action delete_messages get_messages + get_media_group get_history get_history_count read_history diff --git a/pyrogram/methods/messages/__init__.py b/pyrogram/methods/messages/__init__.py index 4c9319a707..f91b794139 100644 --- a/pyrogram/methods/messages/__init__.py +++ b/pyrogram/methods/messages/__init__.py @@ -30,6 +30,7 @@ from .forward_messages import ForwardMessages from .get_history import GetHistory from .get_history_count import GetHistoryCount +from .get_media_group import GetMediaGroup from .get_messages import GetMessages from .iter_history import IterHistory from .read_history import ReadHistory @@ -65,6 +66,7 @@ class Messages( EditMessageText, ForwardMessages, GetHistory, + GetMediaGroup, GetMessages, SendAudio, SendChatAction, diff --git a/pyrogram/methods/messages/get_media_group.py b/pyrogram/methods/messages/get_media_group.py new file mode 100644 index 0000000000..ab20f8ec69 --- /dev/null +++ b/pyrogram/methods/messages/get_media_group.py @@ -0,0 +1,59 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan +# +# 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 +from typing import Union, List + +from pyrogram.scaffold import Scaffold +from pyrogram.types import list + +log = logging.getLogger(__name__) + + +class GetMediaGroup(Scaffold): + async def get_media_group( + self, + chat_id: Union[int, str], + message_id: int + ) -> List["types.Message"]: + """Get the media group a message belongs to. + + Parameters: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + For your personal cloud (Saved Messages) you can simply use "me" or "self". + For a contact that exists in your Telegram address book you can use his phone number (str). + + message_id (``int``): + The id of one of the messages that belong to the media group. + + Returns: + List of :obj:`~pyrogram.types.Message`: On success, a list of messages of the media group is returned. + + Raises: + ValueError: In case the passed message id doesn't belong to a media group. + """ + # There can be maximum 10 items in a media group. + messages = await self.get_messages(chat_id, [msg_id for msg_id in range(message_id - 9, message_id + 10)], replies=0) + + media_group_id = messages[9].media_group_id + + if media_group_id is None: + raise ValueError("The message doesn't belong to a media group") + + return list.List(msg for msg in messages if msg.media_group_id == media_group_id) From 1dc4df8cb1da36730d2318a0dbaab40cb2806b79 Mon Sep 17 00:00:00 2001 From: Alisson Lauffer Date: Sun, 20 Dec 2020 13:05:17 -0300 Subject: [PATCH 0422/1185] Improve typing hints (#537) * Change type1 or type2 to Union[type1, type2] * Address @KunoiSayami suggestions * Change Union[type1, None] to Optional[type1] * Update PR with latest commit changes * Address Dan suggestions --- pyrogram/client.py | 6 ++-- pyrogram/connection/connection.py | 4 ++- .../connection/transport/tcp/tcp_abridged.py | 4 ++- .../transport/tcp/tcp_abridged_o.py | 5 +++- pyrogram/connection/transport/tcp/tcp_full.py | 3 +- .../transport/tcp/tcp_intermediate.py | 3 +- .../transport/tcp/tcp_intermediate_o.py | 3 +- pyrogram/errors/rpc_error.py | 4 +-- pyrogram/filters.py | 6 ++-- pyrogram/methods/chats/set_slow_mode.py | 4 +-- .../methods/chats/update_chat_username.py | 4 +-- pyrogram/methods/messages/copy_message.py | 4 +-- pyrogram/methods/messages/download_media.py | 4 +-- .../methods/messages/edit_inline_caption.py | 4 +-- pyrogram/methods/messages/edit_inline_text.py | 4 +-- .../methods/messages/edit_message_caption.py | 4 +-- .../methods/messages/edit_message_text.py | 4 +-- pyrogram/methods/messages/send_animation.py | 6 ++-- pyrogram/methods/messages/send_audio.py | 6 ++-- .../methods/messages/send_cached_media.py | 6 ++-- pyrogram/methods/messages/send_dice.py | 4 +-- pyrogram/methods/messages/send_document.py | 6 ++-- pyrogram/methods/messages/send_message.py | 4 +-- pyrogram/methods/messages/send_photo.py | 6 ++-- pyrogram/methods/messages/send_sticker.py | 4 +-- pyrogram/methods/messages/send_video.py | 6 ++-- pyrogram/methods/messages/send_video_note.py | 4 +-- pyrogram/methods/messages/send_voice.py | 6 ++-- pyrogram/methods/users/update_username.py | 4 +-- pyrogram/parser/html.py | 4 +-- pyrogram/parser/markdown.py | 4 +-- pyrogram/parser/parser.py | 6 ++-- .../bots_and_keyboards/callback_query.py | 6 ++-- .../inline_query_result_animation.py | 4 +-- .../inline_mode/inline_query_result_photo.py | 4 +-- .../input_media/input_media_animation.py | 4 +-- .../types/input_media/input_media_audio.py | 4 +-- .../types/input_media/input_media_document.py | 4 +-- .../types/input_media/input_media_photo.py | 4 +-- .../types/input_media/input_media_video.py | 4 +-- .../input_text_message_content.py | 4 +-- pyrogram/types/messages_and_media/message.py | 28 +++++++++---------- .../messages_and_media/message_entity.py | 3 +- .../types/messages_and_media/thumbnail.py | 4 +-- pyrogram/types/user_and_chats/chat.py | 4 +-- pyrogram/types/user_and_chats/user.py | 4 +-- pyrogram/utils.py | 4 +-- 47 files changed, 121 insertions(+), 110 deletions(-) diff --git a/pyrogram/client.py b/pyrogram/client.py index 6638b3b433..a7acaed663 100644 --- a/pyrogram/client.py +++ b/pyrogram/client.py @@ -29,7 +29,7 @@ from hashlib import sha256 from importlib import import_module from pathlib import Path -from typing import Union, List +from typing import Union, List, Optional import pyrogram from pyrogram import raw @@ -396,7 +396,7 @@ def parse_mode(self): return self._parse_mode @parse_mode.setter - def parse_mode(self, parse_mode: Union[str, None] = "combined"): + def parse_mode(self, parse_mode: Optional[str] = "combined"): if parse_mode not in self.PARSE_MODES: raise ValueError('parse_mode must be one of {} or None. Not "{}"'.format( ", ".join(f'"{m}"' for m in self.PARSE_MODES[:-1]), @@ -406,7 +406,7 @@ def parse_mode(self, parse_mode: Union[str, None] = "combined"): self._parse_mode = parse_mode # TODO: redundant, remove in next major version - def set_parse_mode(self, parse_mode: Union[str, None] = "combined"): + def set_parse_mode(self, parse_mode: Optional[str] = "combined"): """Set the parse mode to be used globally by the client. When setting the parse mode with this method, all other methods having a *parse_mode* parameter will follow the diff --git a/pyrogram/connection/connection.py b/pyrogram/connection/connection.py index b06024947e..9fe7dfd365 100644 --- a/pyrogram/connection/connection.py +++ b/pyrogram/connection/connection.py @@ -19,6 +19,8 @@ import asyncio import logging +from typing import Optional + from .transport import * from ..session.internals import DataCenter @@ -79,5 +81,5 @@ async def send(self, data: bytes): except Exception: raise OSError - async def recv(self) -> bytes or None: + async def recv(self) -> Optional[bytes]: return await self.protocol.recv() diff --git a/pyrogram/connection/transport/tcp/tcp_abridged.py b/pyrogram/connection/transport/tcp/tcp_abridged.py index 4b4de7b2b5..4c4193f800 100644 --- a/pyrogram/connection/transport/tcp/tcp_abridged.py +++ b/pyrogram/connection/transport/tcp/tcp_abridged.py @@ -18,6 +18,8 @@ import logging +from typing import Optional + from .tcp import TCP log = logging.getLogger(__name__) @@ -41,7 +43,7 @@ async def send(self, data: bytes, *args): + data ) - async def recv(self, length: int = 0) -> bytes or None: + async def recv(self, length: int = 0) -> Optional[bytes]: length = await super().recv(1) if length is None: diff --git a/pyrogram/connection/transport/tcp/tcp_abridged_o.py b/pyrogram/connection/transport/tcp/tcp_abridged_o.py index 04644a634d..2b5ad2c9e3 100644 --- a/pyrogram/connection/transport/tcp/tcp_abridged_o.py +++ b/pyrogram/connection/transport/tcp/tcp_abridged_o.py @@ -19,7 +19,10 @@ import logging import os +from typing import Optional + from pyrogram import utils + from pyrogram.crypto import aes from .tcp import TCP @@ -61,7 +64,7 @@ async def send(self, data: bytes, *args): await super().send(payload) - async def recv(self, length: int = 0) -> bytes or None: + async def recv(self, length: int = 0) -> Optional[bytes]: length = await super().recv(1) if length is None: diff --git a/pyrogram/connection/transport/tcp/tcp_full.py b/pyrogram/connection/transport/tcp/tcp_full.py index 0490fa6543..dc03b9e926 100644 --- a/pyrogram/connection/transport/tcp/tcp_full.py +++ b/pyrogram/connection/transport/tcp/tcp_full.py @@ -17,6 +17,7 @@ # along with Pyrogram. If not, see . import logging +from typing import Optional from binascii import crc32 from struct import pack, unpack @@ -42,7 +43,7 @@ async def send(self, data: bytes, *args): await super().send(data) - async def recv(self, length: int = 0) -> bytes or None: + async def recv(self, length: int = 0) -> Optional[bytes]: length = await super().recv(4) if length is None: diff --git a/pyrogram/connection/transport/tcp/tcp_intermediate.py b/pyrogram/connection/transport/tcp/tcp_intermediate.py index dd7d264a04..b0692d11b7 100644 --- a/pyrogram/connection/transport/tcp/tcp_intermediate.py +++ b/pyrogram/connection/transport/tcp/tcp_intermediate.py @@ -17,6 +17,7 @@ # along with Pyrogram. If not, see . import logging +from typing import Optional from struct import pack, unpack from .tcp import TCP @@ -35,7 +36,7 @@ async def connect(self, address: tuple): async def send(self, data: bytes, *args): await super().send(pack(" bytes or None: + async def recv(self, length: int = 0) -> Optional[bytes]: length = await super().recv(4) if length is None: diff --git a/pyrogram/connection/transport/tcp/tcp_intermediate_o.py b/pyrogram/connection/transport/tcp/tcp_intermediate_o.py index c0c1d915b8..082802f1d8 100644 --- a/pyrogram/connection/transport/tcp/tcp_intermediate_o.py +++ b/pyrogram/connection/transport/tcp/tcp_intermediate_o.py @@ -18,6 +18,7 @@ import logging import os +from typing import Optional from struct import pack, unpack from pyrogram.crypto import aes @@ -62,7 +63,7 @@ async def send(self, data: bytes, *args): ) ) - async def recv(self, length: int = 0) -> bytes or None: + async def recv(self, length: int = 0) -> Optional[bytes]: length = await super().recv(4) if length is None: diff --git a/pyrogram/errors/rpc_error.py b/pyrogram/errors/rpc_error.py index d11467945b..ff232e99c3 100644 --- a/pyrogram/errors/rpc_error.py +++ b/pyrogram/errors/rpc_error.py @@ -19,7 +19,7 @@ import re from datetime import datetime from importlib import import_module -from typing import Type +from typing import Type, Union from pyrogram import raw from pyrogram.raw.core import TLObject @@ -32,7 +32,7 @@ class RPCError(Exception): NAME = None MESSAGE = "{x}" - def __init__(self, x: int or raw.types.RpcError = None, rpc_name: str = None, is_unknown: bool = False): + def __init__(self, x: Union[int, raw.types.RpcError] = None, rpc_name: str = None, is_unknown: bool = False): super().__init__("[{} {}]: {} {}".format( self.CODE, self.ID or self.NAME, diff --git a/pyrogram/filters.py b/pyrogram/filters.py index 5c58743fad..1e992bb44a 100644 --- a/pyrogram/filters.py +++ b/pyrogram/filters.py @@ -699,7 +699,7 @@ async def linked_channel_filter(_, __, m: Message): # endregion -def command(commands: str or List[str], prefixes: str or List[str] = "/", case_sensitive: bool = False): +def command(commands: Union[str, List[str]], prefixes: Union[str, List[str]] = "/", case_sensitive: bool = False): """Filter commands, i.e.: text messages starting with "/" or any other custom prefix. Parameters: @@ -824,7 +824,7 @@ class user(Filter, set): Defaults to None (no users). """ - def __init__(self, users: int or str or list = None): + def __init__(self, users: Union[int, str, List[Union[int, str]]] = None): users = [] if users is None else users if isinstance(users, list) else [users] super().__init__( @@ -856,7 +856,7 @@ class chat(Filter, set): Defaults to None (no chats). """ - def __init__(self, chats: int or str or list = None): + def __init__(self, chats: Union[int, str, List[Union[int, str]]] = None): chats = [] if chats is None else chats if isinstance(chats, list) else [chats] super().__init__( diff --git a/pyrogram/methods/chats/set_slow_mode.py b/pyrogram/methods/chats/set_slow_mode.py index e2db5a85dd..24836bd7a9 100644 --- a/pyrogram/methods/chats/set_slow_mode.py +++ b/pyrogram/methods/chats/set_slow_mode.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Union +from typing import Union, Optional from pyrogram import raw from pyrogram.scaffold import Scaffold @@ -26,7 +26,7 @@ class SetSlowMode(Scaffold): async def set_slow_mode( self, chat_id: Union[int, str], - seconds: Union[int, None] + seconds: Optional[int] ) -> bool: """Set the slow mode interval for a chat. diff --git a/pyrogram/methods/chats/update_chat_username.py b/pyrogram/methods/chats/update_chat_username.py index 876115a4bc..ee69160e7d 100644 --- a/pyrogram/methods/chats/update_chat_username.py +++ b/pyrogram/methods/chats/update_chat_username.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Union +from typing import Union, Optional from pyrogram import raw from pyrogram.scaffold import Scaffold @@ -26,7 +26,7 @@ class UpdateChatUsername(Scaffold): async def update_chat_username( self, chat_id: Union[int, str], - username: Union[str, None] + username: Optional[str] ) -> bool: """Update a channel or a supergroup username. diff --git a/pyrogram/methods/messages/copy_message.py b/pyrogram/methods/messages/copy_message.py index 3c9498c379..9c58831e10 100644 --- a/pyrogram/methods/messages/copy_message.py +++ b/pyrogram/methods/messages/copy_message.py @@ -17,7 +17,7 @@ # along with Pyrogram. If not, see . import logging -from typing import Union, List +from typing import Union, List, Optional from pyrogram import types from pyrogram.scaffold import Scaffold @@ -32,7 +32,7 @@ async def copy_message( from_chat_id: Union[int, str], message_id: int, caption: str = None, - parse_mode: Union[str, None] = object, + parse_mode: Optional[str] = object, caption_entities: List["types.MessageEntity"] = None, disable_notification: bool = None, reply_to_message_id: int = None, diff --git a/pyrogram/methods/messages/download_media.py b/pyrogram/methods/messages/download_media.py index db9493f38e..40530104f2 100644 --- a/pyrogram/methods/messages/download_media.py +++ b/pyrogram/methods/messages/download_media.py @@ -20,7 +20,7 @@ import os import time from datetime import datetime -from typing import Union +from typing import Union, Optional from pyrogram import types from pyrogram.file_id import FileId, FileType, PHOTO_TYPES @@ -37,7 +37,7 @@ async def download_media( block: bool = True, progress: callable = None, progress_args: tuple = () - ) -> Union[str, None]: + ) -> Optional[str]: """Download the media from a message. Parameters: diff --git a/pyrogram/methods/messages/edit_inline_caption.py b/pyrogram/methods/messages/edit_inline_caption.py index 055303b41e..b9f62cc5c0 100644 --- a/pyrogram/methods/messages/edit_inline_caption.py +++ b/pyrogram/methods/messages/edit_inline_caption.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Union +from typing import Optional from pyrogram import types from pyrogram.scaffold import Scaffold @@ -27,7 +27,7 @@ async def edit_inline_caption( self, inline_message_id: str, caption: str, - parse_mode: Union[str, None] = object, + parse_mode: Optional[str] = object, reply_markup: "types.InlineKeyboardMarkup" = None ) -> bool: """Edit the caption of inline media messages. diff --git a/pyrogram/methods/messages/edit_inline_text.py b/pyrogram/methods/messages/edit_inline_text.py index 4558da57d6..778f8b8b54 100644 --- a/pyrogram/methods/messages/edit_inline_text.py +++ b/pyrogram/methods/messages/edit_inline_text.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Union +from typing import Optional from pyrogram import raw from pyrogram import types @@ -30,7 +30,7 @@ async def edit_inline_text( self, inline_message_id: str, text: str, - parse_mode: Union[str, None] = object, + parse_mode: Optional[str] = object, disable_web_page_preview: bool = None, reply_markup: "types.InlineKeyboardMarkup" = None ) -> bool: diff --git a/pyrogram/methods/messages/edit_message_caption.py b/pyrogram/methods/messages/edit_message_caption.py index a1cceffa56..3b9f75d149 100644 --- a/pyrogram/methods/messages/edit_message_caption.py +++ b/pyrogram/methods/messages/edit_message_caption.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Union, List +from typing import Union, List, Optional from pyrogram import types from pyrogram.scaffold import Scaffold @@ -28,7 +28,7 @@ async def edit_message_caption( chat_id: Union[int, str], message_id: int, caption: str, - parse_mode: Union[str, None] = object, + parse_mode: Optional[str] = object, caption_entities: List["types.MessageEntity"] = None, reply_markup: "types.InlineKeyboardMarkup" = None ) -> "types.Message": diff --git a/pyrogram/methods/messages/edit_message_text.py b/pyrogram/methods/messages/edit_message_text.py index ece20d0430..d09dda1265 100644 --- a/pyrogram/methods/messages/edit_message_text.py +++ b/pyrogram/methods/messages/edit_message_text.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Union, List +from typing import Union, List, Optional from pyrogram import raw from pyrogram import types @@ -30,7 +30,7 @@ async def edit_message_text( chat_id: Union[int, str], message_id: int, text: str, - parse_mode: Union[str, None] = object, + parse_mode: Optional[str] = object, entities: List["types.MessageEntity"] = None, disable_web_page_preview: bool = None, reply_markup: "types.InlineKeyboardMarkup" = None diff --git a/pyrogram/methods/messages/send_animation.py b/pyrogram/methods/messages/send_animation.py index e705b5c86e..620b4a9f5f 100644 --- a/pyrogram/methods/messages/send_animation.py +++ b/pyrogram/methods/messages/send_animation.py @@ -18,7 +18,7 @@ import os import re -from typing import Union, BinaryIO, List +from typing import Union, BinaryIO, List, Optional from pyrogram import StopTransmission from pyrogram import raw @@ -36,7 +36,7 @@ async def send_animation( animation: Union[str, BinaryIO], caption: str = "", unsave: bool = False, - parse_mode: Union[str, None] = object, + parse_mode: Optional[str] = object, caption_entities: List["types.MessageEntity"] = None, duration: int = 0, width: int = 0, @@ -54,7 +54,7 @@ async def send_animation( ] = None, progress: callable = None, progress_args: tuple = () - ) -> Union["types.Message", None]: + ) -> Optional["types.Message"]: """Send animation files (animation or H.264/MPEG-4 AVC video without sound). Parameters: diff --git a/pyrogram/methods/messages/send_audio.py b/pyrogram/methods/messages/send_audio.py index 18fd2bf181..1769ecd42e 100644 --- a/pyrogram/methods/messages/send_audio.py +++ b/pyrogram/methods/messages/send_audio.py @@ -18,7 +18,7 @@ import os import re -from typing import Union, BinaryIO, List +from typing import Union, BinaryIO, List, Optional from pyrogram import StopTransmission from pyrogram import raw @@ -35,7 +35,7 @@ async def send_audio( chat_id: Union[int, str], audio: Union[str, BinaryIO], caption: str = "", - parse_mode: Union[str, None] = object, + parse_mode: Optional[str] = object, caption_entities: List["types.MessageEntity"] = None, duration: int = 0, performer: str = None, @@ -53,7 +53,7 @@ async def send_audio( ] = None, progress: callable = None, progress_args: tuple = () - ) -> Union["types.Message", None]: + ) -> Optional["types.Message"]: """Send audio files. For sending voice messages, use the :meth:`~pyrogram.Client.send_voice` method instead. diff --git a/pyrogram/methods/messages/send_cached_media.py b/pyrogram/methods/messages/send_cached_media.py index 09fd96cd2a..bf8826eb31 100644 --- a/pyrogram/methods/messages/send_cached_media.py +++ b/pyrogram/methods/messages/send_cached_media.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Union, List +from typing import Union, List, Optional from pyrogram import raw from pyrogram import types @@ -30,7 +30,7 @@ async def send_cached_media( chat_id: Union[int, str], file_id: str, caption: str = "", - parse_mode: Union[str, None] = object, + parse_mode: Optional[str] = object, caption_entities: List["types.MessageEntity"] = None, disable_notification: bool = None, reply_to_message_id: int = None, @@ -41,7 +41,7 @@ async def send_cached_media( "types.ReplyKeyboardRemove", "types.ForceReply" ] = None - ) -> Union["types.Message", None]: + ) -> Optional["types.Message"]: """Send any media stored on the Telegram servers using a file_id. This convenience method works with any valid file_id only. diff --git a/pyrogram/methods/messages/send_dice.py b/pyrogram/methods/messages/send_dice.py index f7734a4c59..a584fadf8f 100644 --- a/pyrogram/methods/messages/send_dice.py +++ b/pyrogram/methods/messages/send_dice.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Union +from typing import Union, Optional from pyrogram import raw from pyrogram import types @@ -37,7 +37,7 @@ async def send_dice( "types.ReplyKeyboardRemove", "types.ForceReply" ] = None - ) -> Union["types.Message", None]: + ) -> Optional["types.Message"]: """Send a dice with a random value from 1 to 6. Parameters: diff --git a/pyrogram/methods/messages/send_document.py b/pyrogram/methods/messages/send_document.py index 20fea5d5a2..6649e5398f 100644 --- a/pyrogram/methods/messages/send_document.py +++ b/pyrogram/methods/messages/send_document.py @@ -18,7 +18,7 @@ import os import re -from typing import Union, BinaryIO, List +from typing import Union, BinaryIO, List, Optional from pyrogram import StopTransmission from pyrogram import raw @@ -36,7 +36,7 @@ async def send_document( document: Union[str, BinaryIO], thumb: Union[str, BinaryIO] = None, caption: str = "", - parse_mode: Union[str, None] = object, + parse_mode: Optional[str] = object, caption_entities: List["types.MessageEntity"] = None, file_name: str = None, force_document: bool = None, @@ -51,7 +51,7 @@ async def send_document( ] = None, progress: callable = None, progress_args: tuple = () - ) -> Union["types.Message", None]: + ) -> Optional["types.Message"]: """Send generic files. Parameters: diff --git a/pyrogram/methods/messages/send_message.py b/pyrogram/methods/messages/send_message.py index e873b58df7..76d3e279cd 100644 --- a/pyrogram/methods/messages/send_message.py +++ b/pyrogram/methods/messages/send_message.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Union, List +from typing import Union, List, Optional from pyrogram import raw, utils from pyrogram import types @@ -28,7 +28,7 @@ async def send_message( self, chat_id: Union[int, str], text: str, - parse_mode: Union[str, None] = object, + parse_mode: Optional[str] = object, entities: List["types.MessageEntity"] = None, disable_web_page_preview: bool = None, disable_notification: bool = None, diff --git a/pyrogram/methods/messages/send_photo.py b/pyrogram/methods/messages/send_photo.py index 0b84a8c0aa..a8fd0e0bd6 100644 --- a/pyrogram/methods/messages/send_photo.py +++ b/pyrogram/methods/messages/send_photo.py @@ -18,7 +18,7 @@ import os import re -from typing import Union, BinaryIO, List +from typing import Union, BinaryIO, List, Optional import pyrogram from pyrogram import raw @@ -35,7 +35,7 @@ async def send_photo( chat_id: Union[int, str], photo: Union[str, BinaryIO], caption: str = "", - parse_mode: Union[str, None] = object, + parse_mode: Optional[str] = object, caption_entities: List["types.MessageEntity"] = None, ttl_seconds: int = None, disable_notification: bool = None, @@ -49,7 +49,7 @@ async def send_photo( ] = None, progress: callable = None, progress_args: tuple = () - ) -> Union["types.Message", None]: + ) -> Optional["types.Message"]: """Send photos. Parameters: diff --git a/pyrogram/methods/messages/send_sticker.py b/pyrogram/methods/messages/send_sticker.py index 18260bd468..00e1ec2415 100644 --- a/pyrogram/methods/messages/send_sticker.py +++ b/pyrogram/methods/messages/send_sticker.py @@ -18,7 +18,7 @@ import os import re -from typing import Union, BinaryIO +from typing import Union, BinaryIO, Optional from pyrogram import StopTransmission from pyrogram import raw @@ -45,7 +45,7 @@ async def send_sticker( ] = None, progress: callable = None, progress_args: tuple = () - ) -> Union["types.Message", None]: + ) -> Optional["types.Message"]: """Send static .webp or animated .tgs stickers. Parameters: diff --git a/pyrogram/methods/messages/send_video.py b/pyrogram/methods/messages/send_video.py index a5b12aee84..c42434638e 100644 --- a/pyrogram/methods/messages/send_video.py +++ b/pyrogram/methods/messages/send_video.py @@ -18,7 +18,7 @@ import os import re -from typing import Union, BinaryIO, List +from typing import Union, BinaryIO, List, Optional from pyrogram import StopTransmission from pyrogram import raw @@ -35,7 +35,7 @@ async def send_video( chat_id: Union[int, str], video: Union[str, BinaryIO], caption: str = "", - parse_mode: Union[str, None] = object, + parse_mode: Optional[str] = object, caption_entities: List["types.MessageEntity"] = None, ttl_seconds: int = None, duration: int = 0, @@ -55,7 +55,7 @@ async def send_video( ] = None, progress: callable = None, progress_args: tuple = () - ) -> Union["types.Message", None]: + ) -> Optional["types.Message"]: """Send video files. Parameters: diff --git a/pyrogram/methods/messages/send_video_note.py b/pyrogram/methods/messages/send_video_note.py index 66f3c6f3a8..45a7b59a45 100644 --- a/pyrogram/methods/messages/send_video_note.py +++ b/pyrogram/methods/messages/send_video_note.py @@ -17,7 +17,7 @@ # along with Pyrogram. If not, see . import os -from typing import Union, BinaryIO +from typing import Union, BinaryIO, Optional from pyrogram import StopTransmission from pyrogram import raw @@ -47,7 +47,7 @@ async def send_video_note( ] = None, progress: callable = None, progress_args: tuple = () - ) -> Union["types.Message", None]: + ) -> Optional["types.Message"]: """Send video messages. Parameters: diff --git a/pyrogram/methods/messages/send_voice.py b/pyrogram/methods/messages/send_voice.py index 36bea41ce5..5863ae0146 100644 --- a/pyrogram/methods/messages/send_voice.py +++ b/pyrogram/methods/messages/send_voice.py @@ -18,7 +18,7 @@ import os import re -from typing import Union, BinaryIO, List +from typing import Union, BinaryIO, List, Optional from pyrogram import StopTransmission from pyrogram import raw @@ -35,7 +35,7 @@ async def send_voice( chat_id: Union[int, str], voice: Union[str, BinaryIO], caption: str = "", - parse_mode: Union[str, None] = object, + parse_mode: Optional[str] = object, caption_entities: List["types.MessageEntity"] = None, duration: int = 0, disable_notification: bool = None, @@ -49,7 +49,7 @@ async def send_voice( ] = None, progress: callable = None, progress_args: tuple = () - ) -> Union["types.Message", None]: + ) -> Optional["types.Message"]: """Send audio files. Parameters: diff --git a/pyrogram/methods/users/update_username.py b/pyrogram/methods/users/update_username.py index 2689c59db6..fe48554ac4 100644 --- a/pyrogram/methods/users/update_username.py +++ b/pyrogram/methods/users/update_username.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Union +from typing import Optional from pyrogram import raw from pyrogram.scaffold import Scaffold @@ -25,7 +25,7 @@ class UpdateUsername(Scaffold): async def update_username( self, - username: Union[str, None] + username: Optional[str] ) -> bool: """Update your own username. diff --git a/pyrogram/parser/html.py b/pyrogram/parser/html.py index 7be5dc956c..e65076c167 100644 --- a/pyrogram/parser/html.py +++ b/pyrogram/parser/html.py @@ -20,7 +20,7 @@ import logging import re from html.parser import HTMLParser -from typing import Union +from typing import Optional import pyrogram from pyrogram import raw @@ -106,7 +106,7 @@ def error(self, message): class HTML: - def __init__(self, client: Union["pyrogram.Client", None]): + def __init__(self, client: Optional["pyrogram.Client"]): self.client = client async def parse(self, text: str): diff --git a/pyrogram/parser/markdown.py b/pyrogram/parser/markdown.py index e821dbd378..f627bb45c0 100644 --- a/pyrogram/parser/markdown.py +++ b/pyrogram/parser/markdown.py @@ -18,7 +18,7 @@ import html import re -from typing import Union +from typing import Optional import pyrogram from . import utils @@ -53,7 +53,7 @@ class Markdown: - def __init__(self, client: Union["pyrogram.Client", None]): + def __init__(self, client: Optional["pyrogram.Client"]): self.html = HTML(client) async def parse(self, text: str, strict: bool = False): diff --git a/pyrogram/parser/parser.py b/pyrogram/parser/parser.py index 64d172417c..27a0160074 100644 --- a/pyrogram/parser/parser.py +++ b/pyrogram/parser/parser.py @@ -17,7 +17,7 @@ # along with Pyrogram. If not, see . from collections import OrderedDict -from typing import Union +from typing import Optional import pyrogram from .html import HTML @@ -25,12 +25,12 @@ class Parser: - def __init__(self, client: Union["pyrogram.Client", None]): + def __init__(self, client: Optional["pyrogram.Client"]): self.client = client self.html = HTML(client) self.markdown = Markdown(client) - async def parse(self, text: str, mode: Union[str, None] = object): + async def parse(self, text: str, mode: Optional[str] = object): text = str(text).strip() if mode == object: diff --git a/pyrogram/types/bots_and_keyboards/callback_query.py b/pyrogram/types/bots_and_keyboards/callback_query.py index 326c78bb10..422c32934a 100644 --- a/pyrogram/types/bots_and_keyboards/callback_query.py +++ b/pyrogram/types/bots_and_keyboards/callback_query.py @@ -18,7 +18,7 @@ from base64 import b64encode from struct import pack -from typing import Union, List, Match +from typing import Union, List, Match, Optional import pyrogram from pyrogram import raw @@ -171,7 +171,7 @@ async def answer(self, text: str = None, show_alert: bool = None, url: str = Non async def edit_message_text( self, text: str, - parse_mode: Union[str, None] = object, + parse_mode: Optional[str] = object, disable_web_page_preview: bool = None, reply_markup: "types.InlineKeyboardMarkup" = None ) -> Union["types.Message", bool]: @@ -224,7 +224,7 @@ async def edit_message_text( async def edit_message_caption( self, caption: str, - parse_mode: Union[str, None] = object, + parse_mode: Optional[str] = object, reply_markup: "types.InlineKeyboardMarkup" = None ) -> Union["types.Message", bool]: """Edit the caption of media messages attached to callback queries. diff --git a/pyrogram/types/inline_mode/inline_query_result_animation.py b/pyrogram/types/inline_mode/inline_query_result_animation.py index 72c290f007..1f4103e7c8 100644 --- a/pyrogram/types/inline_mode/inline_query_result_animation.py +++ b/pyrogram/types/inline_mode/inline_query_result_animation.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Union +from typing import Optional from pyrogram import raw from pyrogram import types @@ -75,7 +75,7 @@ def __init__( title: str = None, description: str = None, caption: str = "", - parse_mode: Union[str, None] = object, + parse_mode: Optional[str] = object, reply_markup: "types.InlineKeyboardMarkup" = None, input_message_content: "types.InputMessageContent" = None ): diff --git a/pyrogram/types/inline_mode/inline_query_result_photo.py b/pyrogram/types/inline_mode/inline_query_result_photo.py index 6d6ae99d1c..155f740f03 100644 --- a/pyrogram/types/inline_mode/inline_query_result_photo.py +++ b/pyrogram/types/inline_mode/inline_query_result_photo.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Union +from typing import Optional from pyrogram import raw from pyrogram import types @@ -75,7 +75,7 @@ def __init__( title: str = None, description: str = None, caption: str = "", - parse_mode: Union[str, None] = object, + parse_mode: Optional[str] = object, reply_markup: "types.InlineKeyboardMarkup" = None, input_message_content: "types.InputMessageContent" = None ): diff --git a/pyrogram/types/input_media/input_media_animation.py b/pyrogram/types/input_media/input_media_animation.py index bd9ac1b551..13a5d777f4 100644 --- a/pyrogram/types/input_media/input_media_animation.py +++ b/pyrogram/types/input_media/input_media_animation.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Union, List +from typing import Optional, List from .input_media import InputMedia from ..messages_and_media import MessageEntity @@ -65,7 +65,7 @@ def __init__( media: str, thumb: str = None, caption: str = "", - parse_mode: Union[str, None] = object, + parse_mode: Optional[str] = object, caption_entities: List[MessageEntity] = None, width: int = 0, height: int = 0, diff --git a/pyrogram/types/input_media/input_media_audio.py b/pyrogram/types/input_media/input_media_audio.py index be966d5213..be5ef3bd09 100644 --- a/pyrogram/types/input_media/input_media_audio.py +++ b/pyrogram/types/input_media/input_media_audio.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Union, List +from typing import Optional, List from .input_media import InputMedia from ..messages_and_media import MessageEntity @@ -67,7 +67,7 @@ def __init__( media: str, thumb: str = None, caption: str = "", - parse_mode: Union[str, None] = object, + parse_mode: Optional[str] = object, caption_entities: List[MessageEntity] = None, duration: int = 0, performer: str = "", diff --git a/pyrogram/types/input_media/input_media_document.py b/pyrogram/types/input_media/input_media_document.py index 7d6500d4ac..e9f8882c7c 100644 --- a/pyrogram/types/input_media/input_media_document.py +++ b/pyrogram/types/input_media/input_media_document.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Union, List +from typing import Optional, List from .input_media import InputMedia from ..messages_and_media import MessageEntity @@ -56,7 +56,7 @@ def __init__( media: str, thumb: str = None, caption: str = "", - parse_mode: Union[str, None] = object, + parse_mode: Optional[str] = object, caption_entities: List[MessageEntity] = None ): super().__init__(media, caption, parse_mode, caption_entities) diff --git a/pyrogram/types/input_media/input_media_photo.py b/pyrogram/types/input_media/input_media_photo.py index 7b0c97aa8d..486a686982 100644 --- a/pyrogram/types/input_media/input_media_photo.py +++ b/pyrogram/types/input_media/input_media_photo.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Union, List +from typing import Optional, List from .input_media import InputMedia from ..messages_and_media import MessageEntity @@ -51,7 +51,7 @@ def __init__( self, media: str, caption: str = "", - parse_mode: Union[str, None] = object, + parse_mode: Optional[str] = object, caption_entities: List[MessageEntity] = None ): super().__init__(media, caption, parse_mode, caption_entities) diff --git a/pyrogram/types/input_media/input_media_video.py b/pyrogram/types/input_media/input_media_video.py index 8a3013bd70..cb595e1384 100644 --- a/pyrogram/types/input_media/input_media_video.py +++ b/pyrogram/types/input_media/input_media_video.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Union, List +from typing import Optional, List from .input_media import InputMedia from ..messages_and_media import MessageEntity @@ -70,7 +70,7 @@ def __init__( media: str, thumb: str = None, caption: str = "", - parse_mode: Union[str, None] = object, + parse_mode: Optional[str] = object, caption_entities: List[MessageEntity] = None, width: int = 0, height: int = 0, diff --git a/pyrogram/types/input_message_content/input_text_message_content.py b/pyrogram/types/input_message_content/input_text_message_content.py index ac584cc202..19696b2152 100644 --- a/pyrogram/types/input_message_content/input_text_message_content.py +++ b/pyrogram/types/input_message_content/input_text_message_content.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Union +from typing import Optional from pyrogram import raw from pyrogram.parser import Parser @@ -41,7 +41,7 @@ class InputTextMessageContent(InputMessageContent): Disables link previews for links in this message. """ - def __init__(self, message_text: str, parse_mode: Union[str, None] = object, disable_web_page_preview: bool = None): + def __init__(self, message_text: str, parse_mode: Optional[str] = object, disable_web_page_preview: bool = None): super().__init__() self.message_text = message_text diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index b720608e37..304473a3c1 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -18,7 +18,7 @@ import logging from functools import partial -from typing import List, Match, Union, BinaryIO +from typing import List, Match, Union, BinaryIO, Optional import pyrogram from pyrogram import raw @@ -710,7 +710,7 @@ async def reply_text( self, text: str, quote: bool = None, - parse_mode: Union[str, None] = object, + parse_mode: Optional[str] = object, entities: List["types.MessageEntity"] = None, disable_web_page_preview: bool = None, disable_notification: bool = None, @@ -797,7 +797,7 @@ async def reply_animation( animation: Union[str, BinaryIO], quote: bool = None, caption: str = "", - parse_mode: Union[str, None] = object, + parse_mode: Optional[str] = object, caption_entities: List["types.MessageEntity"] = None, duration: int = 0, width: int = 0, @@ -939,7 +939,7 @@ async def reply_audio( audio: Union[str, BinaryIO], quote: bool = None, caption: str = "", - parse_mode: Union[str, None] = object, + parse_mode: Optional[str] = object, caption_entities: List["types.MessageEntity"] = None, duration: int = 0, performer: str = None, @@ -1081,7 +1081,7 @@ async def reply_cached_media( file_id: str, quote: bool = None, caption: str = "", - parse_mode: Union[str, None] = object, + parse_mode: Optional[str] = object, caption_entities: List["types.MessageEntity"] = None, disable_notification: bool = None, reply_to_message_id: int = None, @@ -1294,7 +1294,7 @@ async def reply_document( quote: bool = None, thumb: str = None, caption: str = "", - parse_mode: Union[str, None] = object, + parse_mode: Optional[str] = object, caption_entities: List["types.MessageEntity"] = None, disable_notification: bool = None, reply_to_message_id: int = None, @@ -1694,7 +1694,7 @@ async def reply_photo( photo: Union[str, BinaryIO], quote: bool = None, caption: str = "", - parse_mode: Union[str, None] = object, + parse_mode: Optional[str] = object, caption_entities: List["types.MessageEntity"] = None, ttl_seconds: int = None, disable_notification: bool = None, @@ -2122,7 +2122,7 @@ async def reply_video( video: Union[str, BinaryIO], quote: bool = None, caption: str = "", - parse_mode: Union[str, None] = object, + parse_mode: Optional[str] = object, caption_entities: List["types.MessageEntity"] = None, ttl_seconds: int = None, duration: int = 0, @@ -2394,7 +2394,7 @@ async def reply_voice( voice: Union[str, BinaryIO], quote: bool = None, caption: str = "", - parse_mode: Union[str, None] = object, + parse_mode: Optional[str] = object, caption_entities: List["types.MessageEntity"] = None, duration: int = 0, disable_notification: bool = None, @@ -2516,7 +2516,7 @@ async def reply_voice( async def edit_text( self, text: str, - parse_mode: Union[str, None] = object, + parse_mode: Optional[str] = object, entities: List["types.MessageEntity"] = None, disable_web_page_preview: bool = None, reply_markup: "types.InlineKeyboardMarkup" = None @@ -2579,7 +2579,7 @@ async def edit_text( async def edit_caption( self, caption: str, - parse_mode: Union[str, None] = object, + parse_mode: Optional[str] = object, caption_entities: List["types.MessageEntity"] = None, reply_markup: "types.InlineKeyboardMarkup" = None ) -> "Message": @@ -2711,7 +2711,7 @@ async def edit_reply_markup(self, reply_markup: "types.InlineKeyboardMarkup" = N async def forward( self, - chat_id: int or str, + chat_id: Union[int, str], disable_notification: bool = None, schedule_date: int = None ) -> Union["types.Message", List["types.Message"]]: @@ -2763,7 +2763,7 @@ async def copy( self, chat_id: Union[int, str], caption: str = None, - parse_mode: Union[str, None] = object, + parse_mode: Optional[str] = object, caption_entities: List["types.MessageEntity"] = None, disable_notification: bool = None, reply_to_message_id: int = None, @@ -2975,7 +2975,7 @@ async def delete(self, revoke: bool = True): revoke=revoke ) - async def click(self, x: int or str = 0, y: int = None, quote: bool = None, timeout: int = 10): + async def click(self, x: Union[int, str] = 0, y: int = None, quote: bool = None, timeout: int = 10): """Bound method *click* of :obj:`~pyrogram.types.Message`. Use as a shortcut for clicking a button attached to the message instead of: diff --git a/pyrogram/types/messages_and_media/message_entity.py b/pyrogram/types/messages_and_media/message_entity.py index 0dd13d70e5..ddaebbd83e 100644 --- a/pyrogram/types/messages_and_media/message_entity.py +++ b/pyrogram/types/messages_and_media/message_entity.py @@ -17,6 +17,7 @@ # along with Pyrogram. If not, see . from enum import Enum, auto +from typing import Optional import pyrogram from pyrogram import raw @@ -121,7 +122,7 @@ def __init__( self.language = language @staticmethod - def _parse(client, entity, users: dict) -> "MessageEntity" or None: + def _parse(client, entity, users: dict) -> Optional["MessageEntity"]: type = RAW_ENTITIES_TO_TYPE.get(entity.__class__, None) if type is None: diff --git a/pyrogram/types/messages_and_media/thumbnail.py b/pyrogram/types/messages_and_media/thumbnail.py index 898e793851..f8d3596c2e 100644 --- a/pyrogram/types/messages_and_media/thumbnail.py +++ b/pyrogram/types/messages_and_media/thumbnail.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Union, List +from typing import Union, List, Optional import pyrogram from pyrogram import raw @@ -68,7 +68,7 @@ def __init__( def _parse( client, media: Union["raw.types.Photo", "raw.types.Document"] - ) -> Union[List[Union["types.StrippedThumbnail", "Thumbnail"]], None]: + ) -> Optional[List[Union["types.StrippedThumbnail", "Thumbnail"]]]: if isinstance(media, raw.types.Photo): raw_thumbnails = media.sizes[:-1] elif isinstance(media, raw.types.Document): diff --git a/pyrogram/types/user_and_chats/chat.py b/pyrogram/types/user_and_chats/chat.py index 0445cf0f7c..18cf53916f 100644 --- a/pyrogram/types/user_and_chats/chat.py +++ b/pyrogram/types/user_and_chats/chat.py @@ -229,7 +229,7 @@ def _parse_channel_chat(client, channel: raw.types.Channel) -> "Chat": ) @staticmethod - def _parse(client, message: raw.types.Message or raw.types.MessageService, users: dict, chats: dict) -> "Chat": + def _parse(client, message: Union[raw.types.Message, raw.types.MessageService], users: dict, chats: dict) -> "Chat": if isinstance(message.peer_id, raw.types.PeerUser): return Chat._parse_user_chat(client, users[message.peer_id.user_id]) @@ -248,7 +248,7 @@ def _parse_dialog(client, peer, users: dict, chats: dict): return Chat._parse_channel_chat(client, chats[peer.channel_id]) @staticmethod - async def _parse_full(client, chat_full: raw.types.messages.ChatFull or raw.types.UserFull) -> "Chat": + async def _parse_full(client, chat_full: Union[raw.types.messages.ChatFull, raw.types.UserFull]) -> "Chat": if isinstance(chat_full, raw.types.UserFull): parsed_chat = Chat._parse_user_chat(client, chat_full.user) parsed_chat.bio = chat_full.about diff --git a/pyrogram/types/user_and_chats/user.py b/pyrogram/types/user_and_chats/user.py index f159576038..59282f8ee4 100644 --- a/pyrogram/types/user_and_chats/user.py +++ b/pyrogram/types/user_and_chats/user.py @@ -17,7 +17,7 @@ # along with Pyrogram. If not, see . import html -from typing import List +from typing import List, Optional import pyrogram from pyrogram import raw @@ -201,7 +201,7 @@ def mention(self): return Link(f"tg://user?id={self.id}", self.first_name, self._client.parse_mode) @staticmethod - def _parse(client, user: "raw.types.User") -> "User" or None: + def _parse(client, user: "raw.types.User") -> Optional["User"]: if user is None: return None diff --git a/pyrogram/utils.py b/pyrogram/utils.py index 8b6242ebf7..1969b2422a 100644 --- a/pyrogram/utils.py +++ b/pyrogram/utils.py @@ -24,7 +24,7 @@ import struct from concurrent.futures.thread import ThreadPoolExecutor from getpass import getpass -from typing import Union, List, Dict +from typing import Union, List, Dict, Optional import pyrogram from pyrogram import raw @@ -168,7 +168,7 @@ def unpack_inline_message_id(inline_message_id: str) -> "raw.types.InputBotInlin MAX_USER_ID = 2147483647 -def get_raw_peer_id(peer: raw.base.Peer) -> Union[int, None]: +def get_raw_peer_id(peer: raw.base.Peer) -> Optional[int]: """Get the raw peer id from a Peer object""" if isinstance(peer, raw.types.PeerUser): return peer.user_id From 29fa3ec5200e2f55b464b1bd58ea622be6f90cd7 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 20 Dec 2020 17:57:41 +0100 Subject: [PATCH 0423/1185] Remove unneeded util functions and improve docs --- pyrogram/utils.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/pyrogram/utils.py b/pyrogram/utils.py index 1969b2422a..43b7b5a008 100644 --- a/pyrogram/utils.py +++ b/pyrogram/utils.py @@ -33,21 +33,12 @@ async def ainput(prompt: str = "", *, hide: bool = False): + """Just like the built-in input, but async""" with ThreadPoolExecutor(1) as executor: func = functools.partial(getpass if hide else input, prompt) return await asyncio.get_event_loop().run_in_executor(executor, func) -def get_offset_date(dialogs): - for m in reversed(dialogs.messages): - if isinstance(m, raw.types.MessageEmpty): - continue - else: - return m.date - else: - return 0 - - def get_input_media_from_file_id( file_id: str, expected_file_type: FileType = None From 18b3ca18929ad05609c516070dfd50f828d3ae87 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 20 Dec 2020 17:58:35 +0100 Subject: [PATCH 0424/1185] Simplify mime types guessing and remove unused code --- pyrogram/client.py | 12 ++++-------- pyrogram/scaffold.py | 29 ++--------------------------- 2 files changed, 6 insertions(+), 35 deletions(-) diff --git a/pyrogram/client.py b/pyrogram/client.py index a7acaed663..68afabf6eb 100644 --- a/pyrogram/client.py +++ b/pyrogram/client.py @@ -1045,12 +1045,8 @@ async def get_file( else: return file_name - def guess_mime_type(self, filename: str): - extension = os.path.splitext(filename)[1] - return self.extensions_to_mime_types.get(extension) + def guess_mime_type(self, filename: str) -> Optional[str]: + return self.mimetypes.guess_type(filename)[0] - def guess_extension(self, mime_type: str): - extensions = self.mime_types_to_extensions.get(mime_type) - - if extensions: - return extensions.split(" ")[0] + def guess_extension(self, mime_type: str) -> Optional[str]: + return self.mimetypes.guess_extension(mime_type) diff --git a/pyrogram/scaffold.py b/pyrogram/scaffold.py index a118955c29..395ad7d33c 100644 --- a/pyrogram/scaffold.py +++ b/pyrogram/scaffold.py @@ -21,6 +21,7 @@ import platform import re import sys +from mimetypes import MimeTypes from pathlib import Path import pyrogram @@ -45,33 +46,7 @@ class Scaffold: PARSE_MODES = ["combined", "markdown", "md", "html", None] - MEDIA_TYPE_ID = { - 0: "photo_thumbnail", - 1: "chat_photo", - 2: "photo", - 3: "voice", - 4: "video", - 5: "document", - 8: "sticker", - 9: "audio", - 10: "animation", - 13: "video_note", - 14: "document_thumbnail" - } - - mime_types_to_extensions = {} - extensions_to_mime_types = {} - - with open(f"{os.path.dirname(__file__)}/mime.types", "r", encoding="UTF-8") as f: - for match in re.finditer(r"^([^#\s]+)\s+(.+)$", f.read(), flags=re.M): - mime_type, extensions = match.groups() - - extensions = [f".{ext}" for ext in extensions.split(" ")] - - for ext in extensions: - extensions_to_mime_types[ext] = mime_type - - mime_types_to_extensions[mime_type] = " ".join(extensions) + mimetypes = MimeTypes((f"{os.path.dirname(__file__)}/mime.types",)) def __init__(self): try: From d82ecf048a9a97b4e836bf3e7c2c780b3ca9ff4d Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 20 Dec 2020 18:28:21 +0100 Subject: [PATCH 0425/1185] Remove non-python files --- MANIFEST.in | 1 - pyrogram/{mime.types => mime_types.py} | 25 ++++++++++- pyrogram/scaffold.py | 5 ++- pyrogram/storage/schema.sql | 57 -------------------------- pyrogram/storage/sqlite_storage.py | 45 ++++++++++++++++++-- 5 files changed, 70 insertions(+), 63 deletions(-) rename pyrogram/{mime.types => mime_types.py} (98%) delete mode 100644 pyrogram/storage/schema.sql diff --git a/MANIFEST.in b/MANIFEST.in index 100375d781..4a750253e2 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,7 +1,6 @@ ## Include include README.md COPYING COPYING.lesser NOTICE requirements.txt recursive-include compiler *.py *.tl *.tsv *.txt -recursive-include pyrogram mime.types schema.sql ## Exclude prune pyrogram/errors/exceptions diff --git a/pyrogram/mime.types b/pyrogram/mime_types.py similarity index 98% rename from pyrogram/mime.types rename to pyrogram/mime_types.py index bb8f6fdbbe..69e69df872 100644 --- a/pyrogram/mime.types +++ b/pyrogram/mime_types.py @@ -1,3 +1,24 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan +# +# 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 https://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types. +# Extended with extra mime types specific to Telegram. +mime_types = """ # This file maps Internet media types to unique file extension(s). # Although created for httpd, this file is used by many software systems # and has been placed in the public domain for unlimited redistribution. @@ -1855,4 +1876,6 @@ x-conference/x-cooltalk ice # Telegram animated stickers -application/x-tgsticker tgs +application/x-bad-tgsticker tgs +application/x-tgsticker tgs +""" diff --git a/pyrogram/scaffold.py b/pyrogram/scaffold.py index 395ad7d33c..7d67966826 100644 --- a/pyrogram/scaffold.py +++ b/pyrogram/scaffold.py @@ -21,6 +21,7 @@ import platform import re import sys +from io import StringIO from mimetypes import MimeTypes from pathlib import Path @@ -28,6 +29,7 @@ from pyrogram import __version__ from pyrogram.parser import Parser from pyrogram.session.internals import MsgId +from .mime_types import mime_types class Scaffold: @@ -46,7 +48,8 @@ class Scaffold: PARSE_MODES = ["combined", "markdown", "md", "html", None] - mimetypes = MimeTypes((f"{os.path.dirname(__file__)}/mime.types",)) + mimetypes = MimeTypes() + mimetypes.readfp(StringIO(mime_types)) def __init__(self): try: diff --git a/pyrogram/storage/schema.sql b/pyrogram/storage/schema.sql deleted file mode 100644 index 39c986b5d3..0000000000 --- a/pyrogram/storage/schema.sql +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Pyrogram - Telegram MTProto API Client Library for Python - * Copyright (C) 2017-2020 Dan - * - * 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 . - */ - -CREATE TABLE sessions -( - dc_id INTEGER PRIMARY KEY, - test_mode INTEGER, - auth_key BLOB, - date INTEGER NOT NULL, - user_id INTEGER, - is_bot INTEGER -); - -CREATE TABLE peers -( - id INTEGER PRIMARY KEY, - access_hash INTEGER, - type INTEGER NOT NULL, - username TEXT, - phone_number TEXT, - last_update_on INTEGER NOT NULL DEFAULT (CAST(STRFTIME('%s', 'now') AS INTEGER)) -); - -CREATE TABLE version -( - number INTEGER PRIMARY KEY -); - -CREATE INDEX idx_peers_id ON peers (id); -CREATE INDEX idx_peers_username ON peers (username); -CREATE INDEX idx_peers_phone_number ON peers (phone_number); - -CREATE TRIGGER trg_peers_last_update_on - AFTER UPDATE - ON peers -BEGIN - UPDATE peers - SET last_update_on = CAST(STRFTIME('%s', 'now') AS INTEGER) - WHERE id = NEW.id; -END; \ No newline at end of file diff --git a/pyrogram/storage/sqlite_storage.py b/pyrogram/storage/sqlite_storage.py index 2f3768df2d..e9f401b64d 100644 --- a/pyrogram/storage/sqlite_storage.py +++ b/pyrogram/storage/sqlite_storage.py @@ -19,7 +19,6 @@ import inspect import sqlite3 import time -from pathlib import Path from threading import Lock from typing import List, Tuple, Any @@ -27,6 +26,47 @@ from .storage import Storage from .. import utils +# language=SQLite +SCHEMA = """ +CREATE TABLE sessions +( + dc_id INTEGER PRIMARY KEY, + test_mode INTEGER, + auth_key BLOB, + date INTEGER NOT NULL, + user_id INTEGER, + is_bot INTEGER +); + +CREATE TABLE peers +( + id INTEGER PRIMARY KEY, + access_hash INTEGER, + type INTEGER NOT NULL, + username TEXT, + phone_number TEXT, + last_update_on INTEGER NOT NULL DEFAULT (CAST(STRFTIME('%s', 'now') AS INTEGER)) +); + +CREATE TABLE version +( + number INTEGER PRIMARY KEY +); + +CREATE INDEX idx_peers_id ON peers (id); +CREATE INDEX idx_peers_username ON peers (username); +CREATE INDEX idx_peers_phone_number ON peers (phone_number); + +CREATE TRIGGER trg_peers_last_update_on + AFTER UPDATE + ON peers +BEGIN + UPDATE peers + SET last_update_on = CAST(STRFTIME('%s', 'now') AS INTEGER) + WHERE id = NEW.id; +END; +""" + def get_input_peer(peer_id: int, access_hash: int, peer_type: str): if peer_type in ["user", "bot"]: @@ -61,8 +101,7 @@ def __init__(self, name: str): def create(self): with self.lock, self.conn: - with open(str(Path(__file__).parent / "schema.sql"), "r") as schema: - self.conn.executescript(schema.read()) + self.conn.executescript(SCHEMA) self.conn.execute( "INSERT INTO version VALUES (?)", From 3ca90b84c73296251b28e0117075df00cbd710ad Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 22 Dec 2020 19:57:21 +0100 Subject: [PATCH 0426/1185] Update API schema (Layer 122 patch, again) --- compiler/api/source/main_api.tl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl index b894fd525a..3885d62c15 100644 --- a/compiler/api/source/main_api.tl +++ b/compiler/api/source/main_api.tl @@ -41,7 +41,7 @@ inputMediaPhoto#b3ba0635 flags:# id:InputPhoto ttl_seconds:flags.0?int = InputMe inputMediaGeoPoint#f9c44144 geo_point:InputGeoPoint = InputMedia; inputMediaContact#f8ab7dfb phone_number:string first_name:string last_name:string vcard:string = InputMedia; inputMediaUploadedDocument#5b38c6c1 flags:# nosound_video:flags.3?true force_file:flags.4?true file:InputFile thumb:flags.2?InputFile mime_type:string attributes:Vector stickers:flags.0?Vector ttl_seconds:flags.1?int = InputMedia; -inputMediaDocument#23ab23d2 flags:# id:InputDocument ttl_seconds:flags.0?int = InputMedia; +inputMediaDocument#33473058 flags:# id:InputDocument ttl_seconds:flags.0?int query:flags.1?string = InputMedia; inputMediaVenue#c13d1c11 geo_point:InputGeoPoint title:string address:string provider:string venue_id:string venue_type:string = InputMedia; inputMediaPhotoExternal#e5bbfe1a flags:# url:string ttl_seconds:flags.0?int = InputMedia; inputMediaDocumentExternal#fb52dc99 flags:# url:string ttl_seconds:flags.0?int = InputMedia; @@ -1173,7 +1173,7 @@ groupCall#55903081 flags:# join_muted:flags.1?true can_change_join_muted:flags.2 inputGroupCall#d8aa840f id:long access_hash:long = InputGroupCall; -groupCallParticipant#56b087c9 flags:# muted:flags.0?true left:flags.1?true can_self_unmute:flags.2?true just_joined:flags.4?true user_id:int date:int active_date:flags.3?int source:int = GroupCallParticipant; +groupCallParticipant#56b087c9 flags:# muted:flags.0?true left:flags.1?true can_self_unmute:flags.2?true just_joined:flags.4?true versioned:flags.5?true user_id:int date:int active_date:flags.3?int source:int = GroupCallParticipant; phone.groupCall#66ab0bfc call:GroupCall participants:Vector participants_next_offset:string users:Vector = phone.GroupCall; From b683580b1b95040f438709f03f90a4f5a723fac2 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 23 Dec 2020 13:56:06 +0100 Subject: [PATCH 0427/1185] Update Pyrogram to v1.1.0 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 2fd3a168e4..a382bb76f4 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "1.1.0b1" +__version__ = "1.1.0" __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)" __copyright__ = "Copyright (C) 2017-2020 Dan " From bec7ef96eb6aa74745ff308ac6e42a108977f45e Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 23 Dec 2020 14:21:10 +0100 Subject: [PATCH 0428/1185] Fix sitemap.py breaking after moving it inside the scripts folder --- docs/scripts/sitemap.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/scripts/sitemap.py b/docs/scripts/sitemap.py index 8e9bdded41..bd3124bc5b 100644 --- a/docs/scripts/sitemap.py +++ b/docs/scripts/sitemap.py @@ -51,7 +51,7 @@ def search(path): if not path.endswith(".rst"): return - path = path.split("/")[1:] + path = path.split("/")[2:] if path[0].endswith(".rst"): folder = "." @@ -76,6 +76,6 @@ def search(path): f.write(f" {i[1]}\n") f.write(f" {i[2]}\n") f.write(f" {i[3]}\n") - f.write(f" \n\n") + f.write(f" \n") f.write("") From 865f4274c3d9d945a436f7d239f65f01bf78dcb8 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 23 Dec 2020 16:18:27 +0100 Subject: [PATCH 0429/1185] Fix messages not having a reply-to-message when they actually do --- pyrogram/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/utils.py b/pyrogram/utils.py index 43b7b5a008..9f80591def 100644 --- a/pyrogram/utils.py +++ b/pyrogram/utils.py @@ -91,7 +91,7 @@ async def parse_messages(client, messages: "raw.types.messages.Messages", replie parsed_messages.append(await types.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} + messages_with_replies = {i.id: i.reply_to.reply_to_msg_id for i in messages.messages if i.reply_to} reply_message_ids = [i[0] for i in filter(lambda x: x[1] is not None, messages_with_replies.items())] if reply_message_ids: From df1a792cee6cba2d239ede1ca2a089a5881039df Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 23 Dec 2020 16:19:02 +0100 Subject: [PATCH 0430/1185] Exclude the tests folder from binary distributions Also remove some unneeded package data --- setup.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/setup.py b/setup.py index 4f67b52cf7..d7dcb5fcc5 100644 --- a/setup.py +++ b/setup.py @@ -171,11 +171,7 @@ def run(self): "Documentation": "https://docs.pyrogram.org", }, python_requires="~=3.6", - packages=find_packages(exclude=["compiler*"]), - package_data={ - "pyrogram": ["mime.types"], - "pyrogram.storage": ["schema.sql"] - }, + packages=find_packages(exclude=["compiler*", "tests*"]), zip_safe=False, install_requires=requires, cmdclass={ From d47a0133f10485dc3cf797efed9b84de3a7d445d Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 23 Dec 2020 16:19:52 +0100 Subject: [PATCH 0431/1185] Update Pyrogram to v1.1.1 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index a382bb76f4..d212dbf739 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "1.1.0" +__version__ = "1.1.1" __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)" __copyright__ = "Copyright (C) 2017-2020 Dan " From 19878ae633c81b803fb114f0878d8c0559fc23ad Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 23 Dec 2020 16:26:02 +0100 Subject: [PATCH 0432/1185] Specify that dates are in unix time --- pyrogram/types/user_and_chats/user.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/types/user_and_chats/user.py b/pyrogram/types/user_and_chats/user.py index 59282f8ee4..f0f3f7d5c4 100644 --- a/pyrogram/types/user_and_chats/user.py +++ b/pyrogram/types/user_and_chats/user.py @@ -112,10 +112,10 @@ class User(Object, Update): *None*, for bots. last_online_date (``int``, *optional*): - Last online date of a user. Only available in case status is "*offline*". + Last online date of a user, unix time. Only available in case status is "*offline*". next_offline_date (``int``, *optional*): - Date when a user will automatically go offline. Only available in case status is "*online*". + Date when a user will automatically go offline, unix time. Only available in case status is "*online*". username (``str``, *optional*): User's or bot's username. From 4b77bbd468210010243d7a59a3cf33f4cdeb6f98 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 23 Dec 2020 17:20:06 +0100 Subject: [PATCH 0433/1185] Fix get_dialogs breaking in case of empty messages --- pyrogram/methods/chats/get_dialogs.py | 11 +---------- pyrogram/methods/messages/get_messages.py | 2 +- pyrogram/utils.py | 7 ++++++- 3 files changed, 8 insertions(+), 12 deletions(-) diff --git a/pyrogram/methods/chats/get_dialogs.py b/pyrogram/methods/chats/get_dialogs.py index 487e8f8d5a..50656cfed2 100644 --- a/pyrogram/methods/chats/get_dialogs.py +++ b/pyrogram/methods/chats/get_dialogs.py @@ -91,17 +91,8 @@ async def get_dialogs( for message in r.messages: if isinstance(message, raw.types.MessageEmpty): continue - - peer_id = message.peer_id - - if isinstance(peer_id, raw.types.PeerUser): - if message.out: - chat_id = peer_id.user_id - else: - chat_id = utils.get_raw_peer_id(message.from_id) - else: - chat_id = utils.get_peer_id(peer_id) + chat_id = utils.get_peer_id(message.peer_id) messages[chat_id] = await types.Message._parse(self, message, users, chats) parsed_dialogs = [] diff --git a/pyrogram/methods/messages/get_messages.py b/pyrogram/methods/messages/get_messages.py index 3503f16ade..a4f9e6a5ed 100644 --- a/pyrogram/methods/messages/get_messages.py +++ b/pyrogram/methods/messages/get_messages.py @@ -115,4 +115,4 @@ async def get_messages( messages = await utils.parse_messages(self, r, replies=replies) - return messages if is_iterable else messages[0] + return messages if is_iterable else messages[0] if messages else None diff --git a/pyrogram/utils.py b/pyrogram/utils.py index 9f80591def..220f3759a0 100644 --- a/pyrogram/utils.py +++ b/pyrogram/utils.py @@ -91,7 +91,12 @@ async def parse_messages(client, messages: "raw.types.messages.Messages", replie parsed_messages.append(await types.Message._parse(client, message, users, chats, replies=0)) if replies: - messages_with_replies = {i.id: i.reply_to.reply_to_msg_id for i in messages.messages if i.reply_to} + messages_with_replies = { + i.id: i.reply_to.reply_to_msg_id + for i in messages.messages + if not isinstance(i, raw.types.MessageEmpty) and i.reply_to + } + reply_message_ids = [i[0] for i in filter(lambda x: x[1] is not None, messages_with_replies.items())] if reply_message_ids: From 730243f4516dcc4ffe168550cdcbe5604ca7735d Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 23 Dec 2020 17:20:31 +0100 Subject: [PATCH 0434/1185] Update Pyrogram to v1.1.2 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index d212dbf739..d398bbb09c 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "1.1.1" +__version__ = "1.1.2" __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)" __copyright__ = "Copyright (C) 2017-2020 Dan " From ed79f73bd702e9e92b208d641b0cd8f473fb48d6 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 23 Dec 2020 19:04:28 +0100 Subject: [PATCH 0435/1185] Rework and simplify message parsing --- pyrogram/utils.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/pyrogram/utils.py b/pyrogram/utils.py index 220f3759a0..f3ac7dbc4f 100644 --- a/pyrogram/utils.py +++ b/pyrogram/utils.py @@ -97,9 +97,7 @@ async def parse_messages(client, messages: "raw.types.messages.Messages", replie if not isinstance(i, raw.types.MessageEmpty) and i.reply_to } - reply_message_ids = [i[0] for i in filter(lambda x: x[1] is not None, messages_with_replies.items())] - - if reply_message_ids: + if messages_with_replies: # We need a chat id, but some messages might be empty (no chat attribute available) # Scan until we find a message with a chat available (there must be one, because we are fetching replies) for m in parsed_messages: @@ -111,12 +109,12 @@ async def parse_messages(client, messages: "raw.types.messages.Messages", replie reply_messages = await client.get_messages( chat_id, - reply_to_message_ids=reply_message_ids, + reply_to_message_ids=messages_with_replies.keys(), replies=replies - 1 ) for message in parsed_messages: - reply_id = messages_with_replies[message.message_id] + reply_id = messages_with_replies.get(message.message_id, None) for reply in reply_messages: if reply.message_id == reply_id: From 6e29283d048757eec9f4b07f6a606e135e9cd7d0 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 23 Dec 2020 19:04:54 +0100 Subject: [PATCH 0436/1185] Update Pyrogram to v1.1.3 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index d398bbb09c..39ec8a6ca9 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "1.1.2" +__version__ = "1.1.3" __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)" __copyright__ = "Copyright (C) 2017-2020 Dan " From 01a3aab482efa7075b85626c4660836fdbab16c2 Mon Sep 17 00:00:00 2001 From: Kunoi Sayami <46131041+KunoiSayami@users.noreply.github.com> Date: Fri, 25 Dec 2020 02:18:46 +0800 Subject: [PATCH 0437/1185] Fix typing hint missing in forward_messages function (#569) --- pyrogram/methods/messages/forward_messages.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/methods/messages/forward_messages.py b/pyrogram/methods/messages/forward_messages.py index 82b726113d..0267276807 100644 --- a/pyrogram/methods/messages/forward_messages.py +++ b/pyrogram/methods/messages/forward_messages.py @@ -31,7 +31,7 @@ async def forward_messages( message_ids: Union[int, Iterable[int]], disable_notification: bool = None, schedule_date: int = None - ) -> List["types.Message"]: + ) -> Union["types.Message", List["types.Message"]]: """Forward messages of any kind. Parameters: From 0d357fb5a9157efd7da5e0200bd4f20632f8b624 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 24 Dec 2020 19:35:41 +0100 Subject: [PATCH 0438/1185] Improve HTML parser Closes #567 --- pyrogram/parser/html.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/parser/html.py b/pyrogram/parser/html.py index e65076c167..30bdb622c4 100644 --- a/pyrogram/parser/html.py +++ b/pyrogram/parser/html.py @@ -111,7 +111,7 @@ def __init__(self, client: Optional["pyrogram.Client"]): async def parse(self, text: str): # Strip whitespace characters from the end of the message, but preserve closing tags - text = re.sub(r"\s*()\s*$", r"\1", text) + text = re.sub(r"\s+()\s*$", r"\1", text) parser = Parser(self.client) parser.feed(utils.add_surrogates(text)) From 34f62b9c670c2e8a8e96e0925a5f5e5ca3d22814 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 24 Dec 2020 19:46:52 +0100 Subject: [PATCH 0439/1185] Update Pyrogram to v1.1.4 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 39ec8a6ca9..143e5fc24b 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "1.1.3" +__version__ = "1.1.4" __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)" __copyright__ = "Copyright (C) 2017-2020 Dan " From f764d245f512e3abfd106f6120263538182c5bd3 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 25 Dec 2020 00:05:49 +0100 Subject: [PATCH 0440/1185] Fix bad mime_type default value in case it doesn't exist --- pyrogram/methods/messages/download_media.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/methods/messages/download_media.py b/pyrogram/methods/messages/download_media.py index 40530104f2..c71247d4fb 100644 --- a/pyrogram/methods/messages/download_media.py +++ b/pyrogram/methods/messages/download_media.py @@ -124,7 +124,7 @@ def progress(current, total): file_type = file_id_obj.file_type media_file_name = getattr(media, "file_name", "") file_size = getattr(media, "file_size", 0) - mime_type = getattr(media, "mime_type", None) + mime_type = getattr(media, "mime_type", "") date = getattr(media, "date", 0) directory, file_name = os.path.split(file_name) From 4698f716ad2076687c6d96ed80664ce41458b95c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 25 Dec 2020 00:06:09 +0100 Subject: [PATCH 0441/1185] Update Pyrogram to v1.1.5 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 143e5fc24b..6737c4ae96 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "1.1.4" +__version__ = "1.1.5" __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)" __copyright__ = "Copyright (C) 2017-2020 Dan " From 3d971fb577779ea6e4de24c09da582dab95ae82c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 25 Dec 2020 12:08:48 +0100 Subject: [PATCH 0442/1185] Allow passing False instead of None for optional flag-boolean parameters Previously, passing anything that was not None would result in the boolean flag being set to True, even when passing False. This will make it simpler to deal with optional flag-boolean values in the raw API. --- compiler/api/compiler.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/compiler/api/compiler.py b/compiler/api/compiler.py index acaf9731c5..6b14334d63 100644 --- a/compiler/api/compiler.py +++ b/compiler/api/compiler.py @@ -399,15 +399,19 @@ def start(format: bool = False): write_types = read_types = "" if c.has_flags else "# No flags\n " for arg_name, arg_type in c.args: - flag = FLAGS_RE_2.findall(arg_type) + flag = FLAGS_RE_2.match(arg_type) if arg_name == "flags" and arg_type == "#": write_flags = [] for i in c.args: - flag = FLAGS_RE.match(i[1]) + flag = FLAGS_RE_2.match(i[1]) + if flag: - write_flags.append(f"flags |= (1 << {flag.group(1)}) if self.{i[0]} is not None else 0") + if flag.group(2) == "true": + write_flags.append(f"flags |= (1 << {flag.group(1)}) if self.{i[0]} else 0") + else: + write_flags.append(f"flags |= (1 << {flag.group(1)}) if self.{i[0]} is not None else 0") write_flags = "\n ".join([ "flags = 0", @@ -421,7 +425,7 @@ def start(format: bool = False): continue if flag: - index, flag_type = flag[0] + index, flag_type = flag.groups() if flag_type == "true": read_types += "\n " From d4c07304d0f31e1f95b39fa3a734d0e755a2705b Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 25 Dec 2020 12:42:27 +0100 Subject: [PATCH 0443/1185] Allow copying bots' messages reply markups Even though this often requires a user account to fetch other bots' messages --- pyrogram/types/messages_and_media/message.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index 304473a3c1..dba40d6fca 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -2848,7 +2848,7 @@ async def copy( disable_notification=disable_notification, reply_to_message_id=reply_to_message_id, schedule_date=schedule_date, - reply_markup=reply_markup + reply_markup=self.reply_markup or reply_markup ) elif self.media: send_media = partial( @@ -2857,7 +2857,7 @@ async def copy( disable_notification=disable_notification, reply_to_message_id=reply_to_message_id, schedule_date=schedule_date, - reply_markup=reply_markup + reply_markup=self.reply_markup or reply_markup ) if self.photo: From 449b065fe995e0e131b5927287b6a6b0ff556afb Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 25 Dec 2020 15:22:37 +0100 Subject: [PATCH 0444/1185] Fix get_profile_photos returning a list of None --- pyrogram/types/messages_and_media/photo.py | 28 ++++++++++++---------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/pyrogram/types/messages_and_media/photo.py b/pyrogram/types/messages_and_media/photo.py index 27a6c40991..482a4b47dc 100644 --- a/pyrogram/types/messages_and_media/photo.py +++ b/pyrogram/types/messages_and_media/photo.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import List, Optional +from typing import List import pyrogram from pyrogram import raw @@ -80,20 +80,22 @@ def __init__( self.thumbs = thumbs @staticmethod - def _parse(client, photo: "raw.types.Photo", ttl_seconds: int = None) -> Optional["Photo"]: + def _parse(client, photo: "raw.types.Photo", ttl_seconds: int = None) -> "Photo": if isinstance(photo, raw.types.Photo): - big = photo.sizes[-1] - - if isinstance(big, (raw.types.PhotoStrippedSize, raw.types.PhotoPathSize)): - return None - - if isinstance(big, raw.types.PhotoSizeProgressive): + try: + progressive = next(p for p in photo.sizes if isinstance(p, raw.types.PhotoSizeProgressive)) + except StopIteration: + photo_size_objs = [p for p in photo.sizes if isinstance(p, raw.types.PhotoSize)] + photo_size_objs.sort(key=lambda p: p.size) + + big = photo_size_objs[-1] + else: big = raw.types.PhotoSize( - type=big.type, - location=big.location, - w=big.w, - h=big.h, - size=big.sizes[-1] + type=progressive.type, + location=progressive.location, + w=progressive.w, + h=progressive.h, + size=sorted(progressive.sizes)[-1] ) return Photo( From a3464e0bf1be9628a12a2bb6fdf5e7d89ae15d14 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 25 Dec 2020 15:23:09 +0100 Subject: [PATCH 0445/1185] Update Pyrogram to v1.1.6 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 6737c4ae96..597992b9a9 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "1.1.5" +__version__ = "1.1.6" __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)" __copyright__ = "Copyright (C) 2017-2020 Dan " From c2c857b61bb34eefe8ed096a1df7ece05040a771 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 26 Dec 2020 13:08:40 +0100 Subject: [PATCH 0446/1185] Fix wrong usages of italic delimiters --- pyrogram/methods/messages/copy_message.py | 2 +- .../methods/messages/edit_message_caption.py | 2 +- .../methods/messages/edit_message_text.py | 2 +- pyrogram/methods/messages/send_animation.py | 2 +- pyrogram/methods/messages/send_audio.py | 2 +- .../methods/messages/send_cached_media.py | 2 +- pyrogram/methods/messages/send_document.py | 2 +- pyrogram/methods/messages/send_message.py | 2 +- pyrogram/methods/messages/send_photo.py | 2 +- pyrogram/methods/messages/send_video.py | 2 +- pyrogram/methods/messages/send_voice.py | 2 +- .../input_media/input_media_animation.py | 2 +- .../types/input_media/input_media_audio.py | 2 +- .../types/input_media/input_media_document.py | 2 +- .../types/input_media/input_media_photo.py | 2 +- .../types/input_media/input_media_video.py | 2 +- pyrogram/types/messages_and_media/message.py | 22 +++++++++---------- 17 files changed, 27 insertions(+), 27 deletions(-) diff --git a/pyrogram/methods/messages/copy_message.py b/pyrogram/methods/messages/copy_message.py index 9c58831e10..43973ab003 100644 --- a/pyrogram/methods/messages/copy_message.py +++ b/pyrogram/methods/messages/copy_message.py @@ -76,7 +76,7 @@ async def copy_message( Pass None to completely disable style parsing. caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): - List of special entities that appear in the new caption, which can be specified instead of __parse_mode__. + List of special entities that appear in the new caption, which can be specified instead of *parse_mode*. disable_notification (``bool``, *optional*): Sends the message silently. diff --git a/pyrogram/methods/messages/edit_message_caption.py b/pyrogram/methods/messages/edit_message_caption.py index 3b9f75d149..abd94a26ac 100644 --- a/pyrogram/methods/messages/edit_message_caption.py +++ b/pyrogram/methods/messages/edit_message_caption.py @@ -54,7 +54,7 @@ async def edit_message_caption( Pass None to completely disable style parsing. caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): - List of special entities that appear in the caption, which can be specified instead of __parse_mode__. + List of special entities that appear in the caption, which can be specified instead of *parse_mode*. reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*): An InlineKeyboardMarkup object. diff --git a/pyrogram/methods/messages/edit_message_text.py b/pyrogram/methods/messages/edit_message_text.py index d09dda1265..a3f94d1bd5 100644 --- a/pyrogram/methods/messages/edit_message_text.py +++ b/pyrogram/methods/messages/edit_message_text.py @@ -57,7 +57,7 @@ async def edit_message_text( Pass None to completely disable style parsing. entities (List of :obj:`~pyrogram.types.MessageEntity`): - List of special entities that appear in message text, which can be specified instead of __parse_mode__. + List of special entities that appear in message text, which can be specified instead of *parse_mode*. disable_web_page_preview (``bool``, *optional*): Disables link previews for links in this message. diff --git a/pyrogram/methods/messages/send_animation.py b/pyrogram/methods/messages/send_animation.py index 620b4a9f5f..55e1126daa 100644 --- a/pyrogram/methods/messages/send_animation.py +++ b/pyrogram/methods/messages/send_animation.py @@ -85,7 +85,7 @@ async def send_animation( Pass None to completely disable style parsing. caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): - List of special entities that appear in the caption, which can be specified instead of __parse_mode__. + List of special entities that appear in the caption, which can be specified instead of *parse_mode*. duration (``int``, *optional*): Duration of sent animation in seconds. diff --git a/pyrogram/methods/messages/send_audio.py b/pyrogram/methods/messages/send_audio.py index 1769ecd42e..96829f70b9 100644 --- a/pyrogram/methods/messages/send_audio.py +++ b/pyrogram/methods/messages/send_audio.py @@ -82,7 +82,7 @@ async def send_audio( Pass None to completely disable style parsing. caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): - List of special entities that appear in the caption, which can be specified instead of __parse_mode__. + List of special entities that appear in the caption, which can be specified instead of *parse_mode*. duration (``int``, *optional*): Duration of the audio in seconds. diff --git a/pyrogram/methods/messages/send_cached_media.py b/pyrogram/methods/messages/send_cached_media.py index bf8826eb31..776771f565 100644 --- a/pyrogram/methods/messages/send_cached_media.py +++ b/pyrogram/methods/messages/send_cached_media.py @@ -69,7 +69,7 @@ async def send_cached_media( Pass None to completely disable style parsing. caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): - List of special entities that appear in the caption, which can be specified instead of __parse_mode__. + List of special entities that appear in the caption, which can be specified instead of *parse_mode*. disable_notification (``bool``, *optional*): Sends the message silently. diff --git a/pyrogram/methods/messages/send_document.py b/pyrogram/methods/messages/send_document.py index 6649e5398f..e5cb123b80 100644 --- a/pyrogram/methods/messages/send_document.py +++ b/pyrogram/methods/messages/send_document.py @@ -84,7 +84,7 @@ async def send_document( Pass None to completely disable style parsing. caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): - List of special entities that appear in the caption, which can be specified instead of __parse_mode__. + List of special entities that appear in the caption, which can be specified instead of *parse_mode*. file_name (``str``, *optional*): File name of the document sent. diff --git a/pyrogram/methods/messages/send_message.py b/pyrogram/methods/messages/send_message.py index 76d3e279cd..3d235cc19c 100644 --- a/pyrogram/methods/messages/send_message.py +++ b/pyrogram/methods/messages/send_message.py @@ -60,7 +60,7 @@ async def send_message( Pass None to completely disable style parsing. entities (List of :obj:`~pyrogram.types.MessageEntity`): - List of special entities that appear in message text, which can be specified instead of __parse_mode__. + List of special entities that appear in message text, which can be specified instead of *parse_mode*. disable_web_page_preview (``bool``, *optional*): Disables link previews for links in this message. diff --git a/pyrogram/methods/messages/send_photo.py b/pyrogram/methods/messages/send_photo.py index a8fd0e0bd6..b3cd1dc029 100644 --- a/pyrogram/methods/messages/send_photo.py +++ b/pyrogram/methods/messages/send_photo.py @@ -76,7 +76,7 @@ async def send_photo( Pass None to completely disable style parsing. caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): - List of special entities that appear in the caption, which can be specified instead of __parse_mode__. + List of special entities that appear in the caption, which can be specified instead of *parse_mode*. ttl_seconds (``int``, *optional*): Self-Destruct Timer. diff --git a/pyrogram/methods/messages/send_video.py b/pyrogram/methods/messages/send_video.py index c42434638e..3142c987c9 100644 --- a/pyrogram/methods/messages/send_video.py +++ b/pyrogram/methods/messages/send_video.py @@ -82,7 +82,7 @@ async def send_video( Pass None to completely disable style parsing. caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): - List of special entities that appear in the caption, which can be specified instead of __parse_mode__. + List of special entities that appear in the caption, which can be specified instead of *parse_mode*. ttl_seconds (``int``, *optional*): Self-Destruct Timer. diff --git a/pyrogram/methods/messages/send_voice.py b/pyrogram/methods/messages/send_voice.py index 5863ae0146..269abc4266 100644 --- a/pyrogram/methods/messages/send_voice.py +++ b/pyrogram/methods/messages/send_voice.py @@ -76,7 +76,7 @@ async def send_voice( Pass None to completely disable style parsing. caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): - List of special entities that appear in the caption, which can be specified instead of __parse_mode__. + List of special entities that appear in the caption, which can be specified instead of *parse_mode*. duration (``int``, *optional*): Duration of the voice message in seconds. diff --git a/pyrogram/types/input_media/input_media_animation.py b/pyrogram/types/input_media/input_media_animation.py index 13a5d777f4..840dcfedff 100644 --- a/pyrogram/types/input_media/input_media_animation.py +++ b/pyrogram/types/input_media/input_media_animation.py @@ -48,7 +48,7 @@ class InputMediaAnimation(InputMedia): Pass None to completely disable style parsing. caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): - List of special entities that appear in the caption, which can be specified instead of __parse_mode__. + List of special entities that appear in the caption, which can be specified instead of *parse_mode*. width (``int``, *optional*): Animation width. diff --git a/pyrogram/types/input_media/input_media_audio.py b/pyrogram/types/input_media/input_media_audio.py index be5ef3bd09..0366cedecd 100644 --- a/pyrogram/types/input_media/input_media_audio.py +++ b/pyrogram/types/input_media/input_media_audio.py @@ -50,7 +50,7 @@ class InputMediaAudio(InputMedia): Pass None to completely disable style parsing. caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): - List of special entities that appear in the caption, which can be specified instead of __parse_mode__. + List of special entities that appear in the caption, which can be specified instead of *parse_mode*. duration (``int``, *optional*): Duration of the audio in seconds diff --git a/pyrogram/types/input_media/input_media_document.py b/pyrogram/types/input_media/input_media_document.py index e9f8882c7c..3985eaddad 100644 --- a/pyrogram/types/input_media/input_media_document.py +++ b/pyrogram/types/input_media/input_media_document.py @@ -48,7 +48,7 @@ class InputMediaDocument(InputMedia): Pass None to completely disable style parsing. caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): - List of special entities that appear in the caption, which can be specified instead of __parse_mode__. + List of special entities that appear in the caption, which can be specified instead of *parse_mode*. """ def __init__( diff --git a/pyrogram/types/input_media/input_media_photo.py b/pyrogram/types/input_media/input_media_photo.py index 486a686982..8182533537 100644 --- a/pyrogram/types/input_media/input_media_photo.py +++ b/pyrogram/types/input_media/input_media_photo.py @@ -44,7 +44,7 @@ class InputMediaPhoto(InputMedia): Pass None to completely disable style parsing. caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): - List of special entities that appear in the caption, which can be specified instead of __parse_mode__. + List of special entities that appear in the caption, which can be specified instead of *parse_mode*. """ def __init__( diff --git a/pyrogram/types/input_media/input_media_video.py b/pyrogram/types/input_media/input_media_video.py index cb595e1384..9d58f8ea0b 100644 --- a/pyrogram/types/input_media/input_media_video.py +++ b/pyrogram/types/input_media/input_media_video.py @@ -50,7 +50,7 @@ class InputMediaVideo(InputMedia): Pass None to completely disable style parsing. caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): - List of special entities that appear in the caption, which can be specified instead of __parse_mode__. + List of special entities that appear in the caption, which can be specified instead of *parse_mode*. width (``int``, *optional*): Video width. diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index dba40d6fca..8b46632c11 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -751,7 +751,7 @@ async def reply_text( Pass None to completely disable style parsing. entities (List of :obj:`~pyrogram.types.MessageEntity`): - List of special entities that appear in message text, which can be specified instead of __parse_mode__. + List of special entities that appear in message text, which can be specified instead of *parse_mode*. disable_web_page_preview (``bool``, *optional*): Disables link previews for links in this message. @@ -853,7 +853,7 @@ async def reply_animation( Pass None to completely disable style parsing. caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): - List of special entities that appear in the caption, which can be specified instead of __parse_mode__. + List of special entities that appear in the caption, which can be specified instead of *parse_mode*. duration (``int``, *optional*): Duration of sent animation in seconds. @@ -995,7 +995,7 @@ async def reply_audio( Pass None to completely disable style parsing. caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): - List of special entities that appear in the caption, which can be specified instead of __parse_mode__. + List of special entities that appear in the caption, which can be specified instead of *parse_mode*. duration (``int``, *optional*): Duration of the audio in seconds. @@ -1129,7 +1129,7 @@ async def reply_cached_media( Pass None to completely disable style parsing. caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): - List of special entities that appear in the caption, which can be specified instead of __parse_mode__. + List of special entities that appear in the caption, which can be specified instead of *parse_mode*. disable_notification (``bool``, *optional*): Sends the message silently. @@ -1352,7 +1352,7 @@ async def reply_document( Pass None to completely disable style parsing. caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): - List of special entities that appear in the caption, which can be specified instead of __parse_mode__. + List of special entities that appear in the caption, which can be specified instead of *parse_mode*. disable_notification (``bool``, *optional*): Sends the message silently. @@ -1747,7 +1747,7 @@ async def reply_photo( Pass None to completely disable style parsing. caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): - List of special entities that appear in the caption, which can be specified instead of __parse_mode__. + List of special entities that appear in the caption, which can be specified instead of *parse_mode*. ttl_seconds (``int``, *optional*): Self-Destruct Timer. @@ -2180,7 +2180,7 @@ async def reply_video( Pass None to completely disable style parsing. caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): - List of special entities that appear in the caption, which can be specified instead of __parse_mode__. + List of special entities that appear in the caption, which can be specified instead of *parse_mode*. ttl_seconds (``int``, *optional*): Self-Destruct Timer. @@ -2447,7 +2447,7 @@ async def reply_voice( Pass None to completely disable style parsing. caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): - List of special entities that appear in the caption, which can be specified instead of __parse_mode__. + List of special entities that appear in the caption, which can be specified instead of *parse_mode*. duration (``int``, *optional*): Duration of the voice message in seconds. @@ -2550,7 +2550,7 @@ async def edit_text( Pass None to completely disable style parsing. entities (List of :obj:`~pyrogram.types.MessageEntity`): - List of special entities that appear in message text, which can be specified instead of __parse_mode__. + List of special entities that appear in message text, which can be specified instead of *parse_mode*. disable_web_page_preview (``bool``, *optional*): Disables link previews for links in this message. @@ -2612,7 +2612,7 @@ async def edit_caption( Pass None to completely disable style parsing. caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): - List of special entities that appear in the caption, which can be specified instead of __parse_mode__. + List of special entities that appear in the caption, which can be specified instead of *parse_mode*. reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*): An InlineKeyboardMarkup object. @@ -2811,7 +2811,7 @@ async def copy( Pass None to completely disable style parsing. caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): - List of special entities that appear in the new caption, which can be specified instead of __parse_mode__. + List of special entities that appear in the new caption, which can be specified instead of *parse_mode*. disable_notification (``bool``, *optional*): Sends the message silently. From a8a9a1ac1f51e7f3c7fb3328c414a03f94916eb0 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 27 Dec 2020 21:08:01 +0100 Subject: [PATCH 0447/1185] Also print the exception type when logging query issues Use "repr(e)" instead of "e" alone (i.e "str(e)") because sometimes builtin exceptions have no message (for example: OSError, TimeoutError) --- 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 2fa8c8096b..58d1e457d8 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -433,7 +433,7 @@ async def send( raise e from None (log.warning if retries < 2 else log.info)( - f'[{Session.MAX_RETRIES - retries + 1}] Retrying "{query}" due to {e}') + f'[{Session.MAX_RETRIES - retries + 1}] Retrying "{query}" due to {repr(e)}') await asyncio.sleep(0.5) From 38efceefb15a720811b52a579d5731763676bf34 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 27 Dec 2020 21:08:47 +0100 Subject: [PATCH 0448/1185] Update Pyrogram to v1.1.7 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 597992b9a9..a3747370ce 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "1.1.6" +__version__ = "1.1.7" __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)" __copyright__ = "Copyright (C) 2017-2020 Dan " From 820fe2cbcd9a5d63476339e4595d7433863b0f8c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 1 Jan 2021 22:21:06 +0100 Subject: [PATCH 0449/1185] Fix thumbnail downloads --- pyrogram/client.py | 4 ++-- pyrogram/file_id.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pyrogram/client.py b/pyrogram/client.py index 68afabf6eb..5c95296ad8 100644 --- a/pyrogram/client.py +++ b/pyrogram/client.py @@ -880,7 +880,7 @@ async def get_file( local_id=file_id.local_id, big=file_id.thumbnail_source == ThumbnailSource.CHAT_PHOTO_BIG ) - elif file_type in (FileType.THUMBNAIL, FileType.PHOTO): + elif file_type == FileType.PHOTO: location = raw.types.InputPhotoFileLocation( id=file_id.media_id, access_hash=file_id.access_hash, @@ -892,7 +892,7 @@ async def get_file( id=file_id.media_id, access_hash=file_id.access_hash, file_reference=file_id.file_reference, - thumb_size="" + thumb_size=file_id.thumbnail_size ) limit = 1024 * 1024 diff --git a/pyrogram/file_id.py b/pyrogram/file_id.py index 8bca0eddcd..c91c6431f7 100644 --- a/pyrogram/file_id.py +++ b/pyrogram/file_id.py @@ -166,7 +166,7 @@ def __init__( volume_id: int = None, thumbnail_source: ThumbnailSource = None, thumbnail_file_type: FileType = None, - thumbnail_size: str = None, + thumbnail_size: str = "", secret: int = None, local_id: int = None, chat_id: int = None, From dbadfb28508ed74982d5d6ddd6246ee3244084e0 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 1 Jan 2021 22:55:51 +0100 Subject: [PATCH 0450/1185] Update file_id tests --- tests/test_file_id.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_file_id.py b/tests/test_file_id.py index 98265d1eb5..4760cf4b7b 100644 --- a/tests/test_file_id.py +++ b/tests/test_file_id.py @@ -182,7 +182,7 @@ def test_stringify_file_id(): file_id = "BQACAgIAAx0CAAGgr9AAAgmPX7b4UxbjNoFEO_L0I4s6wrXNJA8AAgQAA4GkuUm9FFvIaOhXWR4E" string = "{'major': 4, 'minor': 30, 'file_type': , 'dc_id': 2, " \ "'file_reference': b'\\x02\\x00\\xa0\\xaf\\xd0\\x00\\x00\\t\\x8f_\\xb6\\xf8S\\x16\\xe36\\x81D;\\xf2\\xf4#\\x8b:\\xc2\\xb5\\xcd$\\x0f', " \ - "'media_id': 5312458109417947140, 'access_hash': 6437869729085068477}" + "'media_id': 5312458109417947140, 'access_hash': 6437869729085068477, 'thumbnail_size': ''}" assert str(FileId.decode(file_id)) == string From 7dda167c0922e25a43531c178f73103cac9b61fa Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 1 Jan 2021 22:58:48 +0100 Subject: [PATCH 0451/1185] Update copyright notice Year 2021 --- NOTICE | 2 +- compiler/__init__.py | 26 +++++++++---------- compiler/api/__init__.py | 26 +++++++++---------- compiler/api/compiler.py | 2 +- compiler/docs/__init__.py | 26 +++++++++---------- compiler/docs/compiler.py | 2 +- compiler/errors/__init__.py | 26 +++++++++---------- compiler/errors/compiler.py | 2 +- compiler/errors/sort.py | 26 +++++++++---------- docs/scripts/releases.py | 2 +- docs/scripts/sitemap.py | 2 +- docs/source/conf.py | 2 +- pyrogram/__init__.py | 2 +- pyrogram/client.py | 2 +- pyrogram/connection/__init__.py | 2 +- pyrogram/connection/connection.py | 2 +- pyrogram/connection/transport/__init__.py | 2 +- pyrogram/connection/transport/tcp/__init__.py | 2 +- pyrogram/connection/transport/tcp/tcp.py | 2 +- .../connection/transport/tcp/tcp_abridged.py | 2 +- .../transport/tcp/tcp_abridged_o.py | 2 +- pyrogram/connection/transport/tcp/tcp_full.py | 2 +- .../transport/tcp/tcp_intermediate.py | 2 +- .../transport/tcp/tcp_intermediate_o.py | 2 +- pyrogram/crypto/__init__.py | 2 +- pyrogram/crypto/aes.py | 2 +- pyrogram/crypto/prime.py | 2 +- pyrogram/crypto/rsa.py | 2 +- pyrogram/dispatcher.py | 2 +- pyrogram/emoji.py | 2 +- pyrogram/errors/__init__.py | 2 +- pyrogram/errors/rpc_error.py | 2 +- pyrogram/file_id.py | 26 +++++++++---------- pyrogram/filters.py | 2 +- pyrogram/handlers/__init__.py | 2 +- pyrogram/handlers/callback_query_handler.py | 2 +- .../handlers/chosen_inline_result_handler.py | 2 +- pyrogram/handlers/deleted_messages_handler.py | 2 +- pyrogram/handlers/disconnect_handler.py | 2 +- pyrogram/handlers/handler.py | 2 +- pyrogram/handlers/inline_query_handler.py | 2 +- pyrogram/handlers/message_handler.py | 2 +- pyrogram/handlers/poll_handler.py | 2 +- pyrogram/handlers/raw_update_handler.py | 2 +- pyrogram/handlers/user_status_handler.py | 2 +- pyrogram/methods/__init__.py | 2 +- pyrogram/methods/advanced/__init__.py | 26 +++++++++---------- pyrogram/methods/advanced/resolve_peer.py | 2 +- pyrogram/methods/advanced/save_file.py | 2 +- pyrogram/methods/advanced/send.py | 2 +- pyrogram/methods/auth/__init__.py | 26 +++++++++---------- .../methods/auth/accept_terms_of_service.py | 2 +- pyrogram/methods/auth/check_password.py | 2 +- pyrogram/methods/auth/connect.py | 2 +- pyrogram/methods/auth/disconnect.py | 2 +- pyrogram/methods/auth/get_password_hint.py | 2 +- pyrogram/methods/auth/initialize.py | 2 +- pyrogram/methods/auth/log_out.py | 2 +- pyrogram/methods/auth/recover_password.py | 2 +- pyrogram/methods/auth/resend_code.py | 2 +- pyrogram/methods/auth/send_code.py | 2 +- pyrogram/methods/auth/send_recovery_code.py | 2 +- pyrogram/methods/auth/sign_in.py | 2 +- pyrogram/methods/auth/sign_in_bot.py | 2 +- pyrogram/methods/auth/sign_up.py | 2 +- pyrogram/methods/auth/terminate.py | 2 +- pyrogram/methods/bots/__init__.py | 2 +- .../methods/bots/answer_callback_query.py | 2 +- pyrogram/methods/bots/answer_inline_query.py | 2 +- pyrogram/methods/bots/get_game_high_scores.py | 2 +- .../methods/bots/get_inline_bot_results.py | 2 +- .../methods/bots/request_callback_answer.py | 2 +- pyrogram/methods/bots/send_game.py | 2 +- .../methods/bots/send_inline_bot_result.py | 2 +- pyrogram/methods/bots/set_game_score.py | 2 +- pyrogram/methods/chats/__init__.py | 2 +- pyrogram/methods/chats/add_chat_members.py | 2 +- pyrogram/methods/chats/archive_chats.py | 2 +- pyrogram/methods/chats/create_channel.py | 2 +- pyrogram/methods/chats/create_group.py | 2 +- pyrogram/methods/chats/create_supergroup.py | 2 +- pyrogram/methods/chats/delete_channel.py | 2 +- pyrogram/methods/chats/delete_chat_photo.py | 2 +- pyrogram/methods/chats/delete_supergroup.py | 2 +- pyrogram/methods/chats/delete_user_history.py | 2 +- .../methods/chats/export_chat_invite_link.py | 2 +- pyrogram/methods/chats/get_chat.py | 2 +- pyrogram/methods/chats/get_chat_member.py | 2 +- pyrogram/methods/chats/get_chat_members.py | 2 +- .../methods/chats/get_chat_members_count.py | 2 +- pyrogram/methods/chats/get_dialogs.py | 2 +- pyrogram/methods/chats/get_dialogs_count.py | 2 +- pyrogram/methods/chats/get_nearby_chats.py | 2 +- pyrogram/methods/chats/iter_chat_members.py | 2 +- pyrogram/methods/chats/iter_dialogs.py | 2 +- pyrogram/methods/chats/join_chat.py | 2 +- pyrogram/methods/chats/kick_chat_member.py | 2 +- pyrogram/methods/chats/leave_chat.py | 2 +- pyrogram/methods/chats/mark_chat_unread.py | 2 +- pyrogram/methods/chats/pin_chat_message.py | 2 +- pyrogram/methods/chats/promote_chat_member.py | 2 +- .../methods/chats/restrict_chat_member.py | 2 +- .../methods/chats/set_administrator_title.py | 2 +- .../methods/chats/set_chat_description.py | 2 +- .../methods/chats/set_chat_permissions.py | 2 +- pyrogram/methods/chats/set_chat_photo.py | 2 +- pyrogram/methods/chats/set_chat_title.py | 2 +- pyrogram/methods/chats/set_slow_mode.py | 2 +- pyrogram/methods/chats/unarchive_chats.py | 2 +- pyrogram/methods/chats/unban_chat_member.py | 2 +- .../methods/chats/unpin_all_chat_messages.py | 2 +- pyrogram/methods/chats/unpin_chat_message.py | 2 +- .../methods/chats/update_chat_username.py | 2 +- pyrogram/methods/contacts/__init__.py | 2 +- pyrogram/methods/contacts/add_contacts.py | 2 +- pyrogram/methods/contacts/delete_contacts.py | 2 +- pyrogram/methods/contacts/get_contacts.py | 2 +- .../methods/contacts/get_contacts_count.py | 2 +- pyrogram/methods/decorators/__init__.py | 2 +- .../methods/decorators/on_callback_query.py | 2 +- .../decorators/on_chosen_inline_result.py | 2 +- .../methods/decorators/on_deleted_messages.py | 2 +- pyrogram/methods/decorators/on_disconnect.py | 2 +- .../methods/decorators/on_inline_query.py | 2 +- pyrogram/methods/decorators/on_message.py | 2 +- pyrogram/methods/decorators/on_poll.py | 2 +- pyrogram/methods/decorators/on_raw_update.py | 2 +- pyrogram/methods/decorators/on_user_status.py | 2 +- pyrogram/methods/messages/__init__.py | 2 +- pyrogram/methods/messages/copy_message.py | 2 +- pyrogram/methods/messages/delete_messages.py | 2 +- pyrogram/methods/messages/download_media.py | 2 +- .../methods/messages/edit_inline_caption.py | 2 +- .../methods/messages/edit_inline_media.py | 2 +- .../messages/edit_inline_reply_markup.py | 2 +- pyrogram/methods/messages/edit_inline_text.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 +- pyrogram/methods/messages/forward_messages.py | 2 +- pyrogram/methods/messages/get_history.py | 2 +- .../methods/messages/get_history_count.py | 2 +- pyrogram/methods/messages/get_media_group.py | 2 +- pyrogram/methods/messages/get_messages.py | 2 +- pyrogram/methods/messages/inline_session.py | 26 +++++++++---------- pyrogram/methods/messages/iter_history.py | 2 +- pyrogram/methods/messages/read_history.py | 2 +- pyrogram/methods/messages/retract_vote.py | 2 +- pyrogram/methods/messages/search_global.py | 2 +- pyrogram/methods/messages/search_messages.py | 2 +- pyrogram/methods/messages/send_animation.py | 2 +- pyrogram/methods/messages/send_audio.py | 2 +- .../methods/messages/send_cached_media.py | 2 +- pyrogram/methods/messages/send_chat_action.py | 2 +- pyrogram/methods/messages/send_contact.py | 2 +- pyrogram/methods/messages/send_dice.py | 2 +- pyrogram/methods/messages/send_document.py | 2 +- pyrogram/methods/messages/send_location.py | 2 +- pyrogram/methods/messages/send_media_group.py | 2 +- pyrogram/methods/messages/send_message.py | 2 +- pyrogram/methods/messages/send_photo.py | 2 +- pyrogram/methods/messages/send_poll.py | 2 +- pyrogram/methods/messages/send_sticker.py | 2 +- pyrogram/methods/messages/send_venue.py | 2 +- pyrogram/methods/messages/send_video.py | 2 +- pyrogram/methods/messages/send_video_note.py | 2 +- pyrogram/methods/messages/send_voice.py | 2 +- pyrogram/methods/messages/stop_poll.py | 2 +- pyrogram/methods/messages/vote_poll.py | 2 +- pyrogram/methods/password/__init__.py | 2 +- .../methods/password/change_cloud_password.py | 2 +- .../methods/password/enable_cloud_password.py | 2 +- .../methods/password/remove_cloud_password.py | 2 +- pyrogram/methods/users/__init__.py | 2 +- pyrogram/methods/users/block_user.py | 2 +- .../methods/users/delete_profile_photos.py | 2 +- pyrogram/methods/users/get_common_chats.py | 2 +- pyrogram/methods/users/get_me.py | 2 +- pyrogram/methods/users/get_profile_photos.py | 2 +- .../methods/users/get_profile_photos_count.py | 2 +- pyrogram/methods/users/get_users.py | 2 +- pyrogram/methods/users/iter_profile_photos.py | 2 +- pyrogram/methods/users/set_profile_photo.py | 2 +- pyrogram/methods/users/unblock_user.py | 2 +- pyrogram/methods/users/update_profile.py | 2 +- pyrogram/methods/users/update_username.py | 2 +- pyrogram/methods/utilities/__init__.py | 26 +++++++++---------- pyrogram/methods/utilities/add_handler.py | 2 +- .../utilities/export_session_string.py | 2 +- pyrogram/methods/utilities/idle.py | 2 +- pyrogram/methods/utilities/remove_handler.py | 2 +- pyrogram/methods/utilities/restart.py | 2 +- pyrogram/methods/utilities/run.py | 2 +- pyrogram/methods/utilities/start.py | 2 +- pyrogram/methods/utilities/stop.py | 2 +- .../methods/utilities/stop_transmission.py | 2 +- pyrogram/mime_types.py | 2 +- pyrogram/parser/__init__.py | 2 +- pyrogram/parser/html.py | 2 +- pyrogram/parser/markdown.py | 2 +- pyrogram/parser/parser.py | 2 +- pyrogram/parser/utils.py | 2 +- pyrogram/raw/__init__.py | 2 +- pyrogram/raw/core/__init__.py | 2 +- pyrogram/raw/core/future_salt.py | 2 +- pyrogram/raw/core/future_salts.py | 2 +- pyrogram/raw/core/gzip_packed.py | 2 +- pyrogram/raw/core/list.py | 2 +- pyrogram/raw/core/message.py | 2 +- pyrogram/raw/core/msg_container.py | 2 +- pyrogram/raw/core/primitives/__init__.py | 2 +- pyrogram/raw/core/primitives/bool.py | 2 +- pyrogram/raw/core/primitives/bytes.py | 2 +- pyrogram/raw/core/primitives/double.py | 2 +- pyrogram/raw/core/primitives/int.py | 2 +- pyrogram/raw/core/primitives/string.py | 2 +- pyrogram/raw/core/primitives/vector.py | 2 +- pyrogram/raw/core/tl_object.py | 2 +- pyrogram/scaffold.py | 2 +- pyrogram/session/__init__.py | 2 +- pyrogram/session/auth.py | 2 +- pyrogram/session/internals/__init__.py | 2 +- pyrogram/session/internals/data_center.py | 2 +- pyrogram/session/internals/msg_factory.py | 2 +- pyrogram/session/internals/msg_id.py | 2 +- pyrogram/session/internals/seq_no.py | 2 +- pyrogram/session/session.py | 2 +- pyrogram/storage/__init__.py | 2 +- pyrogram/storage/file_storage.py | 2 +- pyrogram/storage/memory_storage.py | 2 +- pyrogram/storage/sqlite_storage.py | 2 +- pyrogram/storage/storage.py | 2 +- pyrogram/sync.py | 26 +++++++++---------- pyrogram/syncer.py | 2 +- pyrogram/types/__init__.py | 2 +- pyrogram/types/authorization/__init__.py | 2 +- pyrogram/types/authorization/sent_code.py | 2 +- .../types/authorization/terms_of_service.py | 2 +- pyrogram/types/bots_and_keyboards/__init__.py | 2 +- .../types/bots_and_keyboards/callback_game.py | 2 +- .../bots_and_keyboards/callback_query.py | 2 +- .../types/bots_and_keyboards/force_reply.py | 2 +- .../bots_and_keyboards/game_high_score.py | 2 +- .../inline_keyboard_button.py | 2 +- .../inline_keyboard_markup.py | 2 +- .../bots_and_keyboards/keyboard_button.py | 2 +- .../reply_keyboard_markup.py | 2 +- .../reply_keyboard_remove.py | 2 +- pyrogram/types/inline_mode/__init__.py | 2 +- .../types/inline_mode/chosen_inline_result.py | 2 +- pyrogram/types/inline_mode/inline_query.py | 2 +- .../types/inline_mode/inline_query_result.py | 2 +- .../inline_query_result_animation.py | 2 +- .../inline_query_result_article.py | 2 +- .../inline_mode/inline_query_result_photo.py | 2 +- pyrogram/types/input_media/__init__.py | 2 +- pyrogram/types/input_media/input_media.py | 2 +- .../input_media/input_media_animation.py | 2 +- .../types/input_media/input_media_audio.py | 2 +- .../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 +- .../types/input_message_content/__init__.py | 2 +- .../input_message_content.py | 2 +- .../input_text_message_content.py | 2 +- pyrogram/types/list.py | 2 +- pyrogram/types/messages_and_media/__init__.py | 2 +- .../types/messages_and_media/animation.py | 2 +- pyrogram/types/messages_and_media/audio.py | 2 +- pyrogram/types/messages_and_media/contact.py | 2 +- pyrogram/types/messages_and_media/dice.py | 2 +- pyrogram/types/messages_and_media/document.py | 2 +- pyrogram/types/messages_and_media/game.py | 2 +- pyrogram/types/messages_and_media/location.py | 2 +- pyrogram/types/messages_and_media/message.py | 2 +- .../messages_and_media/message_entity.py | 2 +- pyrogram/types/messages_and_media/photo.py | 2 +- pyrogram/types/messages_and_media/poll.py | 2 +- .../types/messages_and_media/poll_option.py | 2 +- pyrogram/types/messages_and_media/sticker.py | 2 +- .../messages_and_media/stripped_thumbnail.py | 2 +- .../types/messages_and_media/thumbnail.py | 2 +- pyrogram/types/messages_and_media/venue.py | 2 +- pyrogram/types/messages_and_media/video.py | 2 +- .../types/messages_and_media/video_note.py | 2 +- pyrogram/types/messages_and_media/voice.py | 2 +- pyrogram/types/messages_and_media/webpage.py | 2 +- pyrogram/types/object.py | 2 +- pyrogram/types/update.py | 2 +- pyrogram/types/user_and_chats/__init__.py | 2 +- pyrogram/types/user_and_chats/chat.py | 2 +- pyrogram/types/user_and_chats/chat_member.py | 2 +- .../types/user_and_chats/chat_permissions.py | 2 +- pyrogram/types/user_and_chats/chat_photo.py | 2 +- pyrogram/types/user_and_chats/chat_preview.py | 2 +- pyrogram/types/user_and_chats/dialog.py | 2 +- pyrogram/types/user_and_chats/restriction.py | 2 +- pyrogram/types/user_and_chats/user.py | 2 +- pyrogram/utils.py | 2 +- setup.py | 2 +- tests/__init__.py | 26 +++++++++---------- tests/test_file_id.py | 26 +++++++++---------- 304 files changed, 460 insertions(+), 460 deletions(-) diff --git a/NOTICE b/NOTICE index d1c4ccb926..58defc6402 100644 --- a/NOTICE +++ b/NOTICE @@ -1,5 +1,5 @@ Pyrogram - Telegram MTProto API Client Library for Python -Copyright (C) 2017-2020 Dan +Copyright (C) 2017-2021 Dan This file is part of Pyrogram. diff --git a/compiler/__init__.py b/compiler/__init__.py index 00a6c16df9..4ad4f32b47 100644 --- a/compiler/__init__.py +++ b/compiler/__init__.py @@ -1,17 +1,17 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . diff --git a/compiler/api/__init__.py b/compiler/api/__init__.py index 00a6c16df9..4ad4f32b47 100644 --- a/compiler/api/__init__.py +++ b/compiler/api/__init__.py @@ -1,17 +1,17 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . diff --git a/compiler/api/compiler.py b/compiler/api/compiler.py index 6b14334d63..307a9d498c 100644 --- a/compiler/api/compiler.py +++ b/compiler/api/compiler.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/compiler/docs/__init__.py b/compiler/docs/__init__.py index 00a6c16df9..4ad4f32b47 100644 --- a/compiler/docs/__init__.py +++ b/compiler/docs/__init__.py @@ -1,17 +1,17 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index d54339c9a0..b048abdf79 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/compiler/errors/__init__.py b/compiler/errors/__init__.py index 00a6c16df9..4ad4f32b47 100644 --- a/compiler/errors/__init__.py +++ b/compiler/errors/__init__.py @@ -1,17 +1,17 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . diff --git a/compiler/errors/compiler.py b/compiler/errors/compiler.py index d8f556a05f..23948d8558 100644 --- a/compiler/errors/compiler.py +++ b/compiler/errors/compiler.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/compiler/errors/sort.py b/compiler/errors/sort.py index de25b9b192..69fc0437a8 100644 --- a/compiler/errors/sort.py +++ b/compiler/errors/sort.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import csv from pathlib import Path diff --git a/docs/scripts/releases.py b/docs/scripts/releases.py index ce8ab385b2..4a32dd298f 100644 --- a/docs/scripts/releases.py +++ b/docs/scripts/releases.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/docs/scripts/sitemap.py b/docs/scripts/sitemap.py index bd3124bc5b..9c28bd1c5c 100644 --- a/docs/scripts/sitemap.py +++ b/docs/scripts/sitemap.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/docs/source/conf.py b/docs/source/conf.py index a86895cee5..ddfc6479f0 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index a3747370ce..987b784898 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/client.py b/pyrogram/client.py index 5c95296ad8..252286a780 100644 --- a/pyrogram/client.py +++ b/pyrogram/client.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/connection/__init__.py b/pyrogram/connection/__init__.py index 5bc87d33fa..4b63340ee6 100644 --- a/pyrogram/connection/__init__.py +++ b/pyrogram/connection/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/connection/connection.py b/pyrogram/connection/connection.py index 9fe7dfd365..5aac9038d1 100644 --- a/pyrogram/connection/connection.py +++ b/pyrogram/connection/connection.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/connection/transport/__init__.py b/pyrogram/connection/transport/__init__.py index f63b55875d..b856583cf0 100644 --- a/pyrogram/connection/transport/__init__.py +++ b/pyrogram/connection/transport/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/connection/transport/tcp/__init__.py b/pyrogram/connection/transport/tcp/__init__.py index 1909e7230c..6a5456de3f 100644 --- a/pyrogram/connection/transport/tcp/__init__.py +++ b/pyrogram/connection/transport/tcp/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/connection/transport/tcp/tcp.py b/pyrogram/connection/transport/tcp/tcp.py index acc248b7aa..6c3e760315 100644 --- a/pyrogram/connection/transport/tcp/tcp.py +++ b/pyrogram/connection/transport/tcp/tcp.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/connection/transport/tcp/tcp_abridged.py b/pyrogram/connection/transport/tcp/tcp_abridged.py index 4c4193f800..cee9783121 100644 --- a/pyrogram/connection/transport/tcp/tcp_abridged.py +++ b/pyrogram/connection/transport/tcp/tcp_abridged.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/connection/transport/tcp/tcp_abridged_o.py b/pyrogram/connection/transport/tcp/tcp_abridged_o.py index 2b5ad2c9e3..b05cad1628 100644 --- a/pyrogram/connection/transport/tcp/tcp_abridged_o.py +++ b/pyrogram/connection/transport/tcp/tcp_abridged_o.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/connection/transport/tcp/tcp_full.py b/pyrogram/connection/transport/tcp/tcp_full.py index dc03b9e926..7befddb1af 100644 --- a/pyrogram/connection/transport/tcp/tcp_full.py +++ b/pyrogram/connection/transport/tcp/tcp_full.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/connection/transport/tcp/tcp_intermediate.py b/pyrogram/connection/transport/tcp/tcp_intermediate.py index b0692d11b7..659c30e4ce 100644 --- a/pyrogram/connection/transport/tcp/tcp_intermediate.py +++ b/pyrogram/connection/transport/tcp/tcp_intermediate.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/connection/transport/tcp/tcp_intermediate_o.py b/pyrogram/connection/transport/tcp/tcp_intermediate_o.py index 082802f1d8..be783f048f 100644 --- a/pyrogram/connection/transport/tcp/tcp_intermediate_o.py +++ b/pyrogram/connection/transport/tcp/tcp_intermediate_o.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/crypto/__init__.py b/pyrogram/crypto/__init__.py index 3d510a581a..4ad4f32b47 100644 --- a/pyrogram/crypto/__init__.py +++ b/pyrogram/crypto/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/crypto/aes.py b/pyrogram/crypto/aes.py index c094fd2214..05aca1c087 100644 --- a/pyrogram/crypto/aes.py +++ b/pyrogram/crypto/aes.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/crypto/prime.py b/pyrogram/crypto/prime.py index 22210726af..82d1df758e 100644 --- a/pyrogram/crypto/prime.py +++ b/pyrogram/crypto/prime.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/crypto/rsa.py b/pyrogram/crypto/rsa.py index aaec8df6ca..8804f87a60 100644 --- a/pyrogram/crypto/rsa.py +++ b/pyrogram/crypto/rsa.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/dispatcher.py b/pyrogram/dispatcher.py index db3344da93..f4b818a478 100644 --- a/pyrogram/dispatcher.py +++ b/pyrogram/dispatcher.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/emoji.py b/pyrogram/emoji.py index 8d781cab04..69e44fa574 100644 --- a/pyrogram/emoji.py +++ b/pyrogram/emoji.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/errors/__init__.py b/pyrogram/errors/__init__.py index de049e372d..1b94700faa 100644 --- a/pyrogram/errors/__init__.py +++ b/pyrogram/errors/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/errors/rpc_error.py b/pyrogram/errors/rpc_error.py index ff232e99c3..d3cb5c551e 100644 --- a/pyrogram/errors/rpc_error.py +++ b/pyrogram/errors/rpc_error.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/file_id.py b/pyrogram/file_id.py index c91c6431f7..ef1319ecae 100644 --- a/pyrogram/file_id.py +++ b/pyrogram/file_id.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import base64 import logging diff --git a/pyrogram/filters.py b/pyrogram/filters.py index 1e992bb44a..5752b76619 100644 --- a/pyrogram/filters.py +++ b/pyrogram/filters.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/handlers/__init__.py b/pyrogram/handlers/__init__.py index e0c37e87c3..74e230a41f 100644 --- a/pyrogram/handlers/__init__.py +++ b/pyrogram/handlers/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/handlers/callback_query_handler.py b/pyrogram/handlers/callback_query_handler.py index 84e7ebede3..e28e7216a6 100644 --- a/pyrogram/handlers/callback_query_handler.py +++ b/pyrogram/handlers/callback_query_handler.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/handlers/chosen_inline_result_handler.py b/pyrogram/handlers/chosen_inline_result_handler.py index 0e4df6f966..42b5bb79eb 100644 --- a/pyrogram/handlers/chosen_inline_result_handler.py +++ b/pyrogram/handlers/chosen_inline_result_handler.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/handlers/deleted_messages_handler.py b/pyrogram/handlers/deleted_messages_handler.py index 2073092ce0..d57cb5ef25 100644 --- a/pyrogram/handlers/deleted_messages_handler.py +++ b/pyrogram/handlers/deleted_messages_handler.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/handlers/disconnect_handler.py b/pyrogram/handlers/disconnect_handler.py index 59ab401571..2302db5821 100644 --- a/pyrogram/handlers/disconnect_handler.py +++ b/pyrogram/handlers/disconnect_handler.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/handlers/handler.py b/pyrogram/handlers/handler.py index 1f3d2af612..78786ab797 100644 --- a/pyrogram/handlers/handler.py +++ b/pyrogram/handlers/handler.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/handlers/inline_query_handler.py b/pyrogram/handlers/inline_query_handler.py index af7f9e3bad..31f085b35f 100644 --- a/pyrogram/handlers/inline_query_handler.py +++ b/pyrogram/handlers/inline_query_handler.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/handlers/message_handler.py b/pyrogram/handlers/message_handler.py index e9ef2aa53c..f20669afc8 100644 --- a/pyrogram/handlers/message_handler.py +++ b/pyrogram/handlers/message_handler.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/handlers/poll_handler.py b/pyrogram/handlers/poll_handler.py index 3fe7c382af..09940c1252 100644 --- a/pyrogram/handlers/poll_handler.py +++ b/pyrogram/handlers/poll_handler.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/handlers/raw_update_handler.py b/pyrogram/handlers/raw_update_handler.py index ed18fc4bb2..53413c3144 100644 --- a/pyrogram/handlers/raw_update_handler.py +++ b/pyrogram/handlers/raw_update_handler.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/handlers/user_status_handler.py b/pyrogram/handlers/user_status_handler.py index 50c5a89e5a..23b9af2f97 100644 --- a/pyrogram/handlers/user_status_handler.py +++ b/pyrogram/handlers/user_status_handler.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/__init__.py b/pyrogram/methods/__init__.py index 30fe28a388..728af9a21a 100644 --- a/pyrogram/methods/__init__.py +++ b/pyrogram/methods/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/advanced/__init__.py b/pyrogram/methods/advanced/__init__.py index 318f40eef2..934b0b9f21 100644 --- a/pyrogram/methods/advanced/__init__.py +++ b/pyrogram/methods/advanced/__init__.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from .resolve_peer import ResolvePeer from .save_file import SaveFile diff --git a/pyrogram/methods/advanced/resolve_peer.py b/pyrogram/methods/advanced/resolve_peer.py index babc35d3a5..3b3915aca6 100644 --- a/pyrogram/methods/advanced/resolve_peer.py +++ b/pyrogram/methods/advanced/resolve_peer.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/advanced/save_file.py b/pyrogram/methods/advanced/save_file.py index a74c388c17..83f56eb9d0 100644 --- a/pyrogram/methods/advanced/save_file.py +++ b/pyrogram/methods/advanced/save_file.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/advanced/send.py b/pyrogram/methods/advanced/send.py index 661d1be7e0..9ac7ec24d6 100644 --- a/pyrogram/methods/advanced/send.py +++ b/pyrogram/methods/advanced/send.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/auth/__init__.py b/pyrogram/methods/auth/__init__.py index dca53a0854..2227be8102 100644 --- a/pyrogram/methods/auth/__init__.py +++ b/pyrogram/methods/auth/__init__.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from .accept_terms_of_service import AcceptTermsOfService from .check_password import CheckPassword diff --git a/pyrogram/methods/auth/accept_terms_of_service.py b/pyrogram/methods/auth/accept_terms_of_service.py index 33da88af12..b5abab8613 100644 --- a/pyrogram/methods/auth/accept_terms_of_service.py +++ b/pyrogram/methods/auth/accept_terms_of_service.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/auth/check_password.py b/pyrogram/methods/auth/check_password.py index 8942780a65..1dd60f7ebf 100644 --- a/pyrogram/methods/auth/check_password.py +++ b/pyrogram/methods/auth/check_password.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/auth/connect.py b/pyrogram/methods/auth/connect.py index cbb5bec898..7376ddf60e 100644 --- a/pyrogram/methods/auth/connect.py +++ b/pyrogram/methods/auth/connect.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/auth/disconnect.py b/pyrogram/methods/auth/disconnect.py index 08b88e6239..4d516a119e 100644 --- a/pyrogram/methods/auth/disconnect.py +++ b/pyrogram/methods/auth/disconnect.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/auth/get_password_hint.py b/pyrogram/methods/auth/get_password_hint.py index ca1ad66244..44f1439c84 100644 --- a/pyrogram/methods/auth/get_password_hint.py +++ b/pyrogram/methods/auth/get_password_hint.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/auth/initialize.py b/pyrogram/methods/auth/initialize.py index 1b50069274..0fb276f7fd 100644 --- a/pyrogram/methods/auth/initialize.py +++ b/pyrogram/methods/auth/initialize.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/auth/log_out.py b/pyrogram/methods/auth/log_out.py index e554c37763..06c9824bbd 100644 --- a/pyrogram/methods/auth/log_out.py +++ b/pyrogram/methods/auth/log_out.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/auth/recover_password.py b/pyrogram/methods/auth/recover_password.py index f49e4a28a7..03887f4ed8 100644 --- a/pyrogram/methods/auth/recover_password.py +++ b/pyrogram/methods/auth/recover_password.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/auth/resend_code.py b/pyrogram/methods/auth/resend_code.py index 870876a876..cf8ce3be6d 100644 --- a/pyrogram/methods/auth/resend_code.py +++ b/pyrogram/methods/auth/resend_code.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/auth/send_code.py b/pyrogram/methods/auth/send_code.py index 5f0232f252..62fb256bc6 100644 --- a/pyrogram/methods/auth/send_code.py +++ b/pyrogram/methods/auth/send_code.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/auth/send_recovery_code.py b/pyrogram/methods/auth/send_recovery_code.py index 1a128b467d..4c28f1cf0b 100644 --- a/pyrogram/methods/auth/send_recovery_code.py +++ b/pyrogram/methods/auth/send_recovery_code.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/auth/sign_in.py b/pyrogram/methods/auth/sign_in.py index ccd2dabe94..6e5e7c46fe 100644 --- a/pyrogram/methods/auth/sign_in.py +++ b/pyrogram/methods/auth/sign_in.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/auth/sign_in_bot.py b/pyrogram/methods/auth/sign_in_bot.py index c5ada72cdd..490ceced27 100644 --- a/pyrogram/methods/auth/sign_in_bot.py +++ b/pyrogram/methods/auth/sign_in_bot.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/auth/sign_up.py b/pyrogram/methods/auth/sign_up.py index b8d42916c3..bed07f8509 100644 --- a/pyrogram/methods/auth/sign_up.py +++ b/pyrogram/methods/auth/sign_up.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/auth/terminate.py b/pyrogram/methods/auth/terminate.py index 72d8038042..70166fe428 100644 --- a/pyrogram/methods/auth/terminate.py +++ b/pyrogram/methods/auth/terminate.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/bots/__init__.py b/pyrogram/methods/bots/__init__.py index 215df97c05..2c528a158a 100644 --- a/pyrogram/methods/bots/__init__.py +++ b/pyrogram/methods/bots/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/bots/answer_callback_query.py b/pyrogram/methods/bots/answer_callback_query.py index bbc7c72488..ccb1f4449a 100644 --- a/pyrogram/methods/bots/answer_callback_query.py +++ b/pyrogram/methods/bots/answer_callback_query.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/bots/answer_inline_query.py b/pyrogram/methods/bots/answer_inline_query.py index 1915db589f..973d730786 100644 --- a/pyrogram/methods/bots/answer_inline_query.py +++ b/pyrogram/methods/bots/answer_inline_query.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/bots/get_game_high_scores.py b/pyrogram/methods/bots/get_game_high_scores.py index af2c6d7cbc..59ddb67ec5 100644 --- a/pyrogram/methods/bots/get_game_high_scores.py +++ b/pyrogram/methods/bots/get_game_high_scores.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/bots/get_inline_bot_results.py b/pyrogram/methods/bots/get_inline_bot_results.py index e507e313ac..73b4c5d786 100644 --- a/pyrogram/methods/bots/get_inline_bot_results.py +++ b/pyrogram/methods/bots/get_inline_bot_results.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/bots/request_callback_answer.py b/pyrogram/methods/bots/request_callback_answer.py index c42306ee1e..9d32e60f07 100644 --- a/pyrogram/methods/bots/request_callback_answer.py +++ b/pyrogram/methods/bots/request_callback_answer.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/bots/send_game.py b/pyrogram/methods/bots/send_game.py index 3913946ae8..72fc84f4f4 100644 --- a/pyrogram/methods/bots/send_game.py +++ b/pyrogram/methods/bots/send_game.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/bots/send_inline_bot_result.py b/pyrogram/methods/bots/send_inline_bot_result.py index 7d396a609f..e91aac9789 100644 --- a/pyrogram/methods/bots/send_inline_bot_result.py +++ b/pyrogram/methods/bots/send_inline_bot_result.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/bots/set_game_score.py b/pyrogram/methods/bots/set_game_score.py index b9c2fe0ebb..e8af3125ad 100644 --- a/pyrogram/methods/bots/set_game_score.py +++ b/pyrogram/methods/bots/set_game_score.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/chats/__init__.py b/pyrogram/methods/chats/__init__.py index aa90b27e57..184562d83d 100644 --- a/pyrogram/methods/chats/__init__.py +++ b/pyrogram/methods/chats/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/chats/add_chat_members.py b/pyrogram/methods/chats/add_chat_members.py index 6164f411bb..154f1ee1e4 100644 --- a/pyrogram/methods/chats/add_chat_members.py +++ b/pyrogram/methods/chats/add_chat_members.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/chats/archive_chats.py b/pyrogram/methods/chats/archive_chats.py index 144a22718c..df3f69c103 100644 --- a/pyrogram/methods/chats/archive_chats.py +++ b/pyrogram/methods/chats/archive_chats.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/chats/create_channel.py b/pyrogram/methods/chats/create_channel.py index 6c2a597218..3e1fab82a6 100644 --- a/pyrogram/methods/chats/create_channel.py +++ b/pyrogram/methods/chats/create_channel.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/chats/create_group.py b/pyrogram/methods/chats/create_group.py index 7c68f90c05..f1833d80e6 100644 --- a/pyrogram/methods/chats/create_group.py +++ b/pyrogram/methods/chats/create_group.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/chats/create_supergroup.py b/pyrogram/methods/chats/create_supergroup.py index 07b5adca21..866a732b56 100644 --- a/pyrogram/methods/chats/create_supergroup.py +++ b/pyrogram/methods/chats/create_supergroup.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/chats/delete_channel.py b/pyrogram/methods/chats/delete_channel.py index 7d884bb726..0a47fb43aa 100644 --- a/pyrogram/methods/chats/delete_channel.py +++ b/pyrogram/methods/chats/delete_channel.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/chats/delete_chat_photo.py b/pyrogram/methods/chats/delete_chat_photo.py index de9f707c01..7bd82337a9 100644 --- a/pyrogram/methods/chats/delete_chat_photo.py +++ b/pyrogram/methods/chats/delete_chat_photo.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/chats/delete_supergroup.py b/pyrogram/methods/chats/delete_supergroup.py index d4abba8c43..016be5463f 100644 --- a/pyrogram/methods/chats/delete_supergroup.py +++ b/pyrogram/methods/chats/delete_supergroup.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/chats/delete_user_history.py b/pyrogram/methods/chats/delete_user_history.py index d2c1797905..e2f780f9fc 100644 --- a/pyrogram/methods/chats/delete_user_history.py +++ b/pyrogram/methods/chats/delete_user_history.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/chats/export_chat_invite_link.py b/pyrogram/methods/chats/export_chat_invite_link.py index 7b3121cae3..9fbaf8b639 100644 --- a/pyrogram/methods/chats/export_chat_invite_link.py +++ b/pyrogram/methods/chats/export_chat_invite_link.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/chats/get_chat.py b/pyrogram/methods/chats/get_chat.py index cd8f5c9d98..4420b5feb7 100644 --- a/pyrogram/methods/chats/get_chat.py +++ b/pyrogram/methods/chats/get_chat.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/chats/get_chat_member.py b/pyrogram/methods/chats/get_chat_member.py index c4555c2bbd..7f649a2166 100644 --- a/pyrogram/methods/chats/get_chat_member.py +++ b/pyrogram/methods/chats/get_chat_member.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/chats/get_chat_members.py b/pyrogram/methods/chats/get_chat_members.py index d55a741062..a27478fdc3 100644 --- a/pyrogram/methods/chats/get_chat_members.py +++ b/pyrogram/methods/chats/get_chat_members.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/chats/get_chat_members_count.py b/pyrogram/methods/chats/get_chat_members_count.py index 8bb1b7bec7..3fc80e5dd7 100644 --- a/pyrogram/methods/chats/get_chat_members_count.py +++ b/pyrogram/methods/chats/get_chat_members_count.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/chats/get_dialogs.py b/pyrogram/methods/chats/get_dialogs.py index 50656cfed2..4e3b11113e 100644 --- a/pyrogram/methods/chats/get_dialogs.py +++ b/pyrogram/methods/chats/get_dialogs.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/chats/get_dialogs_count.py b/pyrogram/methods/chats/get_dialogs_count.py index 4eef96ad7b..260cc4f6ba 100644 --- a/pyrogram/methods/chats/get_dialogs_count.py +++ b/pyrogram/methods/chats/get_dialogs_count.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/chats/get_nearby_chats.py b/pyrogram/methods/chats/get_nearby_chats.py index 697465035e..22ff4fc07f 100644 --- a/pyrogram/methods/chats/get_nearby_chats.py +++ b/pyrogram/methods/chats/get_nearby_chats.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/chats/iter_chat_members.py b/pyrogram/methods/chats/iter_chat_members.py index 782548729d..86ef457c12 100644 --- a/pyrogram/methods/chats/iter_chat_members.py +++ b/pyrogram/methods/chats/iter_chat_members.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/chats/iter_dialogs.py b/pyrogram/methods/chats/iter_dialogs.py index 0c878f0ef4..2ee8860062 100644 --- a/pyrogram/methods/chats/iter_dialogs.py +++ b/pyrogram/methods/chats/iter_dialogs.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/chats/join_chat.py b/pyrogram/methods/chats/join_chat.py index 88adc4ed58..41d0aa9313 100644 --- a/pyrogram/methods/chats/join_chat.py +++ b/pyrogram/methods/chats/join_chat.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/chats/kick_chat_member.py b/pyrogram/methods/chats/kick_chat_member.py index ce814f25c1..50c9821973 100644 --- a/pyrogram/methods/chats/kick_chat_member.py +++ b/pyrogram/methods/chats/kick_chat_member.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/chats/leave_chat.py b/pyrogram/methods/chats/leave_chat.py index 5c60e6581c..84ab4ebc04 100644 --- a/pyrogram/methods/chats/leave_chat.py +++ b/pyrogram/methods/chats/leave_chat.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/chats/mark_chat_unread.py b/pyrogram/methods/chats/mark_chat_unread.py index 7d8558dad5..a85cfccbcd 100644 --- a/pyrogram/methods/chats/mark_chat_unread.py +++ b/pyrogram/methods/chats/mark_chat_unread.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/chats/pin_chat_message.py b/pyrogram/methods/chats/pin_chat_message.py index 39ddd75e1d..11983f5727 100644 --- a/pyrogram/methods/chats/pin_chat_message.py +++ b/pyrogram/methods/chats/pin_chat_message.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/chats/promote_chat_member.py b/pyrogram/methods/chats/promote_chat_member.py index 15e0837530..03213e7646 100644 --- a/pyrogram/methods/chats/promote_chat_member.py +++ b/pyrogram/methods/chats/promote_chat_member.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/chats/restrict_chat_member.py b/pyrogram/methods/chats/restrict_chat_member.py index 60f7132a91..a7071bab6d 100644 --- a/pyrogram/methods/chats/restrict_chat_member.py +++ b/pyrogram/methods/chats/restrict_chat_member.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/chats/set_administrator_title.py b/pyrogram/methods/chats/set_administrator_title.py index e354fd5b72..8fd1338baa 100644 --- a/pyrogram/methods/chats/set_administrator_title.py +++ b/pyrogram/methods/chats/set_administrator_title.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/chats/set_chat_description.py b/pyrogram/methods/chats/set_chat_description.py index 0dc87c4837..0a7bdf7f87 100644 --- a/pyrogram/methods/chats/set_chat_description.py +++ b/pyrogram/methods/chats/set_chat_description.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/chats/set_chat_permissions.py b/pyrogram/methods/chats/set_chat_permissions.py index aab7697bed..d8a102420a 100644 --- a/pyrogram/methods/chats/set_chat_permissions.py +++ b/pyrogram/methods/chats/set_chat_permissions.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/chats/set_chat_photo.py b/pyrogram/methods/chats/set_chat_photo.py index 0474fd18b8..81260989ee 100644 --- a/pyrogram/methods/chats/set_chat_photo.py +++ b/pyrogram/methods/chats/set_chat_photo.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/chats/set_chat_title.py b/pyrogram/methods/chats/set_chat_title.py index 2ed7d2e836..0cc4c6d2e4 100644 --- a/pyrogram/methods/chats/set_chat_title.py +++ b/pyrogram/methods/chats/set_chat_title.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/chats/set_slow_mode.py b/pyrogram/methods/chats/set_slow_mode.py index 24836bd7a9..77d9e339ca 100644 --- a/pyrogram/methods/chats/set_slow_mode.py +++ b/pyrogram/methods/chats/set_slow_mode.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/chats/unarchive_chats.py b/pyrogram/methods/chats/unarchive_chats.py index af62d9a730..e49c7afdb3 100644 --- a/pyrogram/methods/chats/unarchive_chats.py +++ b/pyrogram/methods/chats/unarchive_chats.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/chats/unban_chat_member.py b/pyrogram/methods/chats/unban_chat_member.py index e6f0b7b0b0..ce00457746 100644 --- a/pyrogram/methods/chats/unban_chat_member.py +++ b/pyrogram/methods/chats/unban_chat_member.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/chats/unpin_all_chat_messages.py b/pyrogram/methods/chats/unpin_all_chat_messages.py index a6f325a4bb..2996f511de 100644 --- a/pyrogram/methods/chats/unpin_all_chat_messages.py +++ b/pyrogram/methods/chats/unpin_all_chat_messages.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/chats/unpin_chat_message.py b/pyrogram/methods/chats/unpin_chat_message.py index 79a4a4c2f3..fe4b71a244 100644 --- a/pyrogram/methods/chats/unpin_chat_message.py +++ b/pyrogram/methods/chats/unpin_chat_message.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/chats/update_chat_username.py b/pyrogram/methods/chats/update_chat_username.py index ee69160e7d..297d83a24c 100644 --- a/pyrogram/methods/chats/update_chat_username.py +++ b/pyrogram/methods/chats/update_chat_username.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/contacts/__init__.py b/pyrogram/methods/contacts/__init__.py index 7c84decce4..3af26f3239 100644 --- a/pyrogram/methods/contacts/__init__.py +++ b/pyrogram/methods/contacts/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/contacts/add_contacts.py b/pyrogram/methods/contacts/add_contacts.py index aabdf064cb..3e41f615a9 100644 --- a/pyrogram/methods/contacts/add_contacts.py +++ b/pyrogram/methods/contacts/add_contacts.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/contacts/delete_contacts.py b/pyrogram/methods/contacts/delete_contacts.py index 2f576a5e2f..9612c12fa2 100644 --- a/pyrogram/methods/contacts/delete_contacts.py +++ b/pyrogram/methods/contacts/delete_contacts.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/contacts/get_contacts.py b/pyrogram/methods/contacts/get_contacts.py index d4f08168e8..9b546a16f2 100644 --- a/pyrogram/methods/contacts/get_contacts.py +++ b/pyrogram/methods/contacts/get_contacts.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/contacts/get_contacts_count.py b/pyrogram/methods/contacts/get_contacts_count.py index 3a16c9050e..f58fb96c63 100644 --- a/pyrogram/methods/contacts/get_contacts_count.py +++ b/pyrogram/methods/contacts/get_contacts_count.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/decorators/__init__.py b/pyrogram/methods/decorators/__init__.py index fbc4fac690..fe96ed13a6 100644 --- a/pyrogram/methods/decorators/__init__.py +++ b/pyrogram/methods/decorators/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/decorators/on_callback_query.py b/pyrogram/methods/decorators/on_callback_query.py index 863fe8f795..f939a3550d 100644 --- a/pyrogram/methods/decorators/on_callback_query.py +++ b/pyrogram/methods/decorators/on_callback_query.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/decorators/on_chosen_inline_result.py b/pyrogram/methods/decorators/on_chosen_inline_result.py index 198fdd965c..e457fcc076 100644 --- a/pyrogram/methods/decorators/on_chosen_inline_result.py +++ b/pyrogram/methods/decorators/on_chosen_inline_result.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/decorators/on_deleted_messages.py b/pyrogram/methods/decorators/on_deleted_messages.py index 90e692da54..4078714086 100644 --- a/pyrogram/methods/decorators/on_deleted_messages.py +++ b/pyrogram/methods/decorators/on_deleted_messages.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/decorators/on_disconnect.py b/pyrogram/methods/decorators/on_disconnect.py index 6e7b1522ff..b6dd821c32 100644 --- a/pyrogram/methods/decorators/on_disconnect.py +++ b/pyrogram/methods/decorators/on_disconnect.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/decorators/on_inline_query.py b/pyrogram/methods/decorators/on_inline_query.py index bf445d0766..dc3edbc82b 100644 --- a/pyrogram/methods/decorators/on_inline_query.py +++ b/pyrogram/methods/decorators/on_inline_query.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/decorators/on_message.py b/pyrogram/methods/decorators/on_message.py index 4f467810ad..ad17bd25ef 100644 --- a/pyrogram/methods/decorators/on_message.py +++ b/pyrogram/methods/decorators/on_message.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/decorators/on_poll.py b/pyrogram/methods/decorators/on_poll.py index 71825c0aee..d2af04ddd4 100644 --- a/pyrogram/methods/decorators/on_poll.py +++ b/pyrogram/methods/decorators/on_poll.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/decorators/on_raw_update.py b/pyrogram/methods/decorators/on_raw_update.py index 9e3a7cd847..4d7dc349d0 100644 --- a/pyrogram/methods/decorators/on_raw_update.py +++ b/pyrogram/methods/decorators/on_raw_update.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/decorators/on_user_status.py b/pyrogram/methods/decorators/on_user_status.py index 2b2ee7fea0..d67cfaec75 100644 --- a/pyrogram/methods/decorators/on_user_status.py +++ b/pyrogram/methods/decorators/on_user_status.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/messages/__init__.py b/pyrogram/methods/messages/__init__.py index f91b794139..879b17f193 100644 --- a/pyrogram/methods/messages/__init__.py +++ b/pyrogram/methods/messages/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/messages/copy_message.py b/pyrogram/methods/messages/copy_message.py index 43973ab003..9d824f395a 100644 --- a/pyrogram/methods/messages/copy_message.py +++ b/pyrogram/methods/messages/copy_message.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/messages/delete_messages.py b/pyrogram/methods/messages/delete_messages.py index 1855ce9bc7..ba9da8967e 100644 --- a/pyrogram/methods/messages/delete_messages.py +++ b/pyrogram/methods/messages/delete_messages.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/messages/download_media.py b/pyrogram/methods/messages/download_media.py index c71247d4fb..826da89e81 100644 --- a/pyrogram/methods/messages/download_media.py +++ b/pyrogram/methods/messages/download_media.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/messages/edit_inline_caption.py b/pyrogram/methods/messages/edit_inline_caption.py index b9f62cc5c0..b291589731 100644 --- a/pyrogram/methods/messages/edit_inline_caption.py +++ b/pyrogram/methods/messages/edit_inline_caption.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/messages/edit_inline_media.py b/pyrogram/methods/messages/edit_inline_media.py index 86e27bae37..1d32eec32a 100644 --- a/pyrogram/methods/messages/edit_inline_media.py +++ b/pyrogram/methods/messages/edit_inline_media.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/messages/edit_inline_reply_markup.py b/pyrogram/methods/messages/edit_inline_reply_markup.py index 6696b1a761..50c1031201 100644 --- a/pyrogram/methods/messages/edit_inline_reply_markup.py +++ b/pyrogram/methods/messages/edit_inline_reply_markup.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/messages/edit_inline_text.py b/pyrogram/methods/messages/edit_inline_text.py index 778f8b8b54..223b0422dc 100644 --- a/pyrogram/methods/messages/edit_inline_text.py +++ b/pyrogram/methods/messages/edit_inline_text.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/messages/edit_message_caption.py b/pyrogram/methods/messages/edit_message_caption.py index abd94a26ac..53e0911c2d 100644 --- a/pyrogram/methods/messages/edit_message_caption.py +++ b/pyrogram/methods/messages/edit_message_caption.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/messages/edit_message_media.py b/pyrogram/methods/messages/edit_message_media.py index 8737bd91d8..17625115d1 100644 --- a/pyrogram/methods/messages/edit_message_media.py +++ b/pyrogram/methods/messages/edit_message_media.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/messages/edit_message_reply_markup.py b/pyrogram/methods/messages/edit_message_reply_markup.py index ae5d04102b..501dc480ba 100644 --- a/pyrogram/methods/messages/edit_message_reply_markup.py +++ b/pyrogram/methods/messages/edit_message_reply_markup.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/messages/edit_message_text.py b/pyrogram/methods/messages/edit_message_text.py index a3f94d1bd5..44e3f48ac3 100644 --- a/pyrogram/methods/messages/edit_message_text.py +++ b/pyrogram/methods/messages/edit_message_text.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/messages/forward_messages.py b/pyrogram/methods/messages/forward_messages.py index 0267276807..1959944d96 100644 --- a/pyrogram/methods/messages/forward_messages.py +++ b/pyrogram/methods/messages/forward_messages.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/messages/get_history.py b/pyrogram/methods/messages/get_history.py index 130c00bb12..0227cd6e12 100644 --- a/pyrogram/methods/messages/get_history.py +++ b/pyrogram/methods/messages/get_history.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/messages/get_history_count.py b/pyrogram/methods/messages/get_history_count.py index e08bf027fc..191a58e083 100644 --- a/pyrogram/methods/messages/get_history_count.py +++ b/pyrogram/methods/messages/get_history_count.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/messages/get_media_group.py b/pyrogram/methods/messages/get_media_group.py index ab20f8ec69..d43fb32818 100644 --- a/pyrogram/methods/messages/get_media_group.py +++ b/pyrogram/methods/messages/get_media_group.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/messages/get_messages.py b/pyrogram/methods/messages/get_messages.py index a4f9e6a5ed..39455ec42a 100644 --- a/pyrogram/methods/messages/get_messages.py +++ b/pyrogram/methods/messages/get_messages.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/messages/inline_session.py b/pyrogram/methods/messages/inline_session.py index 4ad7201068..34b92a2eb6 100644 --- a/pyrogram/methods/messages/inline_session.py +++ b/pyrogram/methods/messages/inline_session.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from asyncio import Lock diff --git a/pyrogram/methods/messages/iter_history.py b/pyrogram/methods/messages/iter_history.py index 6bd3d5815e..9fecc8743e 100644 --- a/pyrogram/methods/messages/iter_history.py +++ b/pyrogram/methods/messages/iter_history.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/messages/read_history.py b/pyrogram/methods/messages/read_history.py index 15f0912870..d06d8dcb0c 100644 --- a/pyrogram/methods/messages/read_history.py +++ b/pyrogram/methods/messages/read_history.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/messages/retract_vote.py b/pyrogram/methods/messages/retract_vote.py index add319c72b..2f9ec33ced 100644 --- a/pyrogram/methods/messages/retract_vote.py +++ b/pyrogram/methods/messages/retract_vote.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/messages/search_global.py b/pyrogram/methods/messages/search_global.py index 17f6d445cf..1e65b2120b 100644 --- a/pyrogram/methods/messages/search_global.py +++ b/pyrogram/methods/messages/search_global.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/messages/search_messages.py b/pyrogram/methods/messages/search_messages.py index 9389c9df9f..03b266c3e4 100644 --- a/pyrogram/methods/messages/search_messages.py +++ b/pyrogram/methods/messages/search_messages.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/messages/send_animation.py b/pyrogram/methods/messages/send_animation.py index 55e1126daa..303af57562 100644 --- a/pyrogram/methods/messages/send_animation.py +++ b/pyrogram/methods/messages/send_animation.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/messages/send_audio.py b/pyrogram/methods/messages/send_audio.py index 96829f70b9..39a3b95d35 100644 --- a/pyrogram/methods/messages/send_audio.py +++ b/pyrogram/methods/messages/send_audio.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/messages/send_cached_media.py b/pyrogram/methods/messages/send_cached_media.py index 776771f565..2d8d9f5bc3 100644 --- a/pyrogram/methods/messages/send_cached_media.py +++ b/pyrogram/methods/messages/send_cached_media.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/messages/send_chat_action.py b/pyrogram/methods/messages/send_chat_action.py index 10d0da4897..586d1ea746 100644 --- a/pyrogram/methods/messages/send_chat_action.py +++ b/pyrogram/methods/messages/send_chat_action.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/messages/send_contact.py b/pyrogram/methods/messages/send_contact.py index 50909e7c95..6cc32c48f2 100644 --- a/pyrogram/methods/messages/send_contact.py +++ b/pyrogram/methods/messages/send_contact.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/messages/send_dice.py b/pyrogram/methods/messages/send_dice.py index a584fadf8f..1b7a88c1af 100644 --- a/pyrogram/methods/messages/send_dice.py +++ b/pyrogram/methods/messages/send_dice.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/messages/send_document.py b/pyrogram/methods/messages/send_document.py index e5cb123b80..2202353631 100644 --- a/pyrogram/methods/messages/send_document.py +++ b/pyrogram/methods/messages/send_document.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/messages/send_location.py b/pyrogram/methods/messages/send_location.py index d144855027..a08c065724 100644 --- a/pyrogram/methods/messages/send_location.py +++ b/pyrogram/methods/messages/send_location.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/messages/send_media_group.py b/pyrogram/methods/messages/send_media_group.py index 21b55d6d01..d591c99b55 100644 --- a/pyrogram/methods/messages/send_media_group.py +++ b/pyrogram/methods/messages/send_media_group.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/messages/send_message.py b/pyrogram/methods/messages/send_message.py index 3d235cc19c..fdffe79fd8 100644 --- a/pyrogram/methods/messages/send_message.py +++ b/pyrogram/methods/messages/send_message.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/messages/send_photo.py b/pyrogram/methods/messages/send_photo.py index b3cd1dc029..01d2e7339b 100644 --- a/pyrogram/methods/messages/send_photo.py +++ b/pyrogram/methods/messages/send_photo.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/messages/send_poll.py b/pyrogram/methods/messages/send_poll.py index 4758e83ed8..9def8afe92 100644 --- a/pyrogram/methods/messages/send_poll.py +++ b/pyrogram/methods/messages/send_poll.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/messages/send_sticker.py b/pyrogram/methods/messages/send_sticker.py index 00e1ec2415..2e12b0db8a 100644 --- a/pyrogram/methods/messages/send_sticker.py +++ b/pyrogram/methods/messages/send_sticker.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/messages/send_venue.py b/pyrogram/methods/messages/send_venue.py index e46119109d..5d969e2ead 100644 --- a/pyrogram/methods/messages/send_venue.py +++ b/pyrogram/methods/messages/send_venue.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/messages/send_video.py b/pyrogram/methods/messages/send_video.py index 3142c987c9..d0ff59119a 100644 --- a/pyrogram/methods/messages/send_video.py +++ b/pyrogram/methods/messages/send_video.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/messages/send_video_note.py b/pyrogram/methods/messages/send_video_note.py index 45a7b59a45..9d4a8a719e 100644 --- a/pyrogram/methods/messages/send_video_note.py +++ b/pyrogram/methods/messages/send_video_note.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/messages/send_voice.py b/pyrogram/methods/messages/send_voice.py index 269abc4266..03967b69d5 100644 --- a/pyrogram/methods/messages/send_voice.py +++ b/pyrogram/methods/messages/send_voice.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/messages/stop_poll.py b/pyrogram/methods/messages/stop_poll.py index c0b0754244..85241d885b 100644 --- a/pyrogram/methods/messages/stop_poll.py +++ b/pyrogram/methods/messages/stop_poll.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/messages/vote_poll.py b/pyrogram/methods/messages/vote_poll.py index e764e0bbba..0c18bc215a 100644 --- a/pyrogram/methods/messages/vote_poll.py +++ b/pyrogram/methods/messages/vote_poll.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/password/__init__.py b/pyrogram/methods/password/__init__.py index ca7074082a..30f3950c78 100644 --- a/pyrogram/methods/password/__init__.py +++ b/pyrogram/methods/password/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/password/change_cloud_password.py b/pyrogram/methods/password/change_cloud_password.py index 4002ef1e36..1f1f4e0364 100644 --- a/pyrogram/methods/password/change_cloud_password.py +++ b/pyrogram/methods/password/change_cloud_password.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/password/enable_cloud_password.py b/pyrogram/methods/password/enable_cloud_password.py index 4fedca154b..ad12cdc440 100644 --- a/pyrogram/methods/password/enable_cloud_password.py +++ b/pyrogram/methods/password/enable_cloud_password.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/password/remove_cloud_password.py b/pyrogram/methods/password/remove_cloud_password.py index ecebe226b5..3d792835eb 100644 --- a/pyrogram/methods/password/remove_cloud_password.py +++ b/pyrogram/methods/password/remove_cloud_password.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/users/__init__.py b/pyrogram/methods/users/__init__.py index 8ecf45f6f4..d9e61e694c 100644 --- a/pyrogram/methods/users/__init__.py +++ b/pyrogram/methods/users/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/users/block_user.py b/pyrogram/methods/users/block_user.py index 6464788901..6d7c8cdadb 100644 --- a/pyrogram/methods/users/block_user.py +++ b/pyrogram/methods/users/block_user.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/users/delete_profile_photos.py b/pyrogram/methods/users/delete_profile_photos.py index 7b3cded025..7f2c3a729e 100644 --- a/pyrogram/methods/users/delete_profile_photos.py +++ b/pyrogram/methods/users/delete_profile_photos.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/users/get_common_chats.py b/pyrogram/methods/users/get_common_chats.py index 5b788877bc..45ab2fe71f 100644 --- a/pyrogram/methods/users/get_common_chats.py +++ b/pyrogram/methods/users/get_common_chats.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/users/get_me.py b/pyrogram/methods/users/get_me.py index 385296717d..0c54572e11 100644 --- a/pyrogram/methods/users/get_me.py +++ b/pyrogram/methods/users/get_me.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/users/get_profile_photos.py b/pyrogram/methods/users/get_profile_photos.py index 6ccfb8f8cf..0a4686ca2e 100644 --- a/pyrogram/methods/users/get_profile_photos.py +++ b/pyrogram/methods/users/get_profile_photos.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/users/get_profile_photos_count.py b/pyrogram/methods/users/get_profile_photos_count.py index 65b4e16849..10aca0c51f 100644 --- a/pyrogram/methods/users/get_profile_photos_count.py +++ b/pyrogram/methods/users/get_profile_photos_count.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/users/get_users.py b/pyrogram/methods/users/get_users.py index cf591d2bb8..4da15e9b76 100644 --- a/pyrogram/methods/users/get_users.py +++ b/pyrogram/methods/users/get_users.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/users/iter_profile_photos.py b/pyrogram/methods/users/iter_profile_photos.py index ee196f6553..96274392f9 100644 --- a/pyrogram/methods/users/iter_profile_photos.py +++ b/pyrogram/methods/users/iter_profile_photos.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/users/set_profile_photo.py b/pyrogram/methods/users/set_profile_photo.py index 9d72c033b1..822500edc5 100644 --- a/pyrogram/methods/users/set_profile_photo.py +++ b/pyrogram/methods/users/set_profile_photo.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/users/unblock_user.py b/pyrogram/methods/users/unblock_user.py index 9f9e76bbc3..2492aeba72 100644 --- a/pyrogram/methods/users/unblock_user.py +++ b/pyrogram/methods/users/unblock_user.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/users/update_profile.py b/pyrogram/methods/users/update_profile.py index 15c7079343..a505c70877 100644 --- a/pyrogram/methods/users/update_profile.py +++ b/pyrogram/methods/users/update_profile.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/users/update_username.py b/pyrogram/methods/users/update_username.py index fe48554ac4..865f3ed9eb 100644 --- a/pyrogram/methods/users/update_username.py +++ b/pyrogram/methods/users/update_username.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/utilities/__init__.py b/pyrogram/methods/utilities/__init__.py index d6ddcd3c1b..40034e6f0d 100644 --- a/pyrogram/methods/utilities/__init__.py +++ b/pyrogram/methods/utilities/__init__.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . from .add_handler import AddHandler from .export_session_string import ExportSessionString diff --git a/pyrogram/methods/utilities/add_handler.py b/pyrogram/methods/utilities/add_handler.py index 3c7440f340..c2f852694c 100644 --- a/pyrogram/methods/utilities/add_handler.py +++ b/pyrogram/methods/utilities/add_handler.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/utilities/export_session_string.py b/pyrogram/methods/utilities/export_session_string.py index 4b9a4fce9b..84aceb46bb 100644 --- a/pyrogram/methods/utilities/export_session_string.py +++ b/pyrogram/methods/utilities/export_session_string.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/utilities/idle.py b/pyrogram/methods/utilities/idle.py index 044e41d11f..ba33265014 100644 --- a/pyrogram/methods/utilities/idle.py +++ b/pyrogram/methods/utilities/idle.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/utilities/remove_handler.py b/pyrogram/methods/utilities/remove_handler.py index 67709cb5ff..97c7f18358 100644 --- a/pyrogram/methods/utilities/remove_handler.py +++ b/pyrogram/methods/utilities/remove_handler.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/utilities/restart.py b/pyrogram/methods/utilities/restart.py index 22c145b536..9d5b7d3a9b 100644 --- a/pyrogram/methods/utilities/restart.py +++ b/pyrogram/methods/utilities/restart.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/utilities/run.py b/pyrogram/methods/utilities/run.py index dccce0f22e..6a55b28cde 100644 --- a/pyrogram/methods/utilities/run.py +++ b/pyrogram/methods/utilities/run.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/utilities/start.py b/pyrogram/methods/utilities/start.py index 8a8862575e..0f08d223c5 100644 --- a/pyrogram/methods/utilities/start.py +++ b/pyrogram/methods/utilities/start.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/utilities/stop.py b/pyrogram/methods/utilities/stop.py index b686211628..acf28f28b2 100644 --- a/pyrogram/methods/utilities/stop.py +++ b/pyrogram/methods/utilities/stop.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/methods/utilities/stop_transmission.py b/pyrogram/methods/utilities/stop_transmission.py index 6eaedf4d56..a0d04b4c52 100644 --- a/pyrogram/methods/utilities/stop_transmission.py +++ b/pyrogram/methods/utilities/stop_transmission.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/mime_types.py b/pyrogram/mime_types.py index 69e69df872..819e07738e 100644 --- a/pyrogram/mime_types.py +++ b/pyrogram/mime_types.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/parser/__init__.py b/pyrogram/parser/__init__.py index ad5c727ab4..c1c03d59d7 100644 --- a/pyrogram/parser/__init__.py +++ b/pyrogram/parser/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/parser/html.py b/pyrogram/parser/html.py index 30bdb622c4..4f4309de2e 100644 --- a/pyrogram/parser/html.py +++ b/pyrogram/parser/html.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/parser/markdown.py b/pyrogram/parser/markdown.py index f627bb45c0..4b9dede8e5 100644 --- a/pyrogram/parser/markdown.py +++ b/pyrogram/parser/markdown.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/parser/parser.py b/pyrogram/parser/parser.py index 27a0160074..24663ab6df 100644 --- a/pyrogram/parser/parser.py +++ b/pyrogram/parser/parser.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/parser/utils.py b/pyrogram/parser/utils.py index 0babc9d7dd..db0fc1756d 100644 --- a/pyrogram/parser/utils.py +++ b/pyrogram/parser/utils.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/raw/__init__.py b/pyrogram/raw/__init__.py index 2bbf90d772..deef4038fc 100644 --- a/pyrogram/raw/__init__.py +++ b/pyrogram/raw/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/raw/core/__init__.py b/pyrogram/raw/core/__init__.py index 0a4b2a8297..8b284c65d0 100644 --- a/pyrogram/raw/core/__init__.py +++ b/pyrogram/raw/core/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/raw/core/future_salt.py b/pyrogram/raw/core/future_salt.py index 7d163e6b4a..85303d1203 100644 --- a/pyrogram/raw/core/future_salt.py +++ b/pyrogram/raw/core/future_salt.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/raw/core/future_salts.py b/pyrogram/raw/core/future_salts.py index f8a4835311..faa4b74163 100644 --- a/pyrogram/raw/core/future_salts.py +++ b/pyrogram/raw/core/future_salts.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/raw/core/gzip_packed.py b/pyrogram/raw/core/gzip_packed.py index 094162d59d..8525338ac8 100644 --- a/pyrogram/raw/core/gzip_packed.py +++ b/pyrogram/raw/core/gzip_packed.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/raw/core/list.py b/pyrogram/raw/core/list.py index c0014b6841..74d39c03bf 100644 --- a/pyrogram/raw/core/list.py +++ b/pyrogram/raw/core/list.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/raw/core/message.py b/pyrogram/raw/core/message.py index e1e595a042..6d50ecf318 100644 --- a/pyrogram/raw/core/message.py +++ b/pyrogram/raw/core/message.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/raw/core/msg_container.py b/pyrogram/raw/core/msg_container.py index aecf7dfc78..ddebce0c38 100644 --- a/pyrogram/raw/core/msg_container.py +++ b/pyrogram/raw/core/msg_container.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/raw/core/primitives/__init__.py b/pyrogram/raw/core/primitives/__init__.py index 9f6de65a0d..575b36966e 100644 --- a/pyrogram/raw/core/primitives/__init__.py +++ b/pyrogram/raw/core/primitives/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/raw/core/primitives/bool.py b/pyrogram/raw/core/primitives/bool.py index a8fb5b4ec5..480a7224ae 100644 --- a/pyrogram/raw/core/primitives/bool.py +++ b/pyrogram/raw/core/primitives/bool.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/raw/core/primitives/bytes.py b/pyrogram/raw/core/primitives/bytes.py index 8c95ebdec9..47f914e08b 100644 --- a/pyrogram/raw/core/primitives/bytes.py +++ b/pyrogram/raw/core/primitives/bytes.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/raw/core/primitives/double.py b/pyrogram/raw/core/primitives/double.py index 43dd5d2c76..232f35c9b7 100644 --- a/pyrogram/raw/core/primitives/double.py +++ b/pyrogram/raw/core/primitives/double.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/raw/core/primitives/int.py b/pyrogram/raw/core/primitives/int.py index 7e6135d1db..aed653ad76 100644 --- a/pyrogram/raw/core/primitives/int.py +++ b/pyrogram/raw/core/primitives/int.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/raw/core/primitives/string.py b/pyrogram/raw/core/primitives/string.py index dd25b5bfbb..c5d19205d6 100644 --- a/pyrogram/raw/core/primitives/string.py +++ b/pyrogram/raw/core/primitives/string.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/raw/core/primitives/vector.py b/pyrogram/raw/core/primitives/vector.py index afb7d591eb..a2be7b4f50 100644 --- a/pyrogram/raw/core/primitives/vector.py +++ b/pyrogram/raw/core/primitives/vector.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/raw/core/tl_object.py b/pyrogram/raw/core/tl_object.py index 1d6b503217..db1d3bfc38 100644 --- a/pyrogram/raw/core/tl_object.py +++ b/pyrogram/raw/core/tl_object.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/scaffold.py b/pyrogram/scaffold.py index 7d67966826..a003359981 100644 --- a/pyrogram/scaffold.py +++ b/pyrogram/scaffold.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/session/__init__.py b/pyrogram/session/__init__.py index 69d414c07b..96e06e0f58 100644 --- a/pyrogram/session/__init__.py +++ b/pyrogram/session/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/session/auth.py b/pyrogram/session/auth.py index 2aa658216e..a3e87ff41c 100644 --- a/pyrogram/session/auth.py +++ b/pyrogram/session/auth.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/session/internals/__init__.py b/pyrogram/session/internals/__init__.py index be75ecc7e3..7f8ef15b82 100644 --- a/pyrogram/session/internals/__init__.py +++ b/pyrogram/session/internals/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/session/internals/data_center.py b/pyrogram/session/internals/data_center.py index 85e9c72ed7..566bd79e43 100644 --- a/pyrogram/session/internals/data_center.py +++ b/pyrogram/session/internals/data_center.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/session/internals/msg_factory.py b/pyrogram/session/internals/msg_factory.py index 98c88b3f89..2adb22b34a 100644 --- a/pyrogram/session/internals/msg_factory.py +++ b/pyrogram/session/internals/msg_factory.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/session/internals/msg_id.py b/pyrogram/session/internals/msg_id.py index 384a3e504f..1af7d42f0a 100644 --- a/pyrogram/session/internals/msg_id.py +++ b/pyrogram/session/internals/msg_id.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/session/internals/seq_no.py b/pyrogram/session/internals/seq_no.py index cded8343ee..0725809c97 100644 --- a/pyrogram/session/internals/seq_no.py +++ b/pyrogram/session/internals/seq_no.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index 58d1e457d8..6ce342eaf2 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/storage/__init__.py b/pyrogram/storage/__init__.py index 05987e2f3c..a73bd5d4df 100644 --- a/pyrogram/storage/__init__.py +++ b/pyrogram/storage/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/storage/file_storage.py b/pyrogram/storage/file_storage.py index a335761a0b..8910a2563b 100644 --- a/pyrogram/storage/file_storage.py +++ b/pyrogram/storage/file_storage.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/storage/memory_storage.py b/pyrogram/storage/memory_storage.py index 7e0094974c..12783bb592 100644 --- a/pyrogram/storage/memory_storage.py +++ b/pyrogram/storage/memory_storage.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/storage/sqlite_storage.py b/pyrogram/storage/sqlite_storage.py index e9f401b64d..73bbb53408 100644 --- a/pyrogram/storage/sqlite_storage.py +++ b/pyrogram/storage/sqlite_storage.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/storage/storage.py b/pyrogram/storage/storage.py index 3ef83027a0..c506c8ca04 100644 --- a/pyrogram/storage/storage.py +++ b/pyrogram/storage/storage.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/sync.py b/pyrogram/sync.py index 2bca9700bb..20bd12ac3d 100644 --- a/pyrogram/sync.py +++ b/pyrogram/sync.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import asyncio import functools diff --git a/pyrogram/syncer.py b/pyrogram/syncer.py index db46e9694c..93fb59f253 100644 --- a/pyrogram/syncer.py +++ b/pyrogram/syncer.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/__init__.py b/pyrogram/types/__init__.py index f0a0665f91..bad120f105 100644 --- a/pyrogram/types/__init__.py +++ b/pyrogram/types/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/authorization/__init__.py b/pyrogram/types/authorization/__init__.py index 2848034100..d5b491da57 100644 --- a/pyrogram/types/authorization/__init__.py +++ b/pyrogram/types/authorization/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/authorization/sent_code.py b/pyrogram/types/authorization/sent_code.py index fcbfeace2b..1f4399013f 100644 --- a/pyrogram/types/authorization/sent_code.py +++ b/pyrogram/types/authorization/sent_code.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/authorization/terms_of_service.py b/pyrogram/types/authorization/terms_of_service.py index 33a94d0a0b..db465e643a 100644 --- a/pyrogram/types/authorization/terms_of_service.py +++ b/pyrogram/types/authorization/terms_of_service.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/bots_and_keyboards/__init__.py b/pyrogram/types/bots_and_keyboards/__init__.py index 2acd8c0b71..2f85dff1a8 100644 --- a/pyrogram/types/bots_and_keyboards/__init__.py +++ b/pyrogram/types/bots_and_keyboards/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/bots_and_keyboards/callback_game.py b/pyrogram/types/bots_and_keyboards/callback_game.py index 9fe8421d34..a89cbbd780 100644 --- a/pyrogram/types/bots_and_keyboards/callback_game.py +++ b/pyrogram/types/bots_and_keyboards/callback_game.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/bots_and_keyboards/callback_query.py b/pyrogram/types/bots_and_keyboards/callback_query.py index 422c32934a..eba23b624f 100644 --- a/pyrogram/types/bots_and_keyboards/callback_query.py +++ b/pyrogram/types/bots_and_keyboards/callback_query.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/bots_and_keyboards/force_reply.py b/pyrogram/types/bots_and_keyboards/force_reply.py index 97c88db095..0456bf48f2 100644 --- a/pyrogram/types/bots_and_keyboards/force_reply.py +++ b/pyrogram/types/bots_and_keyboards/force_reply.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/bots_and_keyboards/game_high_score.py b/pyrogram/types/bots_and_keyboards/game_high_score.py index a744886207..ff67e7505c 100644 --- a/pyrogram/types/bots_and_keyboards/game_high_score.py +++ b/pyrogram/types/bots_and_keyboards/game_high_score.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/bots_and_keyboards/inline_keyboard_button.py b/pyrogram/types/bots_and_keyboards/inline_keyboard_button.py index 2d5212d9d4..b790c1a204 100644 --- a/pyrogram/types/bots_and_keyboards/inline_keyboard_button.py +++ b/pyrogram/types/bots_and_keyboards/inline_keyboard_button.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/bots_and_keyboards/inline_keyboard_markup.py b/pyrogram/types/bots_and_keyboards/inline_keyboard_markup.py index 6332c7cfa7..107b2e2f7b 100644 --- a/pyrogram/types/bots_and_keyboards/inline_keyboard_markup.py +++ b/pyrogram/types/bots_and_keyboards/inline_keyboard_markup.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/bots_and_keyboards/keyboard_button.py b/pyrogram/types/bots_and_keyboards/keyboard_button.py index 855c11bd7e..85adaf20fe 100644 --- a/pyrogram/types/bots_and_keyboards/keyboard_button.py +++ b/pyrogram/types/bots_and_keyboards/keyboard_button.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/bots_and_keyboards/reply_keyboard_markup.py b/pyrogram/types/bots_and_keyboards/reply_keyboard_markup.py index e4283c8bc4..dfbdc9ea45 100644 --- a/pyrogram/types/bots_and_keyboards/reply_keyboard_markup.py +++ b/pyrogram/types/bots_and_keyboards/reply_keyboard_markup.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/bots_and_keyboards/reply_keyboard_remove.py b/pyrogram/types/bots_and_keyboards/reply_keyboard_remove.py index f595d06d5d..020412ae1a 100644 --- a/pyrogram/types/bots_and_keyboards/reply_keyboard_remove.py +++ b/pyrogram/types/bots_and_keyboards/reply_keyboard_remove.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/inline_mode/__init__.py b/pyrogram/types/inline_mode/__init__.py index b532c2e909..fee7b94a43 100644 --- a/pyrogram/types/inline_mode/__init__.py +++ b/pyrogram/types/inline_mode/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/inline_mode/chosen_inline_result.py b/pyrogram/types/inline_mode/chosen_inline_result.py index 11185d9b9e..8da825eacb 100644 --- a/pyrogram/types/inline_mode/chosen_inline_result.py +++ b/pyrogram/types/inline_mode/chosen_inline_result.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/inline_mode/inline_query.py b/pyrogram/types/inline_mode/inline_query.py index 57bdffa059..1883bc207e 100644 --- a/pyrogram/types/inline_mode/inline_query.py +++ b/pyrogram/types/inline_mode/inline_query.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/inline_mode/inline_query_result.py b/pyrogram/types/inline_mode/inline_query_result.py index c70e52c0fd..decb77e176 100644 --- a/pyrogram/types/inline_mode/inline_query_result.py +++ b/pyrogram/types/inline_mode/inline_query_result.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/inline_mode/inline_query_result_animation.py b/pyrogram/types/inline_mode/inline_query_result_animation.py index 1f4103e7c8..2b9725d5d6 100644 --- a/pyrogram/types/inline_mode/inline_query_result_animation.py +++ b/pyrogram/types/inline_mode/inline_query_result_animation.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/inline_mode/inline_query_result_article.py b/pyrogram/types/inline_mode/inline_query_result_article.py index 42bf6f0ab7..eadb0a15db 100644 --- a/pyrogram/types/inline_mode/inline_query_result_article.py +++ b/pyrogram/types/inline_mode/inline_query_result_article.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/inline_mode/inline_query_result_photo.py b/pyrogram/types/inline_mode/inline_query_result_photo.py index 155f740f03..7afd66590c 100644 --- a/pyrogram/types/inline_mode/inline_query_result_photo.py +++ b/pyrogram/types/inline_mode/inline_query_result_photo.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/input_media/__init__.py b/pyrogram/types/input_media/__init__.py index 101703a17e..655a968c73 100644 --- a/pyrogram/types/input_media/__init__.py +++ b/pyrogram/types/input_media/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/input_media/input_media.py b/pyrogram/types/input_media/input_media.py index bcbe2e52be..a510ad2695 100644 --- a/pyrogram/types/input_media/input_media.py +++ b/pyrogram/types/input_media/input_media.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/input_media/input_media_animation.py b/pyrogram/types/input_media/input_media_animation.py index 840dcfedff..82e7591920 100644 --- a/pyrogram/types/input_media/input_media_animation.py +++ b/pyrogram/types/input_media/input_media_animation.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/input_media/input_media_audio.py b/pyrogram/types/input_media/input_media_audio.py index 0366cedecd..ff7bd085f2 100644 --- a/pyrogram/types/input_media/input_media_audio.py +++ b/pyrogram/types/input_media/input_media_audio.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/input_media/input_media_document.py b/pyrogram/types/input_media/input_media_document.py index 3985eaddad..b1d6b4a1b0 100644 --- a/pyrogram/types/input_media/input_media_document.py +++ b/pyrogram/types/input_media/input_media_document.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/input_media/input_media_photo.py b/pyrogram/types/input_media/input_media_photo.py index 8182533537..d675ad19b5 100644 --- a/pyrogram/types/input_media/input_media_photo.py +++ b/pyrogram/types/input_media/input_media_photo.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/input_media/input_media_video.py b/pyrogram/types/input_media/input_media_video.py index 9d58f8ea0b..aef5edf186 100644 --- a/pyrogram/types/input_media/input_media_video.py +++ b/pyrogram/types/input_media/input_media_video.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/input_media/input_phone_contact.py b/pyrogram/types/input_media/input_phone_contact.py index 58c73252c0..e0169a5d81 100644 --- a/pyrogram/types/input_media/input_phone_contact.py +++ b/pyrogram/types/input_media/input_phone_contact.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/input_message_content/__init__.py b/pyrogram/types/input_message_content/__init__.py index 3f82b6b47d..233f53b07a 100644 --- a/pyrogram/types/input_message_content/__init__.py +++ b/pyrogram/types/input_message_content/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/input_message_content/input_message_content.py b/pyrogram/types/input_message_content/input_message_content.py index 4ecbc0a604..3d23b9ea12 100644 --- a/pyrogram/types/input_message_content/input_message_content.py +++ b/pyrogram/types/input_message_content/input_message_content.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/input_message_content/input_text_message_content.py b/pyrogram/types/input_message_content/input_text_message_content.py index 19696b2152..ed943bfdd1 100644 --- a/pyrogram/types/input_message_content/input_text_message_content.py +++ b/pyrogram/types/input_message_content/input_text_message_content.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/list.py b/pyrogram/types/list.py index 2bdc425b56..fe91c4c6d0 100644 --- a/pyrogram/types/list.py +++ b/pyrogram/types/list.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/messages_and_media/__init__.py b/pyrogram/types/messages_and_media/__init__.py index aeae6375fa..864019fe24 100644 --- a/pyrogram/types/messages_and_media/__init__.py +++ b/pyrogram/types/messages_and_media/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/messages_and_media/animation.py b/pyrogram/types/messages_and_media/animation.py index 94c345449f..96a57469e1 100644 --- a/pyrogram/types/messages_and_media/animation.py +++ b/pyrogram/types/messages_and_media/animation.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/messages_and_media/audio.py b/pyrogram/types/messages_and_media/audio.py index ff4207012a..e590d2e367 100644 --- a/pyrogram/types/messages_and_media/audio.py +++ b/pyrogram/types/messages_and_media/audio.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/messages_and_media/contact.py b/pyrogram/types/messages_and_media/contact.py index 8ce25d4033..fe6f55fd60 100644 --- a/pyrogram/types/messages_and_media/contact.py +++ b/pyrogram/types/messages_and_media/contact.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/messages_and_media/dice.py b/pyrogram/types/messages_and_media/dice.py index e89fb38b40..aa67470cc4 100644 --- a/pyrogram/types/messages_and_media/dice.py +++ b/pyrogram/types/messages_and_media/dice.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/messages_and_media/document.py b/pyrogram/types/messages_and_media/document.py index cf27611510..76ebed9b0f 100644 --- a/pyrogram/types/messages_and_media/document.py +++ b/pyrogram/types/messages_and_media/document.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/messages_and_media/game.py b/pyrogram/types/messages_and_media/game.py index a80888a2c5..5e76f5b6a3 100644 --- a/pyrogram/types/messages_and_media/game.py +++ b/pyrogram/types/messages_and_media/game.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/messages_and_media/location.py b/pyrogram/types/messages_and_media/location.py index ac122a6d0e..6cd0e9f189 100644 --- a/pyrogram/types/messages_and_media/location.py +++ b/pyrogram/types/messages_and_media/location.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index 8b46632c11..3fd800b66a 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/messages_and_media/message_entity.py b/pyrogram/types/messages_and_media/message_entity.py index ddaebbd83e..2c7fde7f9e 100644 --- a/pyrogram/types/messages_and_media/message_entity.py +++ b/pyrogram/types/messages_and_media/message_entity.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/messages_and_media/photo.py b/pyrogram/types/messages_and_media/photo.py index 482a4b47dc..c350b6a72f 100644 --- a/pyrogram/types/messages_and_media/photo.py +++ b/pyrogram/types/messages_and_media/photo.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/messages_and_media/poll.py b/pyrogram/types/messages_and_media/poll.py index 459c24bb3f..832b81e258 100644 --- a/pyrogram/types/messages_and_media/poll.py +++ b/pyrogram/types/messages_and_media/poll.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/messages_and_media/poll_option.py b/pyrogram/types/messages_and_media/poll_option.py index 93fda559ad..25eff66387 100644 --- a/pyrogram/types/messages_and_media/poll_option.py +++ b/pyrogram/types/messages_and_media/poll_option.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/messages_and_media/sticker.py b/pyrogram/types/messages_and_media/sticker.py index e192be4a70..87dd2f7e41 100644 --- a/pyrogram/types/messages_and_media/sticker.py +++ b/pyrogram/types/messages_and_media/sticker.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/messages_and_media/stripped_thumbnail.py b/pyrogram/types/messages_and_media/stripped_thumbnail.py index 8f22dd80f7..9c832ffb80 100644 --- a/pyrogram/types/messages_and_media/stripped_thumbnail.py +++ b/pyrogram/types/messages_and_media/stripped_thumbnail.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/messages_and_media/thumbnail.py b/pyrogram/types/messages_and_media/thumbnail.py index f8d3596c2e..7b26c5b8d1 100644 --- a/pyrogram/types/messages_and_media/thumbnail.py +++ b/pyrogram/types/messages_and_media/thumbnail.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/messages_and_media/venue.py b/pyrogram/types/messages_and_media/venue.py index 05f3e33e53..bdf385e504 100644 --- a/pyrogram/types/messages_and_media/venue.py +++ b/pyrogram/types/messages_and_media/venue.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/messages_and_media/video.py b/pyrogram/types/messages_and_media/video.py index c05614c324..b64dcfb6b9 100644 --- a/pyrogram/types/messages_and_media/video.py +++ b/pyrogram/types/messages_and_media/video.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/messages_and_media/video_note.py b/pyrogram/types/messages_and_media/video_note.py index 746045c226..9a5685fc8a 100644 --- a/pyrogram/types/messages_and_media/video_note.py +++ b/pyrogram/types/messages_and_media/video_note.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/messages_and_media/voice.py b/pyrogram/types/messages_and_media/voice.py index 56f6cbafbd..640ad8ccf6 100644 --- a/pyrogram/types/messages_and_media/voice.py +++ b/pyrogram/types/messages_and_media/voice.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/messages_and_media/webpage.py b/pyrogram/types/messages_and_media/webpage.py index edd94934c1..08432eabfc 100644 --- a/pyrogram/types/messages_and_media/webpage.py +++ b/pyrogram/types/messages_and_media/webpage.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/object.py b/pyrogram/types/object.py index c1d9248cc5..8c344bfba7 100644 --- a/pyrogram/types/object.py +++ b/pyrogram/types/object.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/update.py b/pyrogram/types/update.py index 7231a92b04..1937c94b93 100644 --- a/pyrogram/types/update.py +++ b/pyrogram/types/update.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/user_and_chats/__init__.py b/pyrogram/types/user_and_chats/__init__.py index 371757eefe..a38bb4ce1d 100644 --- a/pyrogram/types/user_and_chats/__init__.py +++ b/pyrogram/types/user_and_chats/__init__.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/user_and_chats/chat.py b/pyrogram/types/user_and_chats/chat.py index 18cf53916f..a8b4a950af 100644 --- a/pyrogram/types/user_and_chats/chat.py +++ b/pyrogram/types/user_and_chats/chat.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/user_and_chats/chat_member.py b/pyrogram/types/user_and_chats/chat_member.py index 7f195d4ccb..066976f784 100644 --- a/pyrogram/types/user_and_chats/chat_member.py +++ b/pyrogram/types/user_and_chats/chat_member.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/user_and_chats/chat_permissions.py b/pyrogram/types/user_and_chats/chat_permissions.py index 2d9306adde..150f8321a6 100644 --- a/pyrogram/types/user_and_chats/chat_permissions.py +++ b/pyrogram/types/user_and_chats/chat_permissions.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/user_and_chats/chat_photo.py b/pyrogram/types/user_and_chats/chat_photo.py index 53eeab53a1..cd10c3c2ec 100644 --- a/pyrogram/types/user_and_chats/chat_photo.py +++ b/pyrogram/types/user_and_chats/chat_photo.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/user_and_chats/chat_preview.py b/pyrogram/types/user_and_chats/chat_preview.py index 1a0f9c94f9..f4a8540fda 100644 --- a/pyrogram/types/user_and_chats/chat_preview.py +++ b/pyrogram/types/user_and_chats/chat_preview.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/user_and_chats/dialog.py b/pyrogram/types/user_and_chats/dialog.py index b0212a2739..6c4a408a14 100644 --- a/pyrogram/types/user_and_chats/dialog.py +++ b/pyrogram/types/user_and_chats/dialog.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/user_and_chats/restriction.py b/pyrogram/types/user_and_chats/restriction.py index fd8b7a63fe..e3acd250be 100644 --- a/pyrogram/types/user_and_chats/restriction.py +++ b/pyrogram/types/user_and_chats/restriction.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/types/user_and_chats/user.py b/pyrogram/types/user_and_chats/user.py index f0f3f7d5c4..643284a0ae 100644 --- a/pyrogram/types/user_and_chats/user.py +++ b/pyrogram/types/user_and_chats/user.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/pyrogram/utils.py b/pyrogram/utils.py index f3ac7dbc4f..bc169dedf5 100644 --- a/pyrogram/utils.py +++ b/pyrogram/utils.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/setup.py b/setup.py index d7dcb5fcc5..c2e2ce96b9 100644 --- a/setup.py +++ b/setup.py @@ -1,5 +1,5 @@ # Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Copyright (C) 2017-2021 Dan # # This file is part of Pyrogram. # diff --git a/tests/__init__.py b/tests/__init__.py index 00a6c16df9..4ad4f32b47 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,17 +1,17 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . diff --git a/tests/test_file_id.py b/tests/test_file_id.py index 4760cf4b7b..493590ab11 100644 --- a/tests/test_file_id.py +++ b/tests/test_file_id.py @@ -1,20 +1,20 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2020 Dan +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan # -# This file is part of Pyrogram. +# 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 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. +# 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 . +# You should have received a copy of the GNU Lesser General Public License +# along with Pyrogram. If not, see . import pytest From a48d27f501b73eee842384723c33db298ce1e6c3 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 2 Jan 2021 18:45:43 +0100 Subject: [PATCH 0452/1185] Always run crypto-related functions in the dedicated thread --- pyrogram/__init__.py | 2 +- .../connection/transport/tcp/tcp_abridged_o.py | 8 +++----- pyrogram/session/session.py | 14 +++++++++----- pyrogram/utils.py | 8 -------- 4 files changed, 13 insertions(+), 19 deletions(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 987b784898..7733e0f700 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -46,4 +46,4 @@ class ContinuePropagation(StopAsyncIteration): CRYPTO_EXECUTOR_SIZE_THRESHOLD = 512 -crypto_executor = ThreadPoolExecutor(2, thread_name_prefix="CryptoWorker") +crypto_executor = ThreadPoolExecutor(1, thread_name_prefix="CryptoWorker") diff --git a/pyrogram/connection/transport/tcp/tcp_abridged_o.py b/pyrogram/connection/transport/tcp/tcp_abridged_o.py index b05cad1628..9db148bf2f 100644 --- a/pyrogram/connection/transport/tcp/tcp_abridged_o.py +++ b/pyrogram/connection/transport/tcp/tcp_abridged_o.py @@ -18,11 +18,9 @@ import logging import os - from typing import Optional -from pyrogram import utils - +import pyrogram from pyrogram.crypto import aes from .tcp import TCP @@ -60,7 +58,7 @@ async def connect(self, address: tuple): async def send(self, data: bytes, *args): length = len(data) // 4 data = (bytes([length]) if length <= 126 else b"\x7f" + length.to_bytes(3, "little")) + data - payload = await utils.maybe_run_in_executor(aes.ctr256_encrypt, data, len(data), self.loop, *self.encrypt) + payload = await self.loop.run_in_executor(pyrogram.crypto_executor, aes.ctr256_encrypt, data, *self.encrypt) await super().send(payload) @@ -85,4 +83,4 @@ async def recv(self, length: int = 0) -> Optional[bytes]: if data is None: return None - return await utils.maybe_run_in_executor(aes.ctr256_decrypt, data, len(data), self.loop, *self.decrypt) + return await self.loop.run_in_executor(pyrogram.crypto_executor, aes.ctr256_decrypt, data, *self.decrypt) diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index 6ce342eaf2..686531e386 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -25,7 +25,7 @@ from io import BytesIO import pyrogram -from pyrogram import __copyright__, __license__, __version__, utils +from pyrogram import __copyright__, __license__, __version__ from pyrogram import raw from pyrogram.connection import Connection from pyrogram.crypto import mtproto @@ -217,8 +217,10 @@ async def restart(self): await self.start() async def handle_packet(self, packet): - data = await utils.maybe_run_in_executor( - mtproto.unpack, BytesIO(packet), len(packet), self.loop, + data = await self.loop.run_in_executor( + pyrogram.crypto_executor, + mtproto.unpack, + BytesIO(packet), self.session_id, self.auth_key, self.auth_key_id @@ -360,8 +362,10 @@ async def _send(self, data: TLObject, wait_response: bool = True, timeout: float log.debug(f"Sent:") log.debug(message) - payload = await utils.maybe_run_in_executor( - mtproto.pack, message, len(message), self.loop, + payload = await self.loop.run_in_executor( + pyrogram.crypto_executor, + mtproto.pack, + message, self.current_salt.salt, self.session_id, self.auth_key, diff --git a/pyrogram/utils.py b/pyrogram/utils.py index bc169dedf5..1082dadec9 100644 --- a/pyrogram/utils.py +++ b/pyrogram/utils.py @@ -309,11 +309,3 @@ async def parse_text_entities( "message": text, "entities": entities } - - -async def maybe_run_in_executor(func, data, length, loop, *args): - return ( - func(data, *args) - if length <= pyrogram.CRYPTO_EXECUTOR_SIZE_THRESHOLD - else await loop.run_in_executor(pyrogram.crypto_executor, func, data, *args) - ) From 59ca3bcd3589ec70d848e9b278c0e0aa23de5bb8 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 2 Jan 2021 18:46:18 +0100 Subject: [PATCH 0453/1185] Update Pyrogram to v1.1.8 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 7733e0f700..d82956605a 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "1.1.7" +__version__ = "1.1.8" __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)" __copyright__ = "Copyright (C) 2017-2020 Dan " From d28db9612894068de2255944698c7283b0a9e652 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 3 Jan 2021 20:56:47 +0100 Subject: [PATCH 0454/1185] Add new errors --- compiler/errors/compiler.py | 1 + compiler/errors/source/400_BAD_REQUEST.tsv | 6 ++++++ compiler/errors/source/420_FLOOD.tsv | 1 + 3 files changed, 8 insertions(+) diff --git a/compiler/errors/compiler.py b/compiler/errors/compiler.py index 23948d8558..d5cccaf4f9 100644 --- a/compiler/errors/compiler.py +++ b/compiler/errors/compiler.py @@ -95,6 +95,7 @@ def start(): error_id, error_message = row sub_class = caml(re.sub(r"_X", "_", error_id)) + sub_class = re.sub(r"^2", "Two", sub_class) f_all.write(" \"{}\": \"{}\",\n".format(error_id, sub_class)) diff --git a/compiler/errors/source/400_BAD_REQUEST.tsv b/compiler/errors/source/400_BAD_REQUEST.tsv index 02e6902372..5897eb98bf 100644 --- a/compiler/errors/source/400_BAD_REQUEST.tsv +++ b/compiler/errors/source/400_BAD_REQUEST.tsv @@ -17,6 +17,7 @@ AUTH_TOKEN_INVALID An invalid authorization token was provided AUTOARCHIVE_NOT_AVAILABLE This feature is not yet enabled for your account due to it not receiving too many private messages from strangers BANK_CARD_NUMBER_INVALID The credit card number is invalid BANNED_RIGHTS_INVALID You provided a set of restrictions that is invalid +BASE_PORT_LOC_INVALID The base port location is invalid BOTS_TOO_MUCH The chat has too many bots BOT_CHANNELS_NA Bots can't edit admin privileges BOT_COMMAND_DESCRIPTION_INVALID The command description was empty, too long or had invalid characters @@ -120,13 +121,16 @@ FOLDER_ID_EMPTY The folder you tried to delete was already empty FOLDER_ID_INVALID The folder id is invalid FRESH_CHANGE_ADMINS_FORBIDDEN You can't change administrator settings in this chat because your session was logged-in recently FROM_MESSAGE_BOT_DISABLED Bots can't use fromMessage min constructors +FROM_PEER_INVALID The from peer value is invalid GAME_BOT_INVALID You cannot send that game with the current bot GEO_POINT_INVALID Invalid geo point provided GIF_CONTENT_TYPE_INVALID GIF content-type invalid GIF_ID_INVALID The provided gif/animation id is invalid GRAPH_INVALID_RELOAD Invalid graph token provided, please reload the stats and provide the updated token GRAPH_OUTDATED_RELOAD The graph data is outdated +GROUPCALL_SSRC_DUPLICATE_MUCH Too many group call synchronization source duplicates GROUPED_MEDIA_INVALID The album contains invalid media +GROUP_CALL_INVALID The group call is invalid HASH_INVALID The provided hash is invalid IMAGE_PROCESS_FAILED The server failed to process your image INLINE_RESULT_EXPIRED The inline bot query expired @@ -170,6 +174,7 @@ MSG_WAIT_FAILED A waiting call returned an error MULTI_MEDIA_TOO_LONG The album/media group contains too many items NEW_SALT_INVALID The new salt is invalid NEW_SETTINGS_INVALID The new settings are invalid +NEXT_OFFSET_INVALID The next offset value is invalid OFFSET_INVALID The offset parameter is invalid OFFSET_PEER_ID_INVALID The provided offset peer is invalid OPTIONS_TOO_MUCH The poll options are too many @@ -238,6 +243,7 @@ RANDOM_LENGTH_INVALID The random length is invalid RANGES_INVALID Invalid range provided REACTION_EMPTY The reaction provided is empty REACTION_INVALID Invalid reaction provided (only emoji are allowed) +REFLECTOR_NOT_AVAILABLE The call reflector is not available REPLY_MARKUP_BUY_EMPTY Reply markup for buy button empty REPLY_MARKUP_GAME_EMPTY The provided reply markup for the game is empty REPLY_MARKUP_INVALID The provided reply markup is invalid diff --git a/compiler/errors/source/420_FLOOD.tsv b/compiler/errors/source/420_FLOOD.tsv index a8a573351e..9afa1fa067 100644 --- a/compiler/errors/source/420_FLOOD.tsv +++ b/compiler/errors/source/420_FLOOD.tsv @@ -1,4 +1,5 @@ id message +2FA_CONFIRM_WAIT_X A wait of {x} seconds is required because this account is active and protected by a 2FA password FLOOD_TEST_PHONE_WAIT_X A wait of {x} seconds is required in the test servers FLOOD_WAIT_X A wait of {x} seconds is required SLOWMODE_WAIT_X A wait of {x} seconds is required to send messages in this chat. From 15f95a07065b75e28a7b7f517b24d6680e32dc44 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 5 Jan 2021 14:43:45 +0100 Subject: [PATCH 0455/1185] Update README.md --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a6f0b6cf42..3b139023c4 100644 --- a/README.md +++ b/README.md @@ -34,9 +34,9 @@ async def hello(client, message): app.run() ``` -**Pyrogram** is a modern, elegant and easy-to-use [Telegram](https://telegram.org/) 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://docs.pyrogram.org/topics/mtproto-vs-botapi). +**Pyrogram** is a modern, elegant and easy-to-use [Telegram](https://telegram.org/) client library framework written +from the ground up in Python and C. It enables you to easily create custom Telegram client applications for both user +and bot identities (bot API alternative) via the [MTProto API](https://docs.pyrogram.org/topics/mtproto-vs-botapi). ### Features From 51f771457c99e084627490f2a755afe4c9b19f28 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 8 Jan 2021 09:02:29 +0100 Subject: [PATCH 0456/1185] Fix captions not being preserved when editing media --- pyrogram/methods/messages/edit_message_media.py | 8 +++++++- pyrogram/methods/messages/send_media_group.py | 2 +- pyrogram/types/input_media/input_media_animation.py | 5 +++-- pyrogram/types/input_media/input_media_audio.py | 5 +++-- pyrogram/types/input_media/input_media_document.py | 5 +++-- pyrogram/types/input_media/input_media_photo.py | 5 +++-- pyrogram/types/input_media/input_media_video.py | 5 +++-- 7 files changed, 23 insertions(+), 12 deletions(-) diff --git a/pyrogram/methods/messages/edit_message_media.py b/pyrogram/methods/messages/edit_message_media.py index 17625115d1..bd8ba6060d 100644 --- a/pyrogram/methods/messages/edit_message_media.py +++ b/pyrogram/methods/messages/edit_message_media.py @@ -80,6 +80,11 @@ async def edit_message_media( caption = media.caption parse_mode = media.parse_mode + message, entities = None, None + + if caption is not None: + message, entities = (await self.parser.parse(caption, parse_mode)).values() + if isinstance(media, types.InputMediaPhoto): if os.path.isfile(media.media): media = await self.send( @@ -253,7 +258,8 @@ async def edit_message_media( id=message_id, media=media, reply_markup=reply_markup.write() if reply_markup else None, - **await self.parser.parse(caption, parse_mode) + message=message, + entities=entities ) ) diff --git a/pyrogram/methods/messages/send_media_group.py b/pyrogram/methods/messages/send_media_group.py index d591c99b55..eb1660ec3b 100644 --- a/pyrogram/methods/messages/send_media_group.py +++ b/pyrogram/methods/messages/send_media_group.py @@ -265,7 +265,7 @@ async def send_media_group( raw.types.InputSingleMedia( media=media, random_id=self.rnd_id(), - **await self.parser.parse(i.caption, i.parse_mode) + **await self.parser.parse(i.caption or "", i.parse_mode) ) ) diff --git a/pyrogram/types/input_media/input_media_animation.py b/pyrogram/types/input_media/input_media_animation.py index 82e7591920..a209b80d71 100644 --- a/pyrogram/types/input_media/input_media_animation.py +++ b/pyrogram/types/input_media/input_media_animation.py @@ -38,7 +38,8 @@ class InputMediaAnimation(InputMedia): Thumbnails can't be reused and can be only uploaded as a new file. caption (``str``, *optional*): - Caption of the animation to be sent, 0-1024 characters + Caption of the animation to be sent, 0-1024 characters. + If not specified, the original caption is kept. Pass "" (empty string) to remove the caption. parse_mode (``str``, *optional*): By default, texts are parsed using both Markdown and HTML styles. @@ -64,7 +65,7 @@ def __init__( self, media: str, thumb: str = None, - caption: str = "", + caption: str = None, parse_mode: Optional[str] = object, caption_entities: List[MessageEntity] = None, width: int = 0, diff --git a/pyrogram/types/input_media/input_media_audio.py b/pyrogram/types/input_media/input_media_audio.py index ff7bd085f2..e98d283c55 100644 --- a/pyrogram/types/input_media/input_media_audio.py +++ b/pyrogram/types/input_media/input_media_audio.py @@ -40,7 +40,8 @@ class InputMediaAudio(InputMedia): Thumbnails can't be reused and can be only uploaded as a new file. caption (``str``, *optional*): - Caption of the audio to be sent, 0-1024 characters + Caption of the audio to be sent, 0-1024 characters. + If not specified, the original caption is kept. Pass "" (empty string) to remove the caption. parse_mode (``str``, *optional*): By default, texts are parsed using both Markdown and HTML styles. @@ -66,7 +67,7 @@ def __init__( self, media: str, thumb: str = None, - caption: str = "", + caption: str = None, parse_mode: Optional[str] = object, caption_entities: List[MessageEntity] = None, duration: int = 0, diff --git a/pyrogram/types/input_media/input_media_document.py b/pyrogram/types/input_media/input_media_document.py index b1d6b4a1b0..2701c0ae31 100644 --- a/pyrogram/types/input_media/input_media_document.py +++ b/pyrogram/types/input_media/input_media_document.py @@ -38,7 +38,8 @@ class InputMediaDocument(InputMedia): Thumbnails can't be reused and can be only uploaded as a new file. caption (``str``, *optional*): - Caption of the document to be sent, 0-1024 characters + Caption of the document to be sent, 0-1024 characters. + If not specified, the original caption is kept. Pass "" (empty string) to remove the caption. parse_mode (``str``, *optional*): By default, texts are parsed using both Markdown and HTML styles. @@ -55,7 +56,7 @@ def __init__( self, media: str, thumb: str = None, - caption: str = "", + caption: str = None, parse_mode: Optional[str] = object, caption_entities: List[MessageEntity] = None ): diff --git a/pyrogram/types/input_media/input_media_photo.py b/pyrogram/types/input_media/input_media_photo.py index d675ad19b5..ac1cc4441f 100644 --- a/pyrogram/types/input_media/input_media_photo.py +++ b/pyrogram/types/input_media/input_media_photo.py @@ -34,7 +34,8 @@ class InputMediaPhoto(InputMedia): Sending photo by a URL is currently unsupported. caption (``str``, *optional*): - Caption of the photo to be sent, 0-1024 characters + Caption of the photo to be sent, 0-1024 characters. + If not specified, the original caption is kept. Pass "" (empty string) to remove the caption. parse_mode (``str``, *optional*): By default, texts are parsed using both Markdown and HTML styles. @@ -50,7 +51,7 @@ class InputMediaPhoto(InputMedia): def __init__( self, media: str, - caption: str = "", + caption: str = None, parse_mode: Optional[str] = object, caption_entities: List[MessageEntity] = None ): diff --git a/pyrogram/types/input_media/input_media_video.py b/pyrogram/types/input_media/input_media_video.py index aef5edf186..13ce18d620 100644 --- a/pyrogram/types/input_media/input_media_video.py +++ b/pyrogram/types/input_media/input_media_video.py @@ -40,7 +40,8 @@ class InputMediaVideo(InputMedia): Thumbnails can't be reused and can be only uploaded as a new file. caption (``str``, *optional*): - Caption of the video to be sent, 0-1024 characters + Caption of the video to be sent, 0-1024 characters. + If not specified, the original caption is kept. Pass "" (empty string) to remove the caption. parse_mode (``str``, *optional*): By default, texts are parsed using both Markdown and HTML styles. @@ -69,7 +70,7 @@ def __init__( self, media: str, thumb: str = None, - caption: str = "", + caption: str = None, parse_mode: Optional[str] = object, caption_entities: List[MessageEntity] = None, width: int = 0, From ff5cabb3c8850b57466a37587ec3317d991ba064 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 8 Jan 2021 09:02:57 +0100 Subject: [PATCH 0457/1185] Update Pyrogram to v1.1.9 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index d82956605a..6e71b07a9b 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "1.1.8" +__version__ = "1.1.9" __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)" __copyright__ = "Copyright (C) 2017-2020 Dan " From c25871c452c5d90233dcfe0ab00efe6f8167fa23 Mon Sep 17 00:00:00 2001 From: Pari030 <37542814+Pari030@users.noreply.github.com> Date: Fri, 8 Jan 2021 10:00:45 +0100 Subject: [PATCH 0458/1185] Mention that members_count is obtained from get_chat only (#581) --- pyrogram/types/user_and_chats/chat.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyrogram/types/user_and_chats/chat.py b/pyrogram/types/user_and_chats/chat.py index a8b4a950af..e3d3d1dff4 100644 --- a/pyrogram/types/user_and_chats/chat.py +++ b/pyrogram/types/user_and_chats/chat.py @@ -98,6 +98,7 @@ class Chat(Object): members_count (``int``, *optional*): Chat members count, for groups, supergroups and channels only. + Returned only in :meth:`~pyrogram.Client.get_chat`. restrictions (List of :obj:`~pyrogram.types.Restriction`, *optional*): The list of reasons why this chat might be unavailable to some users. From 99829eff9cf4f50b2ae68e5e8fdfda905eb77d50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=83=9D=E3=82=AD?= Date: Sun, 10 Jan 2021 19:56:42 +0500 Subject: [PATCH 0459/1185] Update copyright year in forgotten places (#583) --- README.md | 2 +- docs/source/conf.py | 2 +- pyrogram/__init__.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 3b139023c4..f8c4432e5f 100644 --- a/README.md +++ b/README.md @@ -71,5 +71,5 @@ pip3 install pyrogram ### Copyright & License -- Copyright (C) 2017-2020 Dan <> +- Copyright (C) 2017-2021 Dan <> - Licensed under the terms of the [GNU Lesser General Public License v3 or later (LGPLv3+)](COPYING.lesser) diff --git a/docs/source/conf.py b/docs/source/conf.py index ddfc6479f0..ca1d54d9aa 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -28,7 +28,7 @@ FriendlyStyle.background_color = "#f3f2f1" project = "Pyrogram" -copyright = "2017-2020, Dan" +copyright = "2017-2021, Dan" author = "Dan" extensions = [ diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 6e71b07a9b..08031e3edc 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -18,7 +18,7 @@ __version__ = "1.1.9" __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)" -__copyright__ = "Copyright (C) 2017-2020 Dan " +__copyright__ = "Copyright (C) 2017-2021 Dan " from concurrent.futures.thread import ThreadPoolExecutor From 38d60f5e1009f517f57480ec2240dc261c0c70eb Mon Sep 17 00:00:00 2001 From: Ripe <42308266+Ripeey@users.noreply.github.com> Date: Sun, 10 Jan 2021 14:59:27 +0000 Subject: [PATCH 0460/1185] Fix DC sessions for inline message edits (#585) --- pyrogram/methods/messages/inline_session.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/pyrogram/methods/messages/inline_session.py b/pyrogram/methods/messages/inline_session.py index 34b92a2eb6..3ea863696c 100644 --- a/pyrogram/methods/messages/inline_session.py +++ b/pyrogram/methods/messages/inline_session.py @@ -26,13 +26,9 @@ lock = Lock() session = None -dest_dc_id = 4 async def get_session(client: "pyrogram.Client", dc_id: int): - if dc_id != dest_dc_id: - return client - if dc_id == await client.storage.dc_id(): return client @@ -43,8 +39,8 @@ async def get_session(client: "pyrogram.Client", dc_id: int): return session session = Session( - client, dest_dc_id, - await Auth(client, dest_dc_id, False).create(), + client, dc_id, + await Auth(client, dc_id, False).create(), False, is_media=True ) @@ -53,7 +49,7 @@ async def get_session(client: "pyrogram.Client", dc_id: int): for _ in range(3): exported_auth = await client.send( raw.functions.auth.ExportAuthorization( - dc_id=dest_dc_id + dc_id=dc_id ) ) From 79583a29fc199f699eb13ab9d181fc0ca7fa1d68 Mon Sep 17 00:00:00 2001 From: Krishna-singhal <65902764+Krishna-Singhal@users.noreply.github.com> Date: Sun, 10 Jan 2021 20:30:38 +0530 Subject: [PATCH 0461/1185] Remove obsolete example from docs (#576) --- pyrogram/methods/messages/forward_messages.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/pyrogram/methods/messages/forward_messages.py b/pyrogram/methods/messages/forward_messages.py index 1959944d96..f8bc9b277c 100644 --- a/pyrogram/methods/messages/forward_messages.py +++ b/pyrogram/methods/messages/forward_messages.py @@ -70,9 +70,6 @@ async def forward_messages( # Forward multiple messages at once app.forward_messages("me", "pyrogram", [3, 20, 27]) - - # Forward messages as copy - app.forward_messages("me", "pyrogram", 20, as_copy=True) """ is_iterable = not isinstance(message_ids, int) From 2ad1d9cbc8e41041aa89c1bdc85df6f8a7a93cc0 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 10 Jan 2021 16:46:16 +0100 Subject: [PATCH 0462/1185] Fix wrong excepted error Closes #582 --- pyrogram/raw/core/primitives/vector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/raw/core/primitives/vector.py b/pyrogram/raw/core/primitives/vector.py index a2be7b4f50..5b2f9ba6f5 100644 --- a/pyrogram/raw/core/primitives/vector.py +++ b/pyrogram/raw/core/primitives/vector.py @@ -33,7 +33,7 @@ class Vector(bytes, TLObject): def _read(b: BytesIO) -> Union[int, Any]: try: return TLObject.read(b) - except KeyError: + except ValueError: b.seek(-4, 1) return Int.read(b) From 63cc36d7599d0106f8570dfb4965ce12c7e07ebe Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 10 Jan 2021 16:46:47 +0100 Subject: [PATCH 0463/1185] Update Pyrogram to v1.1.10 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 08031e3edc..ed40eced93 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "1.1.9" +__version__ = "1.1.10" __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)" __copyright__ = "Copyright (C) 2017-2021 Dan " From 3c992e8f86c0adda7ed08d2bcbe529cda0086d07 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 12 Jan 2021 22:25:19 +0100 Subject: [PATCH 0464/1185] Add missing Message.unpin docs --- compiler/docs/compiler.py | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index b048abdf79..b262b99e33 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -436,6 +436,7 @@ def get_title_list(s: str) -> list: Message.forward Message.copy Message.pin + Message.unpin Message.edit_text Message.edit_caption Message.edit_media From 4b3fc729ade543dce7ace28e7bcdb4d9a3e2e7c4 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 12 Jan 2021 22:26:24 +0100 Subject: [PATCH 0465/1185] Fix small typo: Message.unpin was pinning --- pyrogram/types/messages_and_media/message.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index 3fd800b66a..0ac26f2366 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -3295,7 +3295,7 @@ async def unpin(self) -> bool: Raises: RPCError: In case of a Telegram RPC error. """ - return await self._client.pin_chat_message( + return await self._client.unpin_chat_message( chat_id=self.chat.id, message_id=self.message_id ) From 27614c0c1945c225f00791d78e7281db281c2fc6 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 12 Jan 2021 22:26:59 +0100 Subject: [PATCH 0466/1185] Update Pyrogram to v1.1.11 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index ed40eced93..5ce58b3121 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "1.1.10" +__version__ = "1.1.11" __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)" __copyright__ = "Copyright (C) 2017-2021 Dan " From 54b92c0892b6524c8277d9cb11f973a6398ed1d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=83=9D=E3=82=AD?= Date: Fri, 15 Jan 2021 01:49:51 +0500 Subject: [PATCH 0467/1185] Fix issues with global_search(): add the filter parameter (#589) * this commit will fix issues with global_search() I was recently getting this error on app.global_search() method: ``` File "/home/poki/.local/lib/python3.8/site-packages/pyrogram/methods/messages/search_global.py", line 71, in search_global raw.functions.messages.SearchGlobal( TypeError: __init__() missing 3 required keyword-only arguments: 'filter', 'min_date', and 'max_date' ``` Suprisingly no one has opened an issue for this except me. Here is the context: https://t.me/pyrogramchat/281087 I personally use this method to fetch my global searches into my userbot with is an actual bot. little hacky >_o * Added filter= parameter - An Optional Parameter for global search * Update search_global.py Co-authored-by: Dan <14043624+delivrance@users.noreply.github.com> --- pyrogram/methods/messages/search_global.py | 56 +++++++++++++++++++++- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/pyrogram/methods/messages/search_global.py b/pyrogram/methods/messages/search_global.py index 1e65b2120b..bac5d8049b 100644 --- a/pyrogram/methods/messages/search_global.py +++ b/pyrogram/methods/messages/search_global.py @@ -24,10 +24,31 @@ from pyrogram.scaffold import Scaffold +class Filters: + EMPTY = raw.types.InputMessagesFilterEmpty() + PHOTO = raw.types.InputMessagesFilterPhotos() + VIDEO = raw.types.InputMessagesFilterVideo() + PHOTO_VIDEO = raw.types.InputMessagesFilterPhotoVideo() + DOCUMENT = raw.types.InputMessagesFilterDocument() + URL = raw.types.InputMessagesFilterUrl() + ANIMATION = raw.types.InputMessagesFilterGif() + VOICE_NOTE = raw.types.InputMessagesFilterVoice() + AUDIO = raw.types.InputMessagesFilterMusic() + CHAT_PHOTO = raw.types.InputMessagesFilterChatPhotos() + AUDIO_VIDEO_NOTE = raw.types.InputMessagesFilterRoundVideo() + VIDEO_NOTE = raw.types.InputMessagesFilterRoundVideo() + LOCATION = raw.types.InputMessagesFilterGeo() + CONTACT = raw.types.InputMessagesFilterContacts() + + +POSSIBLE_VALUES = list(map(lambda x: x.lower(), filter(lambda x: not x.startswith("__"), Filters.__dict__.keys()))) + + class SearchGlobal(Scaffold): async def search_global( self, - query: str, + query: str = "", + filter: str = "empty", limit: int = 0, ) -> Optional[AsyncGenerator["types.Message", None]]: """Search messages globally from all of your chats. @@ -38,8 +59,27 @@ async def search_global( retrieved will not have any *reply_to_message* field. Parameters: - query (``str``): + query (``str``, *optional*): Text query string. + Use "@" to search for mentions. + + filter (``str``, *optional*): + Pass a filter in order to search for specific kind of messages only: + + - ``"empty"``: Search for all kind of messages (default). + - ``"photo"``: Search for photos. + - ``"video"``: Search for video. + - ``"photo_video"``: Search for either photo or video. + - ``"document"``: Search for documents (generic files). + - ``"url"``: Search for messages containing URLs (web links). + - ``"animation"``: Search for animations (GIFs). + - ``"voice_note"``: Search for voice notes. + - ``"audio"``: Search for audio files (music). + - ``"chat_photo"``: Search for chat photos. + - ``"audio_video_note"``: Search for either audio or video notes. + - ``"video_note"``: Search for video notes. + - ``"location"``: Search for location messages. + - ``"contact"``: Search for contact messages. limit (``int``, *optional*): Limits the number of messages to be retrieved. @@ -54,7 +94,16 @@ async def search_global( # Search for "pyrogram". Get the first 420 results for message in app.search_global("pyrogram", limit=420): print(message.text) + + # Search for recent photos from Global. Get the first 69 results + for message in app.search_global(filter="photo", limit=69): + print(message.photo) """ + try: + filter = Filters.__dict__[filter.upper()] + except KeyError: + raise ValueError('Invalid filter "{}". Possible values are: {}'.format( + filter, ", ".join(f'"{v}"' for v in POSSIBLE_VALUES))) from None current = 0 # There seems to be an hard limit of 10k, beyond which Telegram starts spitting one message at a time. total = abs(limit) or (1 << 31) @@ -70,6 +119,9 @@ async def search_global( await self.send( raw.functions.messages.SearchGlobal( q=query, + filter=filter, + min_date=0, + max_date=0, offset_rate=offset_date, offset_peer=offset_peer, offset_id=offset_id, From 2b3e5f2b0a7e4e8c5c6fa3190329641e0535e84a Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 14 Jan 2021 21:51:10 +0100 Subject: [PATCH 0468/1185] Update Pyrogram to v1.1.12 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 5ce58b3121..1144c75f8c 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "1.1.11" +__version__ = "1.1.12" __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)" __copyright__ = "Copyright (C) 2017-2021 Dan " From 967f25796c0e315e4c44eaab39780dd5d2614494 Mon Sep 17 00:00:00 2001 From: Harsh <65716674+Harsh-br0@users.noreply.github.com> Date: Sun, 17 Jan 2021 16:46:06 +0530 Subject: [PATCH 0469/1185] Fix strikethrough message entity unparsing (#598) --- pyrogram/parser/html.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/parser/html.py b/pyrogram/parser/html.py index 4f4309de2e..ac9984f6aa 100644 --- a/pyrogram/parser/html.py +++ b/pyrogram/parser/html.py @@ -153,7 +153,7 @@ def unparse(text: str, entities: list): start = entity.offset end = start + entity.length - if entity_type in ("bold", "italic", "underline", "strike"): + if entity_type in ("bold", "italic", "underline", "strikethrough"): start_tag = f"<{entity_type[0]}>" end_tag = f"" elif entity_type in ("code", "pre", "blockquote"): From b9adc5c294c5416c84c4cfbcba73b4dad9a6a3d3 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 18 Jan 2021 07:29:33 +0100 Subject: [PATCH 0470/1185] Add missing bound method Chat.mark_unread --- pyrogram/types/user_and_chats/chat.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/pyrogram/types/user_and_chats/chat.py b/pyrogram/types/user_and_chats/chat.py index e3d3d1dff4..ca5a49e6bf 100644 --- a/pyrogram/types/user_and_chats/chat.py +++ b/pyrogram/types/user_and_chats/chat.py @@ -868,3 +868,23 @@ async def add_members( user_ids=user_ids, forward_limit=forward_limit ) + + async def mark_unread(self, ) -> bool: + """Bound method *mark_unread* of :obj:`~pyrogram.types.Chat`. + + Use as a shortcut for: + + .. code-block:: python + + client.mark_unread(chat_id) + + Example: + .. code-block:: python + + chat.mark_unread() + + Returns: + ``bool``: On success, True is returned. + """ + + return await self._client.mark_chat_unread(self.id) From f9a129ab5fd6be3db32ea76b73133eab47e84608 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 18 Jan 2021 07:29:51 +0100 Subject: [PATCH 0471/1185] Fix wrong emphasize lines --- pyrogram/methods/messages/forward_messages.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/methods/messages/forward_messages.py b/pyrogram/methods/messages/forward_messages.py index f8bc9b277c..b67e50ec0b 100644 --- a/pyrogram/methods/messages/forward_messages.py +++ b/pyrogram/methods/messages/forward_messages.py @@ -63,7 +63,7 @@ async def forward_messages( Example: .. code-block:: python - :emphasize-lines: 2,5,8 + :emphasize-lines: 2,5 # Forward a single message app.forward_messages("me", "pyrogram", 20) From 33d373adbe6baa35f2791c7d6d56713ed0d562d6 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 18 Jan 2021 07:31:06 +0100 Subject: [PATCH 0472/1185] Update Pyrogram to v1.1.13 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 1144c75f8c..907a49e1a9 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "1.1.12" +__version__ = "1.1.13" __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)" __copyright__ = "Copyright (C) 2017-2021 Dan " From c3819ddb56cccb6c28155032a61c3d1b58403675 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 28 Jan 2021 13:03:39 +0100 Subject: [PATCH 0473/1185] Update API schema to Layer 123 --- compiler/api/source/main_api.tl | 47 +++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl index 3885d62c15..a68e527eec 100644 --- a/compiler/api/source/main_api.tl +++ b/compiler/api/source/main_api.tl @@ -87,7 +87,7 @@ storage.fileMp4#b3cea0e4 = storage.FileType; storage.fileWebp#1081464c = storage.FileType; userEmpty#200250ba id:int = User; -user#938458c1 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true apply_min_photo:flags.25?true id:int access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector bot_inline_placeholder:flags.19?string lang_code:flags.22?string = User; +user#938458c1 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true apply_min_photo:flags.25?true fake:flags.26?true id:int access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector bot_inline_placeholder:flags.19?string lang_code:flags.22?string = User; userProfilePhotoEmpty#4f11bae1 = UserProfilePhoto; userProfilePhoto#69d3ab26 flags:# has_video:flags.0?true photo_id:long photo_small:FileLocation photo_big:FileLocation dc_id:int = UserProfilePhoto; @@ -102,11 +102,11 @@ userStatusLastMonth#77ebc742 = UserStatus; chatEmpty#9ba2d800 id:int = Chat; chat#3bda1bde flags:# creator:flags.0?true kicked:flags.1?true left:flags.2?true deactivated:flags.5?true call_active:flags.23?true call_not_empty:flags.24?true id:int title:string photo:ChatPhoto participants_count:int date:int version:int migrated_to:flags.6?InputChannel admin_rights:flags.14?ChatAdminRights default_banned_rights:flags.18?ChatBannedRights = Chat; chatForbidden#7328bdb id:int title:string = Chat; -channel#d31a961e flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true id:int access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int version:int restriction_reason:flags.9?Vector admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int = Chat; +channel#d31a961e flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true fake:flags.25?true id:int access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int version:int restriction_reason:flags.9?Vector admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int = Chat; channelForbidden#289da732 flags:# broadcast:flags.5?true megagroup:flags.8?true id:int access_hash:long title:string until_date:flags.16?int = Chat; -chatFull#dc8c181 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:int about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall = ChatFull; -channelFull#ef3a6acd flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true id:int about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?int migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?int location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall = ChatFull; +chatFull#f3474af6 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:int about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall = ChatFull; +channelFull#7a7de4f7 flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true id:int about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?int migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?int location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall = ChatFull; chatParticipant#c8d7493e user_id:int inviter_id:int date:int = ChatParticipant; chatParticipantCreator#da13538a user_id:int = ChatParticipant; @@ -118,7 +118,7 @@ chatParticipants#3f460fed chat_id:int participants:Vector versi chatPhotoEmpty#37c1011c = ChatPhoto; chatPhoto#d20b9f3c flags:# has_video:flags.0?true photo_small:FileLocation photo_big:FileLocation dc_id:int = ChatPhoto; -messageEmpty#83e5de54 id:int = Message; +messageEmpty#90a6ca84 flags:# id:int peer_id:flags.0?Peer = Message; message#58ae39c9 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true id:int from_id:flags.8?Peer peer_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long restriction_reason:flags.22?Vector = Message; messageService#286fa604 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true legacy:flags.19?true id:int from_id:flags.8?Peer peer_id:Peer reply_to:flags.3?MessageReplyHeader date:int action:MessageAction = Message; @@ -195,7 +195,7 @@ inputPeerNotifySettings#9c3d198e flags:# show_previews:flags.0?Bool silent:flags peerNotifySettings#af509d20 flags:# show_previews:flags.0?Bool silent:flags.1?Bool mute_until:flags.2?int sound:flags.3?string = PeerNotifySettings; -peerSettings#733f2961 flags:# report_spam:flags.0?true add_contact:flags.1?true block_contact:flags.2?true share_contact:flags.3?true need_contacts_exception:flags.4?true report_geo:flags.5?true autoarchived:flags.7?true geo_distance:flags.6?int = PeerSettings; +peerSettings#733f2961 flags:# report_spam:flags.0?true add_contact:flags.1?true block_contact:flags.2?true share_contact:flags.3?true need_contacts_exception:flags.4?true report_geo:flags.5?true autoarchived:flags.7?true invite_members:flags.8?true geo_distance:flags.6?int = PeerSettings; wallPaper#a437c3ed id:long flags:# creator:flags.0?true default:flags.1?true pattern:flags.3?true dark:flags.4?true access_hash:long slug:string document:Document settings:flags.2?WallPaperSettings = WallPaper; wallPaperNoFile#8af40b25 flags:# default:flags.1?true dark:flags.4?true settings:flags.2?WallPaperSettings = WallPaper; @@ -207,6 +207,7 @@ inputReportReasonChildAbuse#adf44ee3 = ReportReason; inputReportReasonOther#e1746d0a text:string = ReportReason; inputReportReasonCopyright#9b89f93a = ReportReason; inputReportReasonGeoIrrelevant#dbd4feed = ReportReason; +inputReportReasonFake#f5ddd6e7 = ReportReason; userFull#edf17c12 flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true user:User about:flags.1?string settings:PeerSettings profile_photo:flags.2?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int = UserFull; @@ -385,7 +386,7 @@ encryptedChatEmpty#ab7ec0a0 id:int = EncryptedChat; encryptedChatWaiting#3bf703dc id:int access_hash:long date:int admin_id:int participant_id:int = EncryptedChat; encryptedChatRequested#62718a82 flags:# folder_id:flags.0?int id:int access_hash:long date:int admin_id:int participant_id:int g_a:bytes = EncryptedChat; encryptedChat#fa56ce36 id:int access_hash:long date:int admin_id:int participant_id:int g_a_or_b:bytes key_fingerprint:long = EncryptedChat; -encryptedChatDiscarded#13d6dd27 id:int = EncryptedChat; +encryptedChatDiscarded#1e1c7c45 flags:# history_deleted:flags.0?true id:int = EncryptedChat; inputEncryptedChat#f141b5e1 chat_id:int access_hash:long = InputEncryptedChat; @@ -433,6 +434,7 @@ sendMessageGamePlayAction#dd6a8f48 = SendMessageAction; sendMessageRecordRoundAction#88f27fbc = SendMessageAction; sendMessageUploadRoundAction#243e1c66 progress:int = SendMessageAction; speakingInGroupCallAction#d92c2285 = SendMessageAction; +sendMessageHistoryImportAction#dbda9246 progress:int = SendMessageAction; contacts.found#b3134d9d my_results:Vector results:Vector chats:Vector users:Vector = contacts.Found; @@ -513,8 +515,7 @@ auth.passwordRecovery#137948a5 email_pattern:string = auth.PasswordRecovery; receivedNotifyMessage#a384b779 id:int flags:int = ReceivedNotifyMessage; -chatInviteEmpty#69df3769 = ExportedChatInvite; -chatInviteExported#fc2e05bc link:string = ExportedChatInvite; +chatInviteExported#6e24fc9d flags:# revoked:flags.0?true permanent:flags.5?true link:string admin_id:int date:int start_date:flags.4?int expire_date:flags.1?int usage_limit:flags.2?int usage:flags.3?int = ExportedChatInvite; chatInviteAlready#5a686d7c chat:Chat = ChatInvite; chatInvite#dfc2f58e flags:# channel:flags.0?true broadcast:flags.1?true public:flags.2?true megagroup:flags.3?true title:string photo:Photo participants_count:int participants:flags.4?Vector = ChatInvite; @@ -639,7 +640,7 @@ messages.botResults#947ca848 flags:# gallery:flags.0?true query_id:long next_off exportedMessageLink#5dab1af4 link:string html:string = ExportedMessageLink; -messageFwdHeader#5f777dce flags:# from_id:flags.0?Peer from_name:flags.5?string date:int channel_post:flags.2?int post_author:flags.3?string saved_from_peer:flags.4?Peer saved_from_msg_id:flags.4?int psa_type:flags.6?string = MessageFwdHeader; +messageFwdHeader#5f777dce flags:# imported:flags.7?true from_id:flags.0?Peer from_name:flags.5?string date:int channel_post:flags.2?int post_author:flags.3?string saved_from_peer:flags.4?Peer saved_from_msg_id:flags.4?int psa_type:flags.6?string = MessageFwdHeader; auth.codeTypeSms#72a3158c = auth.CodeType; auth.codeTypeCall#741cd3e3 = auth.CodeType; @@ -798,7 +799,7 @@ payments.savedInfo#fb8fe43c flags:# has_saved_credentials:flags.1?true saved_inf inputPaymentCredentialsSaved#c10eb2cf id:string tmp_password:bytes = InputPaymentCredentials; inputPaymentCredentials#3417d728 flags:# save:flags.0?true data:DataJSON = InputPaymentCredentials; inputPaymentCredentialsApplePay#aa1c39f payment_data:DataJSON = InputPaymentCredentials; -inputPaymentCredentialsAndroidPay#ca05d50e payment_token:DataJSON google_transaction_id:string = InputPaymentCredentials; +inputPaymentCredentialsGooglePay#8ac32801 payment_token:DataJSON = InputPaymentCredentials; account.tmpPassword#db64fd34 tmp_password:bytes valid_until:int = account.TmpPassword; @@ -1173,7 +1174,7 @@ groupCall#55903081 flags:# join_muted:flags.1?true can_change_join_muted:flags.2 inputGroupCall#d8aa840f id:long access_hash:long = InputGroupCall; -groupCallParticipant#56b087c9 flags:# muted:flags.0?true left:flags.1?true can_self_unmute:flags.2?true just_joined:flags.4?true versioned:flags.5?true user_id:int date:int active_date:flags.3?int source:int = GroupCallParticipant; +groupCallParticipant#64c62a15 flags:# muted:flags.0?true left:flags.1?true can_self_unmute:flags.2?true just_joined:flags.4?true versioned:flags.5?true muted_by_you:flags.9?true user_id:int date:int active_date:flags.3?int source:int volume:flags.7?int = GroupCallParticipant; phone.groupCall#66ab0bfc call:GroupCall participants:Vector participants_next_offset:string users:Vector = phone.GroupCall; @@ -1185,6 +1186,12 @@ inlineQueryPeerTypeChat#d766c50a = InlineQueryPeerType; inlineQueryPeerTypeMegagroup#5ec4be43 = InlineQueryPeerType; inlineQueryPeerTypeBroadcast#6334ee9a = InlineQueryPeerType; +messages.historyImport#1662af0b id:long = messages.HistoryImport; + +messages.historyImportParsed#5e0fb7b9 flags:# pm:flags.0?true group:flags.1?true title:flags.2?string = messages.HistoryImportParsed; + +messages.affectedFoundMessages#ef8d3e6c pts:int pts_count:int offset:int messages:Vector = messages.AffectedFoundMessages; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -1327,12 +1334,12 @@ messages.getFullChat#3b831c66 chat_id:int = messages.ChatFull; messages.editChatTitle#dc452855 chat_id:int title:string = Updates; messages.editChatPhoto#ca4c79d8 chat_id:int photo:InputChatPhoto = Updates; messages.addChatUser#f9a0aa09 chat_id:int user_id:InputUser fwd_limit:int = Updates; -messages.deleteChatUser#e0611f16 chat_id:int user_id:InputUser = Updates; +messages.deleteChatUser#c534459a flags:# revoke_history:flags.0?true chat_id:int user_id:InputUser = Updates; messages.createChat#9cb126e users:Vector title:string = Updates; messages.getDhConfig#26cf8950 version:int random_length:int = messages.DhConfig; messages.requestEncryption#f64daf43 user_id:InputUser random_id:int g_a:bytes = EncryptedChat; messages.acceptEncryption#3dbc0415 peer:InputEncryptedChat g_b:bytes key_fingerprint:long = EncryptedChat; -messages.discardEncryption#edd923c5 chat_id:int = Bool; +messages.discardEncryption#f393aea0 flags:# delete_history:flags.0?true chat_id:int = Bool; messages.setEncryptedTyping#791451ed peer:InputEncryptedChat typing:Bool = Bool; messages.readEncryptedHistory#7f4b690a peer:InputEncryptedChat max_date:int = Bool; messages.sendEncrypted#44fa7a15 flags:# silent:flags.0?true peer:InputEncryptedChat random_id:long data:bytes = messages.SentEncryptedMessage; @@ -1434,6 +1441,12 @@ messages.getReplies#24b581ba peer:InputPeer msg_id:int offset_id:int offset_date messages.getDiscussionMessage#446972fd peer:InputPeer msg_id:int = messages.DiscussionMessage; messages.readDiscussion#f731a9f4 peer:InputPeer msg_id:int read_max_id:int = Bool; messages.unpinAllMessages#f025bc8b peer:InputPeer = messages.AffectedHistory; +messages.deleteChat#83247d11 chat_id:int = Bool; +messages.deletePhoneCallHistory#f9cbe409 flags:# revoke:flags.0?true = messages.AffectedFoundMessages; +messages.checkHistoryImport#43fe19f3 import_head:string = messages.HistoryImportParsed; +messages.initHistoryImport#34090c3b peer:InputPeer file:InputFile media_count:int = messages.HistoryImport; +messages.uploadImportedMedia#2a862092 peer:InputPeer import_id:long file_name:string media:InputMedia = MessageMedia; +messages.startHistoryImport#b43df344 peer:InputPeer import_id:long = Bool; updates.getState#edd4882a = updates.State; updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference; @@ -1485,7 +1498,7 @@ channels.getParticipants#123e05e9 channel:InputChannel filter:ChannelParticipant channels.getParticipant#546dd7a6 channel:InputChannel user_id:InputUser = channels.ChannelParticipant; channels.getChannels#a7f6bbb id:Vector = messages.Chats; channels.getFullChannel#8736a09 channel:InputChannel = messages.ChatFull; -channels.createChannel#3d5fb10f flags:# broadcast:flags.0?true megagroup:flags.1?true title:string about:string geo_point:flags.2?InputGeoPoint address:flags.2?string = Updates; +channels.createChannel#3d5fb10f flags:# broadcast:flags.0?true megagroup:flags.1?true for_import:flags.3?true title:string about:string geo_point:flags.2?InputGeoPoint address:flags.2?string = Updates; channels.editAdmin#d33c8902 channel:InputChannel user_id:InputUser admin_rights:ChatAdminRights rank:string = Updates; channels.editTitle#566decd0 channel:InputChannel title:string = Updates; channels.editPhoto#f12e57c9 channel:InputChannel photo:InputChatPhoto = Updates; @@ -1542,7 +1555,7 @@ phone.sendSignalingData#ff7a9383 peer:InputPhoneCall data:bytes = Bool; phone.createGroupCall#bd3dabe0 peer:InputPeer random_id:int = Updates; phone.joinGroupCall#5f9c8e62 flags:# muted:flags.0?true call:InputGroupCall params:DataJSON = Updates; phone.leaveGroupCall#500377f9 call:InputGroupCall source:int = Updates; -phone.editGroupCallMember#63146ae4 flags:# muted:flags.0?true call:InputGroupCall user_id:InputUser = Updates; +phone.editGroupCallMember#a5e76cd8 flags:# muted:flags.0?true call:InputGroupCall user_id:InputUser volume:flags.1?int = Updates; phone.inviteToGroupCall#7b393160 call:InputGroupCall users:Vector = Updates; phone.discardGroupCall#7a777135 call:InputGroupCall = Updates; phone.toggleGroupCallSettings#74bbb43d flags:# call:InputGroupCall join_muted:flags.0?Bool = Updates; @@ -1565,4 +1578,4 @@ stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats; -// LAYER 122 \ No newline at end of file +// LAYER 123 \ No newline at end of file From 811324b90757abaaaaa2aae93e4c69b7c1c270be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=83=9D=E3=82=AD?= Date: Fri, 29 Jan 2021 16:46:43 +0500 Subject: [PATCH 0474/1185] Add missing docstrings for Chat.iter_members and .get_members (#600) --- pyrogram/types/user_and_chats/chat.py | 81 ++++++++++++++++++++++++++- 1 file changed, 78 insertions(+), 3 deletions(-) diff --git a/pyrogram/types/user_and_chats/chat.py b/pyrogram/types/user_and_chats/chat.py index ca5a49e6bf..b44e8f9d98 100644 --- a/pyrogram/types/user_and_chats/chat.py +++ b/pyrogram/types/user_and_chats/chat.py @@ -791,12 +791,50 @@ async def get_members( client.get_chat_members(chat_id) + + Parameters: + offset (``int``, *optional*): + Sequential number of the first member to be returned. + Only applicable to supergroups and channels. Defaults to 0 [1]_. + + limit (``int``, *optional*): + Limits the number of members to be retrieved. + Only applicable to supergroups and channels. + Defaults to 200, which is also the maximum server limit allowed per method call. + + query (``str``, *optional*): + Query string to filter members based on their display names and usernames. + Only applicable to supergroups and channels. Defaults to "" (empty string) [2]_. + + filter (``str``, *optional*): + Filter used to select the kind of members you want to retrieve. Only applicable for supergroups + and channels. It can be any of the followings: + *"all"* - all kind of members, + *"kicked"* - kicked (banned) members only, + *"restricted"* - restricted members only, + *"bots"* - bots only, + *"recent"* - recent members only, + *"administrators"* - chat administrators only. + Only applicable to supergroups and channels. + Defaults to *"recent"*. + + .. [1] Server limit: on supergroups, you can get up to 10,000 members for a single query and up to 200 members + on channels. + + .. [2] A query string is applicable only for *"all"*, *"kicked"* and *"restricted"* filters only. + Example: .. code-block:: python # Get first 200 recent members chat.get_members() + # Get all administrators + chat.get_members(filter="administrators") + + # Get all bots + chat.get_members(filter="bots") + Returns: List of :obj:`~pyrogram.types.ChatMember`: On success, a list of chat members is returned. """ @@ -821,13 +859,50 @@ def iter_members( .. code-block:: python - for member in client.iter_chat_members(chat_id): - print(member.user.first_name) + Parameters: + offset (``int``, *optional*): + Sequential number of the first member to be returned. + Only applicable to supergroups and channels. Defaults to 0 [1]_. + + limit (``int``, *optional*): + Limits the number of members to be retrieved. + Only applicable to supergroups and channels. + Defaults to 200, which is also the maximum server limit allowed per method call. + + query (``str``, *optional*): + Query string to filter members based on their display names and usernames. + Only applicable to supergroups and channels. Defaults to "" (empty string) [2]_. + + filter (``str``, *optional*): + Filter used to select the kind of members you want to retrieve. Only applicable for supergroups + and channels. It can be any of the followings: + *"all"* - all kind of members, + *"kicked"* - kicked (banned) members only, + *"restricted"* - restricted members only, + *"bots"* - bots only, + *"recent"* - recent members only, + *"administrators"* - chat administrators only. + Only applicable to supergroups and channels. + Defaults to *"recent"*. + + .. [1] Server limit: on supergroups, you can get up to 10,000 members for a single query and up to 200 members + on channels. + + .. [2] A query string is applicable only for *"all"*, *"kicked"* and *"restricted"* filters only. Example: .. code-block:: python - for member in chat.iter_members(): + # Get first 200 recent members + for member in chat.get_members(): + print(member.user.first_name) + + # Get all administrators + for member in chat.iter_members(filter="administrators"): + print(member.user.first_name) + + # Get first 3 bots + for member in chat.iter_members(filter="bots", limit=3): print(member.user.first_name) Returns: From 1bba5cad54337c271256b6e84f40774f48ea119b Mon Sep 17 00:00:00 2001 From: Krishna-singhal <65902764+Krishna-Singhal@users.noreply.github.com> Date: Fri, 29 Jan 2021 17:17:52 +0530 Subject: [PATCH 0475/1185] Fix small typo in copy_message()'s examples (#588) --- pyrogram/methods/messages/copy_message.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/methods/messages/copy_message.py b/pyrogram/methods/messages/copy_message.py index 9d824f395a..3250ac8906 100644 --- a/pyrogram/methods/messages/copy_message.py +++ b/pyrogram/methods/messages/copy_message.py @@ -99,7 +99,7 @@ async def copy_message( .. code-block:: python # Copy a message - app.copy_messages("me", "pyrogram", 20) + app.copy_message("me", "pyrogram", 20) """ message: types.Message = await self.get_messages(from_chat_id, message_id) From 57128c0ab789826561ab0bfaa79ebe797c17218c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 29 Jan 2021 12:57:38 +0100 Subject: [PATCH 0476/1185] Add Chat.is_fake and User.is_fake --- pyrogram/types/user_and_chats/chat.py | 13 ++++++++----- pyrogram/types/user_and_chats/user.py | 6 ++++++ 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/pyrogram/types/user_and_chats/chat.py b/pyrogram/types/user_and_chats/chat.py index b44e8f9d98..6343380994 100644 --- a/pyrogram/types/user_and_chats/chat.py +++ b/pyrogram/types/user_and_chats/chat.py @@ -46,7 +46,10 @@ class Chat(Object): True, if this chat owner is the current user. Supergroups, channels and groups only. is_scam (``bool``, *optional*): - True, if this chat has been flagged for scam. Supergroups, channels and bots only. + True, if this chat has been flagged for scam. + + is_fake (``bool``, *optional*): + True, if this chat has been flagged for impersonation. is_support (``bool``): True, if this chat is part of the Telegram support team. Users and bots only. @@ -125,6 +128,7 @@ def __init__( is_restricted: bool = None, is_creator: bool = None, is_scam: bool = None, + is_fake: bool = None, is_support: bool = None, title: str = None, username: str = None, @@ -152,6 +156,7 @@ def __init__( self.is_restricted = is_restricted self.is_creator = is_creator self.is_scam = is_scam + self.is_fake = is_fake self.is_support = is_support self.title = title self.username = username @@ -181,6 +186,7 @@ def _parse_user_chat(client, user: raw.types.User) -> "Chat": is_verified=getattr(user, "verified", None), is_restricted=getattr(user, "restricted", None), is_scam=getattr(user, "scam", None), + is_fake=getattr(user, "fake", None), is_support=getattr(user, "support", None), username=user.username, first_name=user.first_name, @@ -219,6 +225,7 @@ def _parse_channel_chat(client, channel: raw.types.Channel) -> "Chat": is_restricted=getattr(channel, "restricted", None), is_creator=getattr(channel, "creator", None), is_scam=getattr(channel, "scam", None), + is_fake=getattr(channel, "fake", None), title=channel.title, username=getattr(channel, "username", None), photo=types.ChatPhoto._parse(client, getattr(channel, "photo", None), peer_id, channel.access_hash), @@ -860,10 +867,6 @@ def iter_members( .. code-block:: python Parameters: - offset (``int``, *optional*): - Sequential number of the first member to be returned. - Only applicable to supergroups and channels. Defaults to 0 [1]_. - limit (``int``, *optional*): Limits the number of members to be retrieved. Only applicable to supergroups and channels. diff --git a/pyrogram/types/user_and_chats/user.py b/pyrogram/types/user_and_chats/user.py index 643284a0ae..7b49545044 100644 --- a/pyrogram/types/user_and_chats/user.py +++ b/pyrogram/types/user_and_chats/user.py @@ -91,6 +91,9 @@ class User(Object, Update): is_scam (``bool``, *optional*): True, if this user has been flagged for scam. + is_fake (``bool``, *optional*): + True, if this user has been flagged for impersonation. + is_support (``bool``, *optional*): True, if this user is part of the Telegram support team. @@ -159,6 +162,7 @@ def __init__( is_verified: bool = None, is_restricted: bool = None, is_scam: bool = None, + is_fake: bool = None, is_support: bool = None, first_name: str = None, last_name: str = None, @@ -183,6 +187,7 @@ def __init__( self.is_verified = is_verified self.is_restricted = is_restricted self.is_scam = is_scam + self.is_fake = is_fake self.is_support = is_support self.first_name = first_name self.last_name = last_name @@ -215,6 +220,7 @@ def _parse(client, user: "raw.types.User") -> Optional["User"]: is_verified=user.verified, is_restricted=user.restricted, is_scam=user.scam, + is_fake=user.fake, is_support=user.support, first_name=user.first_name, last_name=user.last_name, From fcf91661fc1ab9437f8bf7cc6899a9021eb3963e Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 29 Jan 2021 12:58:03 +0100 Subject: [PATCH 0477/1185] Fix User._parse_status type hints --- pyrogram/types/user_and_chats/user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/types/user_and_chats/user.py b/pyrogram/types/user_and_chats/user.py index 7b49545044..cc0c2036ce 100644 --- a/pyrogram/types/user_and_chats/user.py +++ b/pyrogram/types/user_and_chats/user.py @@ -235,7 +235,7 @@ def _parse(client, user: "raw.types.User") -> Optional["User"]: ) @staticmethod - def _parse_status(user_status: "raw.types.UpdateUserStatus", is_bot: bool = False): + def _parse_status(user_status: "raw.base.UserStatus", is_bot: bool = False): if isinstance(user_status, raw.types.UserStatusOnline): status, date = "online", user_status.expires elif isinstance(user_status, raw.types.UserStatusOffline): From 2ad21e9096763de54fa5b9244dc71fdd1bc5f930 Mon Sep 17 00:00:00 2001 From: ColinShark Date: Tue, 9 Feb 2021 20:25:27 +0100 Subject: [PATCH 0478/1185] Fix Typo in input_media_audio() (#614) * Fixes #612 --- pyrogram/types/input_media/input_media_audio.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/types/input_media/input_media_audio.py b/pyrogram/types/input_media/input_media_audio.py index e98d283c55..fba368c826 100644 --- a/pyrogram/types/input_media/input_media_audio.py +++ b/pyrogram/types/input_media/input_media_audio.py @@ -56,10 +56,10 @@ class InputMediaAudio(InputMedia): duration (``int``, *optional*): Duration of the audio in seconds - performer (``int``, *optional*): + performer (``str``, *optional*): Performer of the audio - title (``int``, *optional*): + title (``str``, *optional*): Title of the audio """ From b47c5a93b16c084a6b3f867698d0f3c4340803aa Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 12 Feb 2021 12:36:32 +0100 Subject: [PATCH 0479/1185] Fix enum items showing ids instead of names --- pyrogram/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/utils.py b/pyrogram/utils.py index 1082dadec9..236d9c7b2a 100644 --- a/pyrogram/utils.py +++ b/pyrogram/utils.py @@ -52,10 +52,10 @@ def get_input_media_from_file_id( file_type = decoded.file_type if expected_file_type is not None and file_type != expected_file_type: - raise ValueError(f'Expected: "{expected_file_type}", got "{file_type}" file_id instead') + raise ValueError(f"Expected {expected_file_type.name}, got {file_type.name} file id instead") if file_type in (FileType.THUMBNAIL, FileType.CHAT_PHOTO): - raise ValueError(f"This file_id can only be used for download: {file_id}") + raise ValueError(f"This file id can only be used for download: {file_id}") if file_type in PHOTO_TYPES: return raw.types.InputMediaPhoto( From 9f5179863ae56349c288032db6ccefb6df697f50 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 12 Feb 2021 21:21:23 +0100 Subject: [PATCH 0480/1185] Update API schema (Layer 123 patch) --- compiler/api/source/main_api.tl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl index a68e527eec..938e36d005 100644 --- a/compiler/api/source/main_api.tl +++ b/compiler/api/source/main_api.tl @@ -1174,7 +1174,7 @@ groupCall#55903081 flags:# join_muted:flags.1?true can_change_join_muted:flags.2 inputGroupCall#d8aa840f id:long access_hash:long = InputGroupCall; -groupCallParticipant#64c62a15 flags:# muted:flags.0?true left:flags.1?true can_self_unmute:flags.2?true just_joined:flags.4?true versioned:flags.5?true muted_by_you:flags.9?true user_id:int date:int active_date:flags.3?int source:int volume:flags.7?int = GroupCallParticipant; +groupCallParticipant#64c62a15 flags:# muted:flags.0?true left:flags.1?true can_self_unmute:flags.2?true just_joined:flags.4?true versioned:flags.5?true min:flags.8?true muted_by_you:flags.9?true volume_by_admin:flags.10?true user_id:int date:int active_date:flags.3?int source:int volume:flags.7?int = GroupCallParticipant; phone.groupCall#66ab0bfc call:GroupCall participants:Vector participants_next_offset:string users:Vector = phone.GroupCall; From 5dabeeafbb66fbb126df669d1afb08b29d1fee2c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 23 Feb 2021 13:04:10 +0100 Subject: [PATCH 0481/1185] Update API schema to Layer 124 --- compiler/api/source/main_api.tl | 68 ++++++++++++++++++++++++--------- 1 file changed, 50 insertions(+), 18 deletions(-) diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl index 938e36d005..da618bbc3a 100644 --- a/compiler/api/source/main_api.tl +++ b/compiler/api/source/main_api.tl @@ -102,11 +102,11 @@ userStatusLastMonth#77ebc742 = UserStatus; chatEmpty#9ba2d800 id:int = Chat; chat#3bda1bde flags:# creator:flags.0?true kicked:flags.1?true left:flags.2?true deactivated:flags.5?true call_active:flags.23?true call_not_empty:flags.24?true id:int title:string photo:ChatPhoto participants_count:int date:int version:int migrated_to:flags.6?InputChannel admin_rights:flags.14?ChatAdminRights default_banned_rights:flags.18?ChatBannedRights = Chat; chatForbidden#7328bdb id:int title:string = Chat; -channel#d31a961e flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true fake:flags.25?true id:int access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int version:int restriction_reason:flags.9?Vector admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int = Chat; +channel#d31a961e flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true fake:flags.25?true gigagroup:flags.26?true id:int access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int version:int restriction_reason:flags.9?Vector admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int = Chat; channelForbidden#289da732 flags:# broadcast:flags.5?true megagroup:flags.8?true id:int access_hash:long title:string until_date:flags.16?int = Chat; -chatFull#f3474af6 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:int about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall = ChatFull; -channelFull#7a7de4f7 flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true id:int about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?int migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?int location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall = ChatFull; +chatFull#f06c4018 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:int about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int = ChatFull; +channelFull#2548c037 flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true id:int about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?int migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?int location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector = ChatFull; chatParticipant#c8d7493e user_id:int inviter_id:int date:int = ChatParticipant; chatParticipantCreator#da13538a user_id:int = ChatParticipant; @@ -119,8 +119,8 @@ chatPhotoEmpty#37c1011c = ChatPhoto; chatPhoto#d20b9f3c flags:# has_video:flags.0?true photo_small:FileLocation photo_big:FileLocation dc_id:int = ChatPhoto; messageEmpty#90a6ca84 flags:# id:int peer_id:flags.0?Peer = Message; -message#58ae39c9 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true id:int from_id:flags.8?Peer peer_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long restriction_reason:flags.22?Vector = Message; -messageService#286fa604 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true legacy:flags.19?true id:int from_id:flags.8?Peer peer_id:Peer reply_to:flags.3?MessageReplyHeader date:int action:MessageAction = Message; +message#bce383d2 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true id:int from_id:flags.8?Peer peer_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long restriction_reason:flags.22?Vector ttl_period:flags.25?int = Message; +messageService#2b085862 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true legacy:flags.19?true id:int from_id:flags.8?Peer peer_id:Peer reply_to:flags.3?MessageReplyHeader date:int action:MessageAction ttl_period:flags.25?int = Message; messageMediaEmpty#3ded6320 = MessageMedia; messageMediaPhoto#695150d7 flags:# photo:flags.0?Photo ttl_seconds:flags.2?int = MessageMedia; @@ -162,6 +162,7 @@ messageActionContactSignUp#f3f25f76 = MessageAction; messageActionGeoProximityReached#98e0d697 from_id:Peer to_id:Peer distance:int = MessageAction; messageActionGroupCall#7a0d7f42 flags:# call:InputGroupCall duration:flags.0?int = MessageAction; messageActionInviteToGroupCall#76b9f11a call:InputGroupCall users:Vector = MessageAction; +messageActionSetMessagesTTL#aa1afbfd period:int = MessageAction; dialog#2c171f72 flags:# pinned:flags.2?true unread_mark:flags.3?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int = Dialog; dialogFolder#71bd134c flags:# pinned:flags.2?true folder:Folder peer:Peer top_message:int unread_muted_peers_count:int unread_unmuted_peers_count:int unread_muted_messages_count:int unread_unmuted_messages_count:int = Dialog; @@ -204,12 +205,12 @@ inputReportReasonSpam#58dbcab8 = ReportReason; inputReportReasonViolence#1e22c78d = ReportReason; inputReportReasonPornography#2e59d922 = ReportReason; inputReportReasonChildAbuse#adf44ee3 = ReportReason; -inputReportReasonOther#e1746d0a text:string = ReportReason; +inputReportReasonOther#c1e4a2b1 = ReportReason; inputReportReasonCopyright#9b89f93a = ReportReason; inputReportReasonGeoIrrelevant#dbd4feed = ReportReason; inputReportReasonFake#f5ddd6e7 = ReportReason; -userFull#edf17c12 flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true user:User about:flags.1?string settings:PeerSettings profile_photo:flags.2?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int = UserFull; +userFull#139a9a77 flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true user:User about:flags.1?string settings:PeerSettings profile_photo:flags.2?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int ttl_period:flags.14?int = UserFull; contact#f911c994 user_id:int mutual:Bool = Contact; @@ -336,7 +337,6 @@ updateDialogFilter#26ffde7d flags:# id:int filter:flags.0?DialogFilter = Update; updateDialogFilterOrder#a5d72105 order:Vector = Update; updateDialogFilters#3504914f = Update; updatePhoneCallSignalingData#2661bf09 phone_call_id:long data:bytes = Update; -updateChannelParticipant#65d2b464 flags:# channel_id:int date:int user_id:int prev_participant:flags.0?ChannelParticipant new_participant:flags.1?ChannelParticipant qts:int = Update; updateChannelMessageForwards#6e8a84df channel_id:int id:int forwards:int = Update; updateReadChannelDiscussionInbox#1cc7de54 flags:# channel_id:int top_msg_id:int read_max_id:int broadcast_id:flags.0?int broadcast_post:flags.0?int = Update; updateReadChannelDiscussionOutbox#4638a26c channel_id:int top_msg_id:int read_max_id:int = Update; @@ -347,6 +347,10 @@ updatePinnedChannelMessages#8588878b flags:# pinned:flags.0?true channel_id:int updateChat#1330a196 chat_id:int = Update; updateGroupCallParticipants#f2ebdb4e call:InputGroupCall participants:Vector version:int = Update; updateGroupCall#a45eb99b chat_id:int call:GroupCall = Update; +updatePeerHistoryTTL#bb9bb9a5 flags:# peer:Peer ttl_period:flags.0?int = Update; +updateChatParticipant#609a6ed4 flags:# chat_id:int date:int user_id:int prev_participant:flags.0?ChatParticipant new_participant:flags.1?ChatParticipant qts:int = Update; +updateChannelParticipant#65d2b464 flags:# channel_id:int date:int user_id:int prev_participant:flags.0?ChannelParticipant new_participant:flags.1?ChannelParticipant qts:int = Update; +updateBotStopped#30ec6ebc user_id:int stopped:Bool qts:int = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -356,12 +360,12 @@ updates.differenceSlice#a8fb1981 new_messages:Vector new_encrypted_mess updates.differenceTooLong#4afe8f6d pts:int = updates.Difference; updatesTooLong#e317af7e = Updates; -updateShortMessage#2296d2c8 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true id:int user_id:int message:string pts:int pts_count:int date:int fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to:flags.3?MessageReplyHeader entities:flags.7?Vector = Updates; -updateShortChatMessage#402d5dbb flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true id:int from_id:int chat_id:int message:string pts:int pts_count:int date:int fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to:flags.3?MessageReplyHeader entities:flags.7?Vector = Updates; +updateShortMessage#faeff833 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true id:int user_id:int message:string pts:int pts_count:int date:int fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to:flags.3?MessageReplyHeader entities:flags.7?Vector ttl_period:flags.25?int = Updates; +updateShortChatMessage#1157b858 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true id:int from_id:int chat_id:int message:string pts:int pts_count:int date:int fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to:flags.3?MessageReplyHeader entities:flags.7?Vector ttl_period:flags.25?int = Updates; updateShort#78d4dec1 update:Update date:int = Updates; updatesCombined#725b04c3 updates:Vector users:Vector chats:Vector date:int seq_start:int seq:int = Updates; updates#74ae4240 updates:Vector users:Vector chats:Vector date:int seq:int = Updates; -updateShortSentMessage#11f1331c flags:# out:flags.1?true id:int pts:int pts_count:int date:int media:flags.9?MessageMedia entities:flags.7?Vector = Updates; +updateShortSentMessage#9015e101 flags:# out:flags.1?true id:int pts:int pts_count:int date:int media:flags.9?MessageMedia entities:flags.7?Vector ttl_period:flags.25?int = Updates; photos.photos#8dca6aa5 photos:Vector users:Vector = photos.Photos; photos.photosSlice#15051f54 count:int photos:Vector users:Vector = photos.Photos; @@ -864,12 +868,18 @@ channelAdminLogEventActionDiscardGroupCall#db9f9140 call:InputGroupCall = Channe channelAdminLogEventActionParticipantMute#f92424d2 participant:GroupCallParticipant = ChannelAdminLogEventAction; channelAdminLogEventActionParticipantUnmute#e64429c0 participant:GroupCallParticipant = ChannelAdminLogEventAction; channelAdminLogEventActionToggleGroupCallSetting#56d6a247 join_muted:Bool = ChannelAdminLogEventAction; +channelAdminLogEventActionParticipantJoinByInvite#5cdada77 invite:ExportedChatInvite = ChannelAdminLogEventAction; +channelAdminLogEventActionExportedInviteDelete#5a50fca4 invite:ExportedChatInvite = ChannelAdminLogEventAction; +channelAdminLogEventActionExportedInviteRevoke#410a134e invite:ExportedChatInvite = ChannelAdminLogEventAction; +channelAdminLogEventActionExportedInviteEdit#e90ebb59 prev_invite:ExportedChatInvite new_invite:ExportedChatInvite = ChannelAdminLogEventAction; +channelAdminLogEventActionParticipantVolume#3e7f6847 participant:GroupCallParticipant = ChannelAdminLogEventAction; +channelAdminLogEventActionChangeHistoryTTL#6e941a38 prev_value:int new_value:int = ChannelAdminLogEventAction; channelAdminLogEvent#3b5a3e40 id:long date:int user_id:int action:ChannelAdminLogEventAction = ChannelAdminLogEvent; channels.adminLogResults#ed8af74d events:Vector chats:Vector users:Vector = channels.AdminLogResults; -channelAdminLogEventsFilter#ea107ae4 flags:# join:flags.0?true leave:flags.1?true invite:flags.2?true ban:flags.3?true unban:flags.4?true kick:flags.5?true unkick:flags.6?true promote:flags.7?true demote:flags.8?true info:flags.9?true settings:flags.10?true pinned:flags.11?true edit:flags.12?true delete:flags.13?true group_call:flags.14?true = ChannelAdminLogEventsFilter; +channelAdminLogEventsFilter#ea107ae4 flags:# join:flags.0?true leave:flags.1?true invite:flags.2?true ban:flags.3?true unban:flags.4?true kick:flags.5?true unkick:flags.6?true promote:flags.7?true demote:flags.8?true info:flags.9?true settings:flags.10?true pinned:flags.11?true edit:flags.12?true delete:flags.13?true group_call:flags.14?true invites:flags.15?true = ChannelAdminLogEventsFilter; popularContact#5ce14175 client_id:long importers:int = PopularContact; @@ -1027,7 +1037,7 @@ chatOnlines#f041e250 onlines:int = ChatOnlines; statsURL#47a971e0 url:string = StatsURL; -chatAdminRights#5fb224d5 flags:# change_info:flags.0?true post_messages:flags.1?true edit_messages:flags.2?true delete_messages:flags.3?true ban_users:flags.4?true invite_users:flags.5?true pin_messages:flags.7?true add_admins:flags.9?true anonymous:flags.10?true manage_call:flags.11?true = ChatAdminRights; +chatAdminRights#5fb224d5 flags:# change_info:flags.0?true post_messages:flags.1?true edit_messages:flags.2?true delete_messages:flags.3?true ban_users:flags.4?true invite_users:flags.5?true pin_messages:flags.7?true add_admins:flags.9?true anonymous:flags.10?true manage_call:flags.11?true other:flags.12?true = ChatAdminRights; chatBannedRights#9f120418 flags:# view_messages:flags.0?true send_messages:flags.1?true send_media:flags.2?true send_stickers:flags.3?true send_gifs:flags.4?true send_games:flags.5?true send_inline:flags.6?true embed_links:flags.7?true send_polls:flags.8?true change_info:flags.10?true invite_users:flags.15?true pin_messages:flags.17?true until_date:int = ChatBannedRights; @@ -1192,6 +1202,19 @@ messages.historyImportParsed#5e0fb7b9 flags:# pm:flags.0?true group:flags.1?true messages.affectedFoundMessages#ef8d3e6c pts:int pts_count:int offset:int messages:Vector = messages.AffectedFoundMessages; +chatInviteImporter#1e3e6680 user_id:int date:int = ChatInviteImporter; + +messages.exportedChatInvites#bdc62dcc count:int invites:Vector users:Vector = messages.ExportedChatInvites; + +messages.exportedChatInvite#1871be50 invite:ExportedChatInvite users:Vector = messages.ExportedChatInvite; +messages.exportedChatInviteReplaced#222600ef invite:ExportedChatInvite new_invite:ExportedChatInvite users:Vector = messages.ExportedChatInvite; + +messages.chatInviteImporters#81b6b00a count:int importers:Vector users:Vector = messages.ChatInviteImporters; + +chatAdminWithInvites#dfd2330f admin_id:int invites_count:int revoked_invites_count:int = ChatAdminWithInvites; + +messages.chatAdminsWithInvites#b69b72d7 admins:Vector users:Vector = messages.ChatAdminsWithInvites; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -1229,7 +1252,7 @@ account.resetNotifySettings#db7e1747 = Bool; account.updateProfile#78515775 flags:# first_name:flags.0?string last_name:flags.1?string about:flags.2?string = User; account.updateStatus#6628562c offline:Bool = Bool; account.getWallPapers#aabb1763 hash:int = account.WallPapers; -account.reportPeer#ae189d5f peer:InputPeer reason:ReportReason = Bool; +account.reportPeer#c5ba3d86 peer:InputPeer reason:ReportReason message:string = Bool; account.checkUsername#2714d86c username:string = Bool; account.updateUsername#3e0bdd7c username:string = User; account.getPrivacy#dadbc950 key:InputPrivacyKey = account.PrivacyRules; @@ -1288,6 +1311,7 @@ account.getContentSettings#8b9b4dae = account.ContentSettings; account.getMultiWallPapers#65ad71dc wallpapers:Vector = Vector; account.getGlobalPrivacySettings#eb2b4cf6 = GlobalPrivacySettings; account.setGlobalPrivacySettings#1edaaac2 settings:GlobalPrivacySettings = GlobalPrivacySettings; +account.reportProfilePhoto#fa8cc6f5 peer:InputPeer photo_id:InputPhoto reason:ReportReason message:string = Bool; users.getUsers#d91a548 id:Vector = Vector; users.getFullUser#ca30a5b1 id:InputUser = UserFull; @@ -1328,7 +1352,7 @@ messages.sendMedia#3491eba9 flags:# silent:flags.5?true background:flags.6?true messages.forwardMessages#d9fee60e flags:# silent:flags.5?true background:flags.6?true with_my_score:flags.8?true from_peer:InputPeer id:Vector random_id:Vector to_peer:InputPeer schedule_date:flags.10?int = Updates; messages.reportSpam#cf1592db peer:InputPeer = Bool; messages.getPeerSettings#3672e09c peer:InputPeer = PeerSettings; -messages.report#bd82b658 peer:InputPeer id:Vector reason:ReportReason = Bool; +messages.report#8953ab4e peer:InputPeer id:Vector reason:ReportReason message:string = Bool; messages.getChats#3c6aa187 id:Vector = messages.Chats; messages.getFullChat#3b831c66 chat_id:int = messages.ChatFull; messages.editChatTitle#dc452855 chat_id:int title:string = Updates; @@ -1351,7 +1375,7 @@ messages.readMessageContents#36a73f77 id:Vector = messages.AffectedMessages messages.getStickers#43d4f2c emoticon:string hash:int = messages.Stickers; messages.getAllStickers#1c9618b1 hash:int = messages.AllStickers; messages.getWebPagePreview#8b68b0cc flags:# message:string entities:flags.3?Vector = MessageMedia; -messages.exportChatInvite#df7534c peer:InputPeer = ExportedChatInvite; +messages.exportChatInvite#14b9bcd7 flags:# legacy_revoke_permanent:flags.2?true peer:InputPeer expire_date:flags.0?int usage_limit:flags.1?int = ExportedChatInvite; messages.checkChatInvite#3eadb1bb hash:string = ChatInvite; messages.importChatInvite#6c50051c hash:string = Updates; messages.getStickerSet#2619a90e stickerset:InputStickerSet = messages.StickerSet; @@ -1447,6 +1471,13 @@ messages.checkHistoryImport#43fe19f3 import_head:string = messages.HistoryImport messages.initHistoryImport#34090c3b peer:InputPeer file:InputFile media_count:int = messages.HistoryImport; messages.uploadImportedMedia#2a862092 peer:InputPeer import_id:long file_name:string media:InputMedia = MessageMedia; messages.startHistoryImport#b43df344 peer:InputPeer import_id:long = Bool; +messages.getExportedChatInvites#a2b5a3f6 flags:# revoked:flags.3?true peer:InputPeer admin_id:InputUser offset_date:flags.2?int offset_link:flags.2?string limit:int = messages.ExportedChatInvites; +messages.editExportedChatInvite#2e4ffbe flags:# revoked:flags.2?true peer:InputPeer link:string expire_date:flags.0?int usage_limit:flags.1?int = messages.ExportedChatInvite; +messages.deleteRevokedExportedChatInvites#56987bd5 peer:InputPeer admin_id:InputUser = Bool; +messages.deleteExportedChatInvite#d464a42b peer:InputPeer link:string = Bool; +messages.getAdminsWithInvites#3920e6ef peer:InputPeer = messages.ChatAdminsWithInvites; +messages.getChatInviteImporters#26fb7289 peer:InputPeer link:string offset_date:int offset_user:InputUser limit:int = messages.ChatInviteImporters; +messages.setHistoryTTL#b80e5fe4 peer:InputPeer period:int = Updates; updates.getState#edd4882a = updates.State; updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference; @@ -1486,7 +1517,7 @@ help.getUserInfo#38a08d3 user_id:InputUser = help.UserInfo; help.editUserInfo#66b91b70 user_id:InputUser message:string entities:Vector = help.UserInfo; help.getPromoData#c0977421 = help.PromoData; help.hidePromoData#1e251c95 peer:InputPeer = Bool; -help.dismissSuggestion#77fa99f suggestion:string = Bool; +help.dismissSuggestion#f50dbaa1 peer:InputPeer suggestion:string = Bool; help.getCountriesList#735787a8 lang_code:string hash:int = help.CountriesList; channels.readHistory#cc104937 channel:InputChannel max_id:int = Bool; @@ -1524,6 +1555,7 @@ channels.editCreator#8f38cd1f channel:InputChannel user_id:InputUser password:In channels.editLocation#58e63f6d channel:InputChannel geo_point:InputGeoPoint address:string = Bool; channels.toggleSlowMode#edd49ef0 channel:InputChannel seconds:int = Updates; channels.getInactiveChannels#11e831ee = messages.InactiveChats; +channels.convertToGigagroup#b290c69 channel:InputChannel = Updates; bots.sendCustomRequest#aa2769ed custom_method:string params:DataJSON = DataJSON; bots.answerWebhookJSONQuery#e6213f4d query_id:long data:DataJSON = Bool; @@ -1578,4 +1610,4 @@ stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats; -// LAYER 123 \ No newline at end of file +// LAYER 124 \ No newline at end of file From 415beb21f20cfa15877e6dc8b95c375261e1315d Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 27 Feb 2021 18:40:29 +0100 Subject: [PATCH 0482/1185] Update issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 6 +++--- .github/ISSUE_TEMPLATE/feature_request.md | 2 +- .github/ISSUE_TEMPLATE/question.md | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index c46b935a5e..15fc4fa38b 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -3,7 +3,7 @@ name: Bug Report about: Create a bug report affecting the library --- - + ## Checklist - [ ] I am sure the error is coming from Pyrogram's code and not elsewhere. @@ -11,10 +11,10 @@ about: Create a bug report affecting the library - [ ] I ran `pip3 install -U https://github.com/pyrogram/pyrogram/archive/master.zip` and reproduced the issue using the latest development version. ## Description -A clear and concise description of the problem. +A **clear** and **concise** description of the problem. Code snippets must be formatted properly. ## Steps to Reproduce [A minimal, reproducible example](https://stackoverflow.com/help/minimal-reproducible-example). ## Traceback -The full traceback (if applicable). +The full traceback (if applicable). \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 4d2f447c03..279af3e4d5 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -4,7 +4,7 @@ about: Suggest ideas, new features or enhancements labels: "enhancement" --- - + ## Checklist - [ ] I believe the idea is awesome and would benefit the library. diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md index 05f342bcac..883503c446 100644 --- a/.github/ISSUE_TEMPLATE/question.md +++ b/.github/ISSUE_TEMPLATE/question.md @@ -2,10 +2,10 @@ name: Ask Question about: Ask a Pyrogram related question title: For Q&A purposes, please read this template body -labels: "question" +labels: "invalid" --- - + # Important This place is for issues about Pyrogram, it's **not a forum**. From 6a22e2e262adf8932b61f1e74879f6d1072afb4e Mon Sep 17 00:00:00 2001 From: Legenda24 <48826466+Legenda24@users.noreply.github.com> Date: Sat, 27 Feb 2021 22:53:18 +0500 Subject: [PATCH 0483/1185] Add bound method get_media_group() (#593) * Bound method - get_media_group() * Update message.py Co-authored-by: Dan <14043624+delivrance@users.noreply.github.com> --- pyrogram/types/messages_and_media/message.py | 27 ++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index 0ac26f2366..6f8b7e4b00 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -706,6 +706,33 @@ def link(self) -> str: else: return f"https://t.me/c/{utils.get_channel_id(self.chat.id)}/{self.message_id}" + async def get_media_group(self) -> List["types.Message"]: + """Bound method *get_media_group* of :obj:`~pyrogram.types.Message`. + + Use as a shortcut for: + + .. code-block:: python + client.get_media_group( + chat_id=message.chat.id, + message_id=message.message_id + ) + + Example: + .. code-block:: python + message.get_media_group() + + Returns: + List of :obj:`~pyrogram.types.Message`: On success, a list of messages of the media group is returned. + + Raises: + ValueError: In case the passed message id doesn't belong to a media group. + """ + + return await self._client.get_media_group( + chat_id=self.chat.id, + message_id=self.message_id + ) + async def reply_text( self, text: str, From e390aea86e7ed227e42d5905b637a8cbdf00ee34 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 27 Feb 2021 18:56:28 +0100 Subject: [PATCH 0484/1185] Enable docs for Message.get_media_group --- compiler/docs/compiler.py | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index b262b99e33..e98fcd1882 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -459,6 +459,7 @@ def get_title_list(s: str) -> list: Message.reply_video Message.reply_video_note Message.reply_voice + Message.get_media_group """, chat=""" Chat From d7f2bd90300dc7569c766099d2884ae1790b3909 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 27 Feb 2021 19:02:52 +0100 Subject: [PATCH 0485/1185] Fix some index out of range errors Closes #601 --- pyrogram/client.py | 3 ++- pyrogram/handlers/deleted_messages_handler.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pyrogram/client.py b/pyrogram/client.py index 252286a780..35153bde71 100644 --- a/pyrogram/client.py +++ b/pyrogram/client.py @@ -585,7 +585,8 @@ async def handle_updates(self, updates): {c.id: c for c in diff.chats} )) else: - self.dispatcher.updates_queue.put_nowait((diff.other_updates[0], {}, {})) + if diff.other_updates: # The other_updates list can be empty + self.dispatcher.updates_queue.put_nowait((diff.other_updates[0], {}, {})) elif isinstance(updates, raw.types.UpdateShort): self.dispatcher.updates_queue.put_nowait((updates.update, {}, {})) elif isinstance(updates, raw.types.UpdatesTooLong): diff --git a/pyrogram/handlers/deleted_messages_handler.py b/pyrogram/handlers/deleted_messages_handler.py index d57cb5ef25..dff1ebe78f 100644 --- a/pyrogram/handlers/deleted_messages_handler.py +++ b/pyrogram/handlers/deleted_messages_handler.py @@ -52,4 +52,4 @@ def __init__(self, callback: Callable, filters: Filter = None): super().__init__(callback, filters) async def check(self, client: "pyrogram.Client", messages: List[Message]): - return await super().check(client, messages[0]) + return await super().check(client, messages[0]) if messages else False # The messages list can be empty From 1afc9980614513347ffca098d449d63242a04837 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 27 Feb 2021 19:08:44 +0100 Subject: [PATCH 0486/1185] Mention that linked_chat is available only when using get_chat Closes #629 --- pyrogram/types/user_and_chats/chat.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyrogram/types/user_and_chats/chat.py b/pyrogram/types/user_and_chats/chat.py index 6343380994..75c454b542 100644 --- a/pyrogram/types/user_and_chats/chat.py +++ b/pyrogram/types/user_and_chats/chat.py @@ -116,6 +116,7 @@ class Chat(Object): linked_chat (:obj:`~pyrogram.types.Chat`, *optional*): The linked discussion group (in case of channels) or the linked channel (in case of supergroups). + Returned only in :meth:`~pyrogram.Client.get_chat`. """ def __init__( From a390eceba3353dc6a03f85888034c5147c959d9c Mon Sep 17 00:00:00 2001 From: Ripe <42308266+Ripeey@users.noreply.github.com> Date: Sat, 27 Feb 2021 18:57:12 +0000 Subject: [PATCH 0487/1185] Fix Message.copy ReplyMarkup (#604) --- pyrogram/types/messages_and_media/message.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index 6f8b7e4b00..a96b1bed16 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -2875,7 +2875,7 @@ async def copy( disable_notification=disable_notification, reply_to_message_id=reply_to_message_id, schedule_date=schedule_date, - reply_markup=self.reply_markup or reply_markup + reply_markup=reply_markup or self.reply_markup ) elif self.media: send_media = partial( @@ -2884,7 +2884,7 @@ async def copy( disable_notification=disable_notification, reply_to_message_id=reply_to_message_id, schedule_date=schedule_date, - reply_markup=self.reply_markup or reply_markup + reply_markup=reply_markup or self.reply_markup ) if self.photo: From 83f4d12ad20e67eed4a899be4adcdefd2e82ba23 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 27 Feb 2021 20:12:36 +0100 Subject: [PATCH 0488/1185] Allow Message.copy to remove reply markups --- pyrogram/types/messages_and_media/message.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index a96b1bed16..513cef18f7 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -2800,7 +2800,7 @@ async def copy( "types.ReplyKeyboardMarkup", "types.ReplyKeyboardRemove", "types.ForceReply" - ] = None + ] = object ) -> Union["types.Message", List["types.Message"]]: """Bound method *copy* of :obj:`~pyrogram.types.Message`. @@ -2853,6 +2853,8 @@ async def copy( reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. + If not specified, the original reply markup is kept. + Pass None to remove the reply markup. Returns: :obj:`~pyrogram.types.Message`: On success, the copied message is returned. @@ -2875,7 +2877,7 @@ async def copy( disable_notification=disable_notification, reply_to_message_id=reply_to_message_id, schedule_date=schedule_date, - reply_markup=reply_markup or self.reply_markup + reply_markup=self.reply_markup if reply_markup is object else reply_markup ) elif self.media: send_media = partial( @@ -2884,7 +2886,7 @@ async def copy( disable_notification=disable_notification, reply_to_message_id=reply_to_message_id, schedule_date=schedule_date, - reply_markup=reply_markup or self.reply_markup + reply_markup=self.reply_markup if reply_markup is object else reply_markup ) if self.photo: From 2154872acb00fd8bb8aaf4ab215ed125d0b0b6a0 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 27 Feb 2021 20:35:49 +0100 Subject: [PATCH 0489/1185] Add can_manage_voice_chats to admin permissions --- pyrogram/methods/chats/promote_chat_member.py | 7 ++++++- pyrogram/types/user_and_chats/chat_member.py | 8 ++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/pyrogram/methods/chats/promote_chat_member.py b/pyrogram/methods/chats/promote_chat_member.py index 03213e7646..4d0f15c012 100644 --- a/pyrogram/methods/chats/promote_chat_member.py +++ b/pyrogram/methods/chats/promote_chat_member.py @@ -35,7 +35,8 @@ async def promote_chat_member( can_restrict_members: bool = True, can_invite_users: bool = True, can_pin_messages: bool = False, - can_promote_members: bool = False + can_promote_members: bool = False, + can_manage_voice_chats: bool = False ) -> bool: """Promote or demote a user in a supergroup or a channel. @@ -79,6 +80,9 @@ async def promote_chat_member( demote administrators that he has promoted, directly or indirectly (promoted by administrators that were appointed by him). + can_manage_voice_chats (``bool``, *optional*): + Pass True, if the administration can manage voice chats (also called group calls). + Returns: ``bool``: True on success. @@ -102,6 +106,7 @@ async def promote_chat_member( invite_users=can_invite_users or None, pin_messages=can_pin_messages or None, add_admins=can_promote_members or None, + manage_call=can_manage_voice_chats or None ), rank="" ) diff --git a/pyrogram/types/user_and_chats/chat_member.py b/pyrogram/types/user_and_chats/chat_member.py index 066976f784..a3c87c24aa 100644 --- a/pyrogram/types/user_and_chats/chat_member.py +++ b/pyrogram/types/user_and_chats/chat_member.py @@ -100,6 +100,10 @@ class ChatMember(Object): Administrators and restricted only. Groups and supergroups only. True, if the user is allowed to pin messages. + can_manage_voice_chats (``bool``, *optional*): + Administrators only. Groups and supergroups only. + True, if the administrator can manage voice chats (also called group calls). + can_send_messages (``bool``, *optional*): Restricted only. True, if the user is allowed to send text messages, contacts, locations and venues. @@ -154,6 +158,7 @@ def __init__( can_change_info: bool = None, can_invite_users: bool = None, can_pin_messages: bool = None, # Groups and supergroups only + can_manage_voice_chats: bool = None, # Restricted user permissions can_send_messages: bool = None, # Text, contacts, locations and venues @@ -187,6 +192,7 @@ def __init__( self.can_change_info = can_change_info self.can_invite_users = can_invite_users self.can_pin_messages = can_pin_messages + self.can_manage_voice_chats = can_manage_voice_chats self.can_send_messages = can_send_messages self.can_send_media_messages = can_send_media_messages @@ -249,6 +255,7 @@ def _parse(client, member, users) -> "ChatMember": can_invite_users=permissions.invite_users, can_pin_messages=permissions.pin_messages, can_promote_members=permissions.add_admins, + can_manage_voice_chats=permissions.manage_call, is_anonymous=permissions.anonymous, client=client ) @@ -272,6 +279,7 @@ def _parse(client, member, users) -> "ChatMember": can_invite_users=permissions.invite_users, can_pin_messages=permissions.pin_messages, can_promote_members=permissions.add_admins, + can_manage_voice_chats=permissions.manage_call, is_anonymous=permissions.anonymous, client=client ) From 9c0210e87a0e4efa50338f99fb939064387b9a35 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 3 Mar 2021 18:50:17 +0100 Subject: [PATCH 0490/1185] Fix 404 link --- docs/source/powered-by.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/powered-by.rst b/docs/source/powered-by.rst index 6fee50b25c..68ca391e89 100644 --- a/docs/source/powered-by.rst +++ b/docs/source/powered-by.rst @@ -48,7 +48,7 @@ Projects Showcase | **A Telegram userbot based on Pyrogram** | --- by `Colin `_ -- Source Code: https://git.colinshark.de/PyroBot/PyroBot +- Source Code: https://github.com/ColinShark/PyroBot ----- From 52e905d6e7d2847f133700c46b7f84c30029f549 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 3 Mar 2021 18:51:26 +0100 Subject: [PATCH 0491/1185] Update conf.py - Copyright year - Docstrings params fixes --- docs/source/conf.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index ca1d54d9aa..a9fcef52cb 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -18,6 +18,7 @@ import os import sys +from datetime import datetime sys.path.insert(0, os.path.abspath("../..")) @@ -28,7 +29,7 @@ FriendlyStyle.background_color = "#f3f2f1" project = "Pyrogram" -copyright = "2017-2021, Dan" +copyright = f"2017-{datetime.now().year}, Dan" author = "Dan" extensions = [ @@ -68,6 +69,8 @@ "style_external_links": True } +napoleon_use_param = False + html_logo = "_images/pyrogram.png" html_favicon = "_images/favicon.ico" From ee9d0e4622fd7f71ab023c0cfc41605920b7a3af Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 3 Mar 2021 18:57:27 +0100 Subject: [PATCH 0492/1185] Add FAQ about client library & framework definitions --- docs/source/faq.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/docs/source/faq.rst b/docs/source/faq.rst index 6b1e26ff04..8e681fffb3 100644 --- a/docs/source/faq.rst +++ b/docs/source/faq.rst @@ -59,6 +59,21 @@ Why Pyrogram? .. _TgCrypto: https://github.com/pyrogram/tgcrypto +Why is Pyrogram defined as both Client Library and Framework? +------------------------------------------------------------- + +Simply because it falls in both categories, depending on how you use it. + +Pyrogram as a client library makes it easy and intuitive accessing the Telegram API by offering idiomatic Python code +that is generated or hand-written. Low-level details and client-server communication protocols are handled under the +hood. Pyrogram acts as a client library when *you call* its methods and use its types in a batch application that +executes a set of instructions. + +Pyrogram as a framework makes it easy to handle live events by allowing you to register event handlers that will be +executed as soon as they arrive from the server side. Pyrogram acts as a framework when it's Pyrogram itself that +*calls your code*, that is, your registered event handlers. Such applications are usually started and left online +indefinitely, until you decide to stop them. + How stable and reliable is Pyrogram? ------------------------------------ From 46ced1aa11968a5c21fbc9eea6beabb09f0f6f2f Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 3 Mar 2021 18:57:49 +0100 Subject: [PATCH 0493/1185] Fix sphinx warnings --- pyrogram/types/messages_and_media/message.py | 2 ++ pyrogram/types/user_and_chats/chat.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index 513cef18f7..d9b8065b37 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -712,6 +712,7 @@ async def get_media_group(self) -> List["types.Message"]: Use as a shortcut for: .. code-block:: python + client.get_media_group( chat_id=message.chat.id, message_id=message.message_id @@ -719,6 +720,7 @@ async def get_media_group(self) -> List["types.Message"]: Example: .. code-block:: python + message.get_media_group() Returns: diff --git a/pyrogram/types/user_and_chats/chat.py b/pyrogram/types/user_and_chats/chat.py index 75c454b542..200a31ebe4 100644 --- a/pyrogram/types/user_and_chats/chat.py +++ b/pyrogram/types/user_and_chats/chat.py @@ -871,7 +871,7 @@ def iter_members( limit (``int``, *optional*): Limits the number of members to be retrieved. Only applicable to supergroups and channels. - Defaults to 200, which is also the maximum server limit allowed per method call. + Defaults to 200, which is also the maximum server limit allowed per method call [1]_. query (``str``, *optional*): Query string to filter members based on their display names and usernames. From b5c39121c379943a00a394c96cefcbb5ffef4e36 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 3 Mar 2021 18:59:15 +0100 Subject: [PATCH 0494/1185] Small code and docs fixes --- pyrogram/methods/messages/get_media_group.py | 4 ++-- .../messages_and_media/message_entity.py | 24 +++++++++++++------ 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/pyrogram/methods/messages/get_media_group.py b/pyrogram/methods/messages/get_media_group.py index d43fb32818..cf01a8edbd 100644 --- a/pyrogram/methods/messages/get_media_group.py +++ b/pyrogram/methods/messages/get_media_group.py @@ -20,7 +20,7 @@ from typing import Union, List from pyrogram.scaffold import Scaffold -from pyrogram.types import list +from pyrogram import types log = logging.getLogger(__name__) @@ -56,4 +56,4 @@ async def get_media_group( if media_group_id is None: raise ValueError("The message doesn't belong to a media group") - return list.List(msg for msg in messages if msg.media_group_id == media_group_id) + return types.List(msg for msg in messages if msg.media_group_id == media_group_id) diff --git a/pyrogram/types/messages_and_media/message_entity.py b/pyrogram/types/messages_and_media/message_entity.py index 2c7fde7f9e..a0fc86a922 100644 --- a/pyrogram/types/messages_and_media/message_entity.py +++ b/pyrogram/types/messages_and_media/message_entity.py @@ -77,13 +77,23 @@ class MessageEntity(Object): Parameters: type (``str``): - Type of the entity. - Can be "mention" (``@username``), "hashtag" (``#hashtag``), "cashtag" (``$PYRO``), - "bot_command" (``/start@pyrogrambot``), "url" (``https://pyrogram.org``), - "email" (``do-not-reply@pyrogram.org``), "phone_number" (``+1-420-069-1337``), "bold" (**bold text**), - "italic" (*italic text*), "underline" (underlined text), "strikethrough" (strikethrough text), - "code" (monowidth string), "pre" (monowidth block), "text_link" (for clickable text URLs), - "text_mention" (for users without usernames). + Type of the entity. Can be: + + - "mention": ``@username``. + - "hashtag": ``#hashtag``. + - "cashtag": ``$PYRO``. + - "bot_command": ``/start@pyrogrambot``. + - "url": ``https://pyrogram.org`` (see *url* below). + - "email": ``do-not-reply@pyrogram.org``. + - "phone_number": ``+69-420-1337``. + - "bold": **bold text**. + - "italic": *italic text*. + - "underline": underlined text. + - "strikethrough": strikethrough text. + - "code": monowidth string. + - "pre": monowidth block (see *language* below). + - "text_link": for clickable text URLs. + - "text_mention": for users without usernames (see *user* below). offset (``int``): Offset in UTF-16 code units to the start of the entity. From 6daa501972382215bac68bc2d53d62cc38b517ef Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 3 Mar 2021 19:02:20 +0100 Subject: [PATCH 0495/1185] Add a friendly interface for getting chat event logs Add get_chat_event_log method Add ChatEvent and ChatEventFilter types --- compiler/docs/compiler.py | 5 +- pyrogram/methods/chats/__init__.py | 6 +- pyrogram/methods/chats/get_chat_event_log.py | 101 ++++ pyrogram/types/user_and_chats/__init__.py | 5 +- pyrogram/types/user_and_chats/chat_event.py | 547 ++++++++++++++++++ .../types/user_and_chats/chat_event_filter.py | 175 ++++++ 6 files changed, 835 insertions(+), 4 deletions(-) create mode 100644 pyrogram/methods/chats/get_chat_event_log.py create mode 100644 pyrogram/types/user_and_chats/chat_event.py create mode 100644 pyrogram/types/user_and_chats/chat_event_filter.py diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index e98fcd1882..9b9aa49d93 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -221,6 +221,7 @@ def get_title_list(s: str) -> list: delete_user_history set_slow_mode mark_chat_unread + get_chat_event_log """, users=""" Users @@ -245,7 +246,7 @@ def get_title_list(s: str) -> list: delete_contacts """, password=""" - Pssword + Password enable_cloud_password change_cloud_password remove_cloud_password @@ -331,6 +332,8 @@ def get_title_list(s: str) -> list: ChatPhoto ChatMember ChatPermissions + ChatEvent + ChatEventFilter Dialog Restriction """, diff --git a/pyrogram/methods/chats/__init__.py b/pyrogram/methods/chats/__init__.py index 184562d83d..62dfd5bd27 100644 --- a/pyrogram/methods/chats/__init__.py +++ b/pyrogram/methods/chats/__init__.py @@ -27,6 +27,7 @@ from .delete_user_history import DeleteUserHistory from .export_chat_invite_link import ExportChatInviteLink from .get_chat import GetChat +from .get_chat_event_log import GetChatEventLog from .get_chat_member import GetChatMember from .get_chat_members import GetChatMembers from .get_chat_members_count import GetChatMembersCount @@ -38,6 +39,7 @@ from .join_chat import JoinChat from .kick_chat_member import KickChatMember from .leave_chat import LeaveChat +from .mark_chat_unread import MarkChatUnread from .pin_chat_message import PinChatMessage from .promote_chat_member import PromoteChatMember from .restrict_chat_member import RestrictChatMember @@ -52,7 +54,6 @@ from .unpin_all_chat_messages import UnpinAllChatMessages from .unpin_chat_message import UnpinChatMessage from .update_chat_username import UpdateChatUsername -from .mark_chat_unread import MarkChatUnread class Chats( @@ -92,6 +93,7 @@ class Chats( SetSlowMode, DeleteUserHistory, UnpinAllChatMessages, - MarkChatUnread + MarkChatUnread, + GetChatEventLog ): pass diff --git a/pyrogram/methods/chats/get_chat_event_log.py b/pyrogram/methods/chats/get_chat_event_log.py new file mode 100644 index 0000000000..80dfb5bd85 --- /dev/null +++ b/pyrogram/methods/chats/get_chat_event_log.py @@ -0,0 +1,101 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan +# +# 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 Union, List, AsyncGenerator, Optional + +from pyrogram import raw +from pyrogram import types +from pyrogram.scaffold import Scaffold + + +class GetChatEventLog(Scaffold): + async def get_chat_event_log( + self, + chat_id: Union[int, str], + query: str = "", + offset_id: int = 0, + limit: int = 0, + filters: "types.ChatEventFilter" = None, + user_ids: List[Union[int, str]] = None + ) -> Optional[AsyncGenerator["types.ChatEvent", None]]: + """Get the actions taken by chat members and administrators in the last 48h. + + Only available for supergroups and channels. Requires administrator rights. + Results are returned in reverse chronological order (i.e., newest first). + + Args: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + + query (``str``, *optional*): + Search query to filter events based on text. + By default, an empty query is applied and all events will be returned. + + offset_id (``int``, *optional*): + Offset event identifier from which to start returning results. + By default, no offset is applied and events will be returned starting from the latest. + + limit (``int``, *optional*): + Maximum amount of events to be returned. + By default, all events will be returned. + + filters (:obj:`~pyrogram.types.ChatEventFilter`, *optional*): + The types of events to return. + By default, all types will be returned. + + user_ids (List of ``int`` | ``str``, *optional*): + User identifiers (int) or usernames (str) by which to filter events. + By default, events relating to all users will be returned. + + Yields: + :obj:`~pyrogram.types.ChatEvent` objects. + """ + current = 0 + total = abs(limit) or (1 << 31) + limit = min(100, total) + + while True: + r: raw.base.channels.AdminLogResults = await self.send( + raw.functions.channels.GetAdminLog( + channel=await self.resolve_peer(chat_id), + q=query, + min_id=0, + max_id=offset_id, + limit=limit, + events_filter=filters.write(), + admins=( + [await self.resolve_peer(i) for i in user_ids] + if user_ids is not None + else user_ids + ) + ) + ) + + if not r.events: + return + + last = r.events[-1] + offset_id = last.id + + for event in r.events: + yield await types.ChatEvent._parse(self, event, r.users, r.chats) + + current += 1 + + if current >= total: + return diff --git a/pyrogram/types/user_and_chats/__init__.py b/pyrogram/types/user_and_chats/__init__.py index a38bb4ce1d..0c776c9940 100644 --- a/pyrogram/types/user_and_chats/__init__.py +++ b/pyrogram/types/user_and_chats/__init__.py @@ -17,6 +17,7 @@ # along with Pyrogram. If not, see . from .chat import Chat +from .chat_event import ChatEvent from .chat_member import ChatMember from .chat_permissions import ChatPermissions from .chat_photo import ChatPhoto @@ -24,7 +25,9 @@ from .dialog import Dialog from .restriction import Restriction from .user import User +from .chat_event_filter import ChatEventFilter __all__ = [ - "Chat", "ChatMember", "ChatPermissions", "ChatPhoto", "ChatPreview", "Dialog", "User", "Restriction" + "Chat", "ChatMember", "ChatPermissions", "ChatPhoto", "ChatPreview", "Dialog", "User", "Restriction", "ChatEvent", + "ChatEventFilter" ] diff --git a/pyrogram/types/user_and_chats/chat_event.py b/pyrogram/types/user_and_chats/chat_event.py new file mode 100644 index 0000000000..93ef9c3586 --- /dev/null +++ b/pyrogram/types/user_and_chats/chat_event.py @@ -0,0 +1,547 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan +# +# 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 enum import Enum, auto +from typing import List, Optional + +import pyrogram +from pyrogram import raw +from pyrogram import types +from ..object import Object + + +class AutoName(Enum): + def _generate_next_value_(self, *args): + return self.lower() + + +class ChatEventAction(AutoName): + DESCRIPTION_CHANGED = auto() + HISTORY_TTL_CHANGED = auto() + LINKED_CHAT_CHANGED = auto() + # LOCATION_CHANGED = auto() + PHOTO_CHANGED = auto() + # STICKER_SET_CHANGED = auto() + TITLE_CHANGED = auto() + USERNAME_CHANGED = auto() + CHAT_PERMISSIONS_CHANGED = auto() + MESSAGE_DELETED = auto() + # VOICE_CHAT_DISCARDED = auto() + MESSAGE_EDITED = auto() + # LINK_DELETED = auto() + # LINK_EDITED = auto() + # LINK_REVOKED = auto() + MEMBER_INVITED = auto() + MEMBER_JOINED = auto() + # MEMBER_JOINED_BY_LINK = auto() + MEMBER_LEFT = auto() + # MEMBER_MUTED = auto() + ADMIN_RIGHTS_CHANGED = auto() + MEMBER_PERMISSIONS_CHANGED = auto() + # MEMBER_UNMUTED = auto() + # MEMBER_VOLUME_CHANGED = auto() + # VOICE_CHAT_STARTED = auto() + POLL_STOPPED = auto() + # VOICE_CHAT_SETTINGS_CHANGED = auto() + INVITES_ENABLED = auto() + HISTORY_HIDDEN = auto() + SIGNATURES_ENABLED = auto() + SLOW_MODE_CHANGED = auto() + MESSAGE_PINNED = auto() + MESSAGE_UNPINNED = auto() + UNKNOWN = auto() + + +class ChatEvent(Object): + """A chat event from the recent actions log (also known as admin log). + + Parameters: + id (``int``): + Chat event identifier. + + date (``int``): + Date of the event. Unix time. + + action (``str``): + Event action. Can be: + + - "description_changed": the chat description has been changed + (see *old_description* and *new_description* below). + + - "history_ttl_changed": the history time-to-live has been changed + (see *old_history_ttl* and *new_history_ttl* below). + + - "linked_chat_changed": the linked chat has been changed + (see *old_linked_chat* and *new_linked_chat* below). + + - "photo_changed": the chat photo has been changed + (see *old_photo* and *new_photo* below). + + - "title_changed": the chat title has been changed + (see *old_title* and *new_title* below). + + - "username_changed": the chat username has been changed + (see *old_username* and *new_username* below). + + - "chat_permissions_changed": the default chat permissions has been changed + (see *old_chat_permissions* and *new_chat_permissions* below). + + - "message_deleted": a message has been deleted + (see *deleted_message* below). + + - "message_edited": a message has been edited + (see *old_message* and *new_message* below). + + - "member_invited": a member has been invited by someone + (see *invited_member* below). + + - "member_joined": a member joined by themselves. + (see *user* below) + + - "member_left": a member left by themselves. + (see *user* below). + + - "admin_rights_changed": a chat member has been promoted/demoted or their administrator rights has changed + (see *old_admin_rights* and *new_admin_rights* below). + + - "member_permissions_changed": a chat member has been restricted/unrestricted or banned/unbanned, or their + permissions has changed (see *old_member_permissions* and *new_member_permissions* below). + + - "poll_stopped": a poll has been stopped + (see *stopped_poll* below). + + - "invites_enabled": the chat invitation has been enabled or disabled + (see *invites_enabled* below). + + - "history_hidden": the chat history has been hidden or unhidden + (see *history_hidden* below). + + - "signatures_enabled": the message signatures have been enabled or disabled + (see *signatures_enabled* below). + + - "slow_mode_changed": the slow mode has been changes + (see *old_slow_mode* and *new_slow_mode* below). + + - "message_pinned": a message has been pinned + (see *pinned_message* below). + + - "message_unpinned": a message has been unpinned + (see *unpinned_message* below). + + user (:obj:`~pyrogram.types.User`): + User that triggered the event. + + old_description, new_description (``str``, *optional*): + Previous and new chat description. + For "description_changed" only. + + old_history_ttl, new_history_ttl (``int``, *optional*): + Previous and new chat history TTL. + For "history_ttl_changed" only. + + old_linked_chat, new_linked_chat (:obj:`~pyrogram.types.Chat`, *optional*): + Previous and new linked chat. + For "linked_chat_changed" only. + + old_photo, new_photo (:obj:`~pyrogram.types.Photo`, *optional*): + Previous and new chat photo. + For "photo_changed" only. + + old_title, new_title (``str``, *optional*): + Previous and new chat title. + For "title_changed" only. + + old_username, new_username (``str``, *optional*): + Previous and new chat username. + For "username_changed" only. + + old_chat_permissions, new_chat_permissions (:obj:`~pyrogram.types.ChatPermissions`, *optional*): + Previous and new default chat permissions. + For "chat_permissions_changed" only. + + deleted_message (:obj:`~pyrogram.types.Message`, *optional*): + Deleted message. + For "deleted_message" only. + + old_message, new_message (:obj:`~pyrogram.types.Message`, *optional*): + Previous and new message before it has been edited. + For "message_edited" only. + + invited_member (:obj:`~pyrogram.types.ChatMember`, *optional*): + New invited chat member. + For "member_invited" only. + + old_admin_rights, new_admin_rights (:obj:`~pyrogram.types.ChatMember`, *optional*): + Previous and new administrator rights. + For "admin_rights_changed" only. + + old_member_permissions, new_member_permissions (:obj:`~pyrogram.types.ChatMember`, *optional*): + Previous and new member permissions. + For "member_permissions_changed" only. + + stopped_poll (:obj:`~pyrogram.types.Message`, *optional*): + Message containing the stopped poll. + For "poll_stopped" only. + + invites_enabled (``bool``, *optional*): + If chat invites were enabled (True) or disabled (False). + For "invites_enabled" only. + + history_hidden (``bool``, *optional*): + If chat history has been hidden (True) or unhidden (False). + For "history_hidden" only. + + signatures_enabled (``bool``, *optional*): + If message signatures were enabled (True) or disabled (False). + For "signatures_enabled" only. + + old_slow_mode, new_slow_mode (``int``, *optional*): + Previous slow mode value in seconds. + For "slow_mode_changed" only. + + pinned_message (:obj:`~pyrogram.types.Message`, *optional*): + Pinned message. + For "message_pinned" only. + + unpinned_message (:obj:`~pyrogram.types.Message`, *optional*): + Unpinned message. + For "unpinned_message" only. + """ + + def __init__( + self, *, + id: int, + date: int, + user: "types.User", + action: str, + + old_description: str = None, + new_description: str = None, + + old_history_ttl: int = None, + new_history_ttl: int = None, + + old_linked_chat: "types.Chat" = None, + new_linked_chat: "types.Chat" = None, + + old_photo: "types.Photo" = None, + new_photo: "types.Photo" = None, + + old_title: str = None, + new_title: str = None, + + old_username: str = None, + new_username: str = None, + + old_chat_permissions: "types.ChatPermissions" = None, + new_chat_permissions: "types.ChatPermissions" = None, + + deleted_message: "types.Message" = None, + + old_message: "types.Message" = None, + new_message: "types.Message" = None, + + invited_member: "types.ChatMember" = None, + + old_admin_rights: "types.ChatMember" = None, + new_admin_rights: "types.ChatMember" = None, + + old_member_permissions: "types.ChatMember" = None, + new_member_permissions: "types.ChatMember" = None, + + stopped_poll: "types.Message" = None, + + invites_enabled: "types.ChatMember" = None, + + history_hidden: bool = None, + + signatures_enabled: bool = None, + + old_slow_mode: int = None, + new_slow_mode: int = None, + + pinned_message: "types.Message" = None, + unpinned_message: "types.Message" = None + ): + super().__init__() + + self.id = id + self.date = date + self.action = action + self.user = user + + self.old_description = old_description + self.new_description = new_description + + self.old_history_ttl = old_history_ttl + self.new_history_ttl = new_history_ttl + + self.old_linked_chat = old_linked_chat + self.new_linked_chat = new_linked_chat + + self.old_photo = old_photo + self.new_photo = new_photo + + self.old_title = old_title + self.new_title = new_title + + self.old_username = old_username + self.new_username = new_username + + self.old_chat_permissions = old_chat_permissions + self.new_chat_permissions = new_chat_permissions + + self.deleted_message = deleted_message + + self.old_message = old_message + self.new_message = new_message + + self.invited_member = invited_member + + self.old_admin_rights = old_admin_rights + self.new_admin_rights = new_admin_rights + + self.old_member_permissions = old_member_permissions + self.new_member_permissions = new_member_permissions + + self.stopped_poll = stopped_poll + + self.invites_enabled = invites_enabled + + self.history_hidden = history_hidden + + self.signatures_enabled = signatures_enabled + + self.old_slow_mode = old_slow_mode + self.new_slow_mode = new_slow_mode + + self.pinned_message = pinned_message + self.unpinned_message = unpinned_message + + @staticmethod + async def _parse( + client: "pyrogram.Client", + event: "raw.base.ChannelAdminLogEvent", + users: List["raw.base.User"], + chats: List["raw.base.Chat"] + ): + users = {i.id: i for i in users} + chats = {i.id: i for i in chats} + + user = types.User._parse(client, users[event.user_id]) + action = event.action + + old_description: Optional[str] = None + new_description: Optional[str] = None + + old_history_ttl: Optional[int] = None + new_history_ttl: Optional[int] = None + + old_linked_chat: Optional[types.Chat] = None + new_linked_chat: Optional[types.Chat] = None + + old_photo: Optional[types.Photo] = None + new_photo: Optional[types.Photo] = None + + old_title: Optional[str] = None + new_title: Optional[str] = None + + old_username: Optional[str] = None + new_username: Optional[str] = None + + old_chat_permissions: Optional[types.ChatPermissions] = None + new_chat_permissions: Optional[types.ChatPermissions] = None + + deleted_message: Optional[types.Message] = None + + old_message: Optional[types.Message] = None + new_message: Optional[types.Message] = None + + invited_member: Optional[types.ChatMember] = None + + old_admin_rights: Optional[types.ChatMember] = None + new_admin_rights: Optional[types.ChatMember] = None + + old_member_permissions: Optional[types.ChatMember] = None + new_member_permissions: Optional[types.ChatMember] = None + + stopped_poll: Optional[types.Message] = None + + invites_enabled: Optional[bool] = None + + history_hidden: Optional[bool] = None + + signatures_enabled: Optional[bool] = None + + old_slow_mode: Optional[int] = None + new_slow_mode: Optional[int] = None + + pinned_message: Optional[types.Message] = None + unpinned_message: Optional[types.Message] = None + + if isinstance(action, raw.types.ChannelAdminLogEventActionChangeAbout): + old_description = action.prev_value + new_description = action.new_value + action = ChatEventAction.DESCRIPTION_CHANGED.value + + elif isinstance(action, raw.types.ChannelAdminLogEventActionChangeHistoryTTL): + old_history_ttl = action.prev_value + new_history_ttl = action.new_value + action = ChatEventAction.HISTORY_TTL_CHANGED.value + + elif isinstance(action, raw.types.ChannelAdminLogEventActionChangeLinkedChat): + old_linked_chat = types.Chat._parse_chat(client, chats[action.prev_value]) + new_linked_chat = types.Chat._parse_chat(client, chats[action.new_value]) + action = ChatEventAction.LINKED_CHAT_CHANGED.value + + elif isinstance(action, raw.types.ChannelAdminLogEventActionChangePhoto): + old_photo = types.Photo._parse(client, action.prev_photo) + new_photo = types.Photo._parse(client, action.new_photo) + action = ChatEventAction.PHOTO_CHANGED.value + + elif isinstance(action, raw.types.ChannelAdminLogEventActionChangeTitle): + old_title = action.prev_value + new_title = action.new_value + action = ChatEventAction.TITLE_CHANGED.value + + elif isinstance(action, raw.types.ChannelAdminLogEventActionChangeUsername): + old_username = action.prev_value + new_username = action.new_value + action = ChatEventAction.USERNAME_CHANGED.value + + elif isinstance(action, raw.types.ChannelAdminLogEventActionDefaultBannedRights): + old_chat_permissions = types.ChatPermissions._parse(action.prev_banned_rights) + new_chat_permissions = types.ChatPermissions._parse(action.new_banned_rights) + action = ChatEventAction.CHAT_PERMISSIONS_CHANGED.value + + elif isinstance(action, raw.types.ChannelAdminLogEventActionDeleteMessage): + deleted_message = await types.Message._parse(client, action.message, users, chats) + action = ChatEventAction.MESSAGE_DELETED.value + + elif isinstance(action, raw.types.ChannelAdminLogEventActionEditMessage): + old_message = await types.Message._parse(client, action.prev_message, users, chats) + new_message = await types.Message._parse(client, action.new_message, users, chats) + action = ChatEventAction.MESSAGE_EDITED.value + + elif isinstance(action, raw.types.ChannelAdminLogEventActionParticipantInvite): + invited_member = types.ChatMember._parse(client, action.participant, users) + action = ChatEventAction.MEMBER_INVITED.value + + elif isinstance(action, raw.types.ChannelAdminLogEventActionParticipantToggleAdmin): + old_admin_rights = types.ChatMember._parse(client, action.prev_participant, users) + new_admin_rights = types.ChatMember._parse(client, action.new_participant, users) + action = ChatEventAction.ADMIN_RIGHTS_CHANGED.value + + elif isinstance(action, raw.types.ChannelAdminLogEventActionParticipantToggleBan): + old_member_permissions = types.ChatMember._parse(client, action.prev_participant, users) + new_member_permissions = types.ChatMember._parse(client, action.new_participant, users) + action = ChatEventAction.MEMBER_PERMISSIONS_CHANGED.value + + elif isinstance(action, raw.types.ChannelAdminLogEventActionStopPoll): + stopped_poll = await types.Message._parse(client, action.message, users, chats) + action = ChatEventAction.POLL_STOPPED.value + + elif isinstance(action, raw.types.ChannelAdminLogEventActionParticipantJoin): + action = ChatEventAction.MEMBER_JOINED.value + + elif isinstance(action, raw.types.ChannelAdminLogEventActionParticipantLeave): + action = ChatEventAction.MEMBER_LEFT.value + + elif isinstance(action, raw.types.ChannelAdminLogEventActionToggleInvites): + invites_enabled = action.new_value + action = ChatEventAction.INVITES_ENABLED.value + + elif isinstance(action, raw.types.ChannelAdminLogEventActionTogglePreHistoryHidden): + history_hidden = action.new_value + action = ChatEventAction.HISTORY_HIDDEN.value + + elif isinstance(action, raw.types.ChannelAdminLogEventActionToggleSignatures): + signatures_enabled = action.new_value + action = ChatEventAction.SIGNATURES_ENABLED.value + + elif isinstance(action, raw.types.ChannelAdminLogEventActionToggleSlowMode): + old_slow_mode = action.prev_value + new_slow_mode = action.new_value + action = ChatEventAction.SLOW_MODE_CHANGED.value + + elif isinstance(action, raw.types.ChannelAdminLogEventActionUpdatePinned): + message = action.message + + if message.pinned: + pinned_message = await types.Message._parse(client, message, users, chats) + action = ChatEventAction.MESSAGE_PINNED.value + else: + unpinned_message = await types.Message._parse(client, message, users, chats) + action = ChatEventAction.MESSAGE_UNPINNED.value + + else: + action = f"{ChatEventAction.UNKNOWN.value}-{action.QUALNAME}" + + return ChatEvent( + id=event.id, + date=event.date, + user=user, + action=action, + old_description=old_description, + new_description=new_description, + + old_history_ttl=old_history_ttl, + new_history_ttl=new_history_ttl, + + old_linked_chat=old_linked_chat, + new_linked_chat=new_linked_chat, + + old_photo=old_photo, + new_photo=new_photo, + + old_title=old_title, + new_title=new_title, + + old_username=old_username, + new_username=new_username, + + old_chat_permissions=old_chat_permissions, + new_chat_permissions=new_chat_permissions, + + deleted_message=deleted_message, + + old_message=old_message, + new_message=new_message, + + invited_member=invited_member, + + old_admin_rights=old_admin_rights, + new_admin_rights=new_admin_rights, + + old_member_permissions=old_member_permissions, + new_member_permissions=new_member_permissions, + + stopped_poll=stopped_poll, + + invites_enabled=invites_enabled, + + history_hidden=history_hidden, + + signatures_enabled=signatures_enabled, + + old_slow_mode=old_slow_mode, + new_slow_mode=new_slow_mode, + + pinned_message=pinned_message, + unpinned_message=unpinned_message + ) diff --git a/pyrogram/types/user_and_chats/chat_event_filter.py b/pyrogram/types/user_and_chats/chat_event_filter.py new file mode 100644 index 0000000000..edd003eba8 --- /dev/null +++ b/pyrogram/types/user_and_chats/chat_event_filter.py @@ -0,0 +1,175 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan +# +# 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 pyrogram import raw +from ..object import Object + + +class ChatEventFilter(Object): + """Set of filters used to obtain a chat event log. + + Parameters: + new_restrictions (``bool``, *optional*): + True, if member restricted/unrestricted/banned/unbanned events should be returned. + Defaults to False. + + admin_rights (``bool``, *optional*): + True, if member promotion/demotion events should be returned. + Defaults to False. + + new_members (``bool``, *optional*): + True, if members joining events should be returned. + Defaults to False. + + chat_info (``bool``, *optional*): + True, if chat info changes should be returned. That is, when description, linked chat, location, photo, + sticker set, title or username have been modified. + Defaults to False. + + chat_settings (``bool``, *optional*): + True, if chat settings changes should be returned. That is, when invites, hidden history, message + signatures, default chat permissions have been modified. + Defaults to False. + + invite_links (``bool``, *optional*): + True, if invite links events (edit, revoke, delete) should be returned. + Defaults to False. + + deleted_messages (``bool``, *optional*): + True, if deleted messages events should be returned. + Defaults to False. + + edited_messages (``bool``, *optional*): + True, if edited messages events, including closed polls, should be returned. + Defaults to False. + + pinned_messages (``bool``, *optional*): + True, if pinned/unpinned messages events should be returned. + Defaults to False. + + leaving_members (``bool``, *optional*): + True, if members leaving events should be returned. + Defaults to False. + + voice_chats (``bool``, *optional*): + True, if voice chats events should be returned. + Defaults to False. + """ + + def __init__( + self, *, + new_restrictions: bool = False, + admin_rights: bool = False, + new_members: bool = False, + chat_info: bool = False, + chat_settings: bool = False, + invite_links: bool = False, + deleted_messages: bool = False, + edited_messages: bool = False, + pinned_messages: bool = False, + leaving_members: bool = False, + voice_chats: bool = False + ): + super().__init__() + + self.new_restrictions = new_restrictions + self.admin_rights = admin_rights + self.new_members = new_members + self.chat_info = chat_info + self.chat_settings = chat_settings + self.invite_links = invite_links + self.deleted_messages = deleted_messages + self.edited_messages = edited_messages + self.pinned_messages = pinned_messages + self.leaving_members = leaving_members + self.voice_chats = voice_chats + + def write(self) -> "raw.base.ChannelAdminLogEventsFilter": + join = False + leave = False + invite = False + ban = False + unban = False + kick = False + unkick = False + promote = False + demote = False + info = False + settings = False + pinned = False + edit = False + delete = False + group_call = False + invites = False + + if self.new_restrictions: + ban = True + unban = True + kick = True + unkick = True + + if self.admin_rights: + promote = True + demote = True + + if self.new_members: + join = True + invite = True + + if self.chat_info: + info = True + + if self.chat_settings: + settings = True + + if self.invite_links: + invites = True + + if self.deleted_messages: + delete = True + + if self.edited_messages: + edit = True + + if self.pinned_messages: + pinned = True + + if self.leaving_members: + leave = True + + if self.voice_chats: + group_call = True + + return raw.types.ChannelAdminLogEventsFilter( + join=join, + leave=leave, + invite=invite, + ban=ban, + unban=unban, + kick=kick, + unkick=unkick, + promote=promote, + demote=demote, + info=info, + settings=settings, + pinned=pinned, + edit=edit, + delete=delete, + group_call=group_call, + invites=invites + ) From 8bf0706cdf533d3643903251130ecb68f7c150db Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 6 Mar 2021 11:24:30 +0100 Subject: [PATCH 0496/1185] Delete powered-by.rst --- docs/source/powered-by.rst | 86 -------------------------------------- 1 file changed, 86 deletions(-) delete mode 100644 docs/source/powered-by.rst diff --git a/docs/source/powered-by.rst b/docs/source/powered-by.rst deleted file mode 100644 index 68ca391e89..0000000000 --- a/docs/source/powered-by.rst +++ /dev/null @@ -1,86 +0,0 @@ -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`_. - -.. contents:: Contents - :backlinks: none - :depth: 1 - :local: - ------ - -Projects Showcase ------------------ - -`YTAudioBot `_ -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -| **A YouTube audio downloader on Telegram, serving over 450k active users each month.** -| --- 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://github.com/ColinShark/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 - ------ - -`Pyrubrum `_ -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - -| **An intuitive framework for creating Telegram bots** -| --- by `Hearot `_ - -- Source Code: https://github.com/hearot/pyrubrum - ------ - -.. _Feature Request: https://github.com/pyrogram/pyrogram/issues/new?labels=enhancement&template=feature_request.md - From deef5781bf0cf2ef56cc15746e1861bcb7dcd9ad Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 6 Mar 2021 11:24:52 +0100 Subject: [PATCH 0497/1185] Rename support-pyrogram.rst to support.rst --- docs/source/{support-pyrogram.rst => support.rst} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/source/{support-pyrogram.rst => support.rst} (100%) diff --git a/docs/source/support-pyrogram.rst b/docs/source/support.rst similarity index 100% rename from docs/source/support-pyrogram.rst rename to docs/source/support.rst From ce6ba8d122bc35de49ab77dfdbea76c261d96f77 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 6 Mar 2021 11:33:07 +0100 Subject: [PATCH 0498/1185] Update outdated information --- docs/source/faq.rst | 16 ---------------- docs/source/index.rst | 6 ++---- 2 files changed, 2 insertions(+), 20 deletions(-) diff --git a/docs/source/faq.rst b/docs/source/faq.rst index 8e681fffb3..82454d0bb2 100644 --- a/docs/source/faq.rst +++ b/docs/source/faq.rst @@ -74,22 +74,6 @@ executed as soon as they arrive from the server side. Pyrogram acts as a framewo *calls your code*, that is, your registered event handlers. Such applications are usually started and left online indefinitely, until you decide to stop them. -How stable and reliable is Pyrogram? ------------------------------------- - -So far, since its first public release, Pyrogram has always shown itself to be quite reliable in handling client-server -interconnections and just as stable when keeping long running applications online. The only annoying issues faced are -actually coming from Telegram servers internal errors and down times, from which Pyrogram is able to recover itself -automatically. - -To challenge the framework, the creator is constantly keeping a public -`welcome bot `_ online 24/7 on his own, -relatively-busy account for well over a year now. - -In addition to that, about six months ago, one of the most popular Telegram bot has been rewritten from scratch -:doc:`using Pyrogram ` and is serving more than 200,000 Monthly Active Users since -then, uninterruptedly and without any need for restarting it. - What can MTProto do more than the Bot API? ------------------------------------------ diff --git a/docs/source/index.rst b/docs/source/index.rst index c3727360df..3834853434 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -90,8 +90,7 @@ Meta - :doc:`Pyrogram FAQ `: Answers to common Pyrogram questions. - :doc:`Pyrogram Glossary `: List of words with brief explanations. - - :doc:`Powered by Pyrogram `: Collection of Pyrogram Projects. - - :doc:`Support Pyrogram `: Ways to show your appreciation. + - :doc:`Support Pyrogram `: Ways to show your appreciation. - :doc:`About the License `: Information about the Project license. - :doc:`Release Notes `: Release notes for Pyrogram releases. @@ -157,8 +156,7 @@ Last updated on |today| faq glossary - powered-by - support-pyrogram + support license releases/index From 137de2da0f826e4b970576db3749709f6f97f8fb Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 6 Mar 2021 12:42:34 +0100 Subject: [PATCH 0499/1185] Update support.rst --- docs/source/support.rst | 55 +++++++++++++++++++++++++++++++---------- 1 file changed, 42 insertions(+), 13 deletions(-) diff --git a/docs/source/support.rst b/docs/source/support.rst index aa662d49e8..029eeec043 100644 --- a/docs/source/support.rst +++ b/docs/source/support.rst @@ -1,8 +1,19 @@ Support Pyrogram ================ +As a developer, you probably understand that "open source" doesn't mean "free work". If you wish to tip me for Pyrogram +-- or any of my `other works`_ -- you can do so by the ways shown below. Your appreciation means a lot and helps +staying motivated! + +--- `Dan`_ + +----- + +Star +---- + Pyrogram is free and open source software, and thus powered by your love and support! If you like the project and have -found it to be useful, give Pyrogram a `Star on GitHub`_. Your appreciation means a lot and helps staying motivated. +found it to be useful, give Pyrogram a `Star on GitHub`_. .. raw:: html @@ -11,25 +22,43 @@ found it to be useful, give Pyrogram a `Star on GitHub`_. Your appreciation mean ----- -Cloud Credits -------------- +Sponsor +------- -If you need a cloud server to host your applications, try **Hetzner Cloud**. You can sign up with -`this link `_ to get €20 in cloud credits and help support Pyrogram and -my `other projects`_. +You can become a GitHub sponsor: + +.. raw:: html + + + +----- Donate ------ -As a developer, you probably understand that "open source" doesn't mean "free work". A lot of time and resources has -been put into the project and if you'd like to tip me for Pyrogram -- or any of my `other works`_ -- you can use the -PayPal button below. Thank you! +You can donate via PayPal using the button below: -.. image:: https://i.imgur.com/fasFTzK.png - :target: https://paypal.me/delivrance - :width: 128 +.. raw:: html ---- `Dan`_ +
+ + + +
+ +----- + +Cloud Credits +------------- + +If you need a cloud server to host your applications, try **Hetzner Cloud**. You can sign up with +`this link `_ to get €20 in cloud credits and help support Pyrogram and +my `other projects`_. .. _Star on GitHub: https://github.com/pyrogram/pyrogram .. _other projects: https://github.com/delivrance From 31148c3a5638ec7c6717eebbf419c50447a46b4c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 14 Mar 2021 12:25:17 +0100 Subject: [PATCH 0500/1185] Update API schema to Layer 125 --- compiler/api/source/main_api.tl | 51 +++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl index da618bbc3a..21b71663ea 100644 --- a/compiler/api/source/main_api.tl +++ b/compiler/api/source/main_api.tl @@ -70,6 +70,7 @@ inputPhotoFileLocation#40181ffe id:long access_hash:long file_reference:bytes th inputPhotoLegacyFileLocation#d83466f3 id:long access_hash:long file_reference:bytes volume_id:long local_id:int secret:long = InputFileLocation; inputPeerPhotoFileLocation#27d69997 flags:# big:flags.0?true peer:InputPeer volume_id:long local_id:int = InputFileLocation; inputStickerSetThumb#dbaeae9 stickerset:InputStickerSet volume_id:long local_id:int = InputFileLocation; +inputGroupCallStream#bba51639 call:InputGroupCall time_ms:long scale:int = InputFileLocation; peerUser#9db1bc6d user_id:int = Peer; peerChat#bad0e5bb chat_id:int = Peer; @@ -105,8 +106,8 @@ chatForbidden#7328bdb id:int title:string = Chat; channel#d31a961e flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true fake:flags.25?true gigagroup:flags.26?true id:int access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int version:int restriction_reason:flags.9?Vector admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int = Chat; channelForbidden#289da732 flags:# broadcast:flags.5?true megagroup:flags.8?true id:int access_hash:long title:string until_date:flags.16?int = Chat; -chatFull#f06c4018 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:int about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int = ChatFull; -channelFull#2548c037 flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true id:int about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?int migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?int location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector = ChatFull; +chatFull#8a1e2983 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:int about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer = ChatFull; +channelFull#548c3f93 flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true id:int about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?int migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?int location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector groupcall_default_join_as:flags.26?Peer = ChatFull; chatParticipant#c8d7493e user_id:int inviter_id:int date:int = ChatParticipant; chatParticipantCreator#da13538a user_id:int = ChatParticipant; @@ -264,7 +265,7 @@ updateNewMessage#1f2b0afd message:Message pts:int pts_count:int = Update; updateMessageID#4e90bfd6 id:int random_id:long = Update; updateDeleteMessages#a20db0e5 messages:Vector pts:int pts_count:int = Update; updateUserTyping#5c486927 user_id:int action:SendMessageAction = Update; -updateChatUserTyping#9a65ea1f chat_id:int user_id:int action:SendMessageAction = Update; +updateChatUserTyping#86cadb6c chat_id:int from_id:Peer action:SendMessageAction = Update; updateChatParticipants#7761198 participants:ChatParticipants = Update; updateUserStatus#1bfbd823 user_id:int status:UserStatus = Update; updateUserName#a7332b73 user_id:int first_name:string last_name:string username:string = Update; @@ -341,16 +342,16 @@ updateChannelMessageForwards#6e8a84df channel_id:int id:int forwards:int = Updat updateReadChannelDiscussionInbox#1cc7de54 flags:# channel_id:int top_msg_id:int read_max_id:int broadcast_id:flags.0?int broadcast_post:flags.0?int = Update; updateReadChannelDiscussionOutbox#4638a26c channel_id:int top_msg_id:int read_max_id:int = Update; updatePeerBlocked#246a4b22 peer_id:Peer blocked:Bool = Update; -updateChannelUserTyping#ff2abe9f flags:# channel_id:int top_msg_id:flags.0?int user_id:int action:SendMessageAction = Update; +updateChannelUserTyping#6b171718 flags:# channel_id:int top_msg_id:flags.0?int from_id:Peer action:SendMessageAction = Update; updatePinnedMessages#ed85eab5 flags:# pinned:flags.0?true peer:Peer messages:Vector pts:int pts_count:int = Update; updatePinnedChannelMessages#8588878b flags:# pinned:flags.0?true channel_id:int messages:Vector pts:int pts_count:int = Update; updateChat#1330a196 chat_id:int = Update; updateGroupCallParticipants#f2ebdb4e call:InputGroupCall participants:Vector version:int = Update; updateGroupCall#a45eb99b chat_id:int call:GroupCall = Update; updatePeerHistoryTTL#bb9bb9a5 flags:# peer:Peer ttl_period:flags.0?int = Update; -updateChatParticipant#609a6ed4 flags:# chat_id:int date:int user_id:int prev_participant:flags.0?ChatParticipant new_participant:flags.1?ChatParticipant qts:int = Update; -updateChannelParticipant#65d2b464 flags:# channel_id:int date:int user_id:int prev_participant:flags.0?ChannelParticipant new_participant:flags.1?ChannelParticipant qts:int = Update; -updateBotStopped#30ec6ebc user_id:int stopped:Bool qts:int = Update; +updateChatParticipant#f3b3781f flags:# chat_id:int date:int actor_id:int user_id:int prev_participant:flags.0?ChatParticipant new_participant:flags.1?ChatParticipant invite:flags.2?ExportedChatInvite qts:int = Update; +updateChannelParticipant#7fecb1ec flags:# channel_id:int date:int actor_id:int user_id:int prev_participant:flags.0?ChannelParticipant new_participant:flags.1?ChannelParticipant invite:flags.2?ExportedChatInvite qts:int = Update; +updateBotStopped#7f9488a user_id:int date:int stopped:Bool qts:int = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -1180,15 +1181,15 @@ peerBlocked#e8fd8014 peer_id:Peer date:int = PeerBlocked; stats.messageStats#8999f295 views_graph:StatsGraph = stats.MessageStats; groupCallDiscarded#7780bcb4 id:long access_hash:long duration:int = GroupCall; -groupCall#55903081 flags:# join_muted:flags.1?true can_change_join_muted:flags.2?true id:long access_hash:long participants_count:int params:flags.0?DataJSON version:int = GroupCall; +groupCall#c0c2052e flags:# join_muted:flags.1?true can_change_join_muted:flags.2?true id:long access_hash:long participants_count:int params:flags.0?DataJSON title:flags.3?string stream_dc_id:flags.4?int record_start_date:flags.5?int version:int = GroupCall; inputGroupCall#d8aa840f id:long access_hash:long = InputGroupCall; -groupCallParticipant#64c62a15 flags:# muted:flags.0?true left:flags.1?true can_self_unmute:flags.2?true just_joined:flags.4?true versioned:flags.5?true min:flags.8?true muted_by_you:flags.9?true volume_by_admin:flags.10?true user_id:int date:int active_date:flags.3?int source:int volume:flags.7?int = GroupCallParticipant; +groupCallParticipant#19adba89 flags:# muted:flags.0?true left:flags.1?true can_self_unmute:flags.2?true just_joined:flags.4?true versioned:flags.5?true min:flags.8?true muted_by_you:flags.9?true volume_by_admin:flags.10?true self:flags.12?true peer:Peer date:int active_date:flags.3?int source:int volume:flags.7?int about:flags.11?string raise_hand_rating:flags.13?long = GroupCallParticipant; -phone.groupCall#66ab0bfc call:GroupCall participants:Vector participants_next_offset:string users:Vector = phone.GroupCall; +phone.groupCall#9e727aad call:GroupCall participants:Vector participants_next_offset:string chats:Vector users:Vector = phone.GroupCall; -phone.groupParticipants#9cfeb92d count:int participants:Vector next_offset:string users:Vector version:int = phone.GroupParticipants; +phone.groupParticipants#f47751b6 count:int participants:Vector next_offset:string chats:Vector users:Vector version:int = phone.GroupParticipants; inlineQueryPeerTypeSameBotPM#3081ed9d = InlineQueryPeerType; inlineQueryPeerTypePM#833c0fac = InlineQueryPeerType; @@ -1215,6 +1216,12 @@ chatAdminWithInvites#dfd2330f admin_id:int invites_count:int revoked_invites_cou messages.chatAdminsWithInvites#b69b72d7 admins:Vector users:Vector = messages.ChatAdminsWithInvites; +messages.checkedHistoryImportPeer#a24de717 confirm_text:string = messages.CheckedHistoryImportPeer; + +phone.joinAsPeers#afe5623f peers:Vector chats:Vector users:Vector = phone.JoinAsPeers; + +phone.exportedGroupCallInvite#204bd158 link:string = phone.ExportedGroupCallInvite; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -1447,8 +1454,8 @@ messages.getEmojiKeywordsDifference#1508b6af lang_code:string from_version:int = messages.getEmojiKeywordsLanguages#4e9963b2 lang_codes:Vector = Vector; messages.getEmojiURL#d5b10c26 lang_code:string = EmojiURL; messages.getSearchCounters#732eef00 peer:InputPeer filters:Vector = Vector; -messages.requestUrlAuth#e33f5613 peer:InputPeer msg_id:int button_id:int = UrlAuthResult; -messages.acceptUrlAuth#f729ea98 flags:# write_allowed:flags.0?true peer:InputPeer msg_id:int button_id:int = UrlAuthResult; +messages.requestUrlAuth#198fb446 flags:# peer:flags.1?InputPeer msg_id:flags.1?int button_id:flags.1?int url:flags.2?string = UrlAuthResult; +messages.acceptUrlAuth#b12c7125 flags:# write_allowed:flags.0?true peer:flags.1?InputPeer msg_id:flags.1?int button_id:flags.1?int url:flags.2?string = UrlAuthResult; messages.hidePeerSettingsBar#4facb138 peer:InputPeer = Bool; messages.getScheduledHistory#e2c2685b peer:InputPeer hash:int = messages.Messages; messages.getScheduledMessages#bdbb0464 peer:InputPeer id:Vector = messages.Messages; @@ -1472,12 +1479,14 @@ messages.initHistoryImport#34090c3b peer:InputPeer file:InputFile media_count:in messages.uploadImportedMedia#2a862092 peer:InputPeer import_id:long file_name:string media:InputMedia = MessageMedia; messages.startHistoryImport#b43df344 peer:InputPeer import_id:long = Bool; messages.getExportedChatInvites#a2b5a3f6 flags:# revoked:flags.3?true peer:InputPeer admin_id:InputUser offset_date:flags.2?int offset_link:flags.2?string limit:int = messages.ExportedChatInvites; +messages.getExportedChatInvite#73746f5c peer:InputPeer link:string = messages.ExportedChatInvite; messages.editExportedChatInvite#2e4ffbe flags:# revoked:flags.2?true peer:InputPeer link:string expire_date:flags.0?int usage_limit:flags.1?int = messages.ExportedChatInvite; messages.deleteRevokedExportedChatInvites#56987bd5 peer:InputPeer admin_id:InputUser = Bool; messages.deleteExportedChatInvite#d464a42b peer:InputPeer link:string = Bool; messages.getAdminsWithInvites#3920e6ef peer:InputPeer = messages.ChatAdminsWithInvites; messages.getChatInviteImporters#26fb7289 peer:InputPeer link:string offset_date:int offset_user:InputUser limit:int = messages.ChatInviteImporters; messages.setHistoryTTL#b80e5fe4 peer:InputPeer period:int = Updates; +messages.checkHistoryImportPeer#5dc60f03 peer:InputPeer = messages.CheckedHistoryImportPeer; updates.getState#edd4882a = updates.State; updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference; @@ -1541,7 +1550,7 @@ channels.inviteToChannel#199f3a6c channel:InputChannel users:Vector = channels.deleteChannel#c0111fe3 channel:InputChannel = Updates; channels.exportMessageLink#e63fadeb flags:# grouped:flags.0?true thread:flags.1?true channel:InputChannel id:int = ExportedMessageLink; channels.toggleSignatures#1f69b606 channel:InputChannel enabled:Bool = Updates; -channels.getAdminedPublicChannels#f8b036af flags:# by_location:flags.0?true check_limit:flags.1?true = messages.Chats; +channels.getAdminedPublicChannels#f8b036af flags:# by_location:flags.0?true check_limit:flags.1?true for_groupcall:flags.2?true = messages.Chats; channels.editBanned#72796912 channel:InputChannel user_id:InputUser banned_rights:ChatBannedRights = Updates; channels.getAdminLog#33ddf480 flags:# channel:InputChannel q:string events_filter:flags.0?ChannelAdminLogEventsFilter admins:flags.1?Vector max_id:long min_id:long limit:int = channels.AdminLogResults; channels.setStickers#ea8ca4f9 channel:InputChannel stickerset:InputStickerSet = Bool; @@ -1585,15 +1594,19 @@ phone.setCallRating#59ead627 flags:# user_initiative:flags.0?true peer:InputPhon phone.saveCallDebug#277add7e peer:InputPhoneCall debug:DataJSON = Bool; phone.sendSignalingData#ff7a9383 peer:InputPhoneCall data:bytes = Bool; phone.createGroupCall#bd3dabe0 peer:InputPeer random_id:int = Updates; -phone.joinGroupCall#5f9c8e62 flags:# muted:flags.0?true call:InputGroupCall params:DataJSON = Updates; +phone.joinGroupCall#b132ff7b flags:# muted:flags.0?true call:InputGroupCall join_as:InputPeer invite_hash:flags.1?string params:DataJSON = Updates; phone.leaveGroupCall#500377f9 call:InputGroupCall source:int = Updates; -phone.editGroupCallMember#a5e76cd8 flags:# muted:flags.0?true call:InputGroupCall user_id:InputUser volume:flags.1?int = Updates; phone.inviteToGroupCall#7b393160 call:InputGroupCall users:Vector = Updates; phone.discardGroupCall#7a777135 call:InputGroupCall = Updates; -phone.toggleGroupCallSettings#74bbb43d flags:# call:InputGroupCall join_muted:flags.0?Bool = Updates; +phone.toggleGroupCallSettings#74bbb43d flags:# reset_invite_hash:flags.1?true call:InputGroupCall join_muted:flags.0?Bool = Updates; phone.getGroupCall#c7cb017 call:InputGroupCall = phone.GroupCall; -phone.getGroupParticipants#c9f1d285 call:InputGroupCall ids:Vector sources:Vector offset:string limit:int = phone.GroupParticipants; +phone.getGroupParticipants#c558d8ab call:InputGroupCall ids:Vector sources:Vector offset:string limit:int = phone.GroupParticipants; phone.checkGroupCall#b74a7bea call:InputGroupCall source:int = Bool; +phone.toggleGroupCallRecord#c02a66d7 flags:# start:flags.0?true call:InputGroupCall title:flags.1?string = Updates; +phone.editGroupCallParticipant#d975eb80 flags:# muted:flags.0?true call:InputGroupCall participant:InputPeer volume:flags.1?int raise_hand:flags.2?Bool = Updates; +phone.editGroupCallTitle#1ca6ac0a call:InputGroupCall title:string = Updates; +phone.getGroupCallJoinAs#ef7c213a peer:InputPeer = phone.JoinAsPeers; +phone.exportGroupCallInvite#e6aa647f flags:# can_self_unmute:flags.0?true call:InputGroupCall = phone.ExportedGroupCallInvite; langpack.getLangPack#f2f2330a lang_pack:string lang_code:string = LangPackDifference; langpack.getStrings#efea3803 lang_pack:string lang_code:string keys:Vector = Vector; @@ -1610,4 +1623,4 @@ stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats; -// LAYER 124 \ No newline at end of file +// LAYER 125 \ No newline at end of file From 2db8256276e984b49a823ad19b7a02ff8d6d2642 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 14 Mar 2021 12:30:57 +0100 Subject: [PATCH 0501/1185] Update offset by the amount of messages retrieved --- pyrogram/methods/messages/search_messages.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/methods/messages/search_messages.py b/pyrogram/methods/messages/search_messages.py index 03b266c3e4..f8d0cb33d2 100644 --- a/pyrogram/methods/messages/search_messages.py +++ b/pyrogram/methods/messages/search_messages.py @@ -181,7 +181,7 @@ async def search_messages( if not messages: return - offset += 100 + offset += len(messages) for message in messages: yield message From 4afedd7ba6113e106574772a2c0e8188d4038958 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 14 Mar 2021 12:37:13 +0100 Subject: [PATCH 0502/1185] Update errors list --- compiler/errors/source/400_BAD_REQUEST.tsv | 7 +++++++ compiler/errors/source/403_FORBIDDEN.tsv | 3 ++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/compiler/errors/source/400_BAD_REQUEST.tsv b/compiler/errors/source/400_BAD_REQUEST.tsv index 5897eb98bf..7dbf64d1db 100644 --- a/compiler/errors/source/400_BAD_REQUEST.tsv +++ b/compiler/errors/source/400_BAD_REQUEST.tsv @@ -55,6 +55,7 @@ CHAT_ADMIN_REQUIRED The method requires chat admin privileges CHAT_ID_EMPTY The provided chat id is empty CHAT_ID_INVALID The chat id being used is invalid or not known yet. Make sure you see the chat before interacting with it CHAT_INVALID The chat is invalid +CHAT_INVITE_PERMANENT The chat invite link is primary CHAT_LINK_EXISTS The action failed because the supergroup is linked to a channel CHAT_NOT_MODIFIED The chat settings (title, permissions, photo, etc..) were not modified because you tried to edit them using the same content CHAT_RESTRICTED The chat is restricted and cannot be used @@ -98,6 +99,7 @@ ENCRYPTION_ID_INVALID The provided secret chat id is invalid ENTITIES_TOO_LONG The entity provided contains data that is too long, or you passed too many entities to this message ENTITY_MENTION_USER_INVALID The mentioned entity is not an user ERROR_TEXT_EMPTY The provided error message is empty +EXPIRE_DATE_INVALID The expiration date is invalid EXPORT_CARD_INVALID The provided card is invalid EXTERNAL_URL_INVALID The external media URL is invalid FIELD_NAME_EMPTY The field with the name FIELD_NAME is missing @@ -133,6 +135,9 @@ GROUPED_MEDIA_INVALID The album contains invalid media GROUP_CALL_INVALID The group call is invalid HASH_INVALID The provided hash is invalid IMAGE_PROCESS_FAILED The server failed to process your image +IMPORT_FILE_INVALID The imported file is invalid +IMPORT_FORMAT_UNRECOGNIZED The imported format is unrecognized +IMPORT_ID_INVALID The import id is invalid INLINE_RESULT_EXPIRED The inline bot query expired INPUT_CONSTRUCTOR_INVALID The provided constructor is invalid INPUT_FETCH_ERROR An error occurred while deserializing TL parameters @@ -145,6 +150,7 @@ INPUT_USER_DEACTIVATED The target user has been deleted/deactivated INVITE_HASH_EMPTY The invite hash is empty INVITE_HASH_EXPIRED The chat invite link is no longer valid INVITE_HASH_INVALID The invite link hash is invalid +INVITE_REVOKED_MISSING The action required a chat invite link to be revoked first LANG_PACK_INVALID The provided language pack is invalid LASTNAME_INVALID The last name is invalid LIMIT_INVALID The limit parameter is invalid @@ -299,6 +305,7 @@ TYPES_EMPTY The types parameter is empty TYPE_CONSTRUCTOR_INVALID The type constructor is invalid UNTIL_DATE_INVALID That date parameter is invalid URL_INVALID The URL provided is invalid +USAGE_LIMIT_INVALID The usage limit is invalid USERNAME_INVALID The username is invalid USERNAME_NOT_MODIFIED The username was not modified because you tried to edit it using the same one USERNAME_NOT_OCCUPIED The username is not occupied by anyone diff --git a/compiler/errors/source/403_FORBIDDEN.tsv b/compiler/errors/source/403_FORBIDDEN.tsv index e560a0aae9..ff6955f1f1 100644 --- a/compiler/errors/source/403_FORBIDDEN.tsv +++ b/compiler/errors/source/403_FORBIDDEN.tsv @@ -10,6 +10,7 @@ CHAT_SEND_MEDIA_FORBIDDEN You can't send media messages in this chat CHAT_SEND_POLL_FORBIDDEN You can't send polls in this chat CHAT_SEND_STICKERS_FORBIDDEN You can't send stickers in this chat CHAT_WRITE_FORBIDDEN You don't have rights to send messages in this chat +EDIT_BOT_INVITE_FORBIDDEN Bots' chat invite links can't be edited INLINE_BOT_REQUIRED The action must be performed through an inline bot callback MESSAGE_AUTHOR_REQUIRED You are not the author of this message MESSAGE_DELETE_FORBIDDEN You don't have rights to delete messages in this chat, most likely because you are not the author of them @@ -23,4 +24,4 @@ USER_INVALID The provided user is invalid USER_IS_BLOCKED The user is blocked USER_NOT_MUTUAL_CONTACT The provided user is not a mutual contact USER_PRIVACY_RESTRICTED The user's privacy settings is preventing you to perform this action -USER_RESTRICTED You are limited/restricted. You can't perform this action. \ No newline at end of file +USER_RESTRICTED You are limited/restricted. You can't perform this action \ No newline at end of file From 4f16c174a38b153a0e9f83f233dac65f9c54f6db Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 14 Mar 2021 12:39:17 +0100 Subject: [PATCH 0503/1185] Add one more 5xx error --- compiler/errors/source/500_INTERNAL_SERVER_ERROR.tsv | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/errors/source/500_INTERNAL_SERVER_ERROR.tsv b/compiler/errors/source/500_INTERNAL_SERVER_ERROR.tsv index 00a3e28b2d..b959bbe4d9 100644 --- a/compiler/errors/source/500_INTERNAL_SERVER_ERROR.tsv +++ b/compiler/errors/source/500_INTERNAL_SERVER_ERROR.tsv @@ -39,4 +39,5 @@ UNKNOWN_METHOD The method you tried to call cannot be called on non-CDN DCs UPLOAD_NO_VOLUME Telegram is having internal problems. Please try again later VOLUME_LOC_NOT_FOUND Telegram is having internal problems. Please try again later WORKER_BUSY_TOO_LONG_RETRY Server workers are too busy right now due to Telegram having internal problems. Please try again later -WP_ID_GENERATE_FAILED Telegram is having internal problems. Please try again later \ No newline at end of file +WP_ID_GENERATE_FAILED Telegram is having internal problems. Please try again later +GROUPCALL_ADD_PARTICIPANTS_FAILED Failure while adding voice chat member due to Telegram having internal problems. Please try again later \ No newline at end of file From de68f83c1f810a97d8c5b2adea36ebecaceb0201 Mon Sep 17 00:00:00 2001 From: Kunoi Sayami <46131041+KunoiSayami@users.noreply.github.com> Date: Sun, 14 Mar 2021 19:41:26 +0800 Subject: [PATCH 0504/1185] Use fixed length mask instead of dynamic length (#635) --- pyrogram/types/object.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/types/object.py b/pyrogram/types/object.py index 8c344bfba7..daeb89af74 100644 --- a/pyrogram/types/object.py +++ b/pyrogram/types/object.py @@ -56,7 +56,7 @@ def default(obj: "Object"): "_": obj.__class__.__name__, **{ attr: ( - "*" * len(getattr(obj, attr)) + "*" * 9 if attr == "phone_number" else str(datetime.fromtimestamp(getattr(obj, attr))) if attr.endswith("date") else From 9cbbf79972441beed3539c338ecdf7a79d4a7578 Mon Sep 17 00:00:00 2001 From: Mystery Boy <79533586+MysteryBots@users.noreply.github.com> Date: Sun, 14 Mar 2021 17:12:30 +0530 Subject: [PATCH 0505/1185] Add missing info in forward_from_chat (#632) Added info regarding supergroup in forward_from_chat --- pyrogram/types/messages_and_media/message.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index d9b8065b37..65e51e4550 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -84,7 +84,7 @@ class Message(Object, Update): For messages forwarded from users who have hidden their accounts, name of the user. forward_from_chat (:obj:`~pyrogram.types.Chat`, *optional*): - For messages forwarded from channels, information about the original channel. + For messages forwarded from channels, information about the original channel. For messages forwarded from anonymous group administrators, information about the original supergroup. forward_from_message_id (``int``, *optional*): For messages forwarded from channels, identifier of the original message in the channel. From f407facdc793259125cf04a3bd04d551e891692c Mon Sep 17 00:00:00 2001 From: Harsh <65716674+Harsh-br0@users.noreply.github.com> Date: Sun, 14 Mar 2021 17:13:12 +0530 Subject: [PATCH 0506/1185] Fix for strikethrough unparsing in markdown (#627) --- pyrogram/parser/markdown.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/parser/markdown.py b/pyrogram/parser/markdown.py index 4b9dede8e5..f71503d832 100644 --- a/pyrogram/parser/markdown.py +++ b/pyrogram/parser/markdown.py @@ -121,7 +121,7 @@ def unparse(text: str, entities: list): start_tag = end_tag = ITALIC_DELIM elif entity_type == "underline": start_tag = end_tag = UNDERLINE_DELIM - elif entity_type == "strike": + elif entity_type == "strikethrough": start_tag = end_tag = STRIKE_DELIM elif entity_type == "code": start_tag = end_tag = CODE_DELIM From 54b20875b9562522ba9a9f869080b5ff94bf3964 Mon Sep 17 00:00:00 2001 From: Dametto Luca <45915503+LucaTheHacker@users.noreply.github.com> Date: Sun, 14 Mar 2021 12:44:13 +0100 Subject: [PATCH 0507/1185] Fix filters.create documentation (#623) Wrong documentation for filters.create, callable funcitions requires 3 positional arguments (filter, client, update) and not two. --- pyrogram/filters.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pyrogram/filters.py b/pyrogram/filters.py index 5752b76619..a77e058135 100644 --- a/pyrogram/filters.py +++ b/pyrogram/filters.py @@ -127,12 +127,12 @@ def create(func: Callable, name: str = None, **kwargs) -> Filter: Parameters: func (``callable``): - A function that accepts two positional arguments *(filter, update)* and returns a boolean: True if the - update should be handled, False otherwise. The *filter* argument refers to the filter itself and can be used - to access keyword arguments (read below). The *update* argument type will vary depending on which - `Handler `_ is coming from. For example, in a :obj:`~pyrogram.handlers.MessageHandler` the - *update* argument will be a :obj:`~pyrogram.types.Message`; in a - :obj:`~pyrogram.handlers.CallbackQueryHandler` the *update* will be a :obj:`~pyrogram.types.CallbackQuery`. + A function that accepts three positional arguments *(filter, client, update)* and returns a boolean: True if the + update should be handled, False otherwise. + The *filter* argument refers to the filter itself and can be used to access keyword arguments (read below). + The *client* argument refers to the :obj:`~pyrogram.Client` that received the update. + The *update* argument type will vary depending on which `Handler `_ is coming from. + For example, in a :obj:`~pyrogram.handlers.MessageHandler` the *update* argument will be a :obj:`~pyrogram.types.Message`; in a :obj:`~pyrogram.handlers.CallbackQueryHandler` the *update* will be a :obj:`~pyrogram.types.CallbackQuery`. Your function body can then access the incoming update attributes and decide whether to allow it or not. name (``str``, *optional*): From 42b18657437050d40b9f3a0e3ee6dc42783a244c Mon Sep 17 00:00:00 2001 From: Tuqay Abdullazade <57816441+tuqayabdulla@users.noreply.github.com> Date: Wed, 17 Mar 2021 15:37:56 +0400 Subject: [PATCH 0508/1185] Use a shorter if-expression (#621) --- pyrogram/methods/chats/set_slow_mode.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/methods/chats/set_slow_mode.py b/pyrogram/methods/chats/set_slow_mode.py index 77d9e339ca..19e64925a8 100644 --- a/pyrogram/methods/chats/set_slow_mode.py +++ b/pyrogram/methods/chats/set_slow_mode.py @@ -54,7 +54,7 @@ async def set_slow_mode( await self.send( raw.functions.channels.ToggleSlowMode( channel=await self.resolve_peer(chat_id), - seconds=0 if seconds is None else seconds + seconds=seconds or 0 ) ) From ee5f39374cde3b06c0119d362002a3c064afdcbd Mon Sep 17 00:00:00 2001 From: ColinShark Date: Wed, 17 Mar 2021 12:40:36 +0100 Subject: [PATCH 0509/1185] Add support for the improved Invite Links (#639) * Add new invite link export methods * Implement higher-level Invite type * Update Docstrings and rename Invite * Docstrings are now more consistent with other methods * Invite is now InviteLink to be less arbitrary * Add method to get exported links * `get_exported_chat_invites` * prepare `__init__` for the other InvitesV2 methods * Update returned type `str` -> `types.InviteLink` * Add method to edit invite link Since editing the link returns a slightly different type to exporting, I have made a small "hack" in the InviteLink type. * Move Invites V2 methods to their own namespace * Add get_chat_invite_importers and InviteImporter Method to fetch information on users that joined via a specific link and the type to display the information * Add methods to delete revoked links * delete_exported_chat_invite to delete a single revoked link * delete_revoked_exported_chat_invites to delete all revoked links of a specified admin * Renaming Invite(s) to InviteLink(s) As per @delivrance's request https://github.com/pyrogram/pyrogram/pull/630#issuecomment-791893890 Also sorted invites' __init__ alphabetically * Add Method to get admins with exported invite link Documentation needs an update and the respective type needs to be created. I cannot test this, as I lack Creator permissions. * Invite Links overhaul * Rearrange code Co-authored-by: Dan <14043624+delivrance@users.noreply.github.com> --- compiler/docs/compiler.py | 17 ++- compiler/docs/template/methods.rst | 13 +++ pyrogram/methods/__init__.py | 4 +- pyrogram/methods/chats/__init__.py | 2 - pyrogram/methods/chats/set_chat_photo.py | 2 +- pyrogram/methods/invite_links/__init__.py | 46 ++++++++ .../invite_links/create_chat_invite_link.py | 73 +++++++++++++ .../delete_all_chat_invite_links.py | 52 +++++++++ .../invite_links/delete_chat_invite_link.py | 50 +++++++++ .../invite_links/edit_chat_invite_link.py | 78 ++++++++++++++ .../export_chat_invite_link.py | 35 +++--- .../get_chat_admins_with_invite_links.py | 54 ++++++++++ .../get_chat_invite_link_members.py | 91 ++++++++++++++++ .../get_chat_invite_link_members_count.py | 54 ++++++++++ .../invite_links/get_chat_invite_links.py | 98 +++++++++++++++++ .../get_chat_invite_links_count.py | 60 +++++++++++ .../invite_links/revoke_chat_invite_link.py | 66 ++++++++++++ .../methods/messages/edit_inline_media.py | 2 +- pyrogram/methods/messages/get_media_group.py | 5 +- pyrogram/methods/utilities/restart.py | 2 - pyrogram/methods/utilities/stop.py | 2 - pyrogram/scaffold.py | 3 +- pyrogram/types/user_and_chats/__init__.py | 20 +++- .../chat_admin_with_invite_links.py | 63 +++++++++++ .../types/user_and_chats/chat_invite_link.py | 102 ++++++++++++++++++ .../user_and_chats/invite_link_importer.py | 55 ++++++++++ 26 files changed, 1013 insertions(+), 36 deletions(-) create mode 100644 pyrogram/methods/invite_links/__init__.py create mode 100644 pyrogram/methods/invite_links/create_chat_invite_link.py create mode 100644 pyrogram/methods/invite_links/delete_all_chat_invite_links.py create mode 100644 pyrogram/methods/invite_links/delete_chat_invite_link.py create mode 100644 pyrogram/methods/invite_links/edit_chat_invite_link.py rename pyrogram/methods/{chats => invite_links}/export_chat_invite_link.py (72%) create mode 100644 pyrogram/methods/invite_links/get_chat_admins_with_invite_links.py create mode 100644 pyrogram/methods/invite_links/get_chat_invite_link_members.py create mode 100644 pyrogram/methods/invite_links/get_chat_invite_link_members_count.py create mode 100644 pyrogram/methods/invite_links/get_chat_invite_links.py create mode 100644 pyrogram/methods/invite_links/get_chat_invite_links_count.py create mode 100644 pyrogram/methods/invite_links/revoke_chat_invite_link.py create mode 100644 pyrogram/types/user_and_chats/chat_admin_with_invite_links.py create mode 100644 pyrogram/types/user_and_chats/chat_invite_link.py create mode 100644 pyrogram/types/user_and_chats/invite_link_importer.py diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index 9b9aa49d93..78328459a7 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -191,7 +191,6 @@ def get_title_list(s: str) -> list: restrict_chat_member promote_chat_member set_administrator_title - export_chat_invite_link set_chat_photo delete_chat_photo set_chat_title @@ -238,6 +237,20 @@ def get_title_list(s: str) -> list: unblock_user get_common_chats """, + invite_links=""" + Invite Links + export_chat_invite_link + create_chat_invite_link + edit_chat_invite_link + revoke_chat_invite_link + delete_chat_invite_link + delete_all_chat_invite_links + get_chat_invite_links + get_chat_invite_links_count + get_chat_invite_link_members + get_chat_invite_link_members_count + get_chat_admins_with_invite_links + """, contacts=""" Contacts add_contacts @@ -332,6 +345,8 @@ def get_title_list(s: str) -> list: ChatPhoto ChatMember ChatPermissions + ChatInviteLink + ChatAdminWithInviteLinks ChatEvent ChatEventFilter Dialog diff --git a/compiler/docs/template/methods.rst b/compiler/docs/template/methods.rst index f99605ca45..dc2950dca7 100644 --- a/compiler/docs/template/methods.rst +++ b/compiler/docs/template/methods.rst @@ -88,6 +88,19 @@ Users {users} +Invite Links +------------ + +.. autosummary:: + :nosignatures: + + {invite_links} + +.. toctree:: + :hidden: + + {invite_links} + Contacts -------- diff --git a/pyrogram/methods/__init__.py b/pyrogram/methods/__init__.py index 728af9a21a..ee453ce3bb 100644 --- a/pyrogram/methods/__init__.py +++ b/pyrogram/methods/__init__.py @@ -22,6 +22,7 @@ from .chats import Chats from .contacts import Contacts from .decorators import Decorators +from .invite_links import InviteLinks from .messages import Messages from .password import Password from .users import Users @@ -38,6 +39,7 @@ class Methods( Users, Messages, Decorators, - Utilities + Utilities, + InviteLinks, ): pass diff --git a/pyrogram/methods/chats/__init__.py b/pyrogram/methods/chats/__init__.py index 62dfd5bd27..8aeb5fe42c 100644 --- a/pyrogram/methods/chats/__init__.py +++ b/pyrogram/methods/chats/__init__.py @@ -25,7 +25,6 @@ from .delete_chat_photo import DeleteChatPhoto from .delete_supergroup import DeleteSupergroup from .delete_user_history import DeleteUserHistory -from .export_chat_invite_link import ExportChatInviteLink from .get_chat import GetChat from .get_chat_event_log import GetChatEventLog from .get_chat_member import GetChatMember @@ -58,7 +57,6 @@ class Chats( GetChat, - ExportChatInviteLink, LeaveChat, JoinChat, KickChatMember, diff --git a/pyrogram/methods/chats/set_chat_photo.py b/pyrogram/methods/chats/set_chat_photo.py index 81260989ee..e111449040 100644 --- a/pyrogram/methods/chats/set_chat_photo.py +++ b/pyrogram/methods/chats/set_chat_photo.py @@ -21,8 +21,8 @@ from pyrogram import raw from pyrogram import utils -from pyrogram.scaffold import Scaffold from pyrogram.file_id import FileType +from pyrogram.scaffold import Scaffold class SetChatPhoto(Scaffold): diff --git a/pyrogram/methods/invite_links/__init__.py b/pyrogram/methods/invite_links/__init__.py new file mode 100644 index 0000000000..17b1abbee9 --- /dev/null +++ b/pyrogram/methods/invite_links/__init__.py @@ -0,0 +1,46 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan +# +# 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 .create_chat_invite_link import CreateChatInviteLink +from .delete_all_chat_invite_links import DeleteAllChatInviteLinks +from .delete_chat_invite_link import DeleteChatInviteLink +from .edit_chat_invite_link import EditChatInviteLink +from .export_chat_invite_link import ExportChatInviteLink +from .get_chat_admins_with_invite_links import GetChatAdminsWithInviteLinks +from .get_chat_invite_link_members import GetChatInviteLinkMembers +from .get_chat_invite_link_members_count import GetChatInviteLinkMembersCount +from .get_chat_invite_links import GetChatInviteLinks +from .get_chat_invite_links_count import GetChatInviteLinksCount +from .revoke_chat_invite_link import RevokeChatInviteLink + + +class InviteLinks( + RevokeChatInviteLink, + DeleteChatInviteLink, + EditChatInviteLink, + CreateChatInviteLink, + GetChatInviteLinkMembers, + GetChatInviteLinkMembersCount, + GetChatInviteLinks, + ExportChatInviteLink, + DeleteAllChatInviteLinks, + GetChatInviteLinksCount, + GetChatAdminsWithInviteLinks +): + pass diff --git a/pyrogram/methods/invite_links/create_chat_invite_link.py b/pyrogram/methods/invite_links/create_chat_invite_link.py new file mode 100644 index 0000000000..006e7e1445 --- /dev/null +++ b/pyrogram/methods/invite_links/create_chat_invite_link.py @@ -0,0 +1,73 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan +# +# 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 Union + +from pyrogram import raw +from pyrogram import types +from pyrogram.scaffold import Scaffold + + +class CreateChatInviteLink(Scaffold): + async def create_chat_invite_link( + self, + chat_id: Union[int, str], + expire_date: int = None, + member_limit: int = None, + ) -> "types.ChatInviteLink": + """Create an additional invite link for a chat. + + You must be an administrator in the chat for this to work and must have the appropriate admin rights. + + The link can be revoked using the method :meth:`~pyrogram.Client.revoke_chat_invite_link`. + + Parameters: + chat_id (``int`` | ``str``): + Unique identifier for the target chat or username of the target channel/supergroup + (in the format @username). + + expire_date (``int``, *optional*): + Point in time (Unix timestamp) when the link will expire. + Defaults to None (no expiration date). + + member_limit (``int``, *optional*): + Maximum number of users that can be members of the chat simultaneously after joining the chat via + this invite link; 1-99999. + Defaults to None (no member limit). + + Returns: + :obj:`~pyrogram.types.ChatInviteLink`: On success, the new invite link is returned. + + Example: + .. code-block:: python + + # Create a new link without limits + link = app.create_chat_invite_link(chat_id) + + # Create a new link for up to 7 new users + link = app.create_chat_invite_link(chat_id, member_limit=7) + """ + r = await self.send( + raw.functions.messages.ExportChatInvite( + peer=await self.resolve_peer(chat_id), + expire_date=expire_date, + usage_limit=member_limit, + ) + ) + + return types.ChatInviteLink._parse(self, r) diff --git a/pyrogram/methods/invite_links/delete_all_chat_invite_links.py b/pyrogram/methods/invite_links/delete_all_chat_invite_links.py new file mode 100644 index 0000000000..ea1738b9dd --- /dev/null +++ b/pyrogram/methods/invite_links/delete_all_chat_invite_links.py @@ -0,0 +1,52 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan +# +# 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 Union + +from pyrogram import raw +from pyrogram.scaffold import Scaffold + + +class DeleteAllChatInviteLinks(Scaffold): + async def delete_all_chat_invite_links( + self, + chat_id: Union[int, str], + admin_id: Union[int, str], + ) -> bool: + """Delete all revoked invite links of an administrator. + + Parameters: + chat_id (``int`` | ``str``): + Unique identifier for the target chat or username of the target channel/supergroup + (in the format @username). + + admin_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target user. + For you yourself you can simply use "me" or "self". + For a contact that exists in your Telegram address book you can use his phone number (str). + + Returns: + ``bool``: On success ``True`` is returned. + """ + + return await self.send( + raw.functions.messages.DeleteRevokedExportedChatInvites( + peer=await self.resolve_peer(chat_id), + admin_id=await self.resolve_peer(admin_id), + ) + ) diff --git a/pyrogram/methods/invite_links/delete_chat_invite_link.py b/pyrogram/methods/invite_links/delete_chat_invite_link.py new file mode 100644 index 0000000000..987cc78129 --- /dev/null +++ b/pyrogram/methods/invite_links/delete_chat_invite_link.py @@ -0,0 +1,50 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan +# +# 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 Union + +from pyrogram import raw +from pyrogram.scaffold import Scaffold + + +class DeleteChatInviteLink(Scaffold): + async def delete_chat_invite_link( + self, + chat_id: Union[int, str], + invite_link: str, + ) -> bool: + """Delete an already revoked invite link. + + Parameters: + chat_id (``int`` | ``str``): + Unique identifier for the target chat or username of the target channel/supergroup + (in the format @username). + + invite_link (``str``): + The revoked invite link to delete. + + Returns: + ``bool``: On success ``True`` is returned. + """ + + return await self.send( + raw.functions.messages.DeleteExportedChatInvite( + peer=await self.resolve_peer(chat_id), + link=invite_link, + ) + ) diff --git a/pyrogram/methods/invite_links/edit_chat_invite_link.py b/pyrogram/methods/invite_links/edit_chat_invite_link.py new file mode 100644 index 0000000000..07c84d3c4d --- /dev/null +++ b/pyrogram/methods/invite_links/edit_chat_invite_link.py @@ -0,0 +1,78 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan +# +# 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 Union + +from pyrogram import raw +from pyrogram import types +from pyrogram.scaffold import Scaffold + + +class EditChatInviteLink(Scaffold): + async def edit_chat_invite_link( + self, + chat_id: Union[int, str], + invite_link: str, + expire_date: int = None, + member_limit: int = None, + ) -> "types.ChatInviteLink": + """Edit a non-primary invite link. + + You must be an administrator in the chat for this to work and must have the appropriate admin rights. + + Parameters: + chat_id (``int`` | ``str``): + Unique identifier for the target chat or username of the target channel/supergroup + (in the format @username). + + invite_link (``str``): + The invite link to edit + + expire_date (``int``, *optional*): + Point in time (Unix timestamp) when the link will expire. + Defaults to None (no change), pass 0 to set no expiration date. + + member_limit (``int``, *optional*): + Maximum number of users that can be members of the chat simultaneously after joining the chat via this + invite link; 1-99999. + Defaults to None (no change), pass 0 to set no member limit. + + Returns: + :obj:`~pyrogram.types.ChatInviteLink`: On success, the new invite link is returned + + Example: + .. code-block:: python + + # Edit the member limit of a link + link = app.edit_chat_invite_link(chat_id, invite_link, member_limit=9) + + # Set no expiration date of a link + link = app.edit_chat_invite_link(chat_id, invite_link, expire_date=0) + """ + r = await self.send( + raw.functions.messages.EditExportedChatInvite( + peer=await self.resolve_peer(chat_id), + link=invite_link, + expire_date=expire_date, + usage_limit=member_limit, + ) + ) + + users = {i.id: i for i in r.users} + + return types.ChatInviteLink._parse(self, r.invite, users) diff --git a/pyrogram/methods/chats/export_chat_invite_link.py b/pyrogram/methods/invite_links/export_chat_invite_link.py similarity index 72% rename from pyrogram/methods/chats/export_chat_invite_link.py rename to pyrogram/methods/invite_links/export_chat_invite_link.py index 9fbaf8b639..ee59750cbb 100644 --- a/pyrogram/methods/chats/export_chat_invite_link.py +++ b/pyrogram/methods/invite_links/export_chat_invite_link.py @@ -19,20 +19,20 @@ from typing import Union from pyrogram import raw +from pyrogram import types from pyrogram.scaffold import Scaffold class ExportChatInviteLink(Scaffold): async def export_chat_invite_link( self, - chat_id: Union[int, str] - ) -> str: - """Generate a new invite link for a chat; any previously generated link is revoked. + chat_id: Union[int, str], + ) -> "types.ChatInviteLink": + """Generate a new primary invite link for a chat; any previously generated primary link is revoked. - You must be an administrator in the chat for this to work and have the appropriate admin rights. + You must be an administrator in the chat for this to work and must have the appropriate admin rights. .. note :: - Each administrator in a chat generates their own invite links. Bots can't use invite links generated by other administrators. If you want your bot to work with invite links, it will need to generate its own link using this method – after this the link will become available to the bot via the @@ -45,24 +45,19 @@ async def export_chat_invite_link( (in the format @username). Returns: - ``str``: On success, the exported invite link is returned. - - Raises: - ValueError: In case the chat_id belongs to a user. + ``str``: On success, the new invite link as string is returned. Example: .. code-block:: python + # Generate a new primary link link = app.export_chat_invite_link(chat_id) - print(link) """ - peer = await self.resolve_peer(chat_id) - - if isinstance(peer, (raw.types.InputPeerChat, raw.types.InputPeerChannel)): - return (await self.send( - raw.functions.messages.ExportChatInvite( - peer=peer - ) - )).link - else: - raise ValueError(f'The chat_id "{chat_id}" belongs to a user') + r = await self.send( + raw.functions.messages.ExportChatInvite( + peer=await self.resolve_peer(chat_id), + legacy_revoke_permanent=True + ) + ) + + return r.link diff --git a/pyrogram/methods/invite_links/get_chat_admins_with_invite_links.py b/pyrogram/methods/invite_links/get_chat_admins_with_invite_links.py new file mode 100644 index 0000000000..1f2cc1c197 --- /dev/null +++ b/pyrogram/methods/invite_links/get_chat_admins_with_invite_links.py @@ -0,0 +1,54 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan +# +# 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 Union + +from pyrogram import raw, types +from pyrogram.scaffold import Scaffold + + +class GetChatAdminsWithInviteLinks(Scaffold): + async def get_chat_admins_with_invite_links( + self, + chat_id: Union[int, str], + ): + """Get the list of the administrators that have exported invite links in a chat. + + You must be the owner of a chat for this to work. + + Args: + chat_id (``int`` | ``str``): + Unique identifier for the target chat or username of the target channel/supergroup + (in the format @username). + + Returns: + List of :obj:`~pyrogram.types.ChatAdminWithInviteLink`: On success, the list of admins that have exported + invite links is returned. + """ + r = await self.send( + raw.functions.messages.GetAdminsWithInvites( + peer=await self.resolve_peer(chat_id) + ) + ) + + users = {i.id: i for i in r.users} + + return types.List( + types.ChatAdminWithInviteLinks._parse(self, admin, users) + for admin in r.admins + ) diff --git a/pyrogram/methods/invite_links/get_chat_invite_link_members.py b/pyrogram/methods/invite_links/get_chat_invite_link_members.py new file mode 100644 index 0000000000..e48422ea30 --- /dev/null +++ b/pyrogram/methods/invite_links/get_chat_invite_link_members.py @@ -0,0 +1,91 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan +# +# 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 Union, Optional, AsyncGenerator + +from pyrogram import raw +from pyrogram import types +from pyrogram.scaffold import Scaffold + + +class GetChatInviteLinkMembers(Scaffold): + async def get_chat_invite_link_members( + self, + chat_id: Union[int, str], + invite_link: str, + limit: int = 0 + ) -> Optional[AsyncGenerator["types.ChatMember", None]]: + """Get the members who joined the chat with the invite link. + + Parameters: + chat_id (``int`` | ``str``): + Unique identifier for the target chat or username of the target channel/supergroup + (in the format @username). + + invite_link (str): + The invite link. + + limit (``int``, *optional*): + Limits the number of invite links to be retrieved. + By default, no limit is applied and all invite links are returned. + + Returns: + ``Generator``: A generator yielding :obj:`~pyrogram.types.ChatMember` objects. + + Yields: + :obj:`~pyrogram.types.ChatMember` objects. + """ + current = 0 + total = abs(limit) or (1 << 31) - 1 + limit = min(100, total) + + offset_date = 0 + offset_user = raw.types.InputUserEmpty() + + while True: + r = await self.send( + raw.functions.messages.GetChatInviteImporters( + peer=await self.resolve_peer(chat_id), + link=invite_link, + limit=limit, + offset_date=offset_date, + offset_user=offset_user + ) + ) + + if not r.importers: + break + + users = {i.id: i for i in r.users} + + offset_date = r.importers[-1].date + offset_user = await self.resolve_peer(r.importers[-1].user_id) + + for i in r.importers: + user = types.User._parse(self, users[i.user_id]) + + yield types.ChatMember( + user=user, + status="member", + joined_date=i.date + ) + + current += 1 + + if current >= total: + return diff --git a/pyrogram/methods/invite_links/get_chat_invite_link_members_count.py b/pyrogram/methods/invite_links/get_chat_invite_link_members_count.py new file mode 100644 index 0000000000..bd6a2fcc81 --- /dev/null +++ b/pyrogram/methods/invite_links/get_chat_invite_link_members_count.py @@ -0,0 +1,54 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan +# +# 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 Union + +from pyrogram import raw +from pyrogram.scaffold import Scaffold + + +class GetChatInviteLinkMembersCount(Scaffold): + async def get_chat_invite_link_members_count( + self, + chat_id: Union[int, str], + invite_link: str + ) -> int: + """Get the count of the members who joined the chat with the invite link. + + Parameters: + chat_id (``int`` | ``str``): + Unique identifier for the target chat or username of the target channel/supergroup + (in the format @username). + + invite_link (str): + The invite link. + + Returns: + ``int``: On success, the joined chat members count is returned. + """ + r = await self.send( + raw.functions.messages.GetChatInviteImporters( + peer=await self.resolve_peer(chat_id), + link=invite_link, + limit=1, + offset_date=0, + offset_user=raw.types.InputUserEmpty() + ) + ) + + return r.count diff --git a/pyrogram/methods/invite_links/get_chat_invite_links.py b/pyrogram/methods/invite_links/get_chat_invite_links.py new file mode 100644 index 0000000000..17d28a0ced --- /dev/null +++ b/pyrogram/methods/invite_links/get_chat_invite_links.py @@ -0,0 +1,98 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan +# +# 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 Union, Optional, AsyncGenerator + +from pyrogram import raw +from pyrogram import types +from pyrogram.scaffold import Scaffold + + +class GetChatInviteLinks(Scaffold): + async def get_chat_invite_links( + self, + chat_id: Union[int, str], + admin_id: Union[int, str], + revoked: bool = False, + limit: int = 0, + ) -> Optional[AsyncGenerator["types.ChatInviteLink", None]]: + """Get the invite links created by an administrator in a chat. + + .. note:: + + As an administrator you can only get your own links you have exported. + As the chat or channel owner you can get everyones links. + + Parameters: + chat_id (``int`` | ``str``): + Unique identifier for the target chat or username of the target channel/supergroup + (in the format @username). + + admin_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target user. + For you yourself you can simply use "me" or "self". + For a contact that exists in your Telegram address book you can use his phone number (str). + + revoked (``bool``, *optional*): + True, if you want to get revoked links instead. + Defaults to False (get active links only). + + limit (``int``, *optional*): + Limits the number of invite links to be retrieved. + By default, no limit is applied and all invite links are returned. + + Returns: + ``Generator``: A generator yielding :obj:`~pyrogram.types.ChatInviteLink` objects. + + Yields: + :obj:`~pyrogram.types.ChatInviteLink` objects. + """ + current = 0 + total = abs(limit) or (1 << 31) - 1 + limit = min(100, total) + + offset_date = None + offset_link = None + + while True: + r = await self.send( + raw.functions.messages.GetExportedChatInvites( + peer=await self.resolve_peer(chat_id), + admin_id=await self.resolve_peer(admin_id), + limit=limit, + revoked=revoked, + offset_date=offset_date, + offset_link=offset_link + ) + ) + + if not r.invites: + break + + users = {i.id: i for i in r.users} + + offset_date = r.invites[-1].date + offset_link = r.invites[-1].link + + for i in r.invites: + yield types.ChatInviteLink._parse(self, i, users) + + current += 1 + + if current >= total: + return diff --git a/pyrogram/methods/invite_links/get_chat_invite_links_count.py b/pyrogram/methods/invite_links/get_chat_invite_links_count.py new file mode 100644 index 0000000000..52d3528be1 --- /dev/null +++ b/pyrogram/methods/invite_links/get_chat_invite_links_count.py @@ -0,0 +1,60 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan +# +# 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 Union + +from pyrogram import raw +from pyrogram.scaffold import Scaffold + + +class GetChatInviteLinksCount(Scaffold): + async def get_chat_invite_links_count( + self, + chat_id: Union[int, str], + admin_id: Union[int, str], + revoked: bool = False, + ) -> int: + """Get the count of the invite links created by an administrator in a chat. + + Parameters: + chat_id (``int`` | ``str``): + Unique identifier for the target chat or username of the target channel/supergroup + (in the format @username). + + admin_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target user. + For you yourself you can simply use "me" or "self". + For a contact that exists in your Telegram address book you can use his phone number (str). + + revoked (``bool``, *optional*): + True, if you want to get revoked links instead. + Defaults to False (get active links only). + + Returns: + ``int``: On success, the invite links count is returned. + """ + r = await self.send( + raw.functions.messages.GetExportedChatInvites( + peer=await self.resolve_peer(chat_id), + admin_id=await self.resolve_peer(admin_id), + limit=1, + revoked=revoked + ) + ) + + return r.count diff --git a/pyrogram/methods/invite_links/revoke_chat_invite_link.py b/pyrogram/methods/invite_links/revoke_chat_invite_link.py new file mode 100644 index 0000000000..b49a51dd4c --- /dev/null +++ b/pyrogram/methods/invite_links/revoke_chat_invite_link.py @@ -0,0 +1,66 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan +# +# 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 Union + +from pyrogram import raw +from pyrogram import types +from pyrogram.scaffold import Scaffold + + +class RevokeChatInviteLink(Scaffold): + async def revoke_chat_invite_link( + self, + chat_id: Union[int, str], + invite_link: str, + ) -> "types.ChatInviteLink": + """Revoke a previously created invite link. + + If the primary link is revoked, a new link is automatically generated. + + You must be an administrator in the chat for this to work and must have the appropriate admin rights. + + Parameters: + chat_id (``int`` | ``str``): + Unique identifier for the target chat or username of the target channel/supergroup + (in the format @username). + + invite_link (``str``): + The invite link to revoke. + + Returns: + :obj:`~pyrogram.types.ChatInviteLink`: On success, the invite link object is returned. + """ + + r = await self.send( + raw.functions.messages.EditExportedChatInvite( + peer=await self.resolve_peer(chat_id), + link=invite_link, + revoked=True + ) + ) + + users = {i.id: i for i in r.users} + + chat_invite = ( + r.new_invite + if isinstance(r, raw.types.messages.ExportedChatInviteReplaced) + else r.invite + ) + + return types.ChatInviteLink._parse(self, chat_invite, users) diff --git a/pyrogram/methods/messages/edit_inline_media.py b/pyrogram/methods/messages/edit_inline_media.py index 1d32eec32a..e941c131c4 100644 --- a/pyrogram/methods/messages/edit_inline_media.py +++ b/pyrogram/methods/messages/edit_inline_media.py @@ -21,9 +21,9 @@ from pyrogram import raw from pyrogram import types from pyrogram import utils +from pyrogram.file_id import FileType from pyrogram.scaffold import Scaffold from .inline_session import get_session -from pyrogram.file_id import FileType class EditInlineMedia(Scaffold): diff --git a/pyrogram/methods/messages/get_media_group.py b/pyrogram/methods/messages/get_media_group.py index cf01a8edbd..7c5249e9f3 100644 --- a/pyrogram/methods/messages/get_media_group.py +++ b/pyrogram/methods/messages/get_media_group.py @@ -19,8 +19,8 @@ import logging from typing import Union, List -from pyrogram.scaffold import Scaffold from pyrogram import types +from pyrogram.scaffold import Scaffold log = logging.getLogger(__name__) @@ -49,7 +49,8 @@ async def get_media_group( ValueError: In case the passed message id doesn't belong to a media group. """ # There can be maximum 10 items in a media group. - messages = await self.get_messages(chat_id, [msg_id for msg_id in range(message_id - 9, message_id + 10)], replies=0) + messages = await self.get_messages(chat_id, [msg_id for msg_id in range(message_id - 9, message_id + 10)], + replies=0) media_group_id = messages[9].media_group_id diff --git a/pyrogram/methods/utilities/restart.py b/pyrogram/methods/utilities/restart.py index 9d5b7d3a9b..afd22bb478 100644 --- a/pyrogram/methods/utilities/restart.py +++ b/pyrogram/methods/utilities/restart.py @@ -16,8 +16,6 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -import asyncio - from pyrogram.scaffold import Scaffold diff --git a/pyrogram/methods/utilities/stop.py b/pyrogram/methods/utilities/stop.py index acf28f28b2..38f0d4c474 100644 --- a/pyrogram/methods/utilities/stop.py +++ b/pyrogram/methods/utilities/stop.py @@ -16,8 +16,6 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -import asyncio - from pyrogram.scaffold import Scaffold diff --git a/pyrogram/scaffold.py b/pyrogram/scaffold.py index a003359981..434c5bb45d 100644 --- a/pyrogram/scaffold.py +++ b/pyrogram/scaffold.py @@ -41,7 +41,8 @@ class Scaffold: PARENT_DIR = Path(sys.argv[0]).parent - INVITE_LINK_RE = re.compile(r"^(?:https?://)?(?:www\.)?(?:t(?:elegram)?\.(?:org|me|dog)/joinchat/)([\w-]+)$") + # https://regex101.com/r/pme5ZE/2 Matches both the entire link and the hash itself + INVITE_LINK_RE = re.compile(r"^(?:(?:https?://)?(?:www\.)?(?:t(?:elegram)?\.(?:org|me|dog)/joinchat/))?(?P[\w-]+)$") WORKERS = min(32, os.cpu_count() + 4) WORKDIR = PARENT_DIR CONFIG_FILE = PARENT_DIR / "config.ini" diff --git a/pyrogram/types/user_and_chats/__init__.py b/pyrogram/types/user_and_chats/__init__.py index 0c776c9940..7c4a65da64 100644 --- a/pyrogram/types/user_and_chats/__init__.py +++ b/pyrogram/types/user_and_chats/__init__.py @@ -17,17 +17,31 @@ # along with Pyrogram. If not, see . from .chat import Chat +from .chat_admin_with_invite_links import ChatAdminWithInviteLinks from .chat_event import ChatEvent +from .chat_event_filter import ChatEventFilter +from .chat_invite_link import ChatInviteLink from .chat_member import ChatMember from .chat_permissions import ChatPermissions from .chat_photo import ChatPhoto from .chat_preview import ChatPreview from .dialog import Dialog +from .invite_link_importer import InviteLinkImporter from .restriction import Restriction from .user import User -from .chat_event_filter import ChatEventFilter __all__ = [ - "Chat", "ChatMember", "ChatPermissions", "ChatPhoto", "ChatPreview", "Dialog", "User", "Restriction", "ChatEvent", - "ChatEventFilter" + "Chat", + "ChatMember", + "ChatPermissions", + "ChatPhoto", + "ChatPreview", + "Dialog", + "User", + "Restriction", + "ChatEvent", + "ChatEventFilter", + "ChatInviteLink", + "InviteLinkImporter", + "ChatAdminWithInviteLinks" ] diff --git a/pyrogram/types/user_and_chats/chat_admin_with_invite_links.py b/pyrogram/types/user_and_chats/chat_admin_with_invite_links.py new file mode 100644 index 0000000000..c7e617e941 --- /dev/null +++ b/pyrogram/types/user_and_chats/chat_admin_with_invite_links.py @@ -0,0 +1,63 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan +# +# 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 + +import pyrogram +from pyrogram import raw +from pyrogram import types +from ..object import Object + + +class ChatAdminWithInviteLinks(Object): + """Represents a chat administrator that has created invite links in a chat. + + Parameters: + admin (:obj:`~pyrogram.types.User`): + The administrator. + + chat_invite_links_count (``int``): + The number of valid chat invite links created by this administrator. + + revoked_chat_invite_links_count (``int``): + The number of revoked chat invite links created by this administrator. + """ + + def __init__( + self, *, + admin: "types.User", + chat_invite_links_count: int, + revoked_chat_invite_links_count: int = None + ): + super().__init__() + + self.admin = admin + self.chat_invite_links_count = chat_invite_links_count + self.revoked_chat_invite_links_count = revoked_chat_invite_links_count + + @staticmethod + def _parse( + client: "pyrogram.Client", + admin: "raw.types.ChatAdminWithInvites", + users: Dict[int, "raw.types.User"] = None + ) -> "ChatAdminWithInviteLinks": + return ChatAdminWithInviteLinks( + admin=types.User._parse(client, users[admin.admin_id]), + chat_invite_links_count=admin.invites_count, + revoked_chat_invite_links_count=admin.revoked_invites_count + ) diff --git a/pyrogram/types/user_and_chats/chat_invite_link.py b/pyrogram/types/user_and_chats/chat_invite_link.py new file mode 100644 index 0000000000..cee80dd47f --- /dev/null +++ b/pyrogram/types/user_and_chats/chat_invite_link.py @@ -0,0 +1,102 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan +# +# 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 + +import pyrogram +from pyrogram import raw +from pyrogram import types +from ..object import Object + + +class ChatInviteLink(Object): + """An invite link for a chat. + + Parameters: + invite_link (``str``): + The invite link. + + date (``int``): + The date in Unix timestamp when the link was created. + + is_primary (``bool``): + True, if the link is primary. + + is_revoked (``bool``): + True, if the link is revoked. + + creator (:obj:`~pyrogram.types.User`, *optional*): + Creator of the link. + + expire_date (``int``, *optional*): + Point in time (Unix timestamp) when the link will expire or has been expired. + + member_limit (``int``, *optional*): + Maximum number of users that can be members of the chat simultaneously after joining the chat via this + invite link; 1-99999. + + member_count (``int``, *optional*): + Number of users that joined via this link and are currently member of the chat. + """ + + def __init__( + self, *, + invite_link: str, + creator: "types.User", + date: int, + is_primary: bool = None, + is_revoked: bool = None, + start_date: int = None, + expire_date: int = None, + member_limit: int = None, + member_count: int = None + ): + super().__init__() + + self.invite_link = invite_link + self.creator = creator + self.date = date + self.is_primary = is_primary + self.is_revoked = is_revoked + self.start_date = start_date + self.expire_date = expire_date + self.member_limit = member_limit + self.member_count = member_count + + @staticmethod + def _parse( + client: "pyrogram.Client", + invite: "raw.types.ChatInviteExported", + users: Dict[int, "raw.types.User"] = None + ) -> "ChatInviteLink": + creator = ( + types.User._parse(client, users[invite.admin_id]) + if users is not None + else None + ) + + return ChatInviteLink( + invite_link=invite.link, + creator=creator, + date=invite.date, + is_primary=invite.permanent, + is_revoked=invite.revoked, + expire_date=invite.expire_date, + member_limit=invite.usage_limit, + member_count=invite.usage + ) diff --git a/pyrogram/types/user_and_chats/invite_link_importer.py b/pyrogram/types/user_and_chats/invite_link_importer.py new file mode 100644 index 0000000000..4517ae3c8d --- /dev/null +++ b/pyrogram/types/user_and_chats/invite_link_importer.py @@ -0,0 +1,55 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan +# +# 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 pyrogram import raw +from pyrogram import types +from ..object import Object + + +class InviteLinkImporter(Object): + """The date and user of when someone has joined with an invite link. + + Parameters: + date (``int``): + The unix time of when this user used the given link + + user (:obj:`~pyrogram.types.User`): + The user that has used the given invite link + """ + + def __init__(self, *, date, user): + super().__init__(None) + + self.date = date + self.user = user + + @staticmethod + def _parse(client, invite_importers: "raw.types.ChatInviteImporters"): + importers = types.List() + + d = {i.id: i for i in invite_importers.users} + + for j in invite_importers.importers: + importers.append( + InviteLinkImporter( + date=j.date, + user=types.User._parse(client=None, user=d[j.user_id]) + ) + ) + + return importers From 6678af08aa57e5eab9f60b837b2f943ef314f2a3 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 17 Mar 2021 13:18:16 +0100 Subject: [PATCH 0510/1185] Add filters.dice to filter Dice messages --- pyrogram/filters.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/pyrogram/filters.py b/pyrogram/filters.py index a77e058135..a91508b23e 100644 --- a/pyrogram/filters.py +++ b/pyrogram/filters.py @@ -405,7 +405,7 @@ async def venue_filter(_, __, m: Message): # region web_page_filter async def web_page_filter(_, __, m: Message): - return m.web_page + return bool(m.web_page) web_page = create(web_page_filter) @@ -416,13 +416,24 @@ async def web_page_filter(_, __, m: Message): # region poll_filter async def poll_filter(_, __, m: Message): - return m.poll + return bool(m.poll) poll = create(poll_filter) """Filter messages that contain :obj:`~pyrogram.types.Poll` objects.""" +# endregion + +# region dice_filter +async def dice_filter(_, __, m: Message): + return bool(m.dice) + + +dice = create(dice_filter) +"""Filter messages that contain :obj:`~pyrogram.types.Dice` objects.""" + + # endregion # region private_filter From 783e89e0f0319c8163d54d86d0b082e022098e89 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 17 Mar 2021 13:22:19 +0100 Subject: [PATCH 0511/1185] Add filters for voice chat service messages --- pyrogram/filters.py | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/pyrogram/filters.py b/pyrogram/filters.py index a91508b23e..9105642709 100644 --- a/pyrogram/filters.py +++ b/pyrogram/filters.py @@ -643,6 +643,39 @@ async def via_bot_filter(_, __, m: Message): """Filter messages sent via inline bots""" +# endregion + +# region voice_chat_started_filter +async def voice_chat_started_filter(_, __, m: Message): + return bool(m.voice_chat_started) + + +voice_chat_started = create(voice_chat_started_filter) +"""Filter messages for started voice chats""" + + +# endregion + +# region voice_chat_ended_filter +async def voice_chat_ended_filter(_, __, m: Message): + return bool(m.voice_chat_ended) + + +voice_chat_ended = create(voice_chat_ended_filter) +"""Filter messages for ended voice chats""" + + +# endregion + +# region voice_chat_members_invited_filter +async def voice_chat_members_invited_filter(_, __, m: Message): + return bool(m.voice_chat_members_invited) + + +voice_chat_members_invited = create(voice_chat_members_invited_filter) +"""Filter messages for voice chat invited members""" + + # endregion # region service_filter @@ -655,7 +688,8 @@ async def service_filter(_, __, m: Message): 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*. +*channel_chat_created*, *migrate_to_chat_id*, *migrate_from_chat_id*, *pinned_message*, *game_score*, +*voice_chat_started*, *voice_chat_ended*, *voice_chat_members_invited*. """ From 975ff219f2279065a2246b0cc2b857a0a6fb32ca Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 17 Mar 2021 13:23:18 +0100 Subject: [PATCH 0512/1185] Add support for voice chat service messages --- pyrogram/types/messages_and_media/message.py | 30 ++++++++++- pyrogram/types/user_and_chats/__init__.py | 8 ++- .../types/user_and_chats/voice_chat_ended.py | 41 +++++++++++++++ .../voice_chat_members_invited.py | 50 +++++++++++++++++++ .../user_and_chats/voice_chat_started.py | 29 +++++++++++ 5 files changed, 156 insertions(+), 2 deletions(-) create mode 100644 pyrogram/types/user_and_chats/voice_chat_ended.py create mode 100644 pyrogram/types/user_and_chats/voice_chat_members_invited.py create mode 100644 pyrogram/types/user_and_chats/voice_chat_started.py diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index 65e51e4550..91c5f9d53e 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -264,6 +264,15 @@ class Message(Object, Update): E.g.: "/start 1 2 3" would produce ["start", "1", "2", "3"]. Only applicable when using :obj:`~pyrogram.filters.command`. + voice_chat_started (:obj:`~pyrogram.types.VoiceChatStarted`, *optional*): + Service message: the voice chat started. + + voice_chat_ended (:obj:`~pyrogram.types.VoiceChatEnded`, *optional*): + Service message: the voice chat has ended. + + voice_chat_members_invited (:obj:`~pyrogram.types.VoiceChatParticipantsInvited`, *optional*): + Service message: new members were invited to the voice chat. + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. @@ -335,6 +344,9 @@ def __init__( outgoing: bool = None, matches: List[Match] = None, command: List[str] = None, + voice_chat_started: "types.VoiceChatStarted" = None, + voice_chat_ended: "types.VoiceChatEnded" = None, + voice_chat_members_invited: "types.VoiceChatMemberInvited" = None, reply_markup: Union[ "types.InlineKeyboardMarkup", "types.ReplyKeyboardMarkup", @@ -402,6 +414,9 @@ def __init__( self.matches = matches self.command = command self.reply_markup = reply_markup + self.voice_chat_started = voice_chat_started + self.voice_chat_ended = voice_chat_ended + self.voice_chat_members_invited = voice_chat_members_invited @staticmethod async def _parse( @@ -427,6 +442,9 @@ async def _parse( group_chat_created = None channel_chat_created = None new_chat_photo = None + voice_chat_started = None + voice_chat_ended = None + voice_chat_members_invited = None if isinstance(action, raw.types.MessageActionChatAddUser): new_chat_members = [types.User._parse(client, users[i]) for i in action.users] @@ -448,6 +466,13 @@ async def _parse( channel_chat_created = True elif isinstance(action, raw.types.MessageActionChatEditPhoto): new_chat_photo = types.Photo._parse(client, action.photo) + elif isinstance(action, raw.types.MessageActionGroupCall): + if action.duration: + voice_chat_ended = types.VoiceChatEnded._parse(action) + else: + voice_chat_started = types.VoiceChatStarted() + elif isinstance(action, raw.types.MessageActionInviteToGroupCall): + voice_chat_members_invited = types.VoiceChatMembersInvited._parse(client, action, users) user = utils.get_raw_peer_id(message.from_id) or utils.get_raw_peer_id(message.peer_id) from_user = types.User._parse(client, users.get(user, None)) @@ -469,7 +494,10 @@ async def _parse( migrate_from_chat_id=-migrate_from_chat_id if migrate_from_chat_id else None, group_chat_created=group_chat_created, channel_chat_created=channel_chat_created, - client=client + client=client, + voice_chat_started=voice_chat_started, + voice_chat_ended=voice_chat_ended, + voice_chat_members_invited=voice_chat_members_invited # TODO: supergroup_chat_created ) diff --git a/pyrogram/types/user_and_chats/__init__.py b/pyrogram/types/user_and_chats/__init__.py index 7c4a65da64..3fc4eeaf55 100644 --- a/pyrogram/types/user_and_chats/__init__.py +++ b/pyrogram/types/user_and_chats/__init__.py @@ -29,6 +29,9 @@ from .invite_link_importer import InviteLinkImporter from .restriction import Restriction from .user import User +from .voice_chat_ended import VoiceChatEnded +from .voice_chat_members_invited import VoiceChatMembersInvited +from .voice_chat_started import VoiceChatStarted __all__ = [ "Chat", @@ -43,5 +46,8 @@ "ChatEventFilter", "ChatInviteLink", "InviteLinkImporter", - "ChatAdminWithInviteLinks" + "ChatAdminWithInviteLinks", + "VoiceChatStarted", + "VoiceChatEnded", + "VoiceChatMembersInvited" ] diff --git a/pyrogram/types/user_and_chats/voice_chat_ended.py b/pyrogram/types/user_and_chats/voice_chat_ended.py new file mode 100644 index 0000000000..febb813729 --- /dev/null +++ b/pyrogram/types/user_and_chats/voice_chat_ended.py @@ -0,0 +1,41 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan +# +# 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 pyrogram import raw +from ..object import Object + + +class VoiceChatEnded(Object): + """A service message about a voice chat ended in the chat. + + Parameters: + duration (``int``): + Voice chat duration; in seconds. + """ + + def __init__( + self, *, + duration: int + ): + super().__init__() + + self.duration = duration + + @staticmethod + def _parse(action: "raw.types.MessageActionGroupCall") -> "VoiceChatEnded": + return VoiceChatEnded(duration=action.duration) diff --git a/pyrogram/types/user_and_chats/voice_chat_members_invited.py b/pyrogram/types/user_and_chats/voice_chat_members_invited.py new file mode 100644 index 0000000000..0a093cbf92 --- /dev/null +++ b/pyrogram/types/user_and_chats/voice_chat_members_invited.py @@ -0,0 +1,50 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan +# +# 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 List, Dict + +from pyrogram import raw, types +from ..object import Object + + +class VoiceChatMembersInvited(Object): + """A service message about new members invited to a voice chat. + + + Parameters: + users (List of :obj:`~pyrogram.types.User`): + New members that were invited to the voice chat. + """ + + def __init__( + self, *, + users: List["types.User"] + ): + super().__init__() + + self.users = users + + @staticmethod + def _parse( + client, + action: "raw.types.MessageActionInviteToGroupCall", + users: Dict[int, "raw.types.User"] + ) -> "VoiceChatMembersInvited": + users = [types.User._parse(client, users[i]) for i in action.users] + + return VoiceChatMembersInvited(users=users) diff --git a/pyrogram/types/user_and_chats/voice_chat_started.py b/pyrogram/types/user_and_chats/voice_chat_started.py new file mode 100644 index 0000000000..6ac546dce9 --- /dev/null +++ b/pyrogram/types/user_and_chats/voice_chat_started.py @@ -0,0 +1,29 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan +# +# 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 ..object import Object + + +class VoiceChatStarted(Object): + """A service message about a voice chat started in the chat. + + Currently holds no information. + """ + + def __init__(self): + super().__init__() From 89e590b968e27459af18bed6aab8057b94b7bbb6 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 17 Mar 2021 15:03:18 +0100 Subject: [PATCH 0513/1185] Move the "unknown constructor found" logging logic --- pyrogram/crypto/mtproto.py | 11 ++++++++++- pyrogram/raw/core/tl_object.py | 11 +---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pyrogram/crypto/mtproto.py b/pyrogram/crypto/mtproto.py index b67b49f128..bbe9d27a75 100644 --- a/pyrogram/crypto/mtproto.py +++ b/pyrogram/crypto/mtproto.py @@ -60,7 +60,16 @@ def unpack(b: BytesIO, session_id: bytes, auth_key: bytes, auth_key_id: bytes) - # https://core.telegram.org/mtproto/security_guidelines#checking-session-id assert data.read(8) == session_id - message = Message.read(data) + try: + message = Message.read(data) + except KeyError as e: + left = data.read().hex() + + left = [left[i:i + 64] for i in range(0, len(left), 64)] + left = [[left[i:i + 8] for i in range(0, len(left), 8)] for left in left] + left = "\n".join(" ".join(x for x in left) for left in left) + + raise ValueError(f"Unknown constructor found: {hex(e.args[0])}\n{left}") # https://core.telegram.org/mtproto/security_guidelines#checking-sha256-hash-value-of-msg-key # https://core.telegram.org/mtproto/security_guidelines#checking-message-length diff --git a/pyrogram/raw/core/tl_object.py b/pyrogram/raw/core/tl_object.py index db1d3bfc38..3b87d72c76 100644 --- a/pyrogram/raw/core/tl_object.py +++ b/pyrogram/raw/core/tl_object.py @@ -30,16 +30,7 @@ class TLObject: @classmethod def read(cls, data: BytesIO, *args: Any) -> Any: - try: - return cast(TLObject, objects[int.from_bytes(data.read(4), "little")]).read(data, *args) - except KeyError as e: - left = data.read().hex() - - left = [left[i:i + 64] for i in range(0, len(left), 64)] - left = [[left[i:i + 8] for i in range(0, len(left), 8)] for left in left] - left = "\n".join(" ".join(x for x in left) for left in left) - - raise ValueError(f"Unknown constructor found: {hex(e.args[0])}\n{left}") + return cast(TLObject, objects[int.from_bytes(data.read(4), "little")]).read(data, *args) def write(self, *args: Any) -> bytes: pass From 86cc1837b61ef157e1f8b5f886b2b392fd90267e Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 17 Mar 2021 15:09:04 +0100 Subject: [PATCH 0514/1185] Add support for the new bowling animation (dice) --- pyrogram/methods/messages/send_dice.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyrogram/methods/messages/send_dice.py b/pyrogram/methods/messages/send_dice.py index 1b7a88c1af..89a8a538fe 100644 --- a/pyrogram/methods/messages/send_dice.py +++ b/pyrogram/methods/messages/send_dice.py @@ -47,7 +47,10 @@ async def send_dice( For a contact that exists in your Telegram address book you can use his phone number (str). emoji (``str``, *optional*): - Emoji on which the dice throw animation is based. Currently, must be one of "🎲", "🎯", "🏀" or "⚽️". + Emoji on which the dice throw animation is based. + Currently, must be one of "🎲", "🎯", "🏀", "⚽", "🎳", or "🎰". + Dice can have values 1-6 for "🎲", "🎯" and "🎳", values 1-5 for "🏀" and "⚽", and + values 1-64 for "🎰". Defaults to "🎲". disable_notification (``bool``, *optional*): From dff3d993e18d8035f09ff9633ff7cc60a0afd5ac Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 17 Mar 2021 15:11:23 +0100 Subject: [PATCH 0515/1185] Add support for updates about chat member status changes --- compiler/docs/compiler.py | 4 + docs/source/api/decorators.rst | 2 + docs/source/api/handlers.rst | 2 + pyrogram/dispatcher.py | 15 +++- pyrogram/handlers/__init__.py | 1 + .../handlers/chat_member_updated_handler.py | 47 ++++++++++ pyrogram/methods/decorators/__init__.py | 4 +- .../decorators/on_chat_member_updated.py | 56 ++++++++++++ pyrogram/types/user_and_chats/__init__.py | 4 +- .../user_and_chats/chat_member_updated.py | 89 +++++++++++++++++++ 10 files changed, 219 insertions(+), 5 deletions(-) create mode 100644 pyrogram/handlers/chat_member_updated_handler.py create mode 100644 pyrogram/methods/decorators/on_chat_member_updated.py create mode 100644 pyrogram/types/user_and_chats/chat_member_updated.py diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index 78328459a7..0a34b20632 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -349,6 +349,7 @@ def get_title_list(s: str) -> list: ChatAdminWithInviteLinks ChatEvent ChatEventFilter + ChatMemberUpdated Dialog Restriction """, @@ -373,6 +374,9 @@ def get_title_list(s: str) -> list: Poll PollOption Dice + VoiceChatStarted + VoiceChatEnded + VoiceChatMembersInvited """, bots_keyboard=""" Bots & Keyboards diff --git a/docs/source/api/decorators.rst b/docs/source/api/decorators.rst index 13bfc9e408..c859063a70 100644 --- a/docs/source/api/decorators.rst +++ b/docs/source/api/decorators.rst @@ -43,6 +43,7 @@ Index - :meth:`~Client.on_callback_query` - :meth:`~Client.on_inline_query` - :meth:`~Client.on_chosen_inline_result` + - :meth:`~Client.on_chat_member_updated` - :meth:`~Client.on_deleted_messages` - :meth:`~Client.on_user_status` - :meth:`~Client.on_poll` @@ -59,6 +60,7 @@ Details .. autodecorator:: pyrogram.Client.on_callback_query() .. autodecorator:: pyrogram.Client.on_inline_query() .. autodecorator:: pyrogram.Client.on_chosen_inline_result() +.. autodecorator:: pyrogram.Client.on_chat_member_updated() .. autodecorator:: pyrogram.Client.on_deleted_messages() .. autodecorator:: pyrogram.Client.on_user_status() .. autodecorator:: pyrogram.Client.on_poll() diff --git a/docs/source/api/handlers.rst b/docs/source/api/handlers.rst index 4e79d139bc..b06ebbed09 100644 --- a/docs/source/api/handlers.rst +++ b/docs/source/api/handlers.rst @@ -41,6 +41,7 @@ Index - :class:`CallbackQueryHandler` - :class:`InlineQueryHandler` - :class:`ChosenInlineResultHandler` + - :class:`ChatMemberUpdated` - :class:`UserStatusHandler` - :class:`PollHandler` - :class:`DisconnectHandler` @@ -57,6 +58,7 @@ Details .. autoclass:: CallbackQueryHandler() .. autoclass:: InlineQueryHandler() .. autoclass:: ChosenInlineResultHandler() +.. autoclass:: ChatMemberUpdated() .. autoclass:: UserStatusHandler() .. autoclass:: PollHandler() .. autoclass:: DisconnectHandler() diff --git a/pyrogram/dispatcher.py b/pyrogram/dispatcher.py index f4b818a478..0f46d64236 100644 --- a/pyrogram/dispatcher.py +++ b/pyrogram/dispatcher.py @@ -26,7 +26,7 @@ from pyrogram.handlers import ( CallbackQueryHandler, MessageHandler, DeletedMessagesHandler, UserStatusHandler, RawUpdateHandler, InlineQueryHandler, PollHandler, - ChosenInlineResultHandler + ChosenInlineResultHandler, ChatMemberUpdatedHandler ) from pyrogram.raw.types import ( UpdateNewMessage, UpdateNewChannelMessage, UpdateNewScheduledMessage, @@ -34,7 +34,7 @@ UpdateDeleteMessages, UpdateDeleteChannelMessages, UpdateBotCallbackQuery, UpdateInlineBotCallbackQuery, UpdateUserStatus, UpdateBotInlineQuery, UpdateMessagePoll, - UpdateBotInlineSend + UpdateBotInlineSend, UpdateChatParticipant, UpdateChannelParticipant ) log = logging.getLogger(__name__) @@ -62,6 +62,11 @@ class Dispatcher: UpdateInlineBotCallbackQuery ) + CHAT_MEMBER_UPDATES = ( + UpdateChatParticipant, + UpdateChannelParticipant + ) + MESSAGE_UPDATES = NEW_MESSAGE_UPDATES + EDIT_MESSAGE_UPDATES def __init__(self, client: "pyrogram.Client"): @@ -98,6 +103,9 @@ async def poll_parser(update, users, chats): async def chosen_inline_result_parser(update, users, chats): return pyrogram.types.ChosenInlineResult._parse(self.client, update, users), ChosenInlineResultHandler + async def chat_member_updated_parser(update, users, chats): + return pyrogram.types.ChatMemberUpdated._parse(self.client, update, users, chats), ChatMemberUpdatedHandler + self.update_parsers = { Dispatcher.MESSAGE_UPDATES: message_parser, Dispatcher.DELETE_MESSAGES_UPDATES: deleted_messages_parser, @@ -105,7 +113,8 @@ async def chosen_inline_result_parser(update, users, chats): (UpdateUserStatus,): user_status_parser, (UpdateBotInlineQuery,): inline_query_parser, (UpdateMessagePoll,): poll_parser, - (UpdateBotInlineSend,): chosen_inline_result_parser + (UpdateBotInlineSend,): chosen_inline_result_parser, + Dispatcher.CHAT_MEMBER_UPDATES: chat_member_updated_parser } self.update_parsers = {key: value for key_tuple, value in self.update_parsers.items() for key in key_tuple} diff --git a/pyrogram/handlers/__init__.py b/pyrogram/handlers/__init__.py index 74e230a41f..71ecab72b0 100644 --- a/pyrogram/handlers/__init__.py +++ b/pyrogram/handlers/__init__.py @@ -17,6 +17,7 @@ # along with Pyrogram. If not, see . from .callback_query_handler import CallbackQueryHandler +from .chat_member_updated_handler import ChatMemberUpdatedHandler from .chosen_inline_result_handler import ChosenInlineResultHandler from .deleted_messages_handler import DeletedMessagesHandler from .disconnect_handler import DisconnectHandler diff --git a/pyrogram/handlers/chat_member_updated_handler.py b/pyrogram/handlers/chat_member_updated_handler.py new file mode 100644 index 0000000000..d03eae11f3 --- /dev/null +++ b/pyrogram/handlers/chat_member_updated_handler.py @@ -0,0 +1,47 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan +# +# 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 .handler import Handler + + +class ChatMemberUpdatedHandler(Handler): + """The ChatMemberUpdated handler class. Used to handle changes in the status of a chat member. + It is intended to be used with :meth:`~pyrogram.Client.add_handler`. + + For a nicer way to register this handler, have a look at the + :meth:`~pyrogram.Client.on_chat_member_updated` decorator. + + Parameters: + callback (``callable``): + Pass a function that will be called when a new ChatMemberUpdated event arrives. It takes + *(client, chat_member_updated)* as positional arguments (look at the section below for a detailed + description). + + filters (:obj:`Filters`): + Pass one or more filters to allow only a subset of updates to be passed in your callback function. + + Other parameters: + client (:obj:`~pyrogram.Client`): + The Client itself, useful when you want to call other API methods inside the handler. + + chat_member_updated (:obj:`~pyrogram.types.ChatMemberUpdated`): + The received chat member update. + """ + + def __init__(self, callback: callable, filters=None): + super().__init__(callback, filters) diff --git a/pyrogram/methods/decorators/__init__.py b/pyrogram/methods/decorators/__init__.py index fe96ed13a6..01ddffcee6 100644 --- a/pyrogram/methods/decorators/__init__.py +++ b/pyrogram/methods/decorators/__init__.py @@ -17,6 +17,7 @@ # along with Pyrogram. If not, see . from .on_callback_query import OnCallbackQuery +from .on_chat_member_updated import OnChatMemberUpdated from .on_chosen_inline_result import OnChosenInlineResult from .on_deleted_messages import OnDeletedMessages from .on_disconnect import OnDisconnect @@ -36,6 +37,7 @@ class Decorators( OnUserStatus, OnInlineQuery, OnPoll, - OnChosenInlineResult + OnChosenInlineResult, + OnChatMemberUpdated ): pass diff --git a/pyrogram/methods/decorators/on_chat_member_updated.py b/pyrogram/methods/decorators/on_chat_member_updated.py new file mode 100644 index 0000000000..e184bbeff5 --- /dev/null +++ b/pyrogram/methods/decorators/on_chat_member_updated.py @@ -0,0 +1,56 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan +# +# 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 Callable + +import pyrogram +from pyrogram.filters import Filter +from pyrogram.scaffold import Scaffold + + +class OnChatMemberUpdated(Scaffold): + def on_chat_member_updated( + self=None, + filters=None, + group: int = 0 + ) -> callable: + """Decorator for handling event changes on chat members. + + This does the same thing as :meth:`~pyrogram.Client.add_handler` using the + :obj:`~pyrogram.handlers.ChatMemberUpdated`. + + Parameters: + filters (:obj:`~pyrogram.filters`, *optional*): + Pass one or more filters to allow only a subset of updates to be passed in your function. + + group (``int``, *optional*): + The group identifier, defaults to 0. + """ + + def decorator(func: Callable) -> Callable: + if isinstance(self, pyrogram.Client): + self.add_handler(pyrogram.handlers.ChatMemberUpdatedHandler(func, filters), group) + elif isinstance(self, Filter) or self is None: + func.handler = ( + pyrogram.handlers.ChatMemberUpdatedHandler(func, self), + group if filters is None else filters + ) + + return func + + return decorator diff --git a/pyrogram/types/user_and_chats/__init__.py b/pyrogram/types/user_and_chats/__init__.py index 3fc4eeaf55..f3022e3754 100644 --- a/pyrogram/types/user_and_chats/__init__.py +++ b/pyrogram/types/user_and_chats/__init__.py @@ -22,6 +22,7 @@ from .chat_event_filter import ChatEventFilter from .chat_invite_link import ChatInviteLink from .chat_member import ChatMember +from .chat_member_updated import ChatMemberUpdated from .chat_permissions import ChatPermissions from .chat_photo import ChatPhoto from .chat_preview import ChatPreview @@ -49,5 +50,6 @@ "ChatAdminWithInviteLinks", "VoiceChatStarted", "VoiceChatEnded", - "VoiceChatMembersInvited" + "VoiceChatMembersInvited", + "ChatMemberUpdated" ] diff --git a/pyrogram/types/user_and_chats/chat_member_updated.py b/pyrogram/types/user_and_chats/chat_member_updated.py new file mode 100644 index 0000000000..5794069fba --- /dev/null +++ b/pyrogram/types/user_and_chats/chat_member_updated.py @@ -0,0 +1,89 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan +# +# 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, Union + +import pyrogram +from pyrogram import raw +from pyrogram import types +from ..object import Object +from ..update import Update + + +class ChatMemberUpdated(Object, Update): + """Represents changes in the status of a chat member. + + Parameters: + chat (:obj:`~pyrogram.types.Chat`): + Chat the user belongs to. + + from_user (:obj:`~pyrogram.types.User`): + Performer of the action, which resulted in the change. + + date (``int``): + Date the change was done in Unix time. + + old_chat_member (:obj:`~pyrogram.types.ChatMember`): + Previous information about the chat member. + + new_chat_member (:obj:`~pyrogram.types.ChatMember`): + New information about the chat member. + + invite_link (:obj:`~pyrogram.types.ChatInviteLink`, *optional*): + Chat invite link, which was used by the user to join the chat; for joining by invite link events only. + """ + + def __init__( + self, + *, + client: "pyrogram.Client" = None, + chat: "types.Chat", + from_user: "types.User", + date: int, + old_chat_member: "types.ChatMember", + new_chat_member: "types.ChatMember", + invite_link: "types.ChatInviteLink" = None, + ): + super().__init__(client) + + self.chat = chat + self.from_user = from_user + self.date = date + self.old_chat_member = old_chat_member + self.new_chat_member = new_chat_member + self.invite_link = invite_link + + @staticmethod + def _parse( + client: "pyrogram.Client", + update: Union["raw.types.UpdateChatParticipant", "raw.types.UpdateChannelParticipant"], + users: Dict[int, "raw.types.User"], + chats: Dict[int, "raw.types.Chat"] + ) -> "ChatMemberUpdated": + chat_id = getattr(update, "chat_id", None) or getattr(update, "channel_id") + invite_link = types.ChatInviteLink._parse(client, update.invite, users) if update.invite else None + + return ChatMemberUpdated( + chat=types.Chat._parse_chat(client, chats[chat_id]), + from_user=types.User._parse(client, users[update.actor_id]), + date=update.date, + old_chat_member=types.ChatMember._parse(client, update.prev_participant, users), + new_chat_member=types.ChatMember._parse(client, update.new_participant, users), + invite_link=invite_link, + client=client + ) From ddb2d84f968fe5c3120edbf4387937156fe82373 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 17 Mar 2021 15:19:36 +0100 Subject: [PATCH 0516/1185] Add support for can_manage_chat permission --- pyrogram/methods/chats/promote_chat_member.py | 17 ++++++++++++----- pyrogram/types/user_and_chats/chat_member.py | 10 ++++++++++ 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/pyrogram/methods/chats/promote_chat_member.py b/pyrogram/methods/chats/promote_chat_member.py index 4d0f15c012..e45b58764c 100644 --- a/pyrogram/methods/chats/promote_chat_member.py +++ b/pyrogram/methods/chats/promote_chat_member.py @@ -28,12 +28,13 @@ async def promote_chat_member( chat_id: Union[int, str], user_id: Union[int, str], is_anonymous: bool = False, - can_change_info: bool = True, + can_manage_chat: bool = True, + can_change_info: bool = False, can_post_messages: bool = False, can_edit_messages: bool = False, - can_delete_messages: bool = True, - can_restrict_members: bool = True, - can_invite_users: bool = True, + can_delete_messages: bool = False, + can_restrict_members: bool = False, + can_invite_users: bool = False, can_pin_messages: bool = False, can_promote_members: bool = False, can_manage_voice_chats: bool = False @@ -54,6 +55,11 @@ async def promote_chat_member( is_anonymous (``bool``, *optional*): Pass True, if the administrator's presence in the chat is hidden. + can_manage_chat (``bool``, *optional*): + Pass True, if the administrator can access the chat event log, chat statistics, message statistics + in channels, see channel members, see anonymous administrators in supergroups and ignore slow mode. + Implied by any other administrator privilege. + can_change_info (``bool``, *optional*): Pass True, if the administrator can change chat title, photo and other settings. @@ -106,7 +112,8 @@ async def promote_chat_member( invite_users=can_invite_users or None, pin_messages=can_pin_messages or None, add_admins=can_promote_members or None, - manage_call=can_manage_voice_chats or None + manage_call=can_manage_voice_chats or None, + other=can_manage_chat or None ), rank="" ) diff --git a/pyrogram/types/user_and_chats/chat_member.py b/pyrogram/types/user_and_chats/chat_member.py index a3c87c24aa..7853042a7a 100644 --- a/pyrogram/types/user_and_chats/chat_member.py +++ b/pyrogram/types/user_and_chats/chat_member.py @@ -66,6 +66,12 @@ class ChatMember(Object): Administrators only. True, if you are allowed to edit administrator privileges of the user. + can_manage_chat (``bool``, *optional*): + Administrators only. + True, if the administrator can access the chat event log, chat statistics, message statistics in channels, + see channel members, see anonymous administrators in supergroups and ignore slow mode. + Implied by any other administrator privilege. + can_post_messages (``bool``, *optional*): Administrators only. Channels only. True, if the administrator can post messages in the channel. @@ -150,6 +156,7 @@ def __init__( # Admin permissions can_be_edited: bool = None, + can_manage_chat: bool = None, can_post_messages: bool = None, # Channels only can_edit_messages: bool = None, # Channels only can_delete_messages: bool = None, @@ -184,6 +191,7 @@ def __init__( self.is_anonymous = is_anonymous self.can_be_edited = can_be_edited + self.can_manage_chat = can_manage_chat self.can_post_messages = can_post_messages self.can_edit_messages = can_edit_messages self.can_delete_messages = can_delete_messages @@ -248,6 +256,7 @@ def _parse(client, member, users) -> "ChatMember": title=member.rank, invited_by=invited_by, can_change_info=permissions.change_info, + can_manage_chat=permissions.other, can_post_messages=permissions.post_messages, can_edit_messages=permissions.edit_messages, can_delete_messages=permissions.delete_messages, @@ -271,6 +280,7 @@ def _parse(client, member, users) -> "ChatMember": invited_by=invited_by, promoted_by=types.User._parse(client, users[member.promoted_by]), can_be_edited=member.can_edit, + can_manage_chat=permissions.other, can_change_info=permissions.change_info, can_post_messages=permissions.post_messages, can_edit_messages=permissions.edit_messages, From 1c52d21d659a2664fc1e2ccf6cbab8e694cf4ffb Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 17 Mar 2021 15:42:54 +0100 Subject: [PATCH 0517/1185] Minor document fixes --- docs/source/api/handlers.rst | 4 ++-- pyrogram/methods/decorators/on_chat_member_updated.py | 2 +- pyrogram/methods/decorators/on_chosen_inline_result.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/source/api/handlers.rst b/docs/source/api/handlers.rst index b06ebbed09..2d83a05d8a 100644 --- a/docs/source/api/handlers.rst +++ b/docs/source/api/handlers.rst @@ -41,7 +41,7 @@ Index - :class:`CallbackQueryHandler` - :class:`InlineQueryHandler` - :class:`ChosenInlineResultHandler` - - :class:`ChatMemberUpdated` + - :class:`ChatMemberUpdatedHandler` - :class:`UserStatusHandler` - :class:`PollHandler` - :class:`DisconnectHandler` @@ -58,7 +58,7 @@ Details .. autoclass:: CallbackQueryHandler() .. autoclass:: InlineQueryHandler() .. autoclass:: ChosenInlineResultHandler() -.. autoclass:: ChatMemberUpdated() +.. autoclass:: ChatMemberUpdatedHandler() .. autoclass:: UserStatusHandler() .. autoclass:: PollHandler() .. autoclass:: DisconnectHandler() diff --git a/pyrogram/methods/decorators/on_chat_member_updated.py b/pyrogram/methods/decorators/on_chat_member_updated.py index e184bbeff5..fcf2ec6b4d 100644 --- a/pyrogram/methods/decorators/on_chat_member_updated.py +++ b/pyrogram/methods/decorators/on_chat_member_updated.py @@ -32,7 +32,7 @@ def on_chat_member_updated( """Decorator for handling event changes on chat members. This does the same thing as :meth:`~pyrogram.Client.add_handler` using the - :obj:`~pyrogram.handlers.ChatMemberUpdated`. + :obj:`~pyrogram.handlers.ChatMemberUpdatedHandler`. Parameters: filters (:obj:`~pyrogram.filters`, *optional*): diff --git a/pyrogram/methods/decorators/on_chosen_inline_result.py b/pyrogram/methods/decorators/on_chosen_inline_result.py index e457fcc076..ae56ea0738 100644 --- a/pyrogram/methods/decorators/on_chosen_inline_result.py +++ b/pyrogram/methods/decorators/on_chosen_inline_result.py @@ -32,7 +32,7 @@ def on_chosen_inline_result( """Decorator for handling chosen inline results. This does the same thing as :meth:`~pyrogram.Client.add_handler` using the - :obj:`~pyrogram.handlers.ChosenInlineResult`. + :obj:`~pyrogram.handlers.ChosenInlineResultHandler`. Parameters: filters (:obj:`~pyrogram.filters`, *optional*): From a94c3bb4652c28059bf6364e2f5557a5f05e785b Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 17 Mar 2021 16:04:34 +0100 Subject: [PATCH 0518/1185] Add support for invite link chat log events (edit, revoke, delete) --- pyrogram/methods/chats/get_chat_event_log.py | 2 +- pyrogram/types/user_and_chats/chat_event.py | 66 ++++++++++++++++++-- 2 files changed, 61 insertions(+), 7 deletions(-) diff --git a/pyrogram/methods/chats/get_chat_event_log.py b/pyrogram/methods/chats/get_chat_event_log.py index 80dfb5bd85..46c031edb8 100644 --- a/pyrogram/methods/chats/get_chat_event_log.py +++ b/pyrogram/methods/chats/get_chat_event_log.py @@ -77,7 +77,7 @@ async def get_chat_event_log( min_id=0, max_id=offset_id, limit=limit, - events_filter=filters.write(), + events_filter=filters.write() if filters else None, admins=( [await self.resolve_peer(i) for i in user_ids] if user_ids is not None diff --git a/pyrogram/types/user_and_chats/chat_event.py b/pyrogram/types/user_and_chats/chat_event.py index 93ef9c3586..7b97a31c98 100644 --- a/pyrogram/types/user_and_chats/chat_event.py +++ b/pyrogram/types/user_and_chats/chat_event.py @@ -43,9 +43,9 @@ class ChatEventAction(AutoName): MESSAGE_DELETED = auto() # VOICE_CHAT_DISCARDED = auto() MESSAGE_EDITED = auto() - # LINK_DELETED = auto() - # LINK_EDITED = auto() - # LINK_REVOKED = auto() + INVITE_LINK_EDITED = auto() + INVITE_LINK_REVOKED = auto() + INVITE_LINK_DELETED = auto() MEMBER_INVITED = auto() MEMBER_JOINED = auto() # MEMBER_JOINED_BY_LINK = auto() @@ -143,6 +143,15 @@ class ChatEvent(Object): - "message_unpinned": a message has been unpinned (see *unpinned_message* below). + - "invite_link_edited": an invite link has been edited + (see *edited_invite_link* below). + + - "invite_link_revoked": an invite link has been revoked + (see *revoked_invite_link* below). + + - "invite_link_deleted": an invite link has been deleted + (see *deleted_invite_link* below). + user (:obj:`~pyrogram.types.User`): User that triggered the event. @@ -211,7 +220,7 @@ class ChatEvent(Object): For "signatures_enabled" only. old_slow_mode, new_slow_mode (``int``, *optional*): - Previous slow mode value in seconds. + Previous and new slow mode value in seconds. For "slow_mode_changed" only. pinned_message (:obj:`~pyrogram.types.Message`, *optional*): @@ -221,6 +230,18 @@ class ChatEvent(Object): unpinned_message (:obj:`~pyrogram.types.Message`, *optional*): Unpinned message. For "unpinned_message" only. + + old_invite_link, new_invite_link (:obj:`~pyrogram.types.ChatInviteLink`, *optional*): + Previous and new edited invite link. + For "invite_link_edited" only. + + revoked_invite_link (:obj:`~pyrogram.types.ChatInviteLink`, *optional*): + Revoked invite link. + For "invite_link_revoked" only. + + deleted_invite_link (:obj:`~pyrogram.types.ChatInviteLink`, *optional*): + Deleted invite link. + For "invite_link_deleted" only. """ def __init__( @@ -276,7 +297,12 @@ def __init__( new_slow_mode: int = None, pinned_message: "types.Message" = None, - unpinned_message: "types.Message" = None + unpinned_message: "types.Message" = None, + + old_invite_link: "types.ChatInviteLink" = None, + new_invite_link: "types.ChatInviteLink" = None, + revoked_invite_link: "types.ChatInviteLink" = None, + deleted_invite_link: "types.ChatInviteLink" = None, ): super().__init__() @@ -333,6 +359,11 @@ def __init__( self.pinned_message = pinned_message self.unpinned_message = unpinned_message + self.old_invite_link = old_invite_link + self.new_invite_link = new_invite_link + self.revoked_invite_link = revoked_invite_link + self.deleted_invite_link = deleted_invite_link + @staticmethod async def _parse( client: "pyrogram.Client", @@ -394,6 +425,11 @@ async def _parse( pinned_message: Optional[types.Message] = None unpinned_message: Optional[types.Message] = None + old_invite_link: Optional[types.ChatInviteLink] = None + new_invite_link: Optional[types.ChatInviteLink] = None + revoked_invite_link: Optional[types.ChatInviteLink] = None + deleted_invite_link: Optional[types.ChatInviteLink] = None + if isinstance(action, raw.types.ChannelAdminLogEventActionChangeAbout): old_description = action.prev_value new_description = action.new_value @@ -489,6 +525,19 @@ async def _parse( unpinned_message = await types.Message._parse(client, message, users, chats) action = ChatEventAction.MESSAGE_UNPINNED.value + elif isinstance(action, raw.types.ChannelAdminLogEventActionExportedInviteEdit): + old_invite_link = types.ChatInviteLink._parse(client, action.prev_invite, users) + new_invite_link = types.ChatInviteLink._parse(client, action.new_invite, users) + action = ChatEventAction.INVITE_LINK_EDITED.value + + elif isinstance(action, raw.types.ChannelAdminLogEventActionExportedInviteRevoke): + revoked_invite_link = types.ChatInviteLink._parse(client, action.invite, users) + action = ChatEventAction.INVITE_LINK_REVOKED + + elif isinstance(action, raw.types.ChannelAdminLogEventActionExportedInviteDelete): + deleted_invite_link = types.ChatInviteLink._parse(client, action.invite, users) + action = ChatEventAction.INVITE_LINK_DELETED.value + else: action = f"{ChatEventAction.UNKNOWN.value}-{action.QUALNAME}" @@ -543,5 +592,10 @@ async def _parse( new_slow_mode=new_slow_mode, pinned_message=pinned_message, - unpinned_message=unpinned_message + unpinned_message=unpinned_message, + + old_invite_link=old_invite_link, + new_invite_link=new_invite_link, + revoked_invite_link=revoked_invite_link, + deleted_invite_link=deleted_invite_link ) From 182768a5d389f87320767993fcc55f2d2d65cde0 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 17 Mar 2021 17:13:55 +0100 Subject: [PATCH 0519/1185] Add support for LoginUrl buttons --- compiler/docs/compiler.py | 1 + pyrogram/methods/bots/send_game.py | 2 +- .../methods/messages/edit_inline_media.py | 2 +- .../messages/edit_inline_reply_markup.py | 2 +- pyrogram/methods/messages/edit_inline_text.py | 2 +- .../methods/messages/edit_message_media.py | 2 +- .../messages/edit_message_reply_markup.py | 2 +- .../methods/messages/edit_message_text.py | 2 +- pyrogram/methods/messages/send_animation.py | 2 +- pyrogram/methods/messages/send_audio.py | 2 +- .../methods/messages/send_cached_media.py | 2 +- pyrogram/methods/messages/send_contact.py | 2 +- pyrogram/methods/messages/send_dice.py | 2 +- pyrogram/methods/messages/send_document.py | 2 +- pyrogram/methods/messages/send_location.py | 2 +- pyrogram/methods/messages/send_message.py | 2 +- pyrogram/methods/messages/send_photo.py | 2 +- pyrogram/methods/messages/send_poll.py | 2 +- pyrogram/methods/messages/send_sticker.py | 2 +- pyrogram/methods/messages/send_venue.py | 2 +- pyrogram/methods/messages/send_video.py | 2 +- pyrogram/methods/messages/send_video_note.py | 2 +- pyrogram/methods/messages/send_voice.py | 2 +- pyrogram/methods/messages/stop_poll.py | 2 +- pyrogram/types/bots_and_keyboards/__init__.py | 13 ++- .../types/bots_and_keyboards/force_reply.py | 7 +- .../inline_keyboard_button.py | 95 +++++++++++++------ .../inline_keyboard_markup.py | 5 +- .../bots_and_keyboards/keyboard_button.py | 16 ++-- .../types/bots_and_keyboards/login_url.py | 89 +++++++++++++++++ .../reply_keyboard_markup.py | 3 +- .../reply_keyboard_remove.py | 7 +- 32 files changed, 208 insertions(+), 74 deletions(-) create mode 100644 pyrogram/types/bots_and_keyboards/login_url.py diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index 0a34b20632..f7583df346 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -385,6 +385,7 @@ def get_title_list(s: str) -> list: ReplyKeyboardRemove InlineKeyboardMarkup InlineKeyboardButton + LoginUrl ForceReply CallbackQuery GameHighScore diff --git a/pyrogram/methods/bots/send_game.py b/pyrogram/methods/bots/send_game.py index 72fc84f4f4..8f6d10ab82 100644 --- a/pyrogram/methods/bots/send_game.py +++ b/pyrogram/methods/bots/send_game.py @@ -80,7 +80,7 @@ async def send_game( 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 + reply_markup=await reply_markup.write(self) if reply_markup else None ) ) diff --git a/pyrogram/methods/messages/edit_inline_media.py b/pyrogram/methods/messages/edit_inline_media.py index e941c131c4..35a1f2fd4c 100644 --- a/pyrogram/methods/messages/edit_inline_media.py +++ b/pyrogram/methods/messages/edit_inline_media.py @@ -116,7 +116,7 @@ async def edit_inline_media( raw.functions.messages.EditInlineBotMessage( id=unpacked, media=media, - reply_markup=reply_markup.write() if reply_markup else None, + reply_markup=await reply_markup.write(self) if reply_markup else None, **await self.parser.parse(caption, parse_mode) ), sleep_threshold=self.sleep_threshold diff --git a/pyrogram/methods/messages/edit_inline_reply_markup.py b/pyrogram/methods/messages/edit_inline_reply_markup.py index 50c1031201..5966d4b7d2 100644 --- a/pyrogram/methods/messages/edit_inline_reply_markup.py +++ b/pyrogram/methods/messages/edit_inline_reply_markup.py @@ -61,7 +61,7 @@ async def edit_inline_reply_markup( return await session.send( raw.functions.messages.EditInlineBotMessage( id=unpacked, - reply_markup=reply_markup.write() if reply_markup else None, + reply_markup=await reply_markup.write(self) if reply_markup else None, ), sleep_threshold=self.sleep_threshold ) diff --git a/pyrogram/methods/messages/edit_inline_text.py b/pyrogram/methods/messages/edit_inline_text.py index 223b0422dc..e9a79e1407 100644 --- a/pyrogram/methods/messages/edit_inline_text.py +++ b/pyrogram/methods/messages/edit_inline_text.py @@ -82,7 +82,7 @@ async def edit_inline_text( raw.functions.messages.EditInlineBotMessage( id=unpacked, no_webpage=disable_web_page_preview or None, - reply_markup=reply_markup.write() if reply_markup else None, + reply_markup=await reply_markup.write(self) if reply_markup else None, **await self.parser.parse(text, parse_mode) ), sleep_threshold=self.sleep_threshold diff --git a/pyrogram/methods/messages/edit_message_media.py b/pyrogram/methods/messages/edit_message_media.py index bd8ba6060d..a0d365dcc9 100644 --- a/pyrogram/methods/messages/edit_message_media.py +++ b/pyrogram/methods/messages/edit_message_media.py @@ -257,7 +257,7 @@ async def edit_message_media( peer=await self.resolve_peer(chat_id), id=message_id, media=media, - reply_markup=reply_markup.write() if reply_markup else None, + reply_markup=await reply_markup.write(self) if reply_markup else None, message=message, entities=entities ) diff --git a/pyrogram/methods/messages/edit_message_reply_markup.py b/pyrogram/methods/messages/edit_message_reply_markup.py index 501dc480ba..aefb87e9e5 100644 --- a/pyrogram/methods/messages/edit_message_reply_markup.py +++ b/pyrogram/methods/messages/edit_message_reply_markup.py @@ -62,7 +62,7 @@ async def edit_message_reply_markup( raw.functions.messages.EditMessage( peer=await self.resolve_peer(chat_id), id=message_id, - reply_markup=reply_markup.write() if reply_markup else None, + reply_markup=await reply_markup.write(self) if reply_markup else None, ) ) diff --git a/pyrogram/methods/messages/edit_message_text.py b/pyrogram/methods/messages/edit_message_text.py index 44e3f48ac3..443d71f6af 100644 --- a/pyrogram/methods/messages/edit_message_text.py +++ b/pyrogram/methods/messages/edit_message_text.py @@ -85,7 +85,7 @@ async def edit_message_text( 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, + reply_markup=await reply_markup.write(self) if reply_markup else None, **await utils.parse_text_entities(self, text, parse_mode, entities) ) ) diff --git a/pyrogram/methods/messages/send_animation.py b/pyrogram/methods/messages/send_animation.py index 303af57562..d365ae562c 100644 --- a/pyrogram/methods/messages/send_animation.py +++ b/pyrogram/methods/messages/send_animation.py @@ -222,7 +222,7 @@ def progress(current, total): reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), schedule_date=schedule_date, - reply_markup=reply_markup.write() if reply_markup else None, + reply_markup=await reply_markup.write(self) if reply_markup else None, **await utils.parse_text_entities(self, caption, parse_mode, caption_entities) ) ) diff --git a/pyrogram/methods/messages/send_audio.py b/pyrogram/methods/messages/send_audio.py index 39a3b95d35..4fa683a9ea 100644 --- a/pyrogram/methods/messages/send_audio.py +++ b/pyrogram/methods/messages/send_audio.py @@ -217,7 +217,7 @@ def progress(current, total): reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), schedule_date=schedule_date, - reply_markup=reply_markup.write() if reply_markup else None, + reply_markup=await reply_markup.write(self) if reply_markup else None, **await utils.parse_text_entities(self, caption, parse_mode, caption_entities) ) ) diff --git a/pyrogram/methods/messages/send_cached_media.py b/pyrogram/methods/messages/send_cached_media.py index 2d8d9f5bc3..1f1517e70f 100644 --- a/pyrogram/methods/messages/send_cached_media.py +++ b/pyrogram/methods/messages/send_cached_media.py @@ -102,7 +102,7 @@ async def send_cached_media( reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), schedule_date=schedule_date, - reply_markup=reply_markup.write() if reply_markup else None, + reply_markup=await reply_markup.write(self) if reply_markup else None, **await utils.parse_text_entities(self, caption, parse_mode, caption_entities) ) ) diff --git a/pyrogram/methods/messages/send_contact.py b/pyrogram/methods/messages/send_contact.py index 6cc32c48f2..6317a8296a 100644 --- a/pyrogram/methods/messages/send_contact.py +++ b/pyrogram/methods/messages/send_contact.py @@ -97,7 +97,7 @@ async def send_contact( reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), schedule_date=schedule_date, - reply_markup=reply_markup.write() if reply_markup else None + reply_markup=await reply_markup.write(self) if reply_markup else None ) ) diff --git a/pyrogram/methods/messages/send_dice.py b/pyrogram/methods/messages/send_dice.py index 89a8a538fe..4b7422b316 100644 --- a/pyrogram/methods/messages/send_dice.py +++ b/pyrogram/methods/messages/send_dice.py @@ -91,7 +91,7 @@ async def send_dice( reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), schedule_date=schedule_date, - reply_markup=reply_markup.write() if reply_markup else None, + reply_markup=await reply_markup.write(self) if reply_markup else None, message="" ) ) diff --git a/pyrogram/methods/messages/send_document.py b/pyrogram/methods/messages/send_document.py index 2202353631..a7e342855f 100644 --- a/pyrogram/methods/messages/send_document.py +++ b/pyrogram/methods/messages/send_document.py @@ -194,7 +194,7 @@ def progress(current, total): reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), schedule_date=schedule_date, - reply_markup=reply_markup.write() if reply_markup else None, + reply_markup=await reply_markup.write(self) if reply_markup else None, **await utils.parse_text_entities(self, caption, parse_mode, caption_entities) ) ) diff --git a/pyrogram/methods/messages/send_location.py b/pyrogram/methods/messages/send_location.py index a08c065724..e70d9661d0 100644 --- a/pyrogram/methods/messages/send_location.py +++ b/pyrogram/methods/messages/send_location.py @@ -89,7 +89,7 @@ async def send_location( reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), schedule_date=schedule_date, - reply_markup=reply_markup.write() if reply_markup else None + reply_markup=await reply_markup.write(self) if reply_markup else None ) ) diff --git a/pyrogram/methods/messages/send_message.py b/pyrogram/methods/messages/send_message.py index fdffe79fd8..982e4bbbd7 100644 --- a/pyrogram/methods/messages/send_message.py +++ b/pyrogram/methods/messages/send_message.py @@ -130,7 +130,7 @@ async def send_message( reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), schedule_date=schedule_date, - reply_markup=reply_markup.write() if reply_markup else None, + reply_markup=await reply_markup.write(self) if reply_markup else None, message=message, entities=entities ) diff --git a/pyrogram/methods/messages/send_photo.py b/pyrogram/methods/messages/send_photo.py index 01d2e7339b..fcc3c04c6c 100644 --- a/pyrogram/methods/messages/send_photo.py +++ b/pyrogram/methods/messages/send_photo.py @@ -172,7 +172,7 @@ async def send_photo( reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), schedule_date=schedule_date, - reply_markup=reply_markup.write() if reply_markup else None, + reply_markup=await reply_markup.write(self) if reply_markup else None, **await utils.parse_text_entities(self, caption, parse_mode, caption_entities) ) ) diff --git a/pyrogram/methods/messages/send_poll.py b/pyrogram/methods/messages/send_poll.py index 9def8afe92..dc0ccf178c 100644 --- a/pyrogram/methods/messages/send_poll.py +++ b/pyrogram/methods/messages/send_poll.py @@ -117,7 +117,7 @@ async def send_poll( reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), schedule_date=schedule_date, - reply_markup=reply_markup.write() if reply_markup else None + reply_markup=await reply_markup.write(self) if reply_markup else None ) ) diff --git a/pyrogram/methods/messages/send_sticker.py b/pyrogram/methods/messages/send_sticker.py index 2e12b0db8a..c7ae122a57 100644 --- a/pyrogram/methods/messages/send_sticker.py +++ b/pyrogram/methods/messages/send_sticker.py @@ -150,7 +150,7 @@ async def send_sticker( reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), schedule_date=schedule_date, - reply_markup=reply_markup.write() if reply_markup else None, + reply_markup=await reply_markup.write(self) if reply_markup else None, message="" ) ) diff --git a/pyrogram/methods/messages/send_venue.py b/pyrogram/methods/messages/send_venue.py index 5d969e2ead..7dbae7a0e9 100644 --- a/pyrogram/methods/messages/send_venue.py +++ b/pyrogram/methods/messages/send_venue.py @@ -113,7 +113,7 @@ async def send_venue( reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), schedule_date=schedule_date, - reply_markup=reply_markup.write() if reply_markup else None + reply_markup=await reply_markup.write(self) if reply_markup else None ) ) diff --git a/pyrogram/methods/messages/send_video.py b/pyrogram/methods/messages/send_video.py index d0ff59119a..a46439ea49 100644 --- a/pyrogram/methods/messages/send_video.py +++ b/pyrogram/methods/messages/send_video.py @@ -228,7 +228,7 @@ def progress(current, total): reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), schedule_date=schedule_date, - reply_markup=reply_markup.write() if reply_markup else None, + reply_markup=await reply_markup.write(self) if reply_markup else None, **await utils.parse_text_entities(self, caption, parse_mode, caption_entities) ) ) diff --git a/pyrogram/methods/messages/send_video_note.py b/pyrogram/methods/messages/send_video_note.py index 9d4a8a719e..91ba93fe4c 100644 --- a/pyrogram/methods/messages/send_video_note.py +++ b/pyrogram/methods/messages/send_video_note.py @@ -174,7 +174,7 @@ async def send_video_note( reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), schedule_date=schedule_date, - reply_markup=reply_markup.write() if reply_markup else None, + reply_markup=await reply_markup.write(self) if reply_markup else None, message="" ) ) diff --git a/pyrogram/methods/messages/send_voice.py b/pyrogram/methods/messages/send_voice.py index 03967b69d5..c534767bc4 100644 --- a/pyrogram/methods/messages/send_voice.py +++ b/pyrogram/methods/messages/send_voice.py @@ -178,7 +178,7 @@ async def send_voice( reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), schedule_date=schedule_date, - reply_markup=reply_markup.write() if reply_markup else None, + reply_markup=await reply_markup.write(self) if reply_markup else None, **await utils.parse_text_entities(self, caption, parse_mode, caption_entities) ) ) diff --git a/pyrogram/methods/messages/stop_poll.py b/pyrogram/methods/messages/stop_poll.py index 85241d885b..8853c5d6b9 100644 --- a/pyrogram/methods/messages/stop_poll.py +++ b/pyrogram/methods/messages/stop_poll.py @@ -68,7 +68,7 @@ async def stop_poll( answers=[] ) ), - reply_markup=reply_markup.write() if reply_markup else None + reply_markup=await reply_markup.write(self) if reply_markup else None ) ) diff --git a/pyrogram/types/bots_and_keyboards/__init__.py b/pyrogram/types/bots_and_keyboards/__init__.py index 2f85dff1a8..9e486cb56d 100644 --- a/pyrogram/types/bots_and_keyboards/__init__.py +++ b/pyrogram/types/bots_and_keyboards/__init__.py @@ -23,10 +23,19 @@ from .inline_keyboard_button import InlineKeyboardButton from .inline_keyboard_markup import InlineKeyboardMarkup from .keyboard_button import KeyboardButton +from .login_url import LoginUrl from .reply_keyboard_markup import ReplyKeyboardMarkup from .reply_keyboard_remove import ReplyKeyboardRemove __all__ = [ - "CallbackGame", "CallbackQuery", "ForceReply", "GameHighScore", "InlineKeyboardButton", "InlineKeyboardMarkup", - "KeyboardButton", "ReplyKeyboardMarkup", "ReplyKeyboardRemove" + "CallbackGame", + "CallbackQuery", + "ForceReply", + "GameHighScore", + "InlineKeyboardButton", + "InlineKeyboardMarkup", + "KeyboardButton", + "ReplyKeyboardMarkup", + "ReplyKeyboardRemove", + "LoginUrl" ] diff --git a/pyrogram/types/bots_and_keyboards/force_reply.py b/pyrogram/types/bots_and_keyboards/force_reply.py index 0456bf48f2..c52bb76651 100644 --- a/pyrogram/types/bots_and_keyboards/force_reply.py +++ b/pyrogram/types/bots_and_keyboards/force_reply.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 pyrogram from pyrogram import raw from ..object import Object @@ -46,12 +47,12 @@ def __init__( self.selective = selective @staticmethod - def read(o): + def read(b): return ForceReply( - selective=o.selective + selective=b.selective ) - def write(self): + async def write(self, _: "pyrogram.Client"): return raw.types.ReplyKeyboardForceReply( single_use=True, selective=self.selective or None diff --git a/pyrogram/types/bots_and_keyboards/inline_keyboard_button.py b/pyrogram/types/bots_and_keyboards/inline_keyboard_button.py index b790c1a204..444eb5da68 100644 --- a/pyrogram/types/bots_and_keyboards/inline_keyboard_button.py +++ b/pyrogram/types/bots_and_keyboards/inline_keyboard_button.py @@ -18,6 +18,7 @@ from typing import Union +import pyrogram from pyrogram import raw from pyrogram import types from ..object import Object @@ -38,6 +39,10 @@ class InlineKeyboardButton(Object): url (``str``, *optional*): HTTP url to be opened when button is pressed. + login_url (:obj:`~pyrogram.types.LoginUrl`, *optional*): + An HTTP URL used to automatically authorize the user. Can be used as a replacement for + the `Telegram Login Widget `_. + switch_inline_query (``str``, *optional*): If set, pressing the button will prompt the user to select one of their chats, open that chat and insert the bot's username and the specified inline query in the input field. Can be empty, in which case just @@ -51,15 +56,18 @@ class InlineKeyboardButton(Object): chat's input field. Can be empty, in which case only the bot's username will be inserted.This offers a quick way for the user to open your bot in inline mode in the same chat – good for selecting something from multiple options. - """ - # TODO: Add callback_game and pay fields + callback_game (:obj:`~pyrogram.types.CallbackGame`, *optional*): + Description of the game that will be launched when the user presses the button. + **NOTE**: This type of button **must** always be the first button in the first row. + """ def __init__( self, text: str, callback_data: Union[str, bytes] = None, url: str = None, + login_url: "types.LoginUrl" = None, switch_inline_query: str = None, switch_inline_query_current_chat: str = None, callback_game: "types.CallbackGame" = None @@ -68,6 +76,7 @@ def __init__( self.text = str(text) self.url = url + self.login_url = login_url self.callback_data = callback_data self.switch_inline_query = switch_inline_query self.switch_inline_query_current_chat = switch_inline_query_current_chat @@ -75,62 +84,86 @@ def __init__( # self.pay = pay @staticmethod - def read(o): - if isinstance(o, raw.types.KeyboardButtonUrl): - return InlineKeyboardButton( - text=o.text, - url=o.url - ) - - if isinstance(o, raw.types.KeyboardButtonCallback): + def read(b: "raw.base.KeyboardButton"): + if isinstance(b, raw.types.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() + data = b.data.decode() except UnicodeDecodeError: - data = o.data + data = b.data return InlineKeyboardButton( - text=o.text, + text=b.text, callback_data=data ) - if isinstance(o, raw.types.KeyboardButtonSwitchInline): - if o.same_peer: + if isinstance(b, raw.types.KeyboardButtonUrl): + return InlineKeyboardButton( + text=b.text, + url=b.url + ) + + if isinstance(b, raw.types.KeyboardButtonUrlAuth): + return InlineKeyboardButton( + text=b.text, + login_url=types.LoginUrl.read(b) + ) + + if isinstance(b, raw.types.KeyboardButtonSwitchInline): + if b.same_peer: return InlineKeyboardButton( - text=o.text, - switch_inline_query_current_chat=o.query + text=b.text, + switch_inline_query_current_chat=b.query ) else: return InlineKeyboardButton( - text=o.text, - switch_inline_query=o.query + text=b.text, + switch_inline_query=b.query ) - if isinstance(o, raw.types.KeyboardButtonGame): + if isinstance(b, raw.types.KeyboardButtonGame): return InlineKeyboardButton( - text=o.text, + text=b.text, callback_game=types.CallbackGame() ) - def write(self): - if self.callback_data is not None: + async def write(self, client: "pyrogram.Client"): + if 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 raw.types.KeyboardButtonCallback(text=self.text, data=data) - if self.url is not None: - return raw.types.KeyboardButtonUrl(text=self.text, url=self.url) + return raw.types.KeyboardButtonCallback( + text=self.text, + data=data + ) + + if self.url: + return raw.types.KeyboardButtonUrl( + text=self.text, + url=self.url + ) + + if self.login_url: + return self.login_url.write( + text=self.text, + bot=await client.resolve_peer(self.login_url.bot_username) + ) - if self.switch_inline_query is not None: - return raw.types.KeyboardButtonSwitchInline(text=self.text, query=self.switch_inline_query) + if self.switch_inline_query: + return raw.types.KeyboardButtonSwitchInline( + text=self.text, + query=self.switch_inline_query + ) - if self.switch_inline_query_current_chat is not None: + if self.switch_inline_query_current_chat: return raw.types.KeyboardButtonSwitchInline( text=self.text, query=self.switch_inline_query_current_chat, same_peer=True ) - if self.callback_game is not None: - return raw.types.KeyboardButtonGame(text=self.text) + if self.callback_game: + return raw.types.KeyboardButtonGame( + text=self.text + ) diff --git a/pyrogram/types/bots_and_keyboards/inline_keyboard_markup.py b/pyrogram/types/bots_and_keyboards/inline_keyboard_markup.py index 107b2e2f7b..8b5ec13b2c 100644 --- a/pyrogram/types/bots_and_keyboards/inline_keyboard_markup.py +++ b/pyrogram/types/bots_and_keyboards/inline_keyboard_markup.py @@ -18,6 +18,7 @@ from typing import List +import pyrogram from pyrogram import raw from pyrogram import types from ..object import Object @@ -52,9 +53,9 @@ def read(o): inline_keyboard=inline_keyboard ) - def write(self): + async def write(self, client: "pyrogram.Client"): return raw.types.ReplyInlineMarkup( rows=[raw.types.KeyboardButtonRow( - buttons=[j.write() for j in i] + buttons=[await j.write(client) for j in i] ) for i in self.inline_keyboard] ) diff --git a/pyrogram/types/bots_and_keyboards/keyboard_button.py b/pyrogram/types/bots_and_keyboards/keyboard_button.py index 85adaf20fe..204c696a72 100644 --- a/pyrogram/types/bots_and_keyboards/keyboard_button.py +++ b/pyrogram/types/bots_and_keyboards/keyboard_button.py @@ -52,25 +52,23 @@ def __init__( self.request_location = request_location @staticmethod - def read(o): - if isinstance(o, raw.types.KeyboardButton): - return o.text + def read(b): + if isinstance(b, raw.types.KeyboardButton): + return b.text - if isinstance(o, raw.types.KeyboardButtonRequestPhone): + if isinstance(b, raw.types.KeyboardButtonRequestPhone): return KeyboardButton( - text=o.text, + text=b.text, request_contact=True ) - if isinstance(o, raw.types.KeyboardButtonRequestGeoLocation): + if isinstance(b, raw.types.KeyboardButtonRequestGeoLocation): return KeyboardButton( - text=o.text, + text=b.text, request_location=True ) def write(self): - # TODO: Enforce optional args mutual exclusiveness - if self.request_contact: return raw.types.KeyboardButtonRequestPhone(text=self.text) elif self.request_location: diff --git a/pyrogram/types/bots_and_keyboards/login_url.py b/pyrogram/types/bots_and_keyboards/login_url.py new file mode 100644 index 0000000000..21c7a45dcc --- /dev/null +++ b/pyrogram/types/bots_and_keyboards/login_url.py @@ -0,0 +1,89 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan +# +# 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 pyrogram import raw + +from ..object import Object + + +class LoginUrl(Object): + """Represents a parameter of the inline keyboard button used to automatically authorize a user. + + Serves as a great replacement for the Telegram Login Widget when the user is coming from Telegram. + All the user needs to do is tap/click a button and confirm that they want to log in. + + Parameters: + url (``str``): + An HTTP URL to be opened with user authorization data added to the query string when the button is pressed. + If the user refuses to provide authorization data, the original URL without information about the user will + be opened. The data added is the same as described in Receiving authorization data. + + **NOTE**: You **must** always check the hash of the received data to verify the authentication and the + integrity of the data as described in + `Checking authorization `_. + + forward_text (``str``, *optional*): + New text of the button in forwarded messages. + + bot_username (``str``, *optional*): + Username of a bot, which will be used for user authorization. + See `Setting up `_ a bot for more details. + If not specified, the current bot's username will be assumed. The url's domain must be the same as the + domain linked with the bot. + See `Linking your domain to the bot `_ + for more details. + + request_write_access (``str``, *optional*): + Pass True to request the permission for your bot to send messages to the user. + + button_id (``int``): + Button identifier. + """ + + def __init__( + self, *, + url: str, + forward_text: str = None, + bot_username: str = None, + request_write_access: str = None, + button_id: int = None + ): + super().__init__() + + self.url = url + self.forward_text = forward_text + self.bot_username = bot_username + self.request_write_access = request_write_access + self.button_id = button_id + + @staticmethod + def read(b: "raw.types.KeyboardButtonUrlAuth") -> "LoginUrl": + return LoginUrl( + url=b.url, + forward_text=b.fwd_text, + button_id=b.button_id + ) + + def write(self, text: str, bot: "raw.types.InputUser"): + return raw.types.InputKeyboardButtonUrlAuth( + text=text, + url=self.url, + bot=bot, + fwd_text=self.forward_text, + request_write_access=self.request_write_access + ) diff --git a/pyrogram/types/bots_and_keyboards/reply_keyboard_markup.py b/pyrogram/types/bots_and_keyboards/reply_keyboard_markup.py index dfbdc9ea45..21fa6ceb0d 100644 --- a/pyrogram/types/bots_and_keyboards/reply_keyboard_markup.py +++ b/pyrogram/types/bots_and_keyboards/reply_keyboard_markup.py @@ -18,6 +18,7 @@ from typing import List, Union +import pyrogram from pyrogram import raw from pyrogram import types from ..object import Object @@ -81,7 +82,7 @@ def read(kb): selective=kb.selective ) - def write(self): + async def write(self, _: "pyrogram.Client"): return raw.types.ReplyKeyboardMarkup( rows=[raw.types.KeyboardButtonRow( buttons=[ diff --git a/pyrogram/types/bots_and_keyboards/reply_keyboard_remove.py b/pyrogram/types/bots_and_keyboards/reply_keyboard_remove.py index 020412ae1a..9bf16528f9 100644 --- a/pyrogram/types/bots_and_keyboards/reply_keyboard_remove.py +++ b/pyrogram/types/bots_and_keyboards/reply_keyboard_remove.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 pyrogram from pyrogram import raw from ..object import Object @@ -46,12 +47,12 @@ def __init__( self.selective = selective @staticmethod - def read(o): + def read(b): return ReplyKeyboardRemove( - selective=o.selective + selective=b.selective ) - def write(self): + async def write(self, _: "pyrogram.Client"): return raw.types.ReplyKeyboardHide( selective=self.selective or None ) From 2eb7ab2f6e8673336f12b4e470d8325739ea9c53 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 17 Mar 2021 17:26:51 +0100 Subject: [PATCH 0520/1185] Add support for user mentions inside inline query results --- pyrogram/methods/bots/answer_inline_query.py | 2 +- .../inline_keyboard_markup.py | 25 ++++++++++++---- .../types/inline_mode/inline_query_result.py | 3 +- .../inline_query_result_animation.py | 25 +++++++++++----- .../inline_query_result_article.py | 5 ++-- .../inline_mode/inline_query_result_photo.py | 25 +++++++++++----- .../input_message_content.py | 4 ++- .../input_text_message_content.py | 29 ++++++++++++++----- 8 files changed, 85 insertions(+), 33 deletions(-) diff --git a/pyrogram/methods/bots/answer_inline_query.py b/pyrogram/methods/bots/answer_inline_query.py index 973d730786..66bd739ffb 100644 --- a/pyrogram/methods/bots/answer_inline_query.py +++ b/pyrogram/methods/bots/answer_inline_query.py @@ -97,7 +97,7 @@ async def answer_inline_query( return await self.send( raw.functions.messages.SetInlineBotResults( query_id=int(inline_query_id), - results=[await r.write() for r in results], + results=[await r.write(self) for r in results], cache_time=cache_time, gallery=is_gallery or None, private=is_personal or None, diff --git a/pyrogram/types/bots_and_keyboards/inline_keyboard_markup.py b/pyrogram/types/bots_and_keyboards/inline_keyboard_markup.py index 8b5ec13b2c..1571f1ba32 100644 --- a/pyrogram/types/bots_and_keyboards/inline_keyboard_markup.py +++ b/pyrogram/types/bots_and_keyboards/inline_keyboard_markup.py @@ -54,8 +54,23 @@ def read(o): ) async def write(self, client: "pyrogram.Client"): - return raw.types.ReplyInlineMarkup( - rows=[raw.types.KeyboardButtonRow( - buttons=[await j.write(client) for j in i] - ) for i in self.inline_keyboard] - ) + rows = [] + + for r in self.inline_keyboard: + buttons = [] + + for b in r: + buttons.append(await b.write(client)) + + rows.append(raw.types.KeyboardButtonRow(buttons=buttons)) + + return raw.types.ReplyInlineMarkup(rows=rows) + + # There seems to be a Python issues with nested async comprehensions. + # See: https://bugs.python.org/issue33346 + # + # return raw.types.ReplyInlineMarkup( + # rows=[raw.types.KeyboardButtonRow( + # buttons=[await j.write(client) for j in i] + # ) for i in self.inline_keyboard] + # ) diff --git a/pyrogram/types/inline_mode/inline_query_result.py b/pyrogram/types/inline_mode/inline_query_result.py index decb77e176..919b7df565 100644 --- a/pyrogram/types/inline_mode/inline_query_result.py +++ b/pyrogram/types/inline_mode/inline_query_result.py @@ -18,6 +18,7 @@ from uuid import uuid4 +import pyrogram from pyrogram import types from ..object import Object @@ -66,5 +67,5 @@ def __init__( self.input_message_content = input_message_content self.reply_markup = reply_markup - async def write(self): + async def write(self, client: "pyrogram.Client"): pass diff --git a/pyrogram/types/inline_mode/inline_query_result_animation.py b/pyrogram/types/inline_mode/inline_query_result_animation.py index 2b9725d5d6..45af1ed38a 100644 --- a/pyrogram/types/inline_mode/inline_query_result_animation.py +++ b/pyrogram/types/inline_mode/inline_query_result_animation.py @@ -16,11 +16,10 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Optional +from typing import Optional, List -from pyrogram import raw -from pyrogram import types -from pyrogram.parser import Parser +import pyrogram +from pyrogram import raw, types, utils from .inline_query_result import InlineQueryResult @@ -60,6 +59,9 @@ class InlineQueryResultAnimation(InlineQueryResult): Pass "html" to enable HTML-style parsing only. Pass None to completely disable style parsing. + caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): + List of special entities that appear in the caption, which can be specified instead of *parse_mode*. + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*): An InlineKeyboardMarkup object. @@ -76,6 +78,7 @@ def __init__( description: str = None, caption: str = "", parse_mode: Optional[str] = object, + caption_entities: List["types.MessageEntity"] = None, reply_markup: "types.InlineKeyboardMarkup" = None, input_message_content: "types.InputMessageContent" = None ): @@ -87,10 +90,11 @@ def __init__( self.description = description self.caption = caption self.parse_mode = parse_mode + self.caption_entities = caption_entities self.reply_markup = reply_markup self.input_message_content = input_message_content - async def write(self): + async def write(self, client: "pyrogram.Client"): animation = raw.types.InputWebDocument( url=self.animation_url, size=0, @@ -108,6 +112,10 @@ async def write(self): attributes=[] ) + message, entities = (await utils.parse_text_entities( + client, self.caption, self.parse_mode, self.caption_entities + )).values() + return raw.types.InputBotInlineResult( id=self.id, type=self.type, @@ -116,11 +124,12 @@ async def write(self): thumb=thumb, content=animation, send_message=( - self.input_message_content.write(self.reply_markup) + self.input_message_content.write(client, self.reply_markup) if self.input_message_content else raw.types.InputBotInlineMessageMediaAuto( - reply_markup=self.reply_markup.write() if self.reply_markup else None, - **await(Parser(None)).parse(self.caption, self.parse_mode) + reply_markup=await self.reply_markup.write(client) if self.reply_markup else None, + message=message, + entities=entities ) ) ) diff --git a/pyrogram/types/inline_mode/inline_query_result_article.py b/pyrogram/types/inline_mode/inline_query_result_article.py index eadb0a15db..0b5e6008b5 100644 --- a/pyrogram/types/inline_mode/inline_query_result_article.py +++ b/pyrogram/types/inline_mode/inline_query_result_article.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 pyrogram from pyrogram import raw from pyrogram import types @@ -66,11 +67,11 @@ def __init__( self.description = description self.thumb_url = thumb_url - async def write(self): + async def write(self, client: "pyrogram.Client"): return raw.types.InputBotInlineResult( id=self.id, type=self.type, - send_message=await self.input_message_content.write(self.reply_markup), + send_message=await self.input_message_content.write(client, self.reply_markup), title=self.title, description=self.description, url=self.url, diff --git a/pyrogram/types/inline_mode/inline_query_result_photo.py b/pyrogram/types/inline_mode/inline_query_result_photo.py index 7afd66590c..f86c671898 100644 --- a/pyrogram/types/inline_mode/inline_query_result_photo.py +++ b/pyrogram/types/inline_mode/inline_query_result_photo.py @@ -16,11 +16,10 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Optional +from typing import Optional, List -from pyrogram import raw -from pyrogram import types -from pyrogram.parser import Parser +import pyrogram +from pyrogram import raw, types, utils from .inline_query_result import InlineQueryResult @@ -60,6 +59,9 @@ class InlineQueryResultPhoto(InlineQueryResult): Pass "html" to enable HTML-style parsing only. Pass None to completely disable style parsing. + caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): + List of special entities that appear in the caption, which can be specified instead of *parse_mode*. + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*): An InlineKeyboardMarkup object. @@ -76,6 +78,7 @@ def __init__( description: str = None, caption: str = "", parse_mode: Optional[str] = object, + caption_entities: List["types.MessageEntity"] = None, reply_markup: "types.InlineKeyboardMarkup" = None, input_message_content: "types.InputMessageContent" = None ): @@ -87,10 +90,11 @@ def __init__( self.description = description self.caption = caption self.parse_mode = parse_mode + self.caption_entities = caption_entities self.reply_markup = reply_markup self.input_message_content = input_message_content - async def write(self): + async def write(self, client: "pyrogram.Client"): photo = raw.types.InputWebDocument( url=self.photo_url, size=0, @@ -108,6 +112,10 @@ async def write(self): attributes=[] ) + message, entities = (await utils.parse_text_entities( + client, self.caption, self.parse_mode, self.caption_entities + )).values() + return raw.types.InputBotInlineResult( id=self.id, type=self.type, @@ -116,11 +124,12 @@ async def write(self): thumb=thumb, content=photo, send_message=( - await self.input_message_content.write(self.reply_markup) + await self.input_message_content.write(client, self.reply_markup) if self.input_message_content else raw.types.InputBotInlineMessageMediaAuto( - reply_markup=self.reply_markup.write() if self.reply_markup else None, - **await(Parser(None)).parse(self.caption, self.parse_mode) + reply_markup=await self.reply_markup.write(client) if self.reply_markup else None, + message=message, + entities=entities ) ) ) diff --git a/pyrogram/types/input_message_content/input_message_content.py b/pyrogram/types/input_message_content/input_message_content.py index 3d23b9ea12..99bed67de9 100644 --- a/pyrogram/types/input_message_content/input_message_content.py +++ b/pyrogram/types/input_message_content/input_message_content.py @@ -16,6 +16,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import pyrogram + from ..object import Object """- :obj:`~pyrogram.types.InputLocationMessageContent` @@ -34,5 +36,5 @@ class InputMessageContent(Object): def __init__(self): super().__init__() - def write(self, reply_markup): + async def write(self, client: "pyrogram.Client", reply_markup): raise NotImplementedError diff --git a/pyrogram/types/input_message_content/input_text_message_content.py b/pyrogram/types/input_message_content/input_text_message_content.py index ed943bfdd1..a053d818bf 100644 --- a/pyrogram/types/input_message_content/input_text_message_content.py +++ b/pyrogram/types/input_message_content/input_text_message_content.py @@ -16,10 +16,10 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Optional +from typing import Optional, List -from pyrogram import raw -from pyrogram.parser import Parser +import pyrogram +from pyrogram import raw, types, utils from .input_message_content import InputMessageContent @@ -37,20 +37,35 @@ class InputTextMessageContent(InputMessageContent): Pass "html" to enable HTML-style parsing only. Pass None to completely disable style parsing. + entities (List of :obj:`~pyrogram.types.MessageEntity`): + List of special entities that appear in message text, which can be specified instead of *parse_mode*. + disable_web_page_preview (``bool``, *optional*): Disables link previews for links in this message. """ - def __init__(self, message_text: str, parse_mode: Optional[str] = object, disable_web_page_preview: bool = None): + def __init__( + self, + message_text: str, + parse_mode: Optional[str] = object, + entities: List["types.MessageEntity"] = None, + disable_web_page_preview: bool = None + ): super().__init__() self.message_text = message_text self.parse_mode = parse_mode + self.entities = entities self.disable_web_page_preview = disable_web_page_preview - async def write(self, reply_markup): + async def write(self, client: "pyrogram.Client", reply_markup): + message, entities = (await utils.parse_text_entities( + client, self.message_text, self.parse_mode, self.entities + )).values() + return raw.types.InputBotInlineMessageText( no_webpage=self.disable_web_page_preview or None, - reply_markup=reply_markup.write() if reply_markup else None, - **await(Parser(None)).parse(self.message_text, self.parse_mode) + reply_markup=await reply_markup.write(client) if reply_markup else None, + message=message, + entities=entities ) From 3fe43f8413769a446c4d34f120258eaba682a661 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 17 Mar 2021 18:11:18 +0100 Subject: [PATCH 0521/1185] Update ChatInviteLink docs --- pyrogram/types/user_and_chats/chat_invite_link.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyrogram/types/user_and_chats/chat_invite_link.py b/pyrogram/types/user_and_chats/chat_invite_link.py index cee80dd47f..f4803b6a73 100644 --- a/pyrogram/types/user_and_chats/chat_invite_link.py +++ b/pyrogram/types/user_and_chats/chat_invite_link.py @@ -29,7 +29,8 @@ class ChatInviteLink(Object): Parameters: invite_link (``str``): - The invite link. + The invite link. If the link was created by another chat administrator, then the second part of the + link will be replaced with "...". date (``int``): The date in Unix timestamp when the link was created. From c72bbcf9e129fe42415ef33d508a5f5679f92668 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 17 Mar 2021 18:12:34 +0100 Subject: [PATCH 0522/1185] Fix ChatMemberUpdated args being parsed when they don't exist --- .../user_and_chats/chat_member_updated.py | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/pyrogram/types/user_and_chats/chat_member_updated.py b/pyrogram/types/user_and_chats/chat_member_updated.py index 5794069fba..31cec664b7 100644 --- a/pyrogram/types/user_and_chats/chat_member_updated.py +++ b/pyrogram/types/user_and_chats/chat_member_updated.py @@ -38,10 +38,10 @@ class ChatMemberUpdated(Object, Update): date (``int``): Date the change was done in Unix time. - old_chat_member (:obj:`~pyrogram.types.ChatMember`): + old_chat_member (:obj:`~pyrogram.types.ChatMember`, *optional*): Previous information about the chat member. - new_chat_member (:obj:`~pyrogram.types.ChatMember`): + new_chat_member (:obj:`~pyrogram.types.ChatMember`, *optional*): New information about the chat member. invite_link (:obj:`~pyrogram.types.ChatInviteLink`, *optional*): @@ -76,14 +76,26 @@ def _parse( chats: Dict[int, "raw.types.Chat"] ) -> "ChatMemberUpdated": chat_id = getattr(update, "chat_id", None) or getattr(update, "channel_id") - invite_link = types.ChatInviteLink._parse(client, update.invite, users) if update.invite else None + + old_chat_member = None + new_chat_member = None + invite_link = None + + if update.prev_participant: + old_chat_member = types.ChatMember._parse(client, update.prev_participant, users) + + if update.new_participant: + new_chat_member = types.ChatMember._parse(client, update.new_participant, users) + + if update.invite: + invite_link = types.ChatInviteLink._parse(client, update.invite, users) return ChatMemberUpdated( chat=types.Chat._parse_chat(client, chats[chat_id]), from_user=types.User._parse(client, users[update.actor_id]), date=update.date, - old_chat_member=types.ChatMember._parse(client, update.prev_participant, users), - new_chat_member=types.ChatMember._parse(client, update.new_participant, users), + old_chat_member=old_chat_member, + new_chat_member=new_chat_member, invite_link=invite_link, client=client ) From f0b1cc41f35a5e28b075a1b9c993caf0cbdab8fe Mon Sep 17 00:00:00 2001 From: Jonathan <49692607+jonatan1609@users.noreply.github.com> Date: Wed, 17 Mar 2021 21:07:54 +0200 Subject: [PATCH 0523/1185] Allow decorators in plugins to be stacked (#642) This allows registering the same callback function more than once by using different handlers. --- pyrogram/client.py | 11 +++++------ pyrogram/methods/decorators/on_callback_query.py | 11 ++++++++--- .../methods/decorators/on_chosen_inline_result.py | 11 ++++++++--- pyrogram/methods/decorators/on_deleted_messages.py | 11 ++++++++--- pyrogram/methods/decorators/on_inline_query.py | 11 ++++++++--- pyrogram/methods/decorators/on_message.py | 11 ++++++++--- pyrogram/methods/decorators/on_poll.py | 11 ++++++++--- pyrogram/methods/decorators/on_raw_update.py | 11 ++++++++--- pyrogram/methods/decorators/on_user_status.py | 11 ++++++++--- 9 files changed, 69 insertions(+), 30 deletions(-) diff --git a/pyrogram/client.py b/pyrogram/client.py index 35153bde71..2820de1856 100644 --- a/pyrogram/client.py +++ b/pyrogram/client.py @@ -717,13 +717,12 @@ def load_plugins(self): for name in vars(module).keys(): # noinspection PyBroadException try: - handler, group = getattr(module, name).handler - - if isinstance(handler, Handler) and isinstance(group, int): - self.add_handler(handler, group) + for handler, group in getattr(module, name).handlers: + if isinstance(handler, Handler) and isinstance(group, int): + self.add_handler(handler, group) - log.info('[{}] [LOAD] {}("{}") in group {} from "{}"'.format( - self.session_name, 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: diff --git a/pyrogram/methods/decorators/on_callback_query.py b/pyrogram/methods/decorators/on_callback_query.py index f939a3550d..884fbb95f3 100644 --- a/pyrogram/methods/decorators/on_callback_query.py +++ b/pyrogram/methods/decorators/on_callback_query.py @@ -47,9 +47,14 @@ def decorator(func: Callable) -> Callable: if isinstance(self, pyrogram.Client): self.add_handler(pyrogram.handlers.CallbackQueryHandler(func, filters), group) elif isinstance(self, Filter) or self is None: - func.handler = ( - pyrogram.handlers.CallbackQueryHandler(func, self), - group if filters is None else filters + if not hasattr(func, "handlers"): + func.handlers = [] + + func.handlers.append( + ( + pyrogram.handlers.CallbackQueryHandler(func, self), + group if filters is None else filters + ) ) return func diff --git a/pyrogram/methods/decorators/on_chosen_inline_result.py b/pyrogram/methods/decorators/on_chosen_inline_result.py index ae56ea0738..4e972a06a3 100644 --- a/pyrogram/methods/decorators/on_chosen_inline_result.py +++ b/pyrogram/methods/decorators/on_chosen_inline_result.py @@ -47,9 +47,14 @@ def decorator(func: Callable) -> Callable: if isinstance(self, pyrogram.Client): self.add_handler(pyrogram.handlers.ChosenInlineResultHandler(func, filters), group) elif isinstance(self, Filter) or self is None: - func.handler = ( - pyrogram.handlers.ChosenInlineResultHandler(func, self), - group if filters is None else filters + if not hasattr(func, "handlers"): + func.handlers = [] + + func.handlers.append( + ( + pyrogram.handlers.ChosenInlineResultHandler(func, self), + group if filters is None else filters + ) ) return func diff --git a/pyrogram/methods/decorators/on_deleted_messages.py b/pyrogram/methods/decorators/on_deleted_messages.py index 4078714086..d093310c78 100644 --- a/pyrogram/methods/decorators/on_deleted_messages.py +++ b/pyrogram/methods/decorators/on_deleted_messages.py @@ -47,9 +47,14 @@ def decorator(func: Callable) -> Callable: if isinstance(self, pyrogram.Client): self.add_handler(pyrogram.handlers.DeletedMessagesHandler(func, filters), group) elif isinstance(self, Filter) or self is None: - func.handler = ( - pyrogram.handlers.DeletedMessagesHandler(func, self), - group if filters is None else filters + if not hasattr(func, "handlers"): + func.handlers = [] + + func.handlers.append( + ( + pyrogram.handlers.DeletedMessagesHandler(func, self), + group if filters is None else filters + ) ) return func diff --git a/pyrogram/methods/decorators/on_inline_query.py b/pyrogram/methods/decorators/on_inline_query.py index dc3edbc82b..60463f66d8 100644 --- a/pyrogram/methods/decorators/on_inline_query.py +++ b/pyrogram/methods/decorators/on_inline_query.py @@ -47,9 +47,14 @@ def decorator(func: Callable) -> Callable: if isinstance(self, pyrogram.Client): self.add_handler(pyrogram.handlers.InlineQueryHandler(func, filters), group) elif isinstance(self, Filter) or self is None: - func.handler = ( - pyrogram.handlers.InlineQueryHandler(func, self), - group if filters is None else filters + if not hasattr(func, "handlers"): + func.handlers = [] + + func.handlers.append( + ( + pyrogram.handlers.InlineQueryHandler(func, self), + group if filters is None else filters + ) ) return func diff --git a/pyrogram/methods/decorators/on_message.py b/pyrogram/methods/decorators/on_message.py index ad17bd25ef..ec23084f7d 100644 --- a/pyrogram/methods/decorators/on_message.py +++ b/pyrogram/methods/decorators/on_message.py @@ -47,9 +47,14 @@ def decorator(func: Callable) -> Callable: if isinstance(self, pyrogram.Client): self.add_handler(pyrogram.handlers.MessageHandler(func, filters), group) elif isinstance(self, Filter) or self is None: - func.handler = ( - pyrogram.handlers.MessageHandler(func, self), - group if filters is None else filters + if not hasattr(func, "handlers"): + func.handlers = [] + + func.handlers.append( + ( + pyrogram.handlers.MessageHandler(func, self), + group if filters is None else filters + ) ) return func diff --git a/pyrogram/methods/decorators/on_poll.py b/pyrogram/methods/decorators/on_poll.py index d2af04ddd4..fc49b1925a 100644 --- a/pyrogram/methods/decorators/on_poll.py +++ b/pyrogram/methods/decorators/on_poll.py @@ -47,9 +47,14 @@ def decorator(func: Callable) -> Callable: if isinstance(self, pyrogram.Client): self.add_handler(pyrogram.handlers.PollHandler(func, filters), group) elif isinstance(self, Filter) or self is None: - func.handler = ( - pyrogram.handlers.PollHandler(func, self), - group if filters is None else filters + if not hasattr(func, "handlers"): + func.handlers = [] + + func.handlers.append( + ( + pyrogram.handlers.PollHandler(func, self), + group if filters is None else filters + ) ) return func diff --git a/pyrogram/methods/decorators/on_raw_update.py b/pyrogram/methods/decorators/on_raw_update.py index 4d7dc349d0..0ce6bf5df9 100644 --- a/pyrogram/methods/decorators/on_raw_update.py +++ b/pyrogram/methods/decorators/on_raw_update.py @@ -41,9 +41,14 @@ def decorator(func: Callable) -> Callable: if isinstance(self, pyrogram.Client): self.add_handler(pyrogram.handlers.RawUpdateHandler(func), group) else: - func.handler = ( - pyrogram.handlers.RawUpdateHandler(func), - group if self is None else group + if not hasattr(func, "handlers"): + func.handlers = [] + + func.handlers.append( + ( + pyrogram.handlers.RawUpdateHandler(func), + group if self is None else group + ) ) return func diff --git a/pyrogram/methods/decorators/on_user_status.py b/pyrogram/methods/decorators/on_user_status.py index d67cfaec75..fda3fb800c 100644 --- a/pyrogram/methods/decorators/on_user_status.py +++ b/pyrogram/methods/decorators/on_user_status.py @@ -45,9 +45,14 @@ def decorator(func: Callable) -> Callable: if isinstance(self, pyrogram.Client): self.add_handler(pyrogram.handlers.UserStatusHandler(func, filters), group) elif isinstance(self, Filter) or self is None: - func.handler = ( - pyrogram.handlers.UserStatusHandler(func, self), - group if filters is None else filters + if not hasattr(func, "handlers"): + func.handlers = [] + + func.handlers.append( + ( + pyrogram.handlers.UserStatusHandler(func, self), + group if filters is None else filters + ) ) return func From c7914f6c572c67aa9e86f0585d48d344574b0cc1 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 17 Mar 2021 20:10:09 +0100 Subject: [PATCH 0524/1185] Allow stackable plugin decorators for on_chat_member_updated --- pyrogram/methods/decorators/on_chat_member_updated.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/pyrogram/methods/decorators/on_chat_member_updated.py b/pyrogram/methods/decorators/on_chat_member_updated.py index fcf2ec6b4d..080daa1fc2 100644 --- a/pyrogram/methods/decorators/on_chat_member_updated.py +++ b/pyrogram/methods/decorators/on_chat_member_updated.py @@ -46,9 +46,14 @@ def decorator(func: Callable) -> Callable: if isinstance(self, pyrogram.Client): self.add_handler(pyrogram.handlers.ChatMemberUpdatedHandler(func, filters), group) elif isinstance(self, Filter) or self is None: - func.handler = ( - pyrogram.handlers.ChatMemberUpdatedHandler(func, self), - group if filters is None else filters + if not hasattr(func, "handlers"): + func.handlers = [] + + func.handlers.append( + ( + pyrogram.handlers.ChatMemberUpdatedHandler(func, self), + group if filters is None else filters + ) ) return func From b6613fbd63fcab4d2fd16aef26e4aed63a083eda Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 17 Mar 2021 20:38:00 +0100 Subject: [PATCH 0525/1185] Update API schema to Layer 125 (patch) --- compiler/api/source/main_api.tl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl index 21b71663ea..9d1ef45145 100644 --- a/compiler/api/source/main_api.tl +++ b/compiler/api/source/main_api.tl @@ -1181,7 +1181,7 @@ peerBlocked#e8fd8014 peer_id:Peer date:int = PeerBlocked; stats.messageStats#8999f295 views_graph:StatsGraph = stats.MessageStats; groupCallDiscarded#7780bcb4 id:long access_hash:long duration:int = GroupCall; -groupCall#c0c2052e flags:# join_muted:flags.1?true can_change_join_muted:flags.2?true id:long access_hash:long participants_count:int params:flags.0?DataJSON title:flags.3?string stream_dc_id:flags.4?int record_start_date:flags.5?int version:int = GroupCall; +groupCall#c0c2052e flags:# join_muted:flags.1?true can_change_join_muted:flags.2?true join_date_asc:flags.6?true id:long access_hash:long participants_count:int params:flags.0?DataJSON title:flags.3?string stream_dc_id:flags.4?int record_start_date:flags.5?int version:int = GroupCall; inputGroupCall#d8aa840f id:long access_hash:long = InputGroupCall; @@ -1550,7 +1550,7 @@ channels.inviteToChannel#199f3a6c channel:InputChannel users:Vector = channels.deleteChannel#c0111fe3 channel:InputChannel = Updates; channels.exportMessageLink#e63fadeb flags:# grouped:flags.0?true thread:flags.1?true channel:InputChannel id:int = ExportedMessageLink; channels.toggleSignatures#1f69b606 channel:InputChannel enabled:Bool = Updates; -channels.getAdminedPublicChannels#f8b036af flags:# by_location:flags.0?true check_limit:flags.1?true for_groupcall:flags.2?true = messages.Chats; +channels.getAdminedPublicChannels#f8b036af flags:# by_location:flags.0?true check_limit:flags.1?true = messages.Chats; channels.editBanned#72796912 channel:InputChannel user_id:InputUser banned_rights:ChatBannedRights = Updates; channels.getAdminLog#33ddf480 flags:# channel:InputChannel q:string events_filter:flags.0?ChannelAdminLogEventsFilter admins:flags.1?Vector max_id:long min_id:long limit:int = channels.AdminLogResults; channels.setStickers#ea8ca4f9 channel:InputChannel stickerset:InputStickerSet = Bool; From 73a1fee28af6218be38077b4f33ccd223b2d7f20 Mon Sep 17 00:00:00 2001 From: Jonathan <49692607+jonatan1609@users.noreply.github.com> Date: Thu, 18 Mar 2021 12:34:36 +0200 Subject: [PATCH 0526/1185] Fix stackable plugin decorators with include/exclude directives (#643) * combination of decorators with plugins has solved * fixing last pr: allow stackable plugin decorators even in exclude and include as well. * counting plugins has fixed * fix indentation * Update client.py * Update client.py Co-authored-by: Dan <14043624+delivrance@users.noreply.github.com> --- pyrogram/client.py | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/pyrogram/client.py b/pyrogram/client.py index 2820de1856..5f80182db6 100644 --- a/pyrogram/client.py +++ b/pyrogram/client.py @@ -724,7 +724,7 @@ def load_plugins(self): log.info('[{}] [LOAD] {}("{}") in group {} from "{}"'.format( self.session_name, type(handler).__name__, name, group, module_path)) - count += 1 + count += 1 except Exception: pass else: @@ -749,15 +749,14 @@ def load_plugins(self): for name in handlers: # noinspection PyBroadException try: - handler, group = getattr(module, name).handler - - if isinstance(handler, Handler) and isinstance(group, int): - self.add_handler(handler, group) + for handler, group in getattr(module, name).handlers: + if isinstance(handler, Handler) and isinstance(group, int): + self.add_handler(handler, group) - log.info('[{}] [LOAD] {}("{}") in group {} from "{}"'.format( - self.session_name, 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 + count += 1 except Exception: if warn_non_existent_functions: log.warning('[{}] [LOAD] Ignoring non-existent function "{}" from "{}"'.format( @@ -785,15 +784,14 @@ def load_plugins(self): for name in handlers: # noinspection PyBroadException try: - handler, group = getattr(module, name).handler - - if isinstance(handler, Handler) and isinstance(group, int): - self.remove_handler(handler, group) + for handler, group in getattr(module, name).handlers: + if isinstance(handler, Handler) and isinstance(group, int): + self.remove_handler(handler, group) - log.info('[{}] [UNLOAD] {}("{}") from group {} in "{}"'.format( - self.session_name, 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 + count -= 1 except Exception: if warn_non_existent_functions: log.warning('[{}] [UNLOAD] Ignoring non-existent function "{}" from "{}"'.format( From a85ca8cc1a976de8846372c6ed610418a9eaabd7 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 18 Mar 2021 18:17:12 +0100 Subject: [PATCH 0527/1185] Fix type hint --- pyrogram/types/messages_and_media/message.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index 91c5f9d53e..92125cd8d8 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -346,7 +346,7 @@ def __init__( command: List[str] = None, voice_chat_started: "types.VoiceChatStarted" = None, voice_chat_ended: "types.VoiceChatEnded" = None, - voice_chat_members_invited: "types.VoiceChatMemberInvited" = None, + voice_chat_members_invited: "types.VoiceChatMembersInvited" = None, reply_markup: Union[ "types.InlineKeyboardMarkup", "types.ReplyKeyboardMarkup", From 54ad043a703ecab2dfb3a03c99b598749e1bf9c8 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 18 Mar 2021 18:33:11 +0100 Subject: [PATCH 0528/1185] Add get_chat_invite_link method --- compiler/docs/compiler.py | 5 +- pyrogram/methods/invite_links/__init__.py | 4 +- .../invite_links/get_chat_invite_link.py | 54 +++++++++++++++++++ 3 files changed, 60 insertions(+), 3 deletions(-) create mode 100644 pyrogram/methods/invite_links/get_chat_invite_link.py diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index f7583df346..fdc01d5a91 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -245,10 +245,11 @@ def get_title_list(s: str) -> list: revoke_chat_invite_link delete_chat_invite_link delete_all_chat_invite_links - get_chat_invite_links - get_chat_invite_links_count + get_chat_invite_link get_chat_invite_link_members get_chat_invite_link_members_count + get_chat_invite_links + get_chat_invite_links_count get_chat_admins_with_invite_links """, contacts=""" diff --git a/pyrogram/methods/invite_links/__init__.py b/pyrogram/methods/invite_links/__init__.py index 17b1abbee9..9c32eb8aa6 100644 --- a/pyrogram/methods/invite_links/__init__.py +++ b/pyrogram/methods/invite_links/__init__.py @@ -23,6 +23,7 @@ from .edit_chat_invite_link import EditChatInviteLink from .export_chat_invite_link import ExportChatInviteLink from .get_chat_admins_with_invite_links import GetChatAdminsWithInviteLinks +from .get_chat_invite_link import GetChatInviteLink from .get_chat_invite_link_members import GetChatInviteLinkMembers from .get_chat_invite_link_members_count import GetChatInviteLinkMembersCount from .get_chat_invite_links import GetChatInviteLinks @@ -41,6 +42,7 @@ class InviteLinks( ExportChatInviteLink, DeleteAllChatInviteLinks, GetChatInviteLinksCount, - GetChatAdminsWithInviteLinks + GetChatAdminsWithInviteLinks, + GetChatInviteLink ): pass diff --git a/pyrogram/methods/invite_links/get_chat_invite_link.py b/pyrogram/methods/invite_links/get_chat_invite_link.py new file mode 100644 index 0000000000..c5e0339901 --- /dev/null +++ b/pyrogram/methods/invite_links/get_chat_invite_link.py @@ -0,0 +1,54 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan +# +# 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 Union + +from pyrogram import raw +from pyrogram import types +from pyrogram.scaffold import Scaffold + + +class GetChatInviteLink(Scaffold): + async def get_chat_invite_link( + self, + chat_id: Union[int, str], + invite_link: str, + ) -> "types.ChatInviteLink": + """Get detailed information about a chat invite link. + + Parameters: + chat_id (``int`` | ``str``): + Unique identifier for the target chat or username of the target channel/supergroup + (in the format @username). + + invite_link (str): + The invite link. + + Returns: + :obj:`~pyrogram.types.ChatInviteLink`: On success, the invite link is returned. + """ + r = await self.send( + raw.functions.messages.GetExportedChatInvite( + peer=await self.resolve_peer(chat_id), + link=invite_link + ) + ) + + users = {i.id: i for i in r.users} + + return types.ChatInviteLink._parse(self, r.invite, users) From b8cd08adb02b4b7b87f15bc9351db92b20775947 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 19 Mar 2021 17:42:05 +0100 Subject: [PATCH 0529/1185] Use Pyrogram's List when consuming generators This will pretty print them when using non-async methods --- pyrogram/sync.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/sync.py b/pyrogram/sync.py index 20bd12ac3d..51742945ec 100644 --- a/pyrogram/sync.py +++ b/pyrogram/sync.py @@ -31,7 +31,7 @@ def async_to_sync(obj, name): main_loop = asyncio.get_event_loop() async def consume_generator(coroutine): - return [i async for i in coroutine] + return types.List([i async for i in coroutine]) @functools.wraps(function) def async_to_sync_wrap(*args, **kwargs): From 2d785acdc75c422f45805184413f9050b6f4daff Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 19 Mar 2021 20:42:48 +0100 Subject: [PATCH 0530/1185] Add Message.edit and Message.reply to docs --- compiler/docs/compiler.py | 2 ++ pyrogram/types/messages_and_media/message.py | 4 ++++ 2 files changed, 6 insertions(+) diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index fdc01d5a91..fc8734ebff 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -461,10 +461,12 @@ def get_title_list(s: str) -> list: Message.copy Message.pin Message.unpin + Message.edit Message.edit_text Message.edit_caption Message.edit_media Message.edit_reply_markup + Message.reply Message.reply_text Message.reply_animation Message.reply_audio diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index 92125cd8d8..f1cce01dd7 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -776,6 +776,8 @@ async def reply_text( ) -> "Message": """Bound method *reply_text* of :obj:`~pyrogram.types.Message`. + An alias exists as *reply*. + Use as a shortcut for: .. code-block:: python @@ -2580,6 +2582,8 @@ async def edit_text( ) -> "Message": """Bound method *edit_text* of :obj:`~pyrogram.types.Message`. + An alias exists as *edit*. + Use as a shortcut for: .. code-block:: python From 3445507a9a7b3a2b354385dbd4d9a5ad08dfd0e4 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 20 Mar 2021 07:41:49 +0100 Subject: [PATCH 0531/1185] Update types.rst --- compiler/docs/template/types.rst | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/compiler/docs/template/types.rst b/compiler/docs/template/types.rst index e6c9a5d7cc..8b34000623 100644 --- a/compiler/docs/template/types.rst +++ b/compiler/docs/template/types.rst @@ -1,7 +1,9 @@ Available Types =============== -This page is about Pyrogram types. All types listed here are accessible through ``types`` package. +This page is about Pyrogram Types. All types listed here are available through the ``pyrogram.types`` package. +Unless required as argument to a client method, most of the types don't need to be manually instantiated because they +are only returned by other methods. You also don't need to import them, unless you want to type-hint your variables. .. code-block:: python :emphasize-lines: 1 @@ -10,8 +12,11 @@ This page is about Pyrogram types. All types listed here are accessible through .. note:: - **Optional** fields may not exist when irrelevant -- i.e.: they will contain the value of ``None`` and aren't shown - when, for example, using ``print()``. + Optional fields always exist inside the object, but they could be empty and contain the value of ``None``. + Empty fields aren't shown when, for example, using ``print(message)`` and this means that + ``hasattr(message, "photo")`` always returns ``True``. + + To tell whether a field is set or not, do a simple boolean check: ``if message.photo: ...``. .. contents:: Contents :backlinks: none From c971616808751c51d81c6e8540e10b49065a7507 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 20 Mar 2021 10:13:40 +0100 Subject: [PATCH 0532/1185] Add add_contact, improve delete_contacts and import_contacts --- compiler/docs/compiler.py | 5 +- pyrogram/methods/contacts/__init__.py | 8 +- pyrogram/methods/contacts/add_contact.py | 73 +++++++++++++++++++ pyrogram/methods/contacts/delete_contacts.py | 50 +++++++------ .../{add_contacts.py => import_contacts.py} | 6 +- 5 files changed, 112 insertions(+), 30 deletions(-) create mode 100644 pyrogram/methods/contacts/add_contact.py rename pyrogram/methods/contacts/{add_contacts.py => import_contacts.py} (93%) diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index fc8734ebff..3c32e851be 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -254,10 +254,11 @@ def get_title_list(s: str) -> list: """, contacts=""" Contacts - add_contacts + add_contact + delete_contacts + import_contacts get_contacts get_contacts_count - delete_contacts """, password=""" Password diff --git a/pyrogram/methods/contacts/__init__.py b/pyrogram/methods/contacts/__init__.py index 3af26f3239..b542b665ea 100644 --- a/pyrogram/methods/contacts/__init__.py +++ b/pyrogram/methods/contacts/__init__.py @@ -16,16 +16,18 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from .add_contacts import AddContacts +from .add_contact import AddContact from .delete_contacts import DeleteContacts from .get_contacts import GetContacts from .get_contacts_count import GetContactsCount +from .import_contacts import ImportContacts class Contacts( GetContacts, DeleteContacts, - AddContacts, - GetContactsCount + ImportContacts, + GetContactsCount, + AddContact ): pass diff --git a/pyrogram/methods/contacts/add_contact.py b/pyrogram/methods/contacts/add_contact.py new file mode 100644 index 0000000000..5d00253bcb --- /dev/null +++ b/pyrogram/methods/contacts/add_contact.py @@ -0,0 +1,73 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan +# +# 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 Union + +from pyrogram import raw +from pyrogram import types +from pyrogram.scaffold import Scaffold + + +class AddContact(Scaffold): + async def add_contact( + self, + user_id: Union[int, str], + first_name: str, + last_name: str = "", + phone_number: str = "", + share_phone_number: bool = False + ): + """Add an existing Telegram user as contact, even without a phone number. + + Parameters: + user_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target user. + + first_name (``str``, *optional*): + User's first name. + + last_name (``str``, *optional*): + User's last name. + + phone_number (``str``, *optional*): + User's phone number. + + share_phone_number (``bool``, *optional*): + Whether or not to share the phone number with the user. + Defaults to False. + + Returns: + :obj:`~pyrogram.types.User`: On success the user is returned. + + Example: + .. code-block:: python + + app.add_contact(12345678, "Foo") + app.add_contact("username", "Bar") + """ + r = await self.send( + raw.functions.contacts.AddContact( + id=await self.resolve_peer(user_id), + first_name=first_name, + last_name=last_name, + phone=phone_number, + add_phone_privacy_exception=share_phone_number + ) + ) + + return types.User._parse(self, r.users[0]) diff --git a/pyrogram/methods/contacts/delete_contacts.py b/pyrogram/methods/contacts/delete_contacts.py index 9612c12fa2..6b73fa7434 100644 --- a/pyrogram/methods/contacts/delete_contacts.py +++ b/pyrogram/methods/contacts/delete_contacts.py @@ -16,46 +16,52 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import List +from typing import List, Union -from pyrogram import raw -from pyrogram.errors import PeerIdInvalid +from pyrogram import raw, types from pyrogram.scaffold import Scaffold class DeleteContacts(Scaffold): async def delete_contacts( self, - ids: List[int] - ): + user_ids: Union[int, str, List[Union[int, str]]] + ) -> Union["types.User", List["types.User"], None]: """Delete contacts from your Telegram address book. Parameters: - ids (List of ``int``): - A list of unique identifiers for the target users. - Can be an ID (int), a username (string) or phone number (string). + user_ids (``int`` | ``str`` | List of ``int`` or ``str``): + A single user id/username o a list of user identifiers (id or username). Returns: - ``bool``: True on success. + :obj:`~pyrogram.types.User` | List of :obj:`~pyrogram.types.User` | ``None``: In case *user_ids* was an + integer or a string, a single User object is returned. In case *user_ids* was a list, a list of User objects + is returned. In case nothing changed after calling the method (for example, when deleting a non-existent + contact), None is returned. Example: .. code-block:: python + app.delete_contacts(user_id) app.delete_contacts([user_id1, user_id2, user_id3]) """ - contacts = [] - - for i in ids: - try: - input_user = await self.resolve_peer(i) - except PeerIdInvalid: - continue - else: - if isinstance(input_user, raw.types.InputPeerUser): - contacts.append(input_user) - - return await self.send( + is_user_ids_list = isinstance(user_ids, list) + + if not is_user_ids_list: + user_ids = [user_ids] + + r = await self.send( raw.functions.contacts.DeleteContacts( - id=contacts + id=[await self.resolve_peer(i) for i in user_ids] ) ) + + if not r.updates: + return None + + users = types.List([types.User._parse(self, i) for i in r.users]) + + if is_user_ids_list: + return users + else: + return users[0] diff --git a/pyrogram/methods/contacts/add_contacts.py b/pyrogram/methods/contacts/import_contacts.py similarity index 93% rename from pyrogram/methods/contacts/add_contacts.py rename to pyrogram/methods/contacts/import_contacts.py index 3e41f615a9..92410fc135 100644 --- a/pyrogram/methods/contacts/add_contacts.py +++ b/pyrogram/methods/contacts/import_contacts.py @@ -23,12 +23,12 @@ from pyrogram.scaffold import Scaffold -class AddContacts(Scaffold): - async def add_contacts( +class ImportContacts(Scaffold): + async def import_contacts( self, contacts: List["types.InputPhoneContact"] ): - """Add contacts to your Telegram address book. + """Import contacts to your Telegram address book. Parameters: contacts (List of :obj:`~pyrogram.types.InputPhoneContact`): From b39a6cd2d4fa6d7f86d34b273714eac57439c348 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 20 Mar 2021 10:37:01 +0100 Subject: [PATCH 0533/1185] Add FAQ about sqlite3.InterfaceError --- docs/source/faq.rst | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/docs/source/faq.rst b/docs/source/faq.rst index 82454d0bb2..4bfc7135c7 100644 --- a/docs/source/faq.rst +++ b/docs/source/faq.rst @@ -307,16 +307,19 @@ sqlite3.OperationalError: unable to open database file Stackoverflow to the rescue: https://stackoverflow.com/questions/4636970 -FileNotFoundError when using PyInstaller ----------------------------------------- +sqlite3.InterfaceError: Error binding parameter 0 +------------------------------------------------- + +This error occurs when you pass a chat id value of the wrong type when trying to call a method. Most likely, you +accidentally passed the whole user or chat object instead of the id or username. -Pyrogram uses two files that are not Python files, which are not included automatically in the PyInstaller bundle: +.. code-block:: python -- ``pyrogram/mime.types`` -- ``pyrogram/storage/schema.sql`` + # Wrong. You passed the whole Chat instance + app.send_message(chat, "text") -To fix the issue, you have to locate your local Pyrogram installation and pass those files to PyInstaller. More info in -their docs https://pyinstaller.readthedocs.io/en/stable/spec-files.html#adding-files-to-the-bundle. + # Correct + app.send_message(chat.id, "text") My verification code expires immediately! ----------------------------------------- From 66b1229664df43f839bb1e5b661629ea4b12f80c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 20 Mar 2021 10:40:38 +0100 Subject: [PATCH 0534/1185] Fix broken hyperlinks --- pyrogram/filters.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/filters.py b/pyrogram/filters.py index 9105642709..db08f672cb 100644 --- a/pyrogram/filters.py +++ b/pyrogram/filters.py @@ -141,7 +141,7 @@ def create(func: Callable, name: str = None, **kwargs) -> Filter: **kwargs (``any``, *optional*): Any keyword argument you would like to pass. Useful when creating parameterized custom filters, such as - :meth:`~Filters.command` or :meth:`~Filters.regex`. + :meth:`~pyrogram.filters.command` or :meth:`~pyrogram.filters.regex`. """ return type( name or func.__name__ or CUSTOM_FILTER_NAME, From f13651af1dc33e02fe8ad0061e996e76a46b06e8 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 20 Mar 2021 11:05:23 +0100 Subject: [PATCH 0535/1185] Reword the license explanation --- docs/source/license.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/source/license.rst b/docs/source/license.rst index 512f2c5b84..5f1d25ee0d 100644 --- a/docs/source/license.rst +++ b/docs/source/license.rst @@ -8,8 +8,9 @@ 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 license, 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. +In other words: you can use and integrate Pyrogram into your code (either open source, under the same or a different +license, or even proprietary) for any purpose whatsoever without being required to release the source code of your +applications. Derivative works, including modifications to the library itself can only be redistributed under the same +LGPLv3+ license. .. _GNU Lesser General Public License v3 or later (LGPLv3+): https://github.com/pyrogram/pyrogram/blob/develop/COPYING.lesser From 73edcb5e69cc8e28879b60ffe5a26a65645cfe69 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 20 Mar 2021 11:06:05 +0100 Subject: [PATCH 0536/1185] Update voice-calls.rst --- docs/source/topics/voice-calls.rst | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/docs/source/topics/voice-calls.rst b/docs/source/topics/voice-calls.rst index c1a8cc53c3..19950bf40e 100644 --- a/docs/source/topics/voice-calls.rst +++ b/docs/source/topics/voice-calls.rst @@ -1,10 +1,19 @@ Voice Calls =========== -A working proof-of-concept of Telegram voice calls using Pyrogram can be found here: -https://github.com/bakatrouble/pylibtgvoip. Thanks to `@bakatrouble `_. +Both private voice calls and group voice calls are currently supported by third-party libraries that integrate with +Pyrogram. -.. note:: +Libraries +--------- - This page will be updated with more information once voice calls become eventually more usable and more integrated - in Pyrogram itself. +There are currently two main libraries (with very similar names) you can use: + +1. https://github.com/pytgcalls/pytgcalls +2. https://github.com/MarshalX/tgcalls + +Older implementations +--------------------- + +An older implementation of Telegram voice calls can be found at https://github.com/bakatrouble/pylibtgvoip (currently +outdated due to the deprecation of the Telegram VoIP library used underneath). \ No newline at end of file From e01ac7a3d3feba5c2d3b03c37a9a1fc0f915c5b3 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 20 Mar 2021 13:16:55 +0100 Subject: [PATCH 0537/1185] Revert changes to the invite link regex pattern New invite link methods don't need this and this change was breaking old code (get_chat, join_chat) --- pyrogram/scaffold.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyrogram/scaffold.py b/pyrogram/scaffold.py index 434c5bb45d..a003359981 100644 --- a/pyrogram/scaffold.py +++ b/pyrogram/scaffold.py @@ -41,8 +41,7 @@ class Scaffold: PARENT_DIR = Path(sys.argv[0]).parent - # https://regex101.com/r/pme5ZE/2 Matches both the entire link and the hash itself - INVITE_LINK_RE = re.compile(r"^(?:(?:https?://)?(?:www\.)?(?:t(?:elegram)?\.(?:org|me|dog)/joinchat/))?(?P[\w-]+)$") + INVITE_LINK_RE = re.compile(r"^(?:https?://)?(?:www\.)?(?:t(?:elegram)?\.(?:org|me|dog)/joinchat/)([\w-]+)$") WORKERS = min(32, os.cpu_count() + 4) WORKDIR = PARENT_DIR CONFIG_FILE = PARENT_DIR / "config.ini" From b1dc0315a137242c66aca62300c935392028275e Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 21 Mar 2021 16:32:10 +0100 Subject: [PATCH 0538/1185] Update API schema to Layer 126 --- compiler/api/source/main_api.tl | 14 +++++++------- pyrogram/methods/chats/get_chat_member.py | 7 ++++--- pyrogram/methods/chats/get_chat_members.py | 5 +++-- pyrogram/methods/chats/kick_chat_member.py | 2 +- pyrogram/methods/chats/unban_chat_member.py | 2 +- pyrogram/types/user_and_chats/chat_event.py | 10 +++++----- pyrogram/types/user_and_chats/chat_member.py | 10 ++++++++-- .../types/user_and_chats/chat_member_updated.py | 4 ++-- 8 files changed, 31 insertions(+), 23 deletions(-) diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl index 9d1ef45145..c7bea22360 100644 --- a/compiler/api/source/main_api.tl +++ b/compiler/api/source/main_api.tl @@ -598,8 +598,8 @@ channelParticipant#15ebac1d user_id:int date:int = ChannelParticipant; channelParticipantSelf#a3289a6d user_id:int inviter_id:int date:int = ChannelParticipant; channelParticipantCreator#447dca4b flags:# user_id:int admin_rights:ChatAdminRights rank:flags.0?string = ChannelParticipant; channelParticipantAdmin#ccbebbaf flags:# can_edit:flags.0?true self:flags.1?true user_id:int inviter_id:flags.1?int promoted_by:int date:int admin_rights:ChatAdminRights rank:flags.2?string = ChannelParticipant; -channelParticipantBanned#1c0facaf flags:# left:flags.0?true user_id:int kicked_by:int date:int banned_rights:ChatBannedRights = ChannelParticipant; -channelParticipantLeft#c3c6796b user_id:int = ChannelParticipant; +channelParticipantBanned#50a1dfd6 flags:# left:flags.0?true peer:Peer kicked_by:int date:int banned_rights:ChatBannedRights = ChannelParticipant; +channelParticipantLeft#1b03f006 peer:Peer = ChannelParticipant; channelParticipantsRecent#de3f3c79 = ChannelParticipantsFilter; channelParticipantsAdmins#b4608969 = ChannelParticipantsFilter; @@ -610,10 +610,10 @@ channelParticipantsSearch#656ac4b q:string = ChannelParticipantsFilter; channelParticipantsContacts#bb6ae88d q:string = ChannelParticipantsFilter; channelParticipantsMentions#e04b5ceb flags:# q:flags.0?string top_msg_id:flags.1?int = ChannelParticipantsFilter; -channels.channelParticipants#f56ee2a8 count:int participants:Vector users:Vector = channels.ChannelParticipants; +channels.channelParticipants#9ab0feaf count:int participants:Vector chats:Vector users:Vector = channels.ChannelParticipants; channels.channelParticipantsNotModified#f0173fe9 = channels.ChannelParticipants; -channels.channelParticipant#d0d9b163 participant:ChannelParticipant users:Vector = channels.ChannelParticipant; +channels.channelParticipant#dfb80317 participant:ChannelParticipant chats:Vector users:Vector = channels.ChannelParticipant; help.termsOfService#780a0310 flags:# popup:flags.0?true id:DataJSON text:string entities:Vector min_age_confirm:flags.1?int = help.TermsOfService; @@ -1535,7 +1535,7 @@ channels.deleteUserHistory#d10dd71b channel:InputChannel user_id:InputUser = mes channels.reportSpam#fe087810 channel:InputChannel user_id:InputUser id:Vector = Bool; channels.getMessages#ad8c9a23 channel:InputChannel id:Vector = messages.Messages; channels.getParticipants#123e05e9 channel:InputChannel filter:ChannelParticipantsFilter offset:int limit:int hash:int = channels.ChannelParticipants; -channels.getParticipant#546dd7a6 channel:InputChannel user_id:InputUser = channels.ChannelParticipant; +channels.getParticipant#a0ab6cc6 channel:InputChannel participant:InputPeer = channels.ChannelParticipant; channels.getChannels#a7f6bbb id:Vector = messages.Chats; channels.getFullChannel#8736a09 channel:InputChannel = messages.ChatFull; channels.createChannel#3d5fb10f flags:# broadcast:flags.0?true megagroup:flags.1?true for_import:flags.3?true title:string about:string geo_point:flags.2?InputGeoPoint address:flags.2?string = Updates; @@ -1551,7 +1551,7 @@ channels.deleteChannel#c0111fe3 channel:InputChannel = Updates; channels.exportMessageLink#e63fadeb flags:# grouped:flags.0?true thread:flags.1?true channel:InputChannel id:int = ExportedMessageLink; channels.toggleSignatures#1f69b606 channel:InputChannel enabled:Bool = Updates; channels.getAdminedPublicChannels#f8b036af flags:# by_location:flags.0?true check_limit:flags.1?true = messages.Chats; -channels.editBanned#72796912 channel:InputChannel user_id:InputUser banned_rights:ChatBannedRights = Updates; +channels.editBanned#96e6cd81 channel:InputChannel participant:InputPeer banned_rights:ChatBannedRights = Updates; channels.getAdminLog#33ddf480 flags:# channel:InputChannel q:string events_filter:flags.0?ChannelAdminLogEventsFilter admins:flags.1?Vector max_id:long min_id:long limit:int = channels.AdminLogResults; channels.setStickers#ea8ca4f9 channel:InputChannel stickerset:InputStickerSet = Bool; channels.readMessageContents#eab5dc38 channel:InputChannel id:Vector = Bool; @@ -1623,4 +1623,4 @@ stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats; -// LAYER 125 \ No newline at end of file +// LAYER 126 \ No newline at end of file diff --git a/pyrogram/methods/chats/get_chat_member.py b/pyrogram/methods/chats/get_chat_member.py index 7f649a2166..74aff5daa6 100644 --- a/pyrogram/methods/chats/get_chat_member.py +++ b/pyrogram/methods/chats/get_chat_member.py @@ -64,7 +64,7 @@ async def get_chat_member( users = {i.id: i for i in r.users} for member in members: - member = types.ChatMember._parse(self, member, users) + member = types.ChatMember._parse(self, member, users, {}) if isinstance(user, raw.types.InputPeerSelf): if member.user.is_self: @@ -78,12 +78,13 @@ async def get_chat_member( r = await self.send( raw.functions.channels.GetParticipant( channel=chat, - user_id=user + participant=user ) ) users = {i.id: i for i in r.users} + chats = {i.id: i for i in r.chats} - return types.ChatMember._parse(self, r.participant, users) + return types.ChatMember._parse(self, r.participant, users, chats) else: raise ValueError(f'The chat_id "{chat_id}" belongs to a user') diff --git a/pyrogram/methods/chats/get_chat_members.py b/pyrogram/methods/chats/get_chat_members.py index a27478fdc3..1f891b4657 100644 --- a/pyrogram/methods/chats/get_chat_members.py +++ b/pyrogram/methods/chats/get_chat_members.py @@ -115,7 +115,7 @@ async def get_chat_members( members = r.full_chat.participants.participants users = {i.id: i for i in r.users} - return types.List(types.ChatMember._parse(self, member, users) for member in members) + return types.List(types.ChatMember._parse(self, member, users, {}) for member in members) elif isinstance(peer, raw.types.InputPeerChannel): filter = filter.lower() @@ -147,7 +147,8 @@ async def get_chat_members( members = r.participants users = {i.id: i for i in r.users} + chats = {i.id: i for i in r.chats} - return types.List(types.ChatMember._parse(self, member, users) for member in members) + return types.List(types.ChatMember._parse(self, member, users, chats) for member in members) else: raise ValueError(f'The chat_id "{chat_id}" belongs to a user') diff --git a/pyrogram/methods/chats/kick_chat_member.py b/pyrogram/methods/chats/kick_chat_member.py index 50c9821973..495eac9402 100644 --- a/pyrogram/methods/chats/kick_chat_member.py +++ b/pyrogram/methods/chats/kick_chat_member.py @@ -75,7 +75,7 @@ async def kick_chat_member( r = await self.send( raw.functions.channels.EditBanned( channel=chat_peer, - user_id=user_peer, + participant=user_peer, banned_rights=raw.types.ChatBannedRights( until_date=until_date, view_messages=True, diff --git a/pyrogram/methods/chats/unban_chat_member.py b/pyrogram/methods/chats/unban_chat_member.py index ce00457746..44c19286a6 100644 --- a/pyrogram/methods/chats/unban_chat_member.py +++ b/pyrogram/methods/chats/unban_chat_member.py @@ -52,7 +52,7 @@ async def unban_chat_member( await self.send( raw.functions.channels.EditBanned( channel=await self.resolve_peer(chat_id), - user_id=await self.resolve_peer(user_id), + participant=await self.resolve_peer(user_id), banned_rights=raw.types.ChatBannedRights( until_date=0 ) diff --git a/pyrogram/types/user_and_chats/chat_event.py b/pyrogram/types/user_and_chats/chat_event.py index 7b97a31c98..3d0bbe19d1 100644 --- a/pyrogram/types/user_and_chats/chat_event.py +++ b/pyrogram/types/user_and_chats/chat_event.py @@ -475,17 +475,17 @@ async def _parse( action = ChatEventAction.MESSAGE_EDITED.value elif isinstance(action, raw.types.ChannelAdminLogEventActionParticipantInvite): - invited_member = types.ChatMember._parse(client, action.participant, users) + invited_member = types.ChatMember._parse(client, action.participant, users, chats) action = ChatEventAction.MEMBER_INVITED.value elif isinstance(action, raw.types.ChannelAdminLogEventActionParticipantToggleAdmin): - old_admin_rights = types.ChatMember._parse(client, action.prev_participant, users) - new_admin_rights = types.ChatMember._parse(client, action.new_participant, users) + old_admin_rights = types.ChatMember._parse(client, action.prev_participant, users, chats) + new_admin_rights = types.ChatMember._parse(client, action.new_participant, users, chats) action = ChatEventAction.ADMIN_RIGHTS_CHANGED.value elif isinstance(action, raw.types.ChannelAdminLogEventActionParticipantToggleBan): - old_member_permissions = types.ChatMember._parse(client, action.prev_participant, users) - new_member_permissions = types.ChatMember._parse(client, action.new_participant, users) + old_member_permissions = types.ChatMember._parse(client, action.prev_participant, users, chats) + new_member_permissions = types.ChatMember._parse(client, action.new_participant, users, chats) action = ChatEventAction.MEMBER_PERMISSIONS_CHANGED.value elif isinstance(action, raw.types.ChannelAdminLogEventActionStopPoll): diff --git a/pyrogram/types/user_and_chats/chat_member.py b/pyrogram/types/user_and_chats/chat_member.py index 7853042a7a..d33bcfc5d0 100644 --- a/pyrogram/types/user_and_chats/chat_member.py +++ b/pyrogram/types/user_and_chats/chat_member.py @@ -212,8 +212,14 @@ def __init__( self.can_send_polls = can_send_polls @staticmethod - def _parse(client, member, users) -> "ChatMember": - user = types.User._parse(client, users[member.user_id]) + def _parse(client, member, users, chats) -> "ChatMember": + if not isinstance(member, (raw.types.ChannelParticipantBanned, raw.types.ChannelParticipantLeft)): + user = types.User._parse(client, users[member.user_id]) + else: + if isinstance(member.peer, raw.types.PeerUser): + user = types.User._parse(client, users[member.peer.user_id]) + else: + user = None invited_by = ( types.User._parse(client, users[member.inviter_id]) diff --git a/pyrogram/types/user_and_chats/chat_member_updated.py b/pyrogram/types/user_and_chats/chat_member_updated.py index 31cec664b7..a8d7abcafb 100644 --- a/pyrogram/types/user_and_chats/chat_member_updated.py +++ b/pyrogram/types/user_and_chats/chat_member_updated.py @@ -82,10 +82,10 @@ def _parse( invite_link = None if update.prev_participant: - old_chat_member = types.ChatMember._parse(client, update.prev_participant, users) + old_chat_member = types.ChatMember._parse(client, update.prev_participant, users, chats) if update.new_participant: - new_chat_member = types.ChatMember._parse(client, update.new_participant, users) + new_chat_member = types.ChatMember._parse(client, update.new_participant, users, chats) if update.invite: invite_link = types.ChatInviteLink._parse(client, update.invite, users) From a47e079fecd07a45c792e7c0b716bae7c9b30fc7 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 21 Mar 2021 16:32:30 +0100 Subject: [PATCH 0539/1185] Update Pyrogram to v1.2.0 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 907a49e1a9..7b503fcdc0 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "1.1.13" +__version__ = "1.2.0" __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)" __copyright__ = "Copyright (C) 2017-2021 Dan " From 1d55eaa1ba6e39eca7883a829aba802c0a8f362b Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 21 Mar 2021 18:57:06 +0100 Subject: [PATCH 0540/1185] Fix inline keyboard buttons with empty values --- .../bots_and_keyboards/inline_keyboard_button.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pyrogram/types/bots_and_keyboards/inline_keyboard_button.py b/pyrogram/types/bots_and_keyboards/inline_keyboard_button.py index 444eb5da68..84d70642bd 100644 --- a/pyrogram/types/bots_and_keyboards/inline_keyboard_button.py +++ b/pyrogram/types/bots_and_keyboards/inline_keyboard_button.py @@ -129,7 +129,7 @@ def read(b: "raw.base.KeyboardButton"): ) async def write(self, client: "pyrogram.Client"): - if self.callback_data: + if self.callback_data is not None: # 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 @@ -138,32 +138,32 @@ async def write(self, client: "pyrogram.Client"): data=data ) - if self.url: + if self.url is not None: return raw.types.KeyboardButtonUrl( text=self.text, url=self.url ) - if self.login_url: + if self.login_url is not None: return self.login_url.write( text=self.text, bot=await client.resolve_peer(self.login_url.bot_username) ) - if self.switch_inline_query: + if self.switch_inline_query is not None: return raw.types.KeyboardButtonSwitchInline( text=self.text, query=self.switch_inline_query ) - if self.switch_inline_query_current_chat: + if self.switch_inline_query_current_chat is not None: return raw.types.KeyboardButtonSwitchInline( text=self.text, query=self.switch_inline_query_current_chat, same_peer=True ) - if self.callback_game: + if self.callback_game is not None: return raw.types.KeyboardButtonGame( text=self.text ) From 19fab3cc784ea5cd271d7c782616cf9de54b87cc Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 21 Mar 2021 19:00:40 +0100 Subject: [PATCH 0541/1185] Update Pyrogram to v1.2.1 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 7b503fcdc0..acac037a98 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "1.2.0" +__version__ = "1.2.1" __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)" __copyright__ = "Copyright (C) 2017-2021 Dan " From 70efffa896e2ff6e30804e3974bc4c87c5e2e715 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 21 Mar 2021 22:08:04 +0100 Subject: [PATCH 0542/1185] Remove sphinx tabs extension --- docs/requirements.txt | 1 - docs/source/topics/tgcrypto.rst | 21 ++++----------------- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 995cfe8cc4..2cfd56798c 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,7 +1,6 @@ sphinx sphinx_rtd_theme sphinx_copybutton -sphinx_tabs pypandoc requests sphinx-autobuild \ No newline at end of file diff --git a/docs/source/topics/tgcrypto.rst b/docs/source/topics/tgcrypto.rst index 8feab86a64..e0d9beb67f 100644 --- a/docs/source/topics/tgcrypto.rst +++ b/docs/source/topics/tgcrypto.rst @@ -21,23 +21,10 @@ The reason about being an optional package is that TgCrypto requires some extra The errors you receive when trying to install TgCrypto are system dependent, but also descriptive enough to understand what you should do next: -.. tabs:: - - .. tab:: Windows - - Install `Visual C++ 2015 Build Tools `_. - - .. tab:: macOS - - A pop-up will automatically ask you to install the command line developer tools. - - .. tab:: Linux - - Install a proper C compiler (``gcc``, ``clang``) and the Python header files (``python3-dev``). - - .. tab:: Termux - - Install ``clang`` package. +- **Windows**: Install `Visual C++ 2015 Build Tools `_. +- **macOS**: A pop-up will automatically ask you to install the command line developer tools. +- **Linux**: Install a proper C compiler (``gcc``, ``clang``) and the Python header files (``python3-dev``). +- **Termux**: Install ``clang`` package. .. _TgCrypto: https://github.com/pyrogram/tgcrypto From eed331b1b15858af9a0f37f5a8113257c434ffba Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 21 Mar 2021 22:10:33 +0100 Subject: [PATCH 0543/1185] Do not copy empty messages --- pyrogram/types/messages_and_media/message.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index f1cce01dd7..6f31ec7483 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -2902,6 +2902,9 @@ async def copy( elif self.game and not await self._client.storage.is_bot(): log.warning(f"Users cannot send messages with Game media type. " f"chat_id: {self.chat.id}, message_id: {self.message_id}") + elif self.empty: + log.warning(f"Empty messages cannot be copied. " + f"chat_id: {self.chat.id}, message_id: {self.message_id}") elif self.text: return await self._client.send_message( chat_id, From 808346f15be279a3d06dbfd0a7736cf98025a0a9 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 21 Mar 2021 22:13:41 +0100 Subject: [PATCH 0544/1185] Fix wrong example due to a method rename --- pyrogram/methods/contacts/import_contacts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/methods/contacts/import_contacts.py b/pyrogram/methods/contacts/import_contacts.py index 92410fc135..4039cbe500 100644 --- a/pyrogram/methods/contacts/import_contacts.py +++ b/pyrogram/methods/contacts/import_contacts.py @@ -42,7 +42,7 @@ async def import_contacts( from pyrogram.types import InputPhoneContact - app.add_contacts([ + app.import_contacts([ InputPhoneContact("39123456789", "Foo"), InputPhoneContact("38987654321", "Bar"), InputPhoneContact("01234567891", "Baz")]) From ec1cd15094ab7a08c492deab6e95dabc279e2ec7 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 21 Mar 2021 22:14:32 +0100 Subject: [PATCH 0545/1185] Update Pyrogram to v1.2.2 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index acac037a98..1bfe1c2181 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "1.2.1" +__version__ = "1.2.2" __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)" __copyright__ = "Copyright (C) 2017-2021 Dan " From da697aec13eb386ca78ef528fdad8fd41d9bd80f Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 21 Mar 2021 22:39:59 +0100 Subject: [PATCH 0546/1185] Fix messed up method names --- compiler/docs/compiler.py | 8 ++++---- pyrogram/methods/invite_links/__init__.py | 14 +++++++------- ..._links.py => delete_chat_admin_invite_links.py} | 4 ++-- ...ite_links.py => get_chat_admin_invite_links.py} | 4 ++-- ...unt.py => get_chat_admin_invite_links_count.py} | 4 ++-- 5 files changed, 17 insertions(+), 17 deletions(-) rename pyrogram/methods/invite_links/{delete_all_chat_invite_links.py => delete_chat_admin_invite_links.py} (95%) rename pyrogram/methods/invite_links/{get_chat_invite_links.py => get_chat_admin_invite_links.py} (97%) rename pyrogram/methods/invite_links/{get_chat_invite_links_count.py => get_chat_admin_invite_links_count.py} (95%) diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index 3c32e851be..8ee22fff5f 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -239,18 +239,18 @@ def get_title_list(s: str) -> list: """, invite_links=""" Invite Links + get_chat_invite_link export_chat_invite_link create_chat_invite_link edit_chat_invite_link revoke_chat_invite_link delete_chat_invite_link - delete_all_chat_invite_links - get_chat_invite_link get_chat_invite_link_members get_chat_invite_link_members_count - get_chat_invite_links - get_chat_invite_links_count + get_chat_admin_invite_links + get_chat_admin_invite_links_count get_chat_admins_with_invite_links + delete_chat_admin_invite_links """, contacts=""" Contacts diff --git a/pyrogram/methods/invite_links/__init__.py b/pyrogram/methods/invite_links/__init__.py index 9c32eb8aa6..198ce1bd8f 100644 --- a/pyrogram/methods/invite_links/__init__.py +++ b/pyrogram/methods/invite_links/__init__.py @@ -18,7 +18,7 @@ from .create_chat_invite_link import CreateChatInviteLink -from .delete_all_chat_invite_links import DeleteAllChatInviteLinks +from .delete_chat_admin_invite_links import DeleteChatAdminInviteLinks from .delete_chat_invite_link import DeleteChatInviteLink from .edit_chat_invite_link import EditChatInviteLink from .export_chat_invite_link import ExportChatInviteLink @@ -26,8 +26,8 @@ from .get_chat_invite_link import GetChatInviteLink from .get_chat_invite_link_members import GetChatInviteLinkMembers from .get_chat_invite_link_members_count import GetChatInviteLinkMembersCount -from .get_chat_invite_links import GetChatInviteLinks -from .get_chat_invite_links_count import GetChatInviteLinksCount +from .get_chat_admin_invite_links import GetChatAdminInviteLinks +from .get_chat_admin_invite_links_count import GetChatAdminInviteLinksCount from .revoke_chat_invite_link import RevokeChatInviteLink @@ -38,11 +38,11 @@ class InviteLinks( CreateChatInviteLink, GetChatInviteLinkMembers, GetChatInviteLinkMembersCount, - GetChatInviteLinks, + GetChatAdminInviteLinks, ExportChatInviteLink, - DeleteAllChatInviteLinks, - GetChatInviteLinksCount, + DeleteChatAdminInviteLinks, + GetChatAdminInviteLinksCount, GetChatAdminsWithInviteLinks, GetChatInviteLink ): - pass + pass \ No newline at end of file diff --git a/pyrogram/methods/invite_links/delete_all_chat_invite_links.py b/pyrogram/methods/invite_links/delete_chat_admin_invite_links.py similarity index 95% rename from pyrogram/methods/invite_links/delete_all_chat_invite_links.py rename to pyrogram/methods/invite_links/delete_chat_admin_invite_links.py index ea1738b9dd..94a3740890 100644 --- a/pyrogram/methods/invite_links/delete_all_chat_invite_links.py +++ b/pyrogram/methods/invite_links/delete_chat_admin_invite_links.py @@ -22,8 +22,8 @@ from pyrogram.scaffold import Scaffold -class DeleteAllChatInviteLinks(Scaffold): - async def delete_all_chat_invite_links( +class DeleteChatAdminInviteLinks(Scaffold): + async def delete_chat_admin_invite_links( self, chat_id: Union[int, str], admin_id: Union[int, str], diff --git a/pyrogram/methods/invite_links/get_chat_invite_links.py b/pyrogram/methods/invite_links/get_chat_admin_invite_links.py similarity index 97% rename from pyrogram/methods/invite_links/get_chat_invite_links.py rename to pyrogram/methods/invite_links/get_chat_admin_invite_links.py index 17d28a0ced..ade3a84be2 100644 --- a/pyrogram/methods/invite_links/get_chat_invite_links.py +++ b/pyrogram/methods/invite_links/get_chat_admin_invite_links.py @@ -23,8 +23,8 @@ from pyrogram.scaffold import Scaffold -class GetChatInviteLinks(Scaffold): - async def get_chat_invite_links( +class GetChatAdminInviteLinks(Scaffold): + async def get_chat_admin_invite_links( self, chat_id: Union[int, str], admin_id: Union[int, str], diff --git a/pyrogram/methods/invite_links/get_chat_invite_links_count.py b/pyrogram/methods/invite_links/get_chat_admin_invite_links_count.py similarity index 95% rename from pyrogram/methods/invite_links/get_chat_invite_links_count.py rename to pyrogram/methods/invite_links/get_chat_admin_invite_links_count.py index 52d3528be1..5c99405a53 100644 --- a/pyrogram/methods/invite_links/get_chat_invite_links_count.py +++ b/pyrogram/methods/invite_links/get_chat_admin_invite_links_count.py @@ -22,8 +22,8 @@ from pyrogram.scaffold import Scaffold -class GetChatInviteLinksCount(Scaffold): - async def get_chat_invite_links_count( +class GetChatAdminInviteLinksCount(Scaffold): + async def get_chat_admin_invite_links_count( self, chat_id: Union[int, str], admin_id: Union[int, str], From 97bb996a53c996a36c666224623149485f33383e Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 21 Mar 2021 22:42:38 +0100 Subject: [PATCH 0547/1185] Update Pyrogram to v1.2.3 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 1bfe1c2181..bffdf2b5e3 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "1.2.2" +__version__ = "1.2.3" __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)" __copyright__ = "Copyright (C) 2017-2021 Dan " From a86656aefcc93cc3d2f5c98227d5da28fcddb136 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 22 Mar 2021 16:26:39 +0100 Subject: [PATCH 0548/1185] Update Pyrogram to 1.2.4 Add missing parameters to chat.promote_member --- pyrogram/__init__.py | 2 +- pyrogram/types/user_and_chats/chat.py | 16 ++++++++++++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index bffdf2b5e3..de604ba3e8 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "1.2.3" +__version__ = "1.2.4" __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)" __copyright__ = "Copyright (C) 2017-2021 Dan " diff --git a/pyrogram/types/user_and_chats/chat.py b/pyrogram/types/user_and_chats/chat.py index 200a31ebe4..8930bf3afb 100644 --- a/pyrogram/types/user_and_chats/chat.py +++ b/pyrogram/types/user_and_chats/chat.py @@ -609,6 +609,7 @@ async def restrict_member( async def promote_member( self, user_id: Union[int, str], + can_manage_chat: bool = True, can_change_info: bool = True, can_post_messages: bool = False, can_edit_messages: bool = False, @@ -616,7 +617,8 @@ async def promote_member( can_restrict_members: bool = True, can_invite_users: bool = True, can_pin_messages: bool = False, - can_promote_members: bool = False + can_promote_members: bool = False, + can_manage_voice_chats: bool = False ) -> bool: """Bound method *promote_member* of :obj:`~pyrogram.types.Chat`. @@ -640,6 +642,11 @@ async def promote_member( Unique identifier (int) or username (str) of the target user. For a contact that exists in your Telegram address book you can use his phone number (str). + can_manage_chat (``bool``, *optional*): + Pass True, if the administrator can access the chat event log, chat statistics, message statistics + in channels, see channel members, see anonymous administrators in supergroups and ignore slow mode. + Implied by any other administrator privilege. + can_change_info (``bool``, *optional*): Pass True, if the administrator can change chat title, photo and other settings. @@ -666,6 +673,9 @@ async def promote_member( demote administrators that he has promoted, directly or indirectly (promoted by administrators that were appointed by him). + can_manage_voice_chats (``bool``, *optional*): + Pass True, if the administration can manage voice chats (also called group calls). + Returns: ``bool``: True on success. @@ -676,6 +686,7 @@ async def promote_member( return await self._client.promote_chat_member( chat_id=self.id, user_id=user_id, + can_manage_chat=can_manage_chat, can_change_info=can_change_info, can_post_messages=can_post_messages, can_edit_messages=can_edit_messages, @@ -683,7 +694,8 @@ async def promote_member( can_restrict_members=can_restrict_members, can_invite_users=can_invite_users, can_pin_messages=can_pin_messages, - can_promote_members=can_promote_members + can_promote_members=can_promote_members, + can_manage_voice_chats=can_manage_voice_chats ) async def join(self): From 77ab7bffe794b3048f376ce4ef4d3cbe385f9150 Mon Sep 17 00:00:00 2001 From: Leorio Paradinight <62891774+code-rgb@users.noreply.github.com> Date: Thu, 25 Mar 2021 20:01:31 +0530 Subject: [PATCH 0549/1185] Fix Layer 126 changes [user_id -> participant] (#648) --- pyrogram/methods/chats/restrict_chat_member.py | 2 +- pyrogram/methods/chats/set_administrator_title.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/methods/chats/restrict_chat_member.py b/pyrogram/methods/chats/restrict_chat_member.py index a7071bab6d..f8a05a0f15 100644 --- a/pyrogram/methods/chats/restrict_chat_member.py +++ b/pyrogram/methods/chats/restrict_chat_member.py @@ -74,7 +74,7 @@ async def restrict_chat_member( r = await self.send( raw.functions.channels.EditBanned( channel=await self.resolve_peer(chat_id), - user_id=await self.resolve_peer(user_id), + participant=await self.resolve_peer(user_id), banned_rights=raw.types.ChatBannedRights( until_date=until_date, send_messages=True if not permissions.can_send_messages else None, diff --git a/pyrogram/methods/chats/set_administrator_title.py b/pyrogram/methods/chats/set_administrator_title.py index 8fd1338baa..fff4d61731 100644 --- a/pyrogram/methods/chats/set_administrator_title.py +++ b/pyrogram/methods/chats/set_administrator_title.py @@ -60,7 +60,7 @@ async def set_administrator_title( r = (await self.send( raw.functions.channels.GetParticipant( channel=chat_id, - user_id=user_id + participant=user_id ) )).participant From 701c1cde07af779ab18dbf79a3e626f04fa5d5d2 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 25 Mar 2021 15:35:15 +0100 Subject: [PATCH 0550/1185] Update Pyrogram to v1.2.5 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index de604ba3e8..fd59f123be 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "1.2.4" +__version__ = "1.2.5" __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)" __copyright__ = "Copyright (C) 2017-2021 Dan " From 50d1f30ac1db56f28946cec88ec8bad51c331ad8 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 29 Mar 2021 14:14:19 +0200 Subject: [PATCH 0551/1185] Add missing reply_markup for outgoing bot private messages Fix #656 --- pyrogram/methods/messages/send_message.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyrogram/methods/messages/send_message.py b/pyrogram/methods/messages/send_message.py index 982e4bbbd7..552a233bbe 100644 --- a/pyrogram/methods/messages/send_message.py +++ b/pyrogram/methods/messages/send_message.py @@ -155,6 +155,7 @@ async def send_message( text=message, date=r.date, outgoing=r.out, + reply_markup=reply_markup, entities=[ types.MessageEntity._parse(None, entity, {}) for entity in entities From aa6b226e64a4202260b9f904d0c1e02931be41d1 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 29 Mar 2021 14:15:03 +0200 Subject: [PATCH 0552/1185] Update Pyrogram to v1.2.6 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index fd59f123be..8bb911586a 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "1.2.5" +__version__ = "1.2.6" __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)" __copyright__ = "Copyright (C) 2017-2021 Dan " From 818aebcc7c8bcab3ee32dd501a515495f09d714a Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 5 Apr 2021 18:51:42 +0200 Subject: [PATCH 0553/1185] Fix memory session's delete() method not being implemented Closes #660 --- pyrogram/storage/memory_storage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/storage/memory_storage.py b/pyrogram/storage/memory_storage.py index 12783bb592..4934354051 100644 --- a/pyrogram/storage/memory_storage.py +++ b/pyrogram/storage/memory_storage.py @@ -50,4 +50,4 @@ async def open(self): await self.date(0) async def delete(self): - raise NotImplementedError + pass From 0b0bec9e278ee29ed6e84f4237cc9c39cc8ce55c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 5 Apr 2021 18:52:16 +0200 Subject: [PATCH 0554/1185] Update Pyrogram to v1.2.8 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 8bb911586a..2c838f97ea 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "1.2.6" +__version__ = "1.2.8" __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)" __copyright__ = "Copyright (C) 2017-2021 Dan " From b6f97ee924979065112902422dcc785ba64e716e Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 12 Apr 2021 09:14:50 +0200 Subject: [PATCH 0555/1185] Add support for signed error codes --- .../source/500_INTERNAL_SERVER_ERROR.tsv | 3 ++- .../errors/source/503_SERVICE_UNAVAILABLE.tsv | 2 ++ pyrogram/errors/rpc_error.py | 24 +++++++++++++++---- 3 files changed, 23 insertions(+), 6 deletions(-) create mode 100644 compiler/errors/source/503_SERVICE_UNAVAILABLE.tsv diff --git a/compiler/errors/source/500_INTERNAL_SERVER_ERROR.tsv b/compiler/errors/source/500_INTERNAL_SERVER_ERROR.tsv index b959bbe4d9..97d0984cc1 100644 --- a/compiler/errors/source/500_INTERNAL_SERVER_ERROR.tsv +++ b/compiler/errors/source/500_INTERNAL_SERVER_ERROR.tsv @@ -40,4 +40,5 @@ UPLOAD_NO_VOLUME Telegram is having internal problems. Please try again later VOLUME_LOC_NOT_FOUND Telegram is having internal problems. Please try again later WORKER_BUSY_TOO_LONG_RETRY Server workers are too busy right now due to Telegram having internal problems. Please try again later WP_ID_GENERATE_FAILED Telegram is having internal problems. Please try again later -GROUPCALL_ADD_PARTICIPANTS_FAILED Failure while adding voice chat member due to Telegram having internal problems. Please try again later \ No newline at end of file +GROUPCALL_ADD_PARTICIPANTS_FAILED Failure while adding voice chat member due to Telegram having internal problems. Please try again later +No workers running The Telegram server is restarting its workers. Try again later. \ No newline at end of file diff --git a/compiler/errors/source/503_SERVICE_UNAVAILABLE.tsv b/compiler/errors/source/503_SERVICE_UNAVAILABLE.tsv new file mode 100644 index 0000000000..72b79cb565 --- /dev/null +++ b/compiler/errors/source/503_SERVICE_UNAVAILABLE.tsv @@ -0,0 +1,2 @@ +id message +Timeout Telegram is having internal problems. Please try again later. \ No newline at end of file diff --git a/pyrogram/errors/rpc_error.py b/pyrogram/errors/rpc_error.py index d3cb5c551e..71a7249f66 100644 --- a/pyrogram/errors/rpc_error.py +++ b/pyrogram/errors/rpc_error.py @@ -32,8 +32,15 @@ class RPCError(Exception): NAME = None MESSAGE = "{x}" - def __init__(self, x: Union[int, raw.types.RpcError] = None, rpc_name: str = None, is_unknown: bool = False): - super().__init__("[{} {}]: {} {}".format( + def __init__( + self, + x: Union[int, str, raw.types.RpcError] = None, + rpc_name: str = None, + is_unknown: bool = False, + is_signed: bool = False + ): + super().__init__("[{}{} {}]: {} {}".format( + "-" if is_signed else "", self.CODE, self.ID or self.NAME, self.MESSAGE.format(x=x), @@ -52,14 +59,19 @@ def __init__(self, x: Union[int, raw.types.RpcError] = None, rpc_name: str = Non @staticmethod def raise_it(rpc_error: "raw.types.RpcError", rpc_type: Type[TLObject]): error_code = rpc_error.error_code + is_signed = error_code < 0 error_message = rpc_error.error_message rpc_name = ".".join(rpc_type.QUALNAME.split(".")[1:]) + if is_signed: + error_code = -error_code + if error_code not in exceptions: raise UnknownError( x=f"[{error_code} {error_message}]", rpc_name=rpc_name, - is_unknown=True + is_unknown=True, + is_signed=is_signed ) error_id = re.sub(r"_\d+", "_X", error_message) @@ -70,7 +82,8 @@ def raise_it(rpc_error: "raw.types.RpcError", rpc_type: Type[TLObject]): exceptions[error_code]["_"] )(x=f"[{error_code} {error_message}]", rpc_name=rpc_name, - is_unknown=True) + is_unknown=True, + is_signed=is_signed) x = re.search(r"_(\d+)", error_message) x = x.group(1) if x is not None else x @@ -80,7 +93,8 @@ def raise_it(rpc_error: "raw.types.RpcError", rpc_type: Type[TLObject]): exceptions[error_code][error_id] )(x=x, rpc_name=rpc_name, - is_unknown=False) + is_unknown=False, + is_signed=is_signed) class UnknownError(RPCError): From 70ae12eb77c3a89566e9b480635e0bbd523b4d56 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 12 Apr 2021 09:21:20 +0200 Subject: [PATCH 0556/1185] Handle ServiceUnavailable errors #664 --- 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 686531e386..cf92bcf314 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -29,7 +29,7 @@ from pyrogram import raw from pyrogram.connection import Connection from pyrogram.crypto import mtproto -from pyrogram.errors import RPCError, InternalServerError, AuthKeyDuplicated, FloodWait +from pyrogram.errors import RPCError, InternalServerError, AuthKeyDuplicated, FloodWait, ServiceUnavailable from pyrogram.raw.all import layer from pyrogram.raw.core import TLObject, MsgContainer, Int, FutureSalt, FutureSalts from .internals import MsgId, MsgFactory @@ -432,7 +432,7 @@ async def send( log.warning(f'[{self.client.session_name}] Sleeping for {amount}s (required by "{query}")') await asyncio.sleep(amount) - except (OSError, TimeoutError, InternalServerError) as e: + except (OSError, TimeoutError, InternalServerError, ServiceUnavailable) as e: if retries == 0: raise e from None From 9a8bf9d1dc625f09fd6877022bef200cc5ae97c8 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 12 Apr 2021 09:28:58 +0200 Subject: [PATCH 0557/1185] Fix wrongly generated class names --- compiler/errors/compiler.py | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/errors/compiler.py b/compiler/errors/compiler.py index d5cccaf4f9..feda92316c 100644 --- a/compiler/errors/compiler.py +++ b/compiler/errors/compiler.py @@ -96,6 +96,7 @@ def start(): sub_class = caml(re.sub(r"_X", "_", error_id)) sub_class = re.sub(r"^2", "Two", sub_class) + sub_class = re.sub(r" ", "", sub_class) f_all.write(" \"{}\": \"{}\",\n".format(error_id, sub_class)) From 416c351f6bfa10f92d76d0070b059ee558cd1fe7 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 12 Apr 2021 21:16:34 +0200 Subject: [PATCH 0558/1185] Update API schema to Layer 128 --- compiler/api/source/main_api.tl | 50 ++++++++++++++++++--------------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl index c7bea22360..8cd2235c71 100644 --- a/compiler/api/source/main_api.tl +++ b/compiler/api/source/main_api.tl @@ -46,7 +46,7 @@ inputMediaVenue#c13d1c11 geo_point:InputGeoPoint title:string address:string pro inputMediaPhotoExternal#e5bbfe1a flags:# url:string ttl_seconds:flags.0?int = InputMedia; inputMediaDocumentExternal#fb52dc99 flags:# url:string ttl_seconds:flags.0?int = InputMedia; inputMediaGame#d33f43f3 id:InputGame = InputMedia; -inputMediaInvoice#f4e096c3 flags:# title:string description:string photo:flags.0?InputWebDocument invoice:Invoice payload:bytes provider:string provider_data:DataJSON start_param:string = InputMedia; +inputMediaInvoice#f4e096c3 flags:# multiple_allowed:flags.1?true can_forward:flags.2?true title:string description:string photo:flags.0?InputWebDocument invoice:Invoice payload:bytes provider:string provider_data:DataJSON start_param:string = InputMedia; inputMediaGeoLive#971fa843 flags:# stopped:flags.0?true geo_point:InputGeoPoint heading:flags.2?int period:flags.1?int proximity_notification_radius:flags.3?int = InputMedia; inputMediaPoll#f94e5f1 flags:# poll:Poll correct_answers:flags.0?Vector solution:flags.1?string solution_entities:flags.1?Vector = InputMedia; inputMediaDice#e66fbf7b emoticon:string = InputMedia; @@ -68,8 +68,8 @@ inputSecureFileLocation#cbc7ee28 id:long access_hash:long = InputFileLocation; inputTakeoutFileLocation#29be5899 = InputFileLocation; inputPhotoFileLocation#40181ffe id:long access_hash:long file_reference:bytes thumb_size:string = InputFileLocation; inputPhotoLegacyFileLocation#d83466f3 id:long access_hash:long file_reference:bytes volume_id:long local_id:int secret:long = InputFileLocation; -inputPeerPhotoFileLocation#27d69997 flags:# big:flags.0?true peer:InputPeer volume_id:long local_id:int = InputFileLocation; -inputStickerSetThumb#dbaeae9 stickerset:InputStickerSet volume_id:long local_id:int = InputFileLocation; +inputPeerPhotoFileLocation#37257e99 flags:# big:flags.0?true peer:InputPeer photo_id:long = InputFileLocation; +inputStickerSetThumb#9d84f3db stickerset:InputStickerSet thumb_version:int = InputFileLocation; inputGroupCallStream#bba51639 call:InputGroupCall time_ms:long scale:int = InputFileLocation; peerUser#9db1bc6d user_id:int = Peer; @@ -91,7 +91,7 @@ userEmpty#200250ba id:int = User; user#938458c1 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true apply_min_photo:flags.25?true fake:flags.26?true id:int access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector bot_inline_placeholder:flags.19?string lang_code:flags.22?string = User; userProfilePhotoEmpty#4f11bae1 = UserProfilePhoto; -userProfilePhoto#69d3ab26 flags:# has_video:flags.0?true photo_id:long photo_small:FileLocation photo_big:FileLocation dc_id:int = UserProfilePhoto; +userProfilePhoto#82d1f706 flags:# has_video:flags.0?true photo_id:long stripped_thumb:flags.1?bytes dc_id:int = UserProfilePhoto; userStatusEmpty#9d05049 = UserStatus; userStatusOnline#edb93949 expires:int = UserStatus; @@ -117,7 +117,7 @@ chatParticipantsForbidden#fc900c2b flags:# chat_id:int self_participant:flags.0? chatParticipants#3f460fed chat_id:int participants:Vector version:int = ChatParticipants; chatPhotoEmpty#37c1011c = ChatPhoto; -chatPhoto#d20b9f3c flags:# has_video:flags.0?true photo_small:FileLocation photo_big:FileLocation dc_id:int = ChatPhoto; +chatPhoto#1c6e1c11 flags:# has_video:flags.0?true photo_id:long stripped_thumb:flags.1?bytes dc_id:int = ChatPhoto; messageEmpty#90a6ca84 flags:# id:int peer_id:flags.0?Peer = Message; message#bce383d2 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true id:int from_id:flags.8?Peer peer_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long restriction_reason:flags.22?Vector ttl_period:flags.25?int = Message; @@ -164,6 +164,7 @@ messageActionGeoProximityReached#98e0d697 from_id:Peer to_id:Peer distance:int = messageActionGroupCall#7a0d7f42 flags:# call:InputGroupCall duration:flags.0?int = MessageAction; messageActionInviteToGroupCall#76b9f11a call:InputGroupCall users:Vector = MessageAction; messageActionSetMessagesTTL#aa1afbfd period:int = MessageAction; +messageActionGroupCallScheduled#b3a07661 call:InputGroupCall schedule_date:int = MessageAction; dialog#2c171f72 flags:# pinned:flags.2?true unread_mark:flags.3?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int = Dialog; dialogFolder#71bd134c flags:# pinned:flags.2?true folder:Folder peer:Peer top_message:int unread_muted_peers_count:int unread_unmuted_peers_count:int unread_muted_messages_count:int unread_unmuted_messages_count:int = Dialog; @@ -172,10 +173,10 @@ photoEmpty#2331b22d id:long = Photo; photo#fb197a65 flags:# has_stickers:flags.0?true id:long access_hash:long file_reference:bytes date:int sizes:Vector video_sizes:flags.1?Vector dc_id:int = Photo; photoSizeEmpty#e17e23c type:string = PhotoSize; -photoSize#77bfb61b type:string location:FileLocation w:int h:int size:int = PhotoSize; -photoCachedSize#e9a734fa type:string location:FileLocation w:int h:int bytes:bytes = PhotoSize; +photoSize#75c78e60 type:string w:int h:int size:int = PhotoSize; +photoCachedSize#21e1ad6 type:string w:int h:int bytes:bytes = PhotoSize; photoStrippedSize#e0b0bc2e type:string bytes:bytes = PhotoSize; -photoSizeProgressive#5aa86a51 type:string location:FileLocation w:int h:int sizes:Vector = PhotoSize; +photoSizeProgressive#fa3efb95 type:string w:int h:int sizes:Vector = PhotoSize; photoPathSize#d8214d41 type:string bytes:bytes = PhotoSize; geoPointEmpty#1117dd5f = GeoPoint; @@ -532,7 +533,7 @@ inputStickerSetShortName#861cc8a0 short_name:string = InputStickerSet; inputStickerSetAnimatedEmoji#28703c8 = InputStickerSet; inputStickerSetDice#e67f520e emoticon:string = InputStickerSet; -stickerSet#40e237a8 flags:# archived:flags.1?true official:flags.2?true masks:flags.3?true animated:flags.5?true installed_date:flags.0?int id:long access_hash:long title:string short_name:string thumbs:flags.4?Vector thumb_dc_id:flags.4?int count:int hash:int = StickerSet; +stickerSet#d7df217a flags:# archived:flags.1?true official:flags.2?true masks:flags.3?true animated:flags.5?true installed_date:flags.0?int id:long access_hash:long title:string short_name:string thumbs:flags.4?Vector thumb_dc_id:flags.4?int thumb_version:flags.4?int count:int hash:int = StickerSet; messages.stickerSet#b60a24a6 set:StickerSet packs:Vector documents:Vector = messages.StickerSet; @@ -626,6 +627,7 @@ inputBotInlineMessageMediaGeo#96929a85 flags:# geo_point:InputGeoPoint heading:f inputBotInlineMessageMediaVenue#417bbf11 flags:# geo_point:InputGeoPoint title:string address:string provider:string venue_id:string venue_type:string reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; inputBotInlineMessageMediaContact#a6edbffd flags:# phone_number:string first_name:string last_name:string vcard:string reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; inputBotInlineMessageGame#4b425864 flags:# reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; +inputBotInlineMessageMediaInvoice#d5348d85 flags:# multiple_allowed:flags.1?true can_forward:flags.3?true title:string description:string photo:flags.0?InputWebDocument invoice:Invoice payload:bytes provider:string provider_data:DataJSON start_param:string reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; inputBotInlineResult#88bf9319 flags:# id:string type:string title:flags.1?string description:flags.2?string url:flags.3?string thumb:flags.4?InputWebDocument content:flags.5?InputWebDocument send_message:InputBotInlineMessage = InputBotInlineResult; inputBotInlineResultPhoto#a8d864a7 id:string type:string photo:InputPhoto send_message:InputBotInlineMessage = InputBotInlineResult; @@ -637,6 +639,7 @@ botInlineMessageText#8c7f65e2 flags:# no_webpage:flags.0?true message:string ent botInlineMessageMediaGeo#51846fd flags:# geo:GeoPoint heading:flags.0?int period:flags.1?int proximity_notification_radius:flags.3?int reply_markup:flags.2?ReplyMarkup = BotInlineMessage; botInlineMessageMediaVenue#8a86659c flags:# geo:GeoPoint title:string address:string provider:string venue_id:string venue_type:string reply_markup:flags.2?ReplyMarkup = BotInlineMessage; botInlineMessageMediaContact#18d1cdc2 flags:# phone_number:string first_name:string last_name:string vcard:string reply_markup:flags.2?ReplyMarkup = BotInlineMessage; +botInlineMessageMediaInvoice#354a9b09 flags:# shipping_address_requested:flags.1?true test:flags.3?true title:string description:string photo:flags.0?WebDocument currency:string total_amount:long reply_markup:flags.2?ReplyMarkup = BotInlineMessage; botInlineResult#11965f3a flags:# id:string type:string title:flags.1?string description:flags.2?string url:flags.3?string thumb:flags.4?WebDocument content:flags.5?WebDocument send_message:BotInlineMessage = BotInlineResult; botInlineMediaResult#17db940b flags:# id:string type:string photo:flags.0?Photo document:flags.1?Document title:flags.2?string description:flags.3?string send_message:BotInlineMessage = BotInlineResult; @@ -770,7 +773,7 @@ dataJSON#7d748d04 data:string = DataJSON; labeledPrice#cb296bf8 label:string amount:long = LabeledPrice; -invoice#c30aa358 flags:# test:flags.0?true name_requested:flags.1?true phone_requested:flags.2?true email_requested:flags.3?true shipping_address_requested:flags.4?true flexible:flags.5?true phone_to_provider:flags.6?true email_to_provider:flags.7?true currency:string prices:Vector = Invoice; +invoice#cd886e0 flags:# test:flags.0?true name_requested:flags.1?true phone_requested:flags.2?true email_requested:flags.3?true shipping_address_requested:flags.4?true flexible:flags.5?true phone_to_provider:flags.6?true email_to_provider:flags.7?true currency:string prices:Vector max_tip_amount:flags.8?long suggested_tip_amounts:flags.8?Vector = Invoice; paymentCharge#ea02c27e id:string provider_charge_id:string = PaymentCharge; @@ -790,14 +793,14 @@ inputWebFileGeoPointLocation#9f2221c9 geo_point:InputGeoPoint access_hash:long w upload.webFile#21e753bc size:int mime_type:string file_type:storage.FileType mtime:int bytes:bytes = upload.WebFile; -payments.paymentForm#3f56aea3 flags:# can_save_credentials:flags.2?true password_missing:flags.3?true bot_id:int invoice:Invoice provider_id:int url:string native_provider:flags.4?string native_params:flags.4?DataJSON saved_info:flags.0?PaymentRequestedInfo saved_credentials:flags.1?PaymentSavedCredentials users:Vector = payments.PaymentForm; +payments.paymentForm#8d0b2415 flags:# can_save_credentials:flags.2?true password_missing:flags.3?true form_id:long bot_id:int invoice:Invoice provider_id:int url:string native_provider:flags.4?string native_params:flags.4?DataJSON saved_info:flags.0?PaymentRequestedInfo saved_credentials:flags.1?PaymentSavedCredentials users:Vector = payments.PaymentForm; payments.validatedRequestedInfo#d1451883 flags:# id:flags.0?string shipping_options:flags.1?Vector = payments.ValidatedRequestedInfo; payments.paymentResult#4e5f810d updates:Updates = payments.PaymentResult; payments.paymentVerificationNeeded#d8411139 url:string = payments.PaymentResult; -payments.paymentReceipt#500911e1 flags:# date:int bot_id:int invoice:Invoice provider_id:int info:flags.0?PaymentRequestedInfo shipping:flags.1?ShippingOption currency:string total_amount:long credentials_title:string users:Vector = payments.PaymentReceipt; +payments.paymentReceipt#10b555d0 flags:# date:int bot_id:int provider_id:int title:string description:string photo:flags.2?WebDocument invoice:Invoice info:flags.0?PaymentRequestedInfo shipping:flags.1?ShippingOption tip_amount:flags.3?long currency:string total_amount:long credentials_title:string users:Vector = payments.PaymentReceipt; payments.savedInfo#fb8fe43c flags:# has_saved_credentials:flags.1?true saved_info:flags.0?PaymentRequestedInfo = payments.SavedInfo; @@ -1066,8 +1069,6 @@ emojiURL#a575739d url:string = EmojiURL; emojiLanguage#b3fb5361 lang_code:string = EmojiLanguage; -fileLocationToBeDeprecated#bc7fc6cd volume_id:long local_id:int = FileLocation; - folder#ff544e65 flags:# autofill_new_broadcasts:flags.0?true autofill_public_groups:flags.1?true autofill_new_correspondents:flags.2?true id:int title:string photo:flags.3?ChatPhoto = Folder; inputFolderPeer#fbd2c296 peer:InputPeer folder_id:int = InputFolderPeer; @@ -1147,7 +1148,7 @@ stats.broadcastStats#bdf78394 period:StatsDateRangeDays followers:StatsAbsValueA help.promoDataEmpty#98f6ac75 expires:int = help.PromoData; help.promoData#8c39793f flags:# proxy:flags.0?true expires:int peer:Peer chats:Vector users:Vector psa_type:flags.1?string psa_message:flags.2?string = help.PromoData; -videoSize#e831c556 flags:# type:string location:FileLocation w:int h:int size:int video_start_ts:flags.0?double = VideoSize; +videoSize#de33b094 flags:# type:string w:int h:int size:int video_start_ts:flags.0?double = VideoSize; statsGroupTopPoster#18f3d0f7 user_id:int messages:int avg_chars:int = StatsGroupTopPoster; @@ -1181,11 +1182,11 @@ peerBlocked#e8fd8014 peer_id:Peer date:int = PeerBlocked; stats.messageStats#8999f295 views_graph:StatsGraph = stats.MessageStats; groupCallDiscarded#7780bcb4 id:long access_hash:long duration:int = GroupCall; -groupCall#c0c2052e flags:# join_muted:flags.1?true can_change_join_muted:flags.2?true join_date_asc:flags.6?true id:long access_hash:long participants_count:int params:flags.0?DataJSON title:flags.3?string stream_dc_id:flags.4?int record_start_date:flags.5?int version:int = GroupCall; +groupCall#c95c6654 flags:# join_muted:flags.1?true can_change_join_muted:flags.2?true join_date_asc:flags.6?true schedule_start_subscribed:flags.8?true id:long access_hash:long participants_count:int params:flags.0?DataJSON title:flags.3?string stream_dc_id:flags.4?int record_start_date:flags.5?int schedule_date:flags.7?int version:int = GroupCall; inputGroupCall#d8aa840f id:long access_hash:long = InputGroupCall; -groupCallParticipant#19adba89 flags:# muted:flags.0?true left:flags.1?true can_self_unmute:flags.2?true just_joined:flags.4?true versioned:flags.5?true min:flags.8?true muted_by_you:flags.9?true volume_by_admin:flags.10?true self:flags.12?true peer:Peer date:int active_date:flags.3?int source:int volume:flags.7?int about:flags.11?string raise_hand_rating:flags.13?long = GroupCallParticipant; +groupCallParticipant#b96b25ee flags:# muted:flags.0?true left:flags.1?true can_self_unmute:flags.2?true just_joined:flags.4?true versioned:flags.5?true min:flags.8?true muted_by_you:flags.9?true volume_by_admin:flags.10?true self:flags.12?true peer:Peer date:int active_date:flags.3?int source:int volume:flags.7?int about:flags.11?string raise_hand_rating:flags.13?long params:flags.6?DataJSON = GroupCallParticipant; phone.groupCall#9e727aad call:GroupCall participants:Vector participants_next_offset:string chats:Vector users:Vector = phone.GroupCall; @@ -1570,10 +1571,10 @@ bots.sendCustomRequest#aa2769ed custom_method:string params:DataJSON = DataJSON; bots.answerWebhookJSONQuery#e6213f4d query_id:long data:DataJSON = Bool; bots.setBotCommands#805d46f6 commands:Vector = Bool; -payments.getPaymentForm#99f09745 msg_id:int = payments.PaymentForm; -payments.getPaymentReceipt#a092a980 msg_id:int = payments.PaymentReceipt; -payments.validateRequestedInfo#770a8e74 flags:# save:flags.0?true msg_id:int info:PaymentRequestedInfo = payments.ValidatedRequestedInfo; -payments.sendPaymentForm#2b8879b3 flags:# msg_id:int requested_info_id:flags.0?string shipping_option_id:flags.1?string credentials:InputPaymentCredentials = payments.PaymentResult; +payments.getPaymentForm#8a333c8d flags:# peer:InputPeer msg_id:int theme_params:flags.0?DataJSON = payments.PaymentForm; +payments.getPaymentReceipt#2478d1cc peer:InputPeer msg_id:int = payments.PaymentReceipt; +payments.validateRequestedInfo#db103170 flags:# save:flags.0?true peer:InputPeer msg_id:int info:PaymentRequestedInfo = payments.ValidatedRequestedInfo; +payments.sendPaymentForm#30c3bc9d flags:# form_id:long peer:InputPeer msg_id:int requested_info_id:flags.0?string shipping_option_id:flags.1?string credentials:InputPaymentCredentials tip_amount:flags.2?long = payments.PaymentResult; payments.getSavedInfo#227d824b = payments.SavedInfo; payments.clearSavedInfo#d83d70c1 flags:# credentials:flags.0?true info:flags.1?true = Bool; payments.getBankCardData#2e79d779 number:string = payments.BankCardData; @@ -1593,7 +1594,7 @@ phone.discardCall#b2cbc1c0 flags:# video:flags.0?true peer:InputPhoneCall durati phone.setCallRating#59ead627 flags:# user_initiative:flags.0?true peer:InputPhoneCall rating:int comment:string = Updates; phone.saveCallDebug#277add7e peer:InputPhoneCall debug:DataJSON = Bool; phone.sendSignalingData#ff7a9383 peer:InputPhoneCall data:bytes = Bool; -phone.createGroupCall#bd3dabe0 peer:InputPeer random_id:int = Updates; +phone.createGroupCall#48cdc6d8 flags:# peer:InputPeer random_id:int title:flags.0?string schedule_date:flags.1?int = Updates; phone.joinGroupCall#b132ff7b flags:# muted:flags.0?true call:InputGroupCall join_as:InputPeer invite_hash:flags.1?string params:DataJSON = Updates; phone.leaveGroupCall#500377f9 call:InputGroupCall source:int = Updates; phone.inviteToGroupCall#7b393160 call:InputGroupCall users:Vector = Updates; @@ -1607,6 +1608,9 @@ phone.editGroupCallParticipant#d975eb80 flags:# muted:flags.0?true call:InputGro phone.editGroupCallTitle#1ca6ac0a call:InputGroupCall title:string = Updates; phone.getGroupCallJoinAs#ef7c213a peer:InputPeer = phone.JoinAsPeers; phone.exportGroupCallInvite#e6aa647f flags:# can_self_unmute:flags.0?true call:InputGroupCall = phone.ExportedGroupCallInvite; +phone.toggleGroupCallStartSubscription#219c34e6 call:InputGroupCall subscribed:Bool = Updates; +phone.startScheduledGroupCall#5680e342 call:InputGroupCall = Updates; +phone.saveDefaultGroupCallJoinAs#575e1f8c peer:InputPeer join_as:InputPeer = Bool; langpack.getLangPack#f2f2330a lang_pack:string lang_code:string = LangPackDifference; langpack.getStrings#efea3803 lang_pack:string lang_code:string keys:Vector = Vector; @@ -1623,4 +1627,4 @@ stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats; -// LAYER 126 \ No newline at end of file +// LAYER 128 \ No newline at end of file From 0c814e9e5e8c3a78405daaae64ee0687a2900cc5 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 13 Apr 2021 15:53:53 +0200 Subject: [PATCH 0559/1185] Add support for media DC IPs --- pyrogram/connection/connection.py | 11 +++-- pyrogram/session/internals/data_center.py | 53 +++++++++++++++-------- pyrogram/session/session.py | 3 +- 3 files changed, 44 insertions(+), 23 deletions(-) diff --git a/pyrogram/connection/connection.py b/pyrogram/connection/connection.py index 5aac9038d1..d3bf6b287f 100644 --- a/pyrogram/connection/connection.py +++ b/pyrogram/connection/connection.py @@ -38,12 +38,13 @@ class Connection: 4: TCPIntermediateO } - def __init__(self, dc_id: int, test_mode: bool, ipv6: bool, proxy: dict, mode: int = 3): + def __init__(self, dc_id: int, test_mode: bool, ipv6: bool, proxy: dict, media: bool = False, mode: int = 3): self.dc_id = dc_id self.test_mode = test_mode self.ipv6 = ipv6 self.proxy = proxy - self.address = DataCenter(dc_id, test_mode, ipv6) + self.media = media + self.address = DataCenter(dc_id, test_mode, ipv6, media) self.mode = self.MODES.get(mode, TCPAbridged) self.protocol = None # type: TCP @@ -60,11 +61,13 @@ async def connect(self): self.protocol.close() await asyncio.sleep(1) else: - log.info("Connected! {} DC{} - IPv{} - {}".format( + log.info("Connected! {} DC{} - IPv{} - {}{} {}".format( "Test" if self.test_mode else "Production", self.dc_id, "6" if self.ipv6 else "4", - self.mode.__name__ + self.mode.__name__, + " (media)" if self.media else "", + self.address )) break else: diff --git a/pyrogram/session/internals/data_center.py b/pyrogram/session/internals/data_center.py index 566bd79e43..3c9e95675c 100644 --- a/pyrogram/session/internals/data_center.py +++ b/pyrogram/session/internals/data_center.py @@ -16,12 +16,14 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from typing import Tuple + + class DataCenter: TEST = { 1: "149.154.175.10", 2: "149.154.167.40", 3: "149.154.175.117", - 121: "95.213.217.195" } PROD = { @@ -29,15 +31,18 @@ class DataCenter: 2: "149.154.167.51", 3: "149.154.175.100", 4: "149.154.167.91", - 5: "91.108.56.130", - 121: "95.213.217.195" + 5: "91.108.56.130" + } + + PROD_MEDIA = { + 2: "149.154.167.151", + 4: "149.154.164.250" } TEST_IPV6 = { 1: "2001:b28:f23d:f001::e", 2: "2001:67c:4e8:f002::e", 3: "2001:b28:f23d:f003::e", - 121: "2a03:b0c0:3:d0::114:d001" } PROD_IPV6 = { @@ -45,20 +50,32 @@ class DataCenter: 2: "2001:67c:4e8:f002::a", 3: "2001:b28:f23d:f003::a", 4: "2001:67c:4e8:f004::a", - 5: "2001:b28:f23f:f005::a", - 121: "2a03:b0c0:3:d0::114:d001" + 5: "2001:b28:f23f:f005::a" } - def __new__(cls, dc_id: int, test_mode: bool, ipv6: bool): - if ipv6: - return ( - (cls.TEST_IPV6[dc_id], 80) - if test_mode - else (cls.PROD_IPV6[dc_id], 443) - ) + PROD_IPV6_MEDIA = { + 2: "2001:067c:04e8:f002:0000:0000:0000:000b", + 4: "2001:067c:04e8:f004:0000:0000:0000:000b" + } + + def __new__(cls, dc_id: int, test_mode: bool, ipv6: bool, media: bool) -> Tuple[str, int]: + if test_mode: + if ipv6: + ip = cls.TEST_IPV6[dc_id] + else: + ip = cls.TEST[dc_id] + + return ip, 80 else: - return ( - (cls.TEST[dc_id], 80) - if test_mode - else (cls.PROD[dc_id], 443) - ) + if ipv6: + if media: + ip = cls.PROD_IPV6_MEDIA.get(dc_id, cls.PROD_IPV6[dc_id]) + else: + ip = cls.PROD_IPV6[dc_id] + else: + if media: + ip = cls.PROD_MEDIA.get(dc_id, cls.PROD[dc_id]) + else: + ip = cls.PROD[dc_id] + + return ip, 443 diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index 686531e386..f4642e58bf 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -120,7 +120,8 @@ async def start(self): self.dc_id, self.test_mode, self.client.ipv6, - self.client.proxy + self.client.proxy, + self.is_media ) try: From 4f585c156c1a2c6707793a8ad7f2f111515ca23b Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 15 Apr 2021 09:51:50 +0200 Subject: [PATCH 0560/1185] Update Pyrogram to v1.2.9 - Make less noise at the INFO log level --- pyrogram/__init__.py | 2 +- pyrogram/syncer.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 2c838f97ea..c0c7b21916 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "1.2.8" +__version__ = "1.2.9" __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)" __copyright__ = "Copyright (C) 2017-2021 Dan " diff --git a/pyrogram/syncer.py b/pyrogram/syncer.py index 93fb59f253..dbd8dc6495 100644 --- a/pyrogram/syncer.py +++ b/pyrogram/syncer.py @@ -85,4 +85,4 @@ async def sync(cls, client): except Exception as e: log.critical(e, exc_info=True) else: - log.info(f'Synced "{client.storage.name}" in {(time.time() - start) * 1000:.6} ms') + log.debug(f'Synced "{client.storage.name}" in {(time.time() - start) * 1000:.6} ms') From add492c1bea8668c0c5c7b50bcc63bbdc85972ef Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 15 Apr 2021 12:04:43 +0200 Subject: [PATCH 0561/1185] Show the signal name instead of the number --- pyrogram/methods/utilities/idle.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/pyrogram/methods/utilities/idle.py b/pyrogram/methods/utilities/idle.py index ba33265014..7b41f7af67 100644 --- a/pyrogram/methods/utilities/idle.py +++ b/pyrogram/methods/utilities/idle.py @@ -18,12 +18,19 @@ import asyncio import logging -from signal import signal, SIGINT, SIGTERM, SIGABRT +import signal +from signal import signal as signal_fn, SIGINT, SIGTERM, SIGABRT log = logging.getLogger(__name__) is_idling = False +# Signal number to name +signals = { + k: v for v, k in signal.__dict__.items() + if v.startswith("SIG") and not v.startswith("SIG_") +} + async def idle(): """Block the main script execution until a signal is received. @@ -65,14 +72,14 @@ async def idle(): """ global is_idling - def signal_handler(_, __): + def signal_handler(signum, __): global is_idling - logging.info("Stop signal received ({}). Exiting...".format(_)) + logging.info(f"Stop signal received ({signals[signum]}). Exiting...") is_idling = False for s in (SIGINT, SIGTERM, SIGABRT): - signal(s, signal_handler) + signal_fn(s, signal_handler) is_idling = True From c5624c639bd467847fa2922307ccd150c8b193b0 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 15 Apr 2021 12:17:25 +0200 Subject: [PATCH 0562/1185] Cleaner error message --- 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 686531e386..7727f72ca7 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -437,7 +437,7 @@ async def send( raise e from None (log.warning if retries < 2 else log.info)( - f'[{Session.MAX_RETRIES - retries + 1}] Retrying "{query}" due to {repr(e)}') + f'[{Session.MAX_RETRIES - retries + 1}] Retrying "{query}" due to {str(e) or repr(e)}') await asyncio.sleep(0.5) From 2eef1d5fcf0e06705910704a6907c8f1131405f0 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 13 Apr 2021 11:16:45 +0200 Subject: [PATCH 0563/1185] Accommodate changes to photo file ids --- pyrogram/client.py | 3 +- pyrogram/types/messages_and_media/photo.py | 52 +++++------ .../types/messages_and_media/thumbnail.py | 90 +++++++++---------- pyrogram/types/user_and_chats/chat_photo.py | 24 +++-- 4 files changed, 79 insertions(+), 90 deletions(-) diff --git a/pyrogram/client.py b/pyrogram/client.py index 5f80182db6..0dc344eca4 100644 --- a/pyrogram/client.py +++ b/pyrogram/client.py @@ -874,8 +874,7 @@ async def get_file( location = raw.types.InputPeerPhotoFileLocation( peer=peer, - volume_id=file_id.volume_id, - local_id=file_id.local_id, + photo_id=file_id.media_id, big=file_id.thumbnail_source == ThumbnailSource.CHAT_PHOTO_BIG ) elif file_type == FileType.PHOTO: diff --git a/pyrogram/types/messages_and_media/photo.py b/pyrogram/types/messages_and_media/photo.py index c350b6a72f..a512d17b82 100644 --- a/pyrogram/types/messages_and_media/photo.py +++ b/pyrogram/types/messages_and_media/photo.py @@ -82,21 +82,25 @@ def __init__( @staticmethod def _parse(client, photo: "raw.types.Photo", ttl_seconds: int = None) -> "Photo": if isinstance(photo, raw.types.Photo): - try: - progressive = next(p for p in photo.sizes if isinstance(p, raw.types.PhotoSizeProgressive)) - except StopIteration: - photo_size_objs = [p for p in photo.sizes if isinstance(p, raw.types.PhotoSize)] - photo_size_objs.sort(key=lambda p: p.size) - - big = photo_size_objs[-1] - else: - big = raw.types.PhotoSize( - type=progressive.type, - location=progressive.location, - w=progressive.w, - h=progressive.h, - size=sorted(progressive.sizes)[-1] - ) + photos: List[raw.types.PhotoSize] = [] + + for p in photo.sizes: + if isinstance(p, raw.types.PhotoSize): + photos.append(p) + + if isinstance(p, raw.types.PhotoSizeProgressive): + photos.append( + raw.types.PhotoSize( + type=p.type, + w=p.w, + h=p.h, + size=max(p.sizes) + ) + ) + + photos.sort(key=lambda p: p.size) + + main = photos[-1] return Photo( file_id=FileId( @@ -107,19 +111,17 @@ def _parse(client, photo: "raw.types.Photo", ttl_seconds: int = None) -> "Photo" file_reference=photo.file_reference, thumbnail_source=ThumbnailSource.THUMBNAIL, thumbnail_file_type=FileType.PHOTO, - thumbnail_size=big.type, - volume_id=big.location.volume_id, - local_id=big.location.local_id + thumbnail_size=main.type, + volume_id=0, + local_id=0 ).encode(), file_unique_id=FileUniqueId( - file_unique_type=FileUniqueType.PHOTO, - media_id=photo.id, - volume_id=big.location.volume_id, - local_id=big.location.local_id + file_unique_type=FileUniqueType.DOCUMENT, + media_id=photo.id ).encode(), - width=big.w, - height=big.h, - file_size=big.size, + width=main.w, + height=main.h, + file_size=main.size, date=photo.date, ttl_seconds=ttl_seconds, thumbs=types.Thumbnail._parse(client, photo), diff --git a/pyrogram/types/messages_and_media/thumbnail.py b/pyrogram/types/messages_and_media/thumbnail.py index 7b26c5b8d1..1a55c18ce4 100644 --- a/pyrogram/types/messages_and_media/thumbnail.py +++ b/pyrogram/types/messages_and_media/thumbnail.py @@ -16,11 +16,10 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Union, List, Optional +from typing import List, Optional, Union import pyrogram from pyrogram import raw -from pyrogram import types from pyrogram.file_id import FileId, FileType, FileUniqueId, FileUniqueType, ThumbnailSource from ..object import Object @@ -65,55 +64,48 @@ def __init__( self.file_size = file_size @staticmethod - def _parse( - client, - media: Union["raw.types.Photo", "raw.types.Document"] - ) -> Optional[List[Union["types.StrippedThumbnail", "Thumbnail"]]]: + def _parse(client, media: Union["raw.types.Photo", "raw.types.Document"]) -> Optional[List["Thumbnail"]]: if isinstance(media, raw.types.Photo): - raw_thumbnails = media.sizes[:-1] - elif isinstance(media, raw.types.Document): - raw_thumbnails = media.thumbs + raw_thumbs = [i for i in media.sizes if isinstance(i, raw.types.PhotoSize)] + raw_thumbs.sort(key=lambda p: p.size) + raw_thumbs = raw_thumbs[:-1] - if not raw_thumbnails: - return None + file_type = FileType.PHOTO + elif isinstance(media, raw.types.Document): + raw_thumbs = media.thumbs + file_type = FileType.THUMBNAIL else: - return None - - thumbnails = [] - - file_type = FileType.PHOTO if isinstance(media, raw.types.Photo) else FileType.THUMBNAIL - thumbnail_file_type = file_type - - for thumbnail in raw_thumbnails: - # TODO: Enable this - # if isinstance(thumbnail, types.PhotoStrippedSize): - # thumbnails.append(StrippedThumbnail._parse(client, thumbnail)) - if isinstance(thumbnail, raw.types.PhotoSize): - thumbnails.append( - Thumbnail( - file_id=FileId( - file_type=file_type, - dc_id=media.dc_id, - media_id=media.id, - access_hash=media.access_hash, - file_reference=media.file_reference, - thumbnail_file_type=thumbnail_file_type, - thumbnail_source=ThumbnailSource.THUMBNAIL, - thumbnail_size=thumbnail.type, - volume_id=thumbnail.location.volume_id, - local_id=thumbnail.location.local_id - ).encode(), - file_unique_id=FileUniqueId( - file_unique_type=FileUniqueType.PHOTO, - media_id=media.id, - volume_id=thumbnail.location.volume_id, - local_id=thumbnail.location.local_id - ).encode(), - width=thumbnail.w, - height=thumbnail.h, - file_size=thumbnail.size, - client=client - ) + return + + parsed_thumbs = [] + + for thumb in raw_thumbs: + if not isinstance(thumb, raw.types.PhotoSize): + continue + + parsed_thumbs.append( + Thumbnail( + file_id=FileId( + file_type=file_type, + dc_id=media.dc_id, + media_id=media.id, + access_hash=media.access_hash, + file_reference=media.file_reference, + thumbnail_file_type=file_type, + thumbnail_source=ThumbnailSource.THUMBNAIL, + thumbnail_size=thumb.type, + volume_id=0, + local_id=0 + ).encode(), + file_unique_id=FileUniqueId( + file_unique_type=FileUniqueType.DOCUMENT, + media_id=media.id + ).encode(), + width=thumb.w, + height=thumb.h, + file_size=thumb.size, + client=client ) + ) - return thumbnails or None + return parsed_thumbs or None diff --git a/pyrogram/types/user_and_chats/chat_photo.py b/pyrogram/types/user_and_chats/chat_photo.py index cd10c3c2ec..959d1732e0 100644 --- a/pyrogram/types/user_and_chats/chat_photo.py +++ b/pyrogram/types/user_and_chats/chat_photo.py @@ -72,40 +72,36 @@ def _parse( if not isinstance(chat_photo, (raw.types.UserProfilePhoto, raw.types.ChatPhoto)): return None - media_id = chat_photo.photo_id if isinstance(chat_photo, raw.types.UserProfilePhoto) else 0 - return ChatPhoto( small_file_id=FileId( file_type=FileType.CHAT_PHOTO, dc_id=chat_photo.dc_id, - media_id=media_id, + media_id=chat_photo.photo_id, access_hash=0, - volume_id=chat_photo.photo_small.volume_id, + volume_id=0, thumbnail_source=ThumbnailSource.CHAT_PHOTO_SMALL, - local_id=chat_photo.photo_small.local_id, + local_id=0, chat_id=peer_id, chat_access_hash=peer_access_hash ).encode(), small_photo_unique_id=FileUniqueId( - file_unique_type=FileUniqueType.PHOTO, - volume_id=chat_photo.photo_small.volume_id, - local_id=chat_photo.photo_small.local_id + file_unique_type=FileUniqueType.DOCUMENT, + media_id=chat_photo.photo_id ).encode(), big_file_id=FileId( file_type=FileType.CHAT_PHOTO, dc_id=chat_photo.dc_id, - media_id=media_id, + media_id=chat_photo.photo_id, access_hash=0, - volume_id=chat_photo.photo_big.volume_id, + volume_id=0, thumbnail_source=ThumbnailSource.CHAT_PHOTO_BIG, - local_id=chat_photo.photo_big.local_id, + local_id=0, chat_id=peer_id, chat_access_hash=peer_access_hash ).encode(), big_photo_unique_id=FileUniqueId( - file_unique_type=FileUniqueType.PHOTO, - volume_id=chat_photo.photo_big.volume_id, - local_id=chat_photo.photo_big.local_id + file_unique_type=FileUniqueType.DOCUMENT, + media_id=chat_photo.photo_id ).encode(), client=client ) From e4f99df07c2f26be0966eeefdf18ac53e03e5b49 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 26 Apr 2021 16:09:39 +0200 Subject: [PATCH 0564/1185] Better error message in case of empty data in buffer --- pyrogram/crypto/mtproto.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyrogram/crypto/mtproto.py b/pyrogram/crypto/mtproto.py index bbe9d27a75..dad30cd398 100644 --- a/pyrogram/crypto/mtproto.py +++ b/pyrogram/crypto/mtproto.py @@ -63,6 +63,9 @@ def unpack(b: BytesIO, session_id: bytes, auth_key: bytes, auth_key_id: bytes) - try: message = Message.read(data) except KeyError as e: + if e.args[0] == 0: + raise ConnectionError(f"Received empty data. Check your internet connection.") + left = data.read().hex() left = [left[i:i + 64] for i in range(0, len(left), 64)] From 5e77387919d1ef030b9bbf7a65675291cd5aa09a Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 26 Apr 2021 16:12:11 +0200 Subject: [PATCH 0565/1185] Fix empty messages don't have a chat id --- pyrogram/types/messages_and_media/message.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index 6f31ec7483..e96a1d1096 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -2903,8 +2903,7 @@ async def copy( log.warning(f"Users cannot send messages with Game media type. " f"chat_id: {self.chat.id}, message_id: {self.message_id}") elif self.empty: - log.warning(f"Empty messages cannot be copied. " - f"chat_id: {self.chat.id}, message_id: {self.message_id}") + log.warning(f"Empty messages cannot be copied. ") elif self.text: return await self._client.send_message( chat_id, From 710cfa071d5a384c08f9e4ae2fea2065ccdaa3a4 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 26 Apr 2021 16:13:36 +0200 Subject: [PATCH 0566/1185] Lower log level for unmatched closing tag --- pyrogram/parser/html.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/parser/html.py b/pyrogram/parser/html.py index ac9984f6aa..e73cb3aa8a 100644 --- a/pyrogram/parser/html.py +++ b/pyrogram/parser/html.py @@ -96,7 +96,7 @@ def handle_endtag(self, tag): line, offset = self.getpos() offset += 1 - log.warning(f"Unmatched closing tag at line {line}:{offset}") + log.debug(f"Unmatched closing tag at line {line}:{offset}") else: if not self.tag_entities[tag]: self.tag_entities.pop(tag) From fa7673e51c0f555a35bbd11e7b2f72f9ff5f87f6 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 26 Apr 2021 16:20:09 +0200 Subject: [PATCH 0567/1185] Add the field chat_type to the class InlineQuery --- pyrogram/types/inline_mode/inline_query.py | 23 ++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/pyrogram/types/inline_mode/inline_query.py b/pyrogram/types/inline_mode/inline_query.py index 1883bc207e..5c718c33c9 100644 --- a/pyrogram/types/inline_mode/inline_query.py +++ b/pyrogram/types/inline_mode/inline_query.py @@ -43,6 +43,12 @@ class InlineQuery(Object, Update): offset (``str``): Offset of the results to be returned, can be controlled by the bot. + chat_type (``str``, *optional*): + Type of the chat, from which the inline query was sent. + Can be either "sender" for a private chat with the inline query sender, "private", "group", "supergroup", or + "channel". The chat type should be always known for requests sent from official clients and most + third-party clients, unless the request was sent from a secret chat. + location (:obj:`~pyrogram.types.Location`. *optional*): Sender location, only for bots that request user location. @@ -59,6 +65,7 @@ def __init__( from_user: "types.User", query: str, offset: str, + chat_type: str, location: "types.Location" = None, matches: List[Match] = None ): @@ -68,16 +75,32 @@ def __init__( self.from_user = from_user self.query = query self.offset = offset + self.chat_type = chat_type self.location = location self.matches = matches @staticmethod def _parse(client, inline_query: raw.types.UpdateBotInlineQuery, users: dict) -> "InlineQuery": + peer_type = inline_query.peer_type + chat_type = None + + if isinstance(peer_type, raw.types.InlineQueryPeerTypeSameBotPM): + chat_type = "sender" + elif isinstance(peer_type, raw.types.InlineQueryPeerTypePM): + chat_type = "private" + elif isinstance(peer_type, raw.types.InlineQueryPeerTypeChat): + chat_type = "group" + elif isinstance(peer_type, raw.types.InlineQueryPeerTypeMegagroup): + chat_type = "supergroup" + elif isinstance(peer_type, raw.types.InlineQueryPeerTypeBroadcast): + chat_type = "channel" + return InlineQuery( id=str(inline_query.query_id), from_user=types.User._parse(client, users[inline_query.user_id]), query=inline_query.query, offset=inline_query.offset, + chat_type=chat_type, location=types.Location( longitude=inline_query.geo.long, latitude=inline_query.geo.lat, From 89860a4b4cad2b4fc921d133d210f267fd358059 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 26 Apr 2021 16:56:10 +0200 Subject: [PATCH 0568/1185] Add VoiceChatScheduled type and Message.voice_chat_scheduled field --- compiler/docs/compiler.py | 1 + pyrogram/types/messages_and_media/message.py | 9 ++++ pyrogram/types/user_and_chats/__init__.py | 4 +- .../user_and_chats/voice_chat_scheduled.py | 41 +++++++++++++++++++ 4 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 pyrogram/types/user_and_chats/voice_chat_scheduled.py diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index 8ee22fff5f..1d4ba9e4fe 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -376,6 +376,7 @@ def get_title_list(s: str) -> list: Poll PollOption Dice + VoiceChatScheduled VoiceChatStarted VoiceChatEnded VoiceChatMembersInvited diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index e96a1d1096..279ac3ea3b 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -264,6 +264,9 @@ class Message(Object, Update): E.g.: "/start 1 2 3" would produce ["start", "1", "2", "3"]. Only applicable when using :obj:`~pyrogram.filters.command`. + voice_chat_scheduled (:obj:`~pyrogram.types.VoiceChatScheduled`, *optional*): + Service message: voice chat scheduled. + voice_chat_started (:obj:`~pyrogram.types.VoiceChatStarted`, *optional*): Service message: the voice chat started. @@ -344,6 +347,7 @@ def __init__( outgoing: bool = None, matches: List[Match] = None, command: List[str] = None, + voice_chat_scheduled: "types.VoiceChatScheduled" = None, voice_chat_started: "types.VoiceChatStarted" = None, voice_chat_ended: "types.VoiceChatEnded" = None, voice_chat_members_invited: "types.VoiceChatMembersInvited" = None, @@ -414,6 +418,7 @@ def __init__( self.matches = matches self.command = command self.reply_markup = reply_markup + self.voice_chat_scheduled = voice_chat_scheduled self.voice_chat_started = voice_chat_started self.voice_chat_ended = voice_chat_ended self.voice_chat_members_invited = voice_chat_members_invited @@ -442,6 +447,7 @@ async def _parse( group_chat_created = None channel_chat_created = None new_chat_photo = None + voice_chat_scheduled = None voice_chat_started = None voice_chat_ended = None voice_chat_members_invited = None @@ -466,6 +472,8 @@ async def _parse( channel_chat_created = True elif isinstance(action, raw.types.MessageActionChatEditPhoto): new_chat_photo = types.Photo._parse(client, action.photo) + elif isinstance(action, raw.types.MessageActionGroupCallScheduled): + voice_chat_scheduled = types.VoiceChatScheduled._parse(action) elif isinstance(action, raw.types.MessageActionGroupCall): if action.duration: voice_chat_ended = types.VoiceChatEnded._parse(action) @@ -495,6 +503,7 @@ async def _parse( group_chat_created=group_chat_created, channel_chat_created=channel_chat_created, client=client, + voice_chat_scheduled=voice_chat_scheduled, voice_chat_started=voice_chat_started, voice_chat_ended=voice_chat_ended, voice_chat_members_invited=voice_chat_members_invited diff --git a/pyrogram/types/user_and_chats/__init__.py b/pyrogram/types/user_and_chats/__init__.py index f3022e3754..39e5d786ac 100644 --- a/pyrogram/types/user_and_chats/__init__.py +++ b/pyrogram/types/user_and_chats/__init__.py @@ -32,6 +32,7 @@ from .user import User from .voice_chat_ended import VoiceChatEnded from .voice_chat_members_invited import VoiceChatMembersInvited +from .voice_chat_scheduled import VoiceChatScheduled from .voice_chat_started import VoiceChatStarted __all__ = [ @@ -51,5 +52,6 @@ "VoiceChatStarted", "VoiceChatEnded", "VoiceChatMembersInvited", - "ChatMemberUpdated" + "ChatMemberUpdated", + "VoiceChatScheduled" ] diff --git a/pyrogram/types/user_and_chats/voice_chat_scheduled.py b/pyrogram/types/user_and_chats/voice_chat_scheduled.py new file mode 100644 index 0000000000..fc7a5f1e5f --- /dev/null +++ b/pyrogram/types/user_and_chats/voice_chat_scheduled.py @@ -0,0 +1,41 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan +# +# 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 pyrogram import raw +from ..object import Object + + +class VoiceChatScheduled(Object): + """A service message about a voice chat scheduled in the chat. + + Parameters: + start_date (``int``): + Point in time (Unix timestamp) when the voice chat is supposed to be started by a chat administrator. + """ + + def __init__( + self, *, + start_date: int + ): + super().__init__() + + self.start_date = start_date + + @staticmethod + def _parse(action: "raw.types.MessageActionGroupCallScheduled") -> "VoiceChatScheduled": + return VoiceChatScheduled(start_date=action.schedule_date) From 2de1606e3de254a65a3c12ba0a5364019a25324d Mon Sep 17 00:00:00 2001 From: Mario A <10923513+Midblyte@users.noreply.github.com> Date: Mon, 26 Apr 2021 20:45:23 +0200 Subject: [PATCH 0569/1185] Fix GameHighScore in the chat with yourself (#669) Signed-off-by: Mario A --- pyrogram/types/bots_and_keyboards/game_high_score.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/types/bots_and_keyboards/game_high_score.py b/pyrogram/types/bots_and_keyboards/game_high_score.py index ff67e7505c..b9a77d332c 100644 --- a/pyrogram/types/bots_and_keyboards/game_high_score.py +++ b/pyrogram/types/bots_and_keyboards/game_high_score.py @@ -64,7 +64,7 @@ def _parse(client, game_high_score: raw.types.HighScore, users: dict) -> "GameHi @staticmethod def _parse_action(client, service: raw.types.MessageService, users: dict): return GameHighScore( - user=types.User._parse(client, users[utils.get_raw_peer_id(service.from_id)]), + user=types.User._parse(client, users[utils.get_raw_peer_id(service.from_id or service.peer_id)]), score=service.action.score, client=client ) From 928c8acd01a4d46c863f7fa9088637e10d46d75c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 3 May 2021 20:58:37 +0200 Subject: [PATCH 0570/1185] Fix iter_profile_photos wrong hinted return type --- pyrogram/methods/users/iter_profile_photos.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/methods/users/iter_profile_photos.py b/pyrogram/methods/users/iter_profile_photos.py index 96274392f9..50af4c442d 100644 --- a/pyrogram/methods/users/iter_profile_photos.py +++ b/pyrogram/methods/users/iter_profile_photos.py @@ -28,7 +28,7 @@ async def iter_profile_photos( chat_id: Union[int, str], offset: int = 0, limit: int = 0, - ) -> Optional[AsyncGenerator["types.Message", None]]: + ) -> Optional[AsyncGenerator["types.Photo", None]]: """Iterate through a chat or a user profile photos sequentially. This convenience method does the same as repeatedly calling :meth:`~pyrogram.Client.get_profile_photos` in a From a3e46b495771bf8fe707421d1200d34cbe50a53b Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 3 May 2021 21:07:36 +0200 Subject: [PATCH 0571/1185] Cleanup connection info logs --- pyrogram/connection/connection.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pyrogram/connection/connection.py b/pyrogram/connection/connection.py index d3bf6b287f..3ab52d7105 100644 --- a/pyrogram/connection/connection.py +++ b/pyrogram/connection/connection.py @@ -18,7 +18,6 @@ import asyncio import logging - from typing import Optional from .transport import * @@ -61,13 +60,12 @@ async def connect(self): self.protocol.close() await asyncio.sleep(1) else: - log.info("Connected! {} DC{} - IPv{} - {}{} {}".format( + log.info("Connected! {} DC{}{} - IPv{} - {}".format( "Test" if self.test_mode else "Production", self.dc_id, + " (media)" if self.media else "", "6" if self.ipv6 else "4", self.mode.__name__, - " (media)" if self.media else "", - self.address )) break else: From e9e6c30d05f5ebe84d6dbe8cbc0623afb97026d8 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 5 May 2021 13:58:03 +0200 Subject: [PATCH 0572/1185] Handle bot commands with trailing usernames Closes #139, #615 --- pyrogram/filters.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/pyrogram/filters.py b/pyrogram/filters.py index db08f672cb..aaff1aa1bd 100644 --- a/pyrogram/filters.py +++ b/pyrogram/filters.py @@ -764,15 +764,21 @@ def command(commands: Union[str, List[str]], prefixes: Union[str, List[str]] = " Examples: when True, command="Start" would trigger /Start but not /start. """ command_re = re.compile(r"([\"'])(.*?)(? Date: Wed, 5 May 2021 13:58:47 +0200 Subject: [PATCH 0573/1185] Update handling errors example Use asyncio.sleep instead of time.sleep --- docs/source/start/errors.rst | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/source/start/errors.rst b/docs/source/start/errors.rst index f5747d7741..8e592f18c7 100644 --- a/docs/source/start/errors.rst +++ b/docs/source/start/errors.rst @@ -96,10 +96,12 @@ The value is stored in the ``x`` attribute of the exception object: .. code-block:: python - import time + import asyncio from pyrogram.errors import FloodWait - try: - ... # Your code - except FloodWait as e: - time.sleep(e.x) # Wait "x" seconds before continuing + ... + try: + ... # Your code + except FloodWait as e: + await asyncio.sleep(e.x) # Wait "x" seconds before continuing + ... \ No newline at end of file From 6cf9dd839e29ab339dc024ae53dae37c66f9e0db Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 5 May 2021 13:59:24 +0200 Subject: [PATCH 0574/1185] Update configuration for tests --- dev-requirements.txt | 4 +++- tox.ini | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/dev-requirements.txt b/dev-requirements.txt index ed89aca2e4..17f8c0348c 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,3 +1,5 @@ -r requirements.txt -pytest \ No newline at end of file +pytest +pytest-asyncio +pytest-cov \ No newline at end of file diff --git a/tox.ini b/tox.ini index af7893a13b..1795b1f225 100644 --- a/tox.ini +++ b/tox.ini @@ -1,4 +1,4 @@ [testenv] deps = -rdev-requirements.txt -commands = pytest {posargs} +commands = coverage run -m pytest {posargs} skip_install = true \ No newline at end of file From 35df2cc1f3813d3c32a0f615d18ada22c727ab7c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 5 May 2021 14:00:25 +0200 Subject: [PATCH 0575/1185] Add filters.command tests --- tests/filters/__init__.py | 34 +++++++++ tests/filters/test_command.py | 132 ++++++++++++++++++++++++++++++++++ 2 files changed, 166 insertions(+) create mode 100644 tests/filters/__init__.py create mode 100644 tests/filters/test_command.py diff --git a/tests/filters/__init__.py b/tests/filters/__init__.py new file mode 100644 index 0000000000..56187dd8b2 --- /dev/null +++ b/tests/filters/__init__.py @@ -0,0 +1,34 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan +# +# 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 . + +class Client: + @staticmethod + async def get_me(): + return User("username") + + +class User: + def __init__(self, username: str = None): + self.username = username + + +class Message: + def __init__(self, text: str = None, caption: str = None): + self.text = text + self.caption = caption + self.command = None diff --git a/tests/filters/test_command.py b/tests/filters/test_command.py new file mode 100644 index 0000000000..a29ee8f60a --- /dev/null +++ b/tests/filters/test_command.py @@ -0,0 +1,132 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan +# +# 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 pytest + +from pyrogram import filters +from tests.filters import Client, Message + +c = Client() + + +@pytest.mark.asyncio +async def test_single(): + f = filters.command("start") + + m = Message("/start") + assert await f(c, m) + + +@pytest.mark.asyncio +async def test_multiple(): + f = filters.command(["start", "help"]) + + m = Message("/start") + assert await f(c, m) + + m = Message("/help") + assert await f(c, m) + + m = Message("/settings") + assert not await f(c, m) + + +@pytest.mark.asyncio +async def test_prefixes(): + f = filters.command("start", prefixes=list(".!#")) + + m = Message(".start") + assert await f(c, m) + + m = Message("!start") + assert await f(c, m) + + m = Message("#start") + assert await f(c, m) + + m = Message("/start") + assert not await f(c, m) + + +@pytest.mark.asyncio +async def test_case_sensitive(): + f = filters.command("start", case_sensitive=True) + + m = Message("/start") + assert await f(c, m) + + m = Message("/StArT") + assert not await f(c, m) + + +@pytest.mark.asyncio +async def test_case_insensitive(): + f = filters.command("start", case_sensitive=False) + + m = Message("/start") + assert await f(c, m) + + m = Message("/StArT") + assert await f(c, m) + + +@pytest.mark.asyncio +async def test_with_mention(): + f = filters.command("start") + + m = Message("/start@username") + assert await f(c, m) + + m = Message("/start@UserName") + assert await f(c, m) + + m = Message("/start@another") + assert not await f(c, m) + + +@pytest.mark.asyncio +async def test_with_args(): + f = filters.command("start") + + m = Message("/start a b c") + await f(c, m) + assert m.command == ["start"] + list("abc") + + m = Message("/start 'a b' c") + await f(c, m) + assert m.command == ["start", "a b", "c"] + + m = Message('/start a b "c d"') + await f(c, m) + assert m.command == ["start"] + list("ab") + ["c d"] + + +@pytest.mark.asyncio +async def test_caption(): + f = filters.command("start") + + m = Message(caption="/start") + assert await f(c, m) + + +@pytest.mark.asyncio +async def test_no_text(): + f = filters.command("start") + + m = Message() + assert not await f(c, m) From 97bd5443337a5ace31aa2becdecae122bfad1449 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 5 May 2021 14:04:39 +0200 Subject: [PATCH 0576/1185] Fix save_file path argument docs. Closes #671 --- pyrogram/methods/advanced/save_file.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pyrogram/methods/advanced/save_file.py b/pyrogram/methods/advanced/save_file.py index 83f56eb9d0..bc99b859b0 100644 --- a/pyrogram/methods/advanced/save_file.py +++ b/pyrogram/methods/advanced/save_file.py @@ -54,8 +54,9 @@ async def save_file( available yet in the Client class as an easy-to-use method). Parameters: - path (``str``): - The path of the file you want to upload that exists on your local machine. + path (``str`` | ``BinaryIO``): + The path of the file you want to upload that exists on your local machine or a binary file-like object + with its attribute ".name" set for in-memory uploads. file_id (``int``, *optional*): In case a file part expired, pass the file_id and the file_part to retry uploading that specific chunk. From 1d940b96a3d75ed5e6c29388a16c6b4b77a12cf0 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 5 May 2021 14:38:29 +0200 Subject: [PATCH 0577/1185] Turn boolean .media and .service fields into strings This way they can hold more info about the kind of media and service messages. For example: - message.media == "document" <-> message.document - message.service == "new_chat_title" <-> message.new_chat_title --- pyrogram/types/messages_and_media/message.py | 73 +++++++++++++++----- 1 file changed, 55 insertions(+), 18 deletions(-) diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index 279ac3ea3b..f349d1d76d 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -106,16 +106,17 @@ class Message(Object, Update): The message is empty. A message can be empty in case it was deleted or you tried to retrieve a message that doesn't exist yet. - service (``bool``, *optional*): - The message is a service message. - A service message has one and only one of these 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. - - media (``bool``, *optional*): - The message is a media message. + service (``str``, *optional*): + The message is a service message. This field will contain the name of the service message. + A service message has one and only one of these fields set: new_chat_members, left_chat_member, + new_chat_title, new_chat_photo, delete_chat_photo, group_chat_created, channel_chat_created, + migrate_to_chat_id, migrate_from_chat_id, pinned_message, game_high_score, voice_chat_started, + voice_chat_ended, voice_chat_scheduled, voice_chat_members_invited. + + media (``str``, *optional*): + The message is a media message. This field will contain the name of the media message. A media message has one and only one of these fields set: audio, document, photo, sticker, video, animation, - voice, video_note, contact, location, venue. + voice, video_note, contact, location, venue, poll, web_page, dice, game. edit_date (``int``, *optional*): Date the message was last edited in Unix time. @@ -304,10 +305,10 @@ def __init__( reply_to_message: "Message" = None, mentioned: bool = None, empty: bool = None, - service: bool = None, + service: str = None, scheduled: bool = None, from_scheduled: bool = None, - media: bool = None, + media: str = None, edit_date: int = None, media_group_id: str = None, author_signature: str = None, @@ -452,35 +453,51 @@ async def _parse( voice_chat_ended = None voice_chat_members_invited = None + service_type = None + if isinstance(action, raw.types.MessageActionChatAddUser): new_chat_members = [types.User._parse(client, users[i]) for i in action.users] + service_type = "new_chat_members" elif isinstance(action, raw.types.MessageActionChatJoinedByLink): new_chat_members = [types.User._parse(client, users[utils.get_raw_peer_id(message.from_id)])] + service_type = "new_chat_members" elif isinstance(action, raw.types.MessageActionChatDeleteUser): left_chat_member = types.User._parse(client, users[action.user_id]) + service_type = "left_chat_member" elif isinstance(action, raw.types.MessageActionChatEditTitle): new_chat_title = action.title + service_type = "new_chat_title" elif isinstance(action, raw.types.MessageActionChatDeletePhoto): delete_chat_photo = True + service_type = "delete_chat_photo" elif isinstance(action, raw.types.MessageActionChatMigrateTo): migrate_to_chat_id = action.channel_id + service_type = "migrate_to_chat_id" elif isinstance(action, raw.types.MessageActionChannelMigrateFrom): migrate_from_chat_id = action.chat_id + service_type = "migrate_from_chat_id" elif isinstance(action, raw.types.MessageActionChatCreate): group_chat_created = True + service_type = "group_chat_created" elif isinstance(action, raw.types.MessageActionChannelCreate): channel_chat_created = True + service_type = "channel_chat_created" elif isinstance(action, raw.types.MessageActionChatEditPhoto): new_chat_photo = types.Photo._parse(client, action.photo) + service_type = "new_chat_photo" elif isinstance(action, raw.types.MessageActionGroupCallScheduled): voice_chat_scheduled = types.VoiceChatScheduled._parse(action) + service_type = "voice_chat_scheduled" elif isinstance(action, raw.types.MessageActionGroupCall): if action.duration: voice_chat_ended = types.VoiceChatEnded._parse(action) + service_type = "voice_chat_ended" else: voice_chat_started = types.VoiceChatStarted() + service_type = "voice_chat_started" elif isinstance(action, raw.types.MessageActionInviteToGroupCall): voice_chat_members_invited = types.VoiceChatMembersInvited._parse(client, action, users) + service_type = "voice_chat_members_invited" user = utils.get_raw_peer_id(message.from_id) or utils.get_raw_peer_id(message.peer_id) from_user = types.User._parse(client, users.get(user, None)) @@ -492,7 +509,7 @@ async def _parse( chat=types.Chat._parse(client, message, users, chats), from_user=from_user, sender_chat=sender_chat, - service=True, + service=service_type, new_chat_members=new_chat_members, left_chat_member=left_chat_member, new_chat_title=new_chat_title, @@ -502,11 +519,11 @@ async def _parse( migrate_from_chat_id=-migrate_from_chat_id if migrate_from_chat_id else None, group_chat_created=group_chat_created, channel_chat_created=channel_chat_created, - client=client, voice_chat_scheduled=voice_chat_scheduled, voice_chat_started=voice_chat_started, voice_chat_ended=voice_chat_ended, - voice_chat_members_invited=voice_chat_members_invited + voice_chat_members_invited=voice_chat_members_invited, + client=client # TODO: supergroup_chat_created ) @@ -517,6 +534,8 @@ async def _parse( reply_to_message_ids=message.id, replies=0 ) + + parsed_message.service = "pinned_message" except MessageIdsEmpty: pass @@ -530,9 +549,13 @@ async def _parse( reply_to_message_ids=message.id, replies=0 ) + + parsed_message.service = "game_high_score" except MessageIdsEmpty: pass + + return parsed_message if isinstance(message, raw.types.Message): @@ -581,18 +604,24 @@ async def _parse( dice = None media = message.media + media_type = None if media: if isinstance(media, raw.types.MessageMediaPhoto): photo = types.Photo._parse(client, media.photo, media.ttl_seconds) + media_type = "photo" elif isinstance(media, raw.types.MessageMediaGeo): location = types.Location._parse(client, media.geo) + media_type = "location" elif isinstance(media, raw.types.MessageMediaContact): contact = types.Contact._parse(client, media) + media_type = "contact" elif isinstance(media, raw.types.MessageMediaVenue): venue = types.Venue._parse(client, media) + media_type = "venue" elif isinstance(media, raw.types.MessageMediaGame): game = types.Game._parse(client, message) + media_type = "game" elif isinstance(media, raw.types.MessageMediaDocument): doc = media.document @@ -610,20 +639,23 @@ async def _parse( if audio_attributes.voice: voice = types.Voice._parse(client, doc, audio_attributes) + media_type = "voice" else: audio = types.Audio._parse(client, doc, audio_attributes, file_name) + media_type = "audio" elif raw.types.DocumentAttributeAnimated in attributes: video_attributes = attributes.get(raw.types.DocumentAttributeVideo, None) - animation = types.Animation._parse(client, doc, video_attributes, file_name) + media_type = "animation" elif raw.types.DocumentAttributeVideo in attributes: video_attributes = attributes[raw.types.DocumentAttributeVideo] if video_attributes.round_message: video_note = types.VideoNote._parse(client, doc, video_attributes) + media_type = "video_note" else: - video = types.Video._parse(client, doc, video_attributes, file_name, - media.ttl_seconds) + video = types.Video._parse(client, doc, video_attributes, file_name, media.ttl_seconds) + media_type = "video" elif raw.types.DocumentAttributeSticker in attributes: sticker = await types.Sticker._parse( client, doc, @@ -631,17 +663,22 @@ async def _parse( attributes[raw.types.DocumentAttributeSticker], file_name ) + media_type = "sticker" else: document = types.Document._parse(client, doc, file_name) + media_type = "document" elif isinstance(media, raw.types.MessageMediaWebPage): if isinstance(media.webpage, raw.types.WebPage): web_page = types.WebPage._parse(client, media.webpage) + media_type = "web_page" else: media = None elif isinstance(media, raw.types.MessageMediaPoll): poll = types.Poll._parse(client, media) + media_type = "poll" elif isinstance(media, raw.types.MessageMediaDice): dice = types.Dice._parse(client, media) + media_type = "dice" else: media = None @@ -699,7 +736,7 @@ async def _parse( mentioned=message.mentioned, scheduled=is_scheduled, from_scheduled=message.from_scheduled, - media=bool(media) or None, + media=media_type, edit_date=message.edit_date, media_group_id=message.grouped_id, photo=photo, From 4fc45014457b8a4dbceda6bf696bcbbc11a6aa41 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 6 May 2021 13:02:26 +0200 Subject: [PATCH 0578/1185] Remove trailing username when adding args to Message.command Fixes #676 --- pyrogram/filters.py | 10 +++++----- tests/filters/test_command.py | 4 ++++ 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/pyrogram/filters.py b/pyrogram/filters.py index aaff1aa1bd..4a07413d42 100644 --- a/pyrogram/filters.py +++ b/pyrogram/filters.py @@ -770,7 +770,7 @@ async def func(flt, client: pyrogram.Client, message: Message): nonlocal username if username is None: - username = (await client.get_me()).username + username = (await client.get_me()).username or "" text = message.text or message.caption message.command = None @@ -778,8 +778,6 @@ async def func(flt, client: pyrogram.Client, message: Message): if not text: return False - pattern = rf"^(?:{{cmd}}|{{cmd}}@{username})(?:\s|$)" if username else r"^{cmd}(?:\s|$)" - for prefix in flt.prefixes: if not text.startswith(prefix): continue @@ -787,17 +785,19 @@ async def func(flt, client: pyrogram.Client, message: Message): without_prefix = text[len(prefix):] for cmd in flt.commands: - if not re.match(pattern.format(cmd=re.escape(cmd)), without_prefix, + if not re.match(rf"^(?:{cmd}(?:@?{username})?)(?:\s|$)", without_prefix, flags=re.IGNORECASE if not flt.case_sensitive else 0): continue + without_command = re.sub(rf"{cmd}(?:@?{username})?\s", "", without_prefix, count=1) + # match.groups are 1-indexed, group(1) is the quote, group(2) is the text # between the quotes, group(3) is unquoted, whitespace-split text # Remove the escape character from the arguments message.command = [cmd] + [ re.sub(r"\\([\"'])", r"\1", m.group(2) or m.group(3) or "") - for m in command_re.finditer(without_prefix[len(cmd):]) + for m in command_re.finditer(without_command) ] return True diff --git a/tests/filters/test_command.py b/tests/filters/test_command.py index a29ee8f60a..0cd00e956a 100644 --- a/tests/filters/test_command.py +++ b/tests/filters/test_command.py @@ -107,6 +107,10 @@ async def test_with_args(): await f(c, m) assert m.command == ["start"] + list("abc") + m = Message('/start@username a b c') + await f(c, m) + assert m.command == ["start"] + list("abc") + m = Message("/start 'a b' c") await f(c, m) assert m.command == ["start", "a b", "c"] From 636ff776d61a5823be88d75621f4e9c3b6fe9c55 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 6 May 2021 19:20:12 +0200 Subject: [PATCH 0579/1185] Fix duplicated commands in Message.command Also add more test cases Related to #676 --- pyrogram/filters.py | 2 +- tests/filters/test_command.py | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/pyrogram/filters.py b/pyrogram/filters.py index 4a07413d42..9a1b2bddfe 100644 --- a/pyrogram/filters.py +++ b/pyrogram/filters.py @@ -789,7 +789,7 @@ async def func(flt, client: pyrogram.Client, message: Message): flags=re.IGNORECASE if not flt.case_sensitive else 0): continue - without_command = re.sub(rf"{cmd}(?:@?{username})?\s", "", without_prefix, count=1) + without_command = re.sub(rf"{cmd}(?:@?{username})?\s?", "", without_prefix, count=1) # match.groups are 1-indexed, group(1) is the quote, group(2) is the text # between the quotes, group(3) is unquoted, whitespace-split text diff --git a/tests/filters/test_command.py b/tests/filters/test_command.py index 0cd00e956a..d9a7490e1a 100644 --- a/tests/filters/test_command.py +++ b/tests/filters/test_command.py @@ -103,6 +103,14 @@ async def test_with_mention(): async def test_with_args(): f = filters.command("start") + m = Message("/start") + await f(c, m) + assert m.command == ["start"] + + m = Message("/start@username") + await f(c, m) + assert m.command == ["start"] + m = Message("/start a b c") await f(c, m) assert m.command == ["start"] + list("abc") From 934091d8ea6d59f9ad510a8edb84fb6af836f010 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 7 May 2021 13:18:00 +0200 Subject: [PATCH 0580/1185] Fix get_me being called for every command It needs to be called once only --- pyrogram/filters.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/pyrogram/filters.py b/pyrogram/filters.py index 9a1b2bddfe..d969026be6 100644 --- a/pyrogram/filters.py +++ b/pyrogram/filters.py @@ -740,10 +740,15 @@ async def linked_channel_filter(_, __, m: Message): linked_channel = create(linked_channel_filter) """Filter messages that are automatically forwarded from the linked channel to the group chat.""" - # endregion +# region command_filter + +# Used by the command filter below +username = None + + def command(commands: Union[str, List[str]], prefixes: Union[str, List[str]] = "/", case_sensitive: bool = False): """Filter commands, i.e.: text messages starting with "/" or any other custom prefix. @@ -764,10 +769,10 @@ def command(commands: Union[str, List[str]], prefixes: Union[str, List[str]] = " Examples: when True, command="Start" would trigger /Start but not /start. """ command_re = re.compile(r"([\"'])(.*?)(? Date: Sun, 9 May 2021 13:39:49 +0300 Subject: [PATCH 0581/1185] Add method Client.copy_media_group (#592) * Add method client.copy_media_group * -updated wrong variable names * Replace with * Update copy_media_group.py * Update copy_media_group.py * Update compiler.py * Update copy_media_group.py Co-authored-by: Dan <14043624+delivrance@users.noreply.github.com> --- compiler/docs/compiler.py | 1 + pyrogram/methods/messages/__init__.py | 4 +- pyrogram/methods/messages/copy_media_group.py | 133 ++++++++++++++++++ 3 files changed, 137 insertions(+), 1 deletion(-) create mode 100644 pyrogram/methods/messages/copy_media_group.py diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index 1d4ba9e4fe..9513899a18 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -144,6 +144,7 @@ def get_title_list(s: str) -> list: send_message forward_messages copy_message + copy_media_group send_photo send_audio send_document diff --git a/pyrogram/methods/messages/__init__.py b/pyrogram/methods/messages/__init__.py index 879b17f193..5ebedfde28 100644 --- a/pyrogram/methods/messages/__init__.py +++ b/pyrogram/methods/messages/__init__.py @@ -17,6 +17,7 @@ # along with Pyrogram. If not, see . from .copy_message import CopyMessage +from .copy_media_group import CopyMediaGroup from .delete_messages import DeleteMessages from .download_media import DownloadMedia from .edit_inline_caption import EditInlineCaption @@ -98,6 +99,7 @@ class Messages( SendDice, SearchMessages, SearchGlobal, - CopyMessage + CopyMessage, + CopyMediaGroup ): pass diff --git a/pyrogram/methods/messages/copy_media_group.py b/pyrogram/methods/messages/copy_media_group.py new file mode 100644 index 0000000000..7845863ccd --- /dev/null +++ b/pyrogram/methods/messages/copy_media_group.py @@ -0,0 +1,133 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan +# +# 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 Union, List, Optional + +from pyrogram import types, utils, raw +from pyrogram.scaffold import Scaffold + + +class CopyMediaGroup(Scaffold): + async def copy_media_group( + self, + chat_id: Union[int, str], + from_chat_id: Union[int, str], + message_id: int, + captions: Union[List[str], str] = None, + disable_notification: bool = None, + reply_to_message_id: int = None, + schedule_date: int = None, + ) -> List["types.Message"]: + """Copy a media group by providing one of the message ids. + + Parameters: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + For your personal cloud (Saved Messages) you can simply use "me" or "self". + For a contact that exists in your Telegram address book you can use his phone number (str). + + from_chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the source chat where the original media group was sent. + For your personal cloud (Saved Messages) you can simply use "me" or "self". + For a contact that exists in your Telegram address book you can use his phone number (str). + + message_id (``int``): + Message identifier in the chat specified in *from_chat_id*. + + captions (``str`` | List of ``str`` , *optional*): + New caption for media, 0-1024 characters after entities parsing for each media. + If not specified, the original caption is kept. + Pass "" (empty string) to remove the caption. + + If a ``string`` is passed, it becomes a caption only for the first media. + If a list of ``string`` passed, each element becomes caption for each media element. + You can pass ``None`` in list to keep the original caption (see examples below). + + disable_notification (``bool``, *optional*): + Sends the message silently. + Users will receive a notification with no sound. + + reply_to_message_id (``int``, *optional*): + If the message is a reply, ID of the original message. + + schedule_date (``int``, *optional*): + Date when the message will be automatically sent. Unix time. + + Returns: + List of :obj:`~pyrogram.types.Message`: On success, a list of copied messages is returned. + + Example: + .. code-block:: python + + # Copy a media group + app.copy_media_group("me", source_chat, message_id) + app.copy_media_group("me", source_chat, message_id, captions="single caption") + app.copy_media_group("me", source_chat, message_id, captions=["caption 1", None, ""]) + """ + + media_group = await self.get_media_group(from_chat_id, message_id) + multi_media = [] + + for i, message in enumerate(media_group): + if message.photo: + file_id = message.photo.file_id + elif message.audio: + file_id = message.audio.file_id + elif message.document: + file_id = message.document.file_id + elif message.video: + file_id = message.video.file_id + else: + raise ValueError("Message with this type can't be copied.") + + media = utils.get_input_media_from_file_id(file_id=file_id) + multi_media.append( + raw.types.InputSingleMedia( + media=media, + random_id=self.rnd_id(), + **await self.parser.parse( + captions[i] if isinstance(captions, list) and i < len(captions) and captions[i] else + captions if isinstance(captions, str) and i == 0 else + message.caption if message.caption and message.caption != "None" and not type(captions) is str else "") + ) + ) + + r = await self.send( + raw.functions.messages.SendMultiMedia( + peer=await self.resolve_peer(chat_id), + multi_media=multi_media, + silent=disable_notification or None, + reply_to_msg_id=reply_to_message_id, + schedule_date=schedule_date + ), + sleep_threshold=60 + ) + + return await utils.parse_messages( + self, + raw.types.messages.Messages( + messages=[m.message for m in filter( + lambda u: isinstance(u, (raw.types.UpdateNewMessage, + raw.types.UpdateNewChannelMessage, + raw.types.UpdateNewScheduledMessage)), + r.updates + )], + users=r.users, + chats=r.chats + ) + ) From ecd83c594cc72e10e13d8c427e817f3495d97252 Mon Sep 17 00:00:00 2001 From: Danipulok <45077699+Danipulok@users.noreply.github.com> Date: Sun, 9 May 2021 13:43:23 +0300 Subject: [PATCH 0582/1185] Fixed method Client.get_media_group (#619) * Fixed method * Added exception in case message_id is invalid Co-authored-by: Dan <14043624+delivrance@users.noreply.github.com> --- pyrogram/methods/messages/get_media_group.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/pyrogram/methods/messages/get_media_group.py b/pyrogram/methods/messages/get_media_group.py index 7c5249e9f3..83fff12a15 100644 --- a/pyrogram/methods/messages/get_media_group.py +++ b/pyrogram/methods/messages/get_media_group.py @@ -46,13 +46,27 @@ async def get_media_group( List of :obj:`~pyrogram.types.Message`: On success, a list of messages of the media group is returned. Raises: - ValueError: In case the passed message id doesn't belong to a media group. + ValueError: + In case the passed message_id is negative or equal 0. + In case target message doesn't belong to a media group. """ + # There can be maximum 10 items in a media group. messages = await self.get_messages(chat_id, [msg_id for msg_id in range(message_id - 9, message_id + 10)], replies=0) - media_group_id = messages[9].media_group_id + if message_id <= 0: + raise ValueError("Passed message_id is negative or equal to zero.") + + messages = await self.get_messages( + chat_id=chat_id, + message_ids=[msg_id for msg_id in range(message_id - 9, message_id + 10)], + replies=0 + ) + + # There can be maximum 10 items in a media group. + # The if/else condition to fix the problem of getting correct `media_group_id` when it has `message_id` less then 10. + media_group_id = messages[9].media_group_id if len(messages) == 19 else messages[message_id-1].media_group_id if media_group_id is None: raise ValueError("The message doesn't belong to a media group") From ed3576e8e655a24192303b0e9295f1fe16c0e97a Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 11 May 2021 10:18:30 +0200 Subject: [PATCH 0583/1185] Enable GitHub Actions on Pull Requests --- .github/workflows/python.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 7a4b996e7b..5bece5fc7a 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -1,6 +1,6 @@ name: Pyrogram -on: [ push ] +on: [ push, pull_request ] jobs: build: @@ -30,4 +30,4 @@ jobs: - name: Run tests run: | - tox \ No newline at end of file + tox From 293e852afd622f996596c59378ac6a31e56742cf Mon Sep 17 00:00:00 2001 From: Jonathan <49692607+jonatan1609@users.noreply.github.com> Date: Tue, 11 May 2021 11:22:17 +0300 Subject: [PATCH 0584/1185] Add new method set_bot_commands (#657) * a new method set_bot_commands * Delete bot_commands_list.py * Update set_bot_commands.py * Update __init__.py * Update set_bot_commands.py * Update set_bot_commands.py * Update bot_command.py * Update set_bot_commands.py * Update set_bot_commands.py * Update compiler.py Co-authored-by: Dan <14043624+delivrance@users.noreply.github.com> --- compiler/docs/compiler.py | 1 + pyrogram/methods/bots/__init__.py | 4 +- pyrogram/methods/bots/set_bot_commands.py | 59 +++++++++++++++++++ pyrogram/types/bots_and_keyboards/__init__.py | 4 +- .../types/bots_and_keyboards/bot_command.py | 44 ++++++++++++++ 5 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 pyrogram/methods/bots/set_bot_commands.py create mode 100644 pyrogram/types/bots_and_keyboards/bot_command.py diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index 9513899a18..a5ce87e41a 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -394,6 +394,7 @@ def get_title_list(s: str) -> list: CallbackQuery GameHighScore CallbackGame + BotCommand """, input_media=""" Input Media diff --git a/pyrogram/methods/bots/__init__.py b/pyrogram/methods/bots/__init__.py index 2c528a158a..8cc7dd3066 100644 --- a/pyrogram/methods/bots/__init__.py +++ b/pyrogram/methods/bots/__init__.py @@ -24,6 +24,7 @@ from .send_game import SendGame from .send_inline_bot_result import SendInlineBotResult from .set_game_score import SetGameScore +from .set_bot_commands import SetBotCommands class Bots( @@ -34,6 +35,7 @@ class Bots( SendInlineBotResult, SendGame, SetGameScore, - GetGameHighScores + GetGameHighScores, + SetBotCommands ): pass diff --git a/pyrogram/methods/bots/set_bot_commands.py b/pyrogram/methods/bots/set_bot_commands.py new file mode 100644 index 0000000000..3a3c8f54a8 --- /dev/null +++ b/pyrogram/methods/bots/set_bot_commands.py @@ -0,0 +1,59 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan +# +# 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 Optional, List + +from pyrogram import raw +from pyrogram import types +from pyrogram.scaffold import Scaffold + + +class SetBotCommands(Scaffold): + async def set_bot_commands(self, commands: Optional[List[types.BotCommand]]): + """Set the bot commands list. + + The commands passed will overwrite any command set previously. + This method can be used by the own bot only. + + Parameters: + commands (List of :obj:`~pyrogram.types.BotCommand`): + A list of bot commands. + Pass None to remove all commands. + + Returns: + ``bool``: True on success, False otherwise. + + Example: + .. code-block:: python + + from pyrogram.types import BotCommand + + # Set new commands + app.set_bot_commands([ + BotCommand("start", "Start the bot"), + BotCommand("settings", "Bot settings")]) + + # Remove commands + app.set_bot_commands(None) + """ + + return await self.send( + raw.functions.bots.SetBotCommands( + commands=[c.write() for c in commands or []] + ) + ) diff --git a/pyrogram/types/bots_and_keyboards/__init__.py b/pyrogram/types/bots_and_keyboards/__init__.py index 9e486cb56d..a1f3a662d8 100644 --- a/pyrogram/types/bots_and_keyboards/__init__.py +++ b/pyrogram/types/bots_and_keyboards/__init__.py @@ -26,6 +26,7 @@ from .login_url import LoginUrl from .reply_keyboard_markup import ReplyKeyboardMarkup from .reply_keyboard_remove import ReplyKeyboardRemove +from .bot_command import BotCommand __all__ = [ "CallbackGame", @@ -37,5 +38,6 @@ "KeyboardButton", "ReplyKeyboardMarkup", "ReplyKeyboardRemove", - "LoginUrl" + "LoginUrl", + "BotCommand", ] diff --git a/pyrogram/types/bots_and_keyboards/bot_command.py b/pyrogram/types/bots_and_keyboards/bot_command.py new file mode 100644 index 0000000000..f2db126a53 --- /dev/null +++ b/pyrogram/types/bots_and_keyboards/bot_command.py @@ -0,0 +1,44 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan +# +# 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 pyrogram import raw +from ..object import Object + + +class BotCommand(Object): + """A bot command with the standard slash "/" prefix. + + Parameters: + command (``str``): + The bot command, for example: "/start". + + description (``str``): + Description of the bot command. + """ + + def __init__(self, command: str, description: str): + super().__init__() + + self.command = command + self.description = description + + def write(self): + return raw.types.BotCommand( + command=self.command, + description=self.description + ) From 3be981ada10f187d29bccdbb7ea09623549cc488 Mon Sep 17 00:00:00 2001 From: trenoduro Date: Tue, 11 May 2021 10:45:55 +0200 Subject: [PATCH 0585/1185] Improved deleted message handling (#645) * Tidy up namings of invite link methods * Improved deleted message handling Co-authored-by: Dan <14043624+delivrance@users.noreply.github.com> --- pyrogram/handlers/deleted_messages_handler.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pyrogram/handlers/deleted_messages_handler.py b/pyrogram/handlers/deleted_messages_handler.py index dff1ebe78f..e41158f93b 100644 --- a/pyrogram/handlers/deleted_messages_handler.py +++ b/pyrogram/handlers/deleted_messages_handler.py @@ -52,4 +52,10 @@ def __init__(self, callback: Callable, filters: Filter = None): super().__init__(callback, filters) async def check(self, client: "pyrogram.Client", messages: List[Message]): - return await super().check(client, messages[0]) if messages else False # The messages list can be empty + # Every message should be checked, if at least one matches the filter True is returned + # otherwise, or if the list is empty, False is returned + for message in messages: + if await super().check(client, message): + return True + else: + return False From 56e79c10f8c9c465ccb04d4bc3e1e4ee110ec007 Mon Sep 17 00:00:00 2001 From: Leorio Paradinight <62891774+code-rgb@users.noreply.github.com> Date: Wed, 12 May 2021 11:58:21 +0530 Subject: [PATCH 0586/1185] Fix captions being None when editing media message (#617) * added missing doc string * Fix for "None" for default caption --- pyrogram/types/input_media/input_media.py | 2 +- pyrogram/types/input_media/input_media_animation.py | 5 +++-- pyrogram/types/input_media/input_media_audio.py | 5 +++-- pyrogram/types/input_media/input_media_document.py | 5 +++-- pyrogram/types/input_media/input_media_photo.py | 6 +++--- pyrogram/types/input_media/input_media_video.py | 6 +++--- 6 files changed, 16 insertions(+), 13 deletions(-) diff --git a/pyrogram/types/input_media/input_media.py b/pyrogram/types/input_media/input_media.py index a510ad2695..16a72bdadd 100644 --- a/pyrogram/types/input_media/input_media.py +++ b/pyrogram/types/input_media/input_media.py @@ -37,7 +37,7 @@ class InputMedia(Object): def __init__( self, media: str, - caption: str = None, + caption: str = "", parse_mode: str = None, caption_entities: List[MessageEntity] = None ): diff --git a/pyrogram/types/input_media/input_media_animation.py b/pyrogram/types/input_media/input_media_animation.py index a209b80d71..e39a148e82 100644 --- a/pyrogram/types/input_media/input_media_animation.py +++ b/pyrogram/types/input_media/input_media_animation.py @@ -29,7 +29,8 @@ class InputMediaAnimation(InputMedia): media (``str``): Animation to send. Pass a file_id as string to send a file that exists on the Telegram servers or - pass a file path as string to upload a new file that exists on your local machine. + pass a file path as string to upload a new file that exists on your local machine or + pass an HTTP URL as a string for Telegram to get an animation from the Internet. thumb (``str``, *optional*): Thumbnail of the animation file sent. @@ -65,7 +66,7 @@ def __init__( self, media: str, thumb: str = None, - caption: str = None, + caption: str = "", parse_mode: Optional[str] = object, caption_entities: List[MessageEntity] = None, width: int = 0, diff --git a/pyrogram/types/input_media/input_media_audio.py b/pyrogram/types/input_media/input_media_audio.py index fba368c826..e47bc8185f 100644 --- a/pyrogram/types/input_media/input_media_audio.py +++ b/pyrogram/types/input_media/input_media_audio.py @@ -31,7 +31,8 @@ class InputMediaAudio(InputMedia): media (``str``): Audio to send. Pass a file_id as string to send an audio that exists on the Telegram servers or - pass a file path as string to upload a new audio that exists on your local machine. + pass a file path as string to upload a new audio that exists on your local machine or + pass an HTTP URL as a string for Telegram to get an audio file from the Internet. thumb (``str``, *optional*): Thumbnail of the music file album cover. @@ -67,7 +68,7 @@ def __init__( self, media: str, thumb: str = None, - caption: str = None, + caption: str = "", parse_mode: Optional[str] = object, caption_entities: List[MessageEntity] = None, duration: int = 0, diff --git a/pyrogram/types/input_media/input_media_document.py b/pyrogram/types/input_media/input_media_document.py index 2701c0ae31..12b9fc940d 100644 --- a/pyrogram/types/input_media/input_media_document.py +++ b/pyrogram/types/input_media/input_media_document.py @@ -29,7 +29,8 @@ class InputMediaDocument(InputMedia): media (``str``): File to send. Pass a file_id as string to send a file that exists on the Telegram servers or - pass a file path as string to upload a new file that exists on your local machine. + pass a file path as string to upload a new file that exists on your local machine or + pass an HTTP URL as a string for Telegram to get a file from the Internet. thumb (``str``): Thumbnail of the file sent. @@ -56,7 +57,7 @@ def __init__( self, media: str, thumb: str = None, - caption: str = None, + caption: str = "", parse_mode: Optional[str] = object, caption_entities: List[MessageEntity] = None ): diff --git a/pyrogram/types/input_media/input_media_photo.py b/pyrogram/types/input_media/input_media_photo.py index ac1cc4441f..a821354054 100644 --- a/pyrogram/types/input_media/input_media_photo.py +++ b/pyrogram/types/input_media/input_media_photo.py @@ -30,8 +30,8 @@ class InputMediaPhoto(InputMedia): media (``str``): Photo to send. Pass a file_id as string to send a photo that exists on the Telegram servers or - pass a file path as string to upload a new photo that exists on your local machine. - Sending photo by a URL is currently unsupported. + pass a file path as string to upload a new photo that exists on your local machine or + pass an HTTP URL as a string for Telegram to get a photo from the Internet. caption (``str``, *optional*): Caption of the photo to be sent, 0-1024 characters. @@ -51,7 +51,7 @@ class InputMediaPhoto(InputMedia): def __init__( self, media: str, - caption: str = None, + caption: str = "", parse_mode: Optional[str] = object, caption_entities: List[MessageEntity] = None ): diff --git a/pyrogram/types/input_media/input_media_video.py b/pyrogram/types/input_media/input_media_video.py index 13ce18d620..b0c368307d 100644 --- a/pyrogram/types/input_media/input_media_video.py +++ b/pyrogram/types/input_media/input_media_video.py @@ -30,8 +30,8 @@ class InputMediaVideo(InputMedia): media (``str``): Video to send. Pass a file_id as string to send a video that exists on the Telegram servers or - pass a file path as string to upload a new video that exists on your local machine. - Sending video by a URL is currently unsupported. + pass a file path as string to upload a new video that exists on your local machine or + pass an HTTP URL as a string for Telegram to get a video from the Internet. thumb (``str``): Thumbnail of the video sent. @@ -70,7 +70,7 @@ def __init__( self, media: str, thumb: str = None, - caption: str = None, + caption: str = "", parse_mode: Optional[str] = object, caption_entities: List[MessageEntity] = None, width: int = 0, From fd04648ea01a38ba938243527da6a36c04ed1375 Mon Sep 17 00:00:00 2001 From: Mahesh <44301650+Mahesh0253@users.noreply.github.com> Date: Wed, 12 May 2021 11:59:49 +0530 Subject: [PATCH 0587/1185] Add support for editing inline messages' media using local files (#675) * Fix edit_inline_media for local files * Update edit_inline_media.py * Update edit_inline_media.py Co-authored-by: Dan <14043624+delivrance@users.noreply.github.com> --- .../methods/messages/edit_inline_media.py | 77 +++++++++++++++++-- 1 file changed, 72 insertions(+), 5 deletions(-) diff --git a/pyrogram/methods/messages/edit_inline_media.py b/pyrogram/methods/messages/edit_inline_media.py index 35a1f2fd4c..13b2736649 100644 --- a/pyrogram/methods/messages/edit_inline_media.py +++ b/pyrogram/methods/messages/edit_inline_media.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 os import re from pyrogram import raw @@ -72,35 +73,101 @@ async def edit_inline_media( parse_mode = media.parse_mode if isinstance(media, types.InputMediaPhoto): - if re.match("^https?://", media.media): + if os.path.isfile(media.media): + media = raw.types.InputMediaUploadedPhoto( + file=await self.save_file(media.media) + ) + elif re.match("^https?://", media.media): media = raw.types.InputMediaPhotoExternal( url=media.media ) else: media = utils.get_input_media_from_file_id(media.media, FileType.PHOTO) elif isinstance(media, types.InputMediaVideo): - if re.match("^https?://", media.media): + if os.path.isfile(media.media): + media = raw.types.InputMediaUploadedDocument( + mime_type=self.guess_mime_type(media.media) or "video/mp4", + thumb=await self.save_file(media.thumb), + file=await self.save_file(media.media), + attributes=[ + raw.types.DocumentAttributeVideo( + supports_streaming=media.supports_streaming or None, + duration=media.duration, + w=media.width, + h=media.height + ), + raw.types.DocumentAttributeFilename( + file_name=os.path.basename(media.media) + ) + ] + ) + elif re.match("^https?://", media.media): media = raw.types.InputMediaDocumentExternal( url=media.media ) else: media = utils.get_input_media_from_file_id(media.media, FileType.VIDEO) elif isinstance(media, types.InputMediaAudio): - if re.match("^https?://", media.media): + if os.path.isfile(media.media): + media = raw.types.InputMediaUploadedDocument( + mime_type=self.guess_mime_type(media.media) or "audio/mpeg", + thumb=await self.save_file(media.thumb), + file=await self.save_file(media.media), + attributes=[ + raw.types.DocumentAttributeAudio( + duration=media.duration, + performer=media.performer, + title=media.title + ), + raw.types.DocumentAttributeFilename( + file_name=os.path.basename(media.media) + ) + ] + ) + elif re.match("^https?://", media.media): media = raw.types.InputMediaDocumentExternal( url=media.media ) else: media = utils.get_input_media_from_file_id(media.media, FileType.AUDIO) elif isinstance(media, types.InputMediaAnimation): - if re.match("^https?://", media.media): + if os.path.isfile(media.media): + media = raw.types.InputMediaUploadedDocument( + mime_type=self.guess_mime_type(media.media) or "video/mp4", + thumb=await self.save_file(media.thumb), + file=await self.save_file(media.media), + attributes=[ + raw.types.DocumentAttributeVideo( + supports_streaming=True, + duration=media.duration, + w=media.width, + h=media.height + ), + raw.types.DocumentAttributeFilename( + file_name=os.path.basename(media.media) + ), + raw.types.DocumentAttributeAnimated() + ] + ) + elif re.match("^https?://", media.media): media = raw.types.InputMediaDocumentExternal( url=media.media ) else: media = utils.get_input_media_from_file_id(media.media, FileType.ANIMATION) elif isinstance(media, types.InputMediaDocument): - if re.match("^https?://", media.media): + if os.path.isfile(media.media): + media = raw.types.InputMediaUploadedDocument( + mime_type=self.guess_mime_type(media.media) or "application/zip", + thumb=await self.save_file(media.thumb), + file=await self.save_file(media.media), + attributes=[ + raw.types.DocumentAttributeFilename( + file_name=os.path.basename(media.media) + ) + ] + ) + elif re.match("^https?://", media.media): media = raw.types.InputMediaDocumentExternal( url=media.media ) From 0c46b653ecadab33155b15aef3f5c4c24b738784 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 12 May 2021 08:31:08 +0200 Subject: [PATCH 0588/1185] Remove unneeded check --- pyrogram/methods/messages/send_media_group.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/methods/messages/send_media_group.py b/pyrogram/methods/messages/send_media_group.py index eb1660ec3b..d591c99b55 100644 --- a/pyrogram/methods/messages/send_media_group.py +++ b/pyrogram/methods/messages/send_media_group.py @@ -265,7 +265,7 @@ async def send_media_group( raw.types.InputSingleMedia( media=media, random_id=self.rnd_id(), - **await self.parser.parse(i.caption or "", i.parse_mode) + **await self.parser.parse(i.caption, i.parse_mode) ) ) From b4bdab10475fab9cc35ffaf2b807c72046056bd9 Mon Sep 17 00:00:00 2001 From: Alan <40204040+JollyGoal@users.noreply.github.com> Date: Wed, 12 May 2021 11:47:41 +0500 Subject: [PATCH 0589/1185] Add InlineQueryResultAudio (#539) * Added audio support for inline query * mime-type removed * Update inline_query_result_audio.py Co-authored-by: Dan <14043624+delivrance@users.noreply.github.com> --- pyrogram/types/inline_mode/__init__.py | 3 +- .../inline_mode/inline_query_result_audio.py | 124 ++++++++++++++++++ 2 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 pyrogram/types/inline_mode/inline_query_result_audio.py diff --git a/pyrogram/types/inline_mode/__init__.py b/pyrogram/types/inline_mode/__init__.py index fee7b94a43..41e2eb5b6b 100644 --- a/pyrogram/types/inline_mode/__init__.py +++ b/pyrogram/types/inline_mode/__init__.py @@ -22,8 +22,9 @@ from .inline_query_result_animation import InlineQueryResultAnimation from .inline_query_result_article import InlineQueryResultArticle from .inline_query_result_photo import InlineQueryResultPhoto +from .inline_query_result_audio import InlineQueryResultAudio __all__ = [ "InlineQuery", "InlineQueryResult", "InlineQueryResultArticle", "InlineQueryResultPhoto", - "InlineQueryResultAnimation", "ChosenInlineResult" + "InlineQueryResultAnimation", "InlineQueryResultAudio", "ChosenInlineResult" ] diff --git a/pyrogram/types/inline_mode/inline_query_result_audio.py b/pyrogram/types/inline_mode/inline_query_result_audio.py new file mode 100644 index 0000000000..48f88271cf --- /dev/null +++ b/pyrogram/types/inline_mode/inline_query_result_audio.py @@ -0,0 +1,124 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2020 Dan +# +# 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 Union + +from pyrogram import raw +from pyrogram import types +from pyrogram.parser import Parser +from pyrogram.types import InlineQueryResult + + +class InlineQueryResultAudio(InlineQueryResult): + """Link to an audio file. + + By default, this audio file will be sent by the user with optional caption. + Alternatively, you can use *input_message_content* to send a message with the specified content instead of the + audio. + + Parameters: + audio_url (``str``): + A valid URL for the audio file. + + title (``str``): + Title for the result. + + id (``str``, *optional*): + Unique identifier for this result, 1-64 bytes. + Defaults to a randomly generated UUID4. + + performer (``str``, *optional*): + Audio performer. + + audio_duration (``int``, *optional*): + Audio duration in seconds. + + caption (``str``, *optional*): + Caption of the photo to be sent, 0-1024 characters. + + parse_mode (``str``, *optional*): + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" or "md" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. + + caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): + List of special entities that appear in the caption, which can be specified instead of *parse_mode*. + + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*): + Inline keyboard attached to the message. + + input_message_content (:obj:`~pyrogram.types.InputMessageContent`, *optional*): + Content of the message to be sent instead of the photo. + """ + + def __init__( + self, + audio_url: str, + title: str, + id: str = None, + performer: str = "", + audio_duration: int = 0, + caption: str = "", + parse_mode: Union[str, None] = object, + caption_entities: List["types.MessageEntity"] = None, + reply_markup: "types.InlineKeyboardMarkup" = None, + input_message_content: "types.InputMessageContent" = None + ): + super().__init__("audio", id, input_message_content, reply_markup) + + self.audio_url = audio_url + self.title = title + self.performer = performer + self.audio_duration = audio_duration + self.caption = caption + self.parse_mode = parse_mode + self.caption_entities = caption_entities + + async def write(self): + audio = raw.types.InputWebDocument( + url=self.audio_url, + size=0, + mime_type="audio/mpeg", + attributes=[raw.types.DocumentAttributeAudio( + duration=self.audio_duration, + title=self.title, + performer=self.performer + )], + ) + + message, entities = (await utils.parse_text_entities( + client, self.caption, self.parse_mode, self.caption_entities + )).values() + + return raw.types.InputBotInlineResult( + id=self.id, + type=self.type, + title=self.title, + content=audio, + send_message=( + await self.input_message_content.write(client, self.reply_markup) + if self.input_message_content + else raw.types.InputBotInlineMessageMediaAuto( + reply_markup=await self.reply_markup.write(client) if self.reply_markup else None, + message=message, + entities=entities + ) + ) + ) From df4419da8ff3c4917375fbc22ba288c1db99df97 Mon Sep 17 00:00:00 2001 From: Alisson Lauffer Date: Wed, 12 May 2021 03:49:48 -0300 Subject: [PATCH 0590/1185] Make parse_mode always lowercase (#575) * Make parse_mode always lowercase * Update client.py Co-authored-by: Dan <14043624+delivrance@users.noreply.github.com> --- pyrogram/client.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyrogram/client.py b/pyrogram/client.py index 0dc344eca4..b54794fad2 100644 --- a/pyrogram/client.py +++ b/pyrogram/client.py @@ -397,6 +397,9 @@ def parse_mode(self): @parse_mode.setter def parse_mode(self, parse_mode: Optional[str] = "combined"): + if isinstance(parse_mode, str): + parse_mode = parse_mode.lower() + if parse_mode not in self.PARSE_MODES: raise ValueError('parse_mode must be one of {} or None. Not "{}"'.format( ", ".join(f'"{m}"' for m in self.PARSE_MODES[:-1]), From 170442069713792a98f2b0fa543ff76e0d8ff840 Mon Sep 17 00:00:00 2001 From: Lorenzo Delmonte Date: Wed, 12 May 2021 08:52:28 +0200 Subject: [PATCH 0591/1185] Fix wrongly passed positional arguments (#603) Since CallbackQuery.edit_message_text takes 4 arguments and CallbackQuery.edit_message_caption only 3, the reply_markup ends up to be the disable_web_page_preview one. Resolve this by specifying the argument name --- pyrogram/types/bots_and_keyboards/callback_query.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/types/bots_and_keyboards/callback_query.py b/pyrogram/types/bots_and_keyboards/callback_query.py index eba23b624f..11e749d135 100644 --- a/pyrogram/types/bots_and_keyboards/callback_query.py +++ b/pyrogram/types/bots_and_keyboards/callback_query.py @@ -252,7 +252,7 @@ async def edit_message_caption( Raises: RPCError: In case of a Telegram RPC error. """ - return await self.edit_message_text(caption, parse_mode, reply_markup) + return await self.edit_message_text(caption, parse_mode, reply_markup=reply_markup) async def edit_message_media( self, From a56b1a3287c4b847dac2e6c38b612e5fc19ec924 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=D1=91Nya?= Date: Wed, 17 Feb 2021 23:58:32 +0300 Subject: [PATCH 0592/1185] add InlineQueryResultVideo --- pyrogram/types/inline_mode/__init__.py | 3 +- .../inline_mode/inline_query_result_video.py | 136 ++++++++++++++++++ 2 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 pyrogram/types/inline_mode/inline_query_result_video.py diff --git a/pyrogram/types/inline_mode/__init__.py b/pyrogram/types/inline_mode/__init__.py index 41e2eb5b6b..9d464eb48e 100644 --- a/pyrogram/types/inline_mode/__init__.py +++ b/pyrogram/types/inline_mode/__init__.py @@ -22,9 +22,10 @@ from .inline_query_result_animation import InlineQueryResultAnimation from .inline_query_result_article import InlineQueryResultArticle from .inline_query_result_photo import InlineQueryResultPhoto +from .inline_query_result_video import InlineQueryResultVideo from .inline_query_result_audio import InlineQueryResultAudio __all__ = [ "InlineQuery", "InlineQueryResult", "InlineQueryResultArticle", "InlineQueryResultPhoto", - "InlineQueryResultAnimation", "InlineQueryResultAudio", "ChosenInlineResult" + "InlineQueryResultAnimation", "InlineQueryResultAudio", "InlineQueryResultVideo", "ChosenInlineResult" ] diff --git a/pyrogram/types/inline_mode/inline_query_result_video.py b/pyrogram/types/inline_mode/inline_query_result_video.py new file mode 100644 index 0000000000..8375fd8952 --- /dev/null +++ b/pyrogram/types/inline_mode/inline_query_result_video.py @@ -0,0 +1,136 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan +# +# 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 Optional + +from pyrogram import raw +from pyrogram import types +from pyrogram.parser import Parser +from .inline_query_result import InlineQueryResult + + +class InlineQueryResultVideo(InlineQueryResult): + """Link to a video. + + By default, this video will be sent by the user with optional caption. + Alternatively, you can use *input_message_content* to send a message with the specified content instead of the + video. + + Parameters: + video_url (``str``): + A valid URL for the embedded video player or video file. + + thumb_url (``str``): + URL of the thumbnail (jpeg only) for the video. + + id (``str``, *optional*): + Unique identifier for this result, 1-64 bytes. + Defaults to a randomly generated UUID4. + + title (``str``, *optional*): + Title for the result. + + mime_type (``str``, *optional*): + Mime type of the content of video url, “text/html” or “video/mp4”. + + description (``str``, *optional*): + Short description of the result. + + caption (``str``, *optional*): + Caption of the video to be sent, 0-1024 characters. + + parse_mode (``str``, *optional*): + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + Pass "markdown" or "md" to enable Markdown-style parsing only. + Pass "html" to enable HTML-style parsing only. + Pass None to completely disable style parsing. + + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*): + An InlineKeyboardMarkup object. + + input_message_content (:obj:`~pyrogram.types.InputMessageContent`): + Content of the message to be sent instead of the video. This field is required if InlineQueryResultVideo is + used to send an HTML-page as a result (e.g., a YouTube video). + """ + + def __init__( + self, + video_url: str, + thumb_url: str, + id: str = None, + title: str = None, + mime_type: str = None, + description: str = None, + caption: str = "", + parse_mode: Optional[str] = object, + reply_markup: "types.InlineKeyboardMarkup" = None, + input_message_content: "types.InputMessageContent" = None + ): + super().__init__("video", id, input_message_content, reply_markup) + + self.video_url = video_url + + self.thumb_url = thumb_url + self.title = title + + if mime_type != "text/html" and mime_type != "video/mp4": + raise ValueError("Invalid mime type") + + self.mime_type = mime_type + self.description = description + self.caption = caption + self.parse_mode = parse_mode + self.reply_markup = reply_markup + + if mime_type == "text/html" and input_message_content is None: + raise ValueError("input_message_content is required for videos with `text/html` mime type") + + self.input_message_content = input_message_content + + async def write(self): + video = raw.types.InputWebDocument( + url=self.video_url, + size=0, + mime_type=self.mime_type, + attributes=[] + ) + + thumb = raw.types.InputWebDocument( + url=self.thumb_url, + size=0, + mime_type="image/jpeg", + attributes=[] + ) + + return raw.types.InputBotInlineResult( + id=self.id, + type=self.type, + title=self.title, + description=self.description, + thumb=thumb, + content=video, + send_message=( + await self.input_message_content.write(self.reply_markup) + if self.input_message_content + else raw.types.InputBotInlineMessageMediaAuto( + reply_markup=self.reply_markup.write() if self.reply_markup else None, + **await(Parser(None)).parse(self.caption, self.parse_mode) + ) + ) + ) From 5fdb361487f58fba9bd475dc060ace204dc2723d Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 12 May 2021 09:11:52 +0200 Subject: [PATCH 0593/1185] Fixes for InlineQueryResult{Audio,Video} --- .../inline_mode/inline_query_result_audio.py | 35 ++++---- .../inline_mode/inline_query_result_video.py | 80 ++++++++++++------- 2 files changed, 66 insertions(+), 49 deletions(-) diff --git a/pyrogram/types/inline_mode/inline_query_result_audio.py b/pyrogram/types/inline_mode/inline_query_result_audio.py index 48f88271cf..46e9a3600a 100644 --- a/pyrogram/types/inline_mode/inline_query_result_audio.py +++ b/pyrogram/types/inline_mode/inline_query_result_audio.py @@ -16,11 +16,10 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Union +from typing import Union, List -from pyrogram import raw -from pyrogram import types -from pyrogram.parser import Parser +import pyrogram +from pyrogram import raw, types, utils from pyrogram.types import InlineQueryResult @@ -69,17 +68,17 @@ class InlineQueryResultAudio(InlineQueryResult): """ def __init__( - self, - audio_url: str, - title: str, - id: str = None, - performer: str = "", - audio_duration: int = 0, - caption: str = "", - parse_mode: Union[str, None] = object, - caption_entities: List["types.MessageEntity"] = None, - reply_markup: "types.InlineKeyboardMarkup" = None, - input_message_content: "types.InputMessageContent" = None + self, + audio_url: str, + title: str, + id: str = None, + performer: str = "", + audio_duration: int = 0, + caption: str = "", + parse_mode: Union[str, None] = object, + caption_entities: List["types.MessageEntity"] = None, + reply_markup: "types.InlineKeyboardMarkup" = None, + input_message_content: "types.InputMessageContent" = None ): super().__init__("audio", id, input_message_content, reply_markup) @@ -91,7 +90,7 @@ def __init__( self.parse_mode = parse_mode self.caption_entities = caption_entities - async def write(self): + async def write(self, client: "pyrogram.Client"): audio = raw.types.InputWebDocument( url=self.audio_url, size=0, @@ -100,9 +99,9 @@ async def write(self): duration=self.audio_duration, title=self.title, performer=self.performer - )], + )] ) - + message, entities = (await utils.parse_text_entities( client, self.caption, self.parse_mode, self.caption_entities )).values() diff --git a/pyrogram/types/inline_mode/inline_query_result_video.py b/pyrogram/types/inline_mode/inline_query_result_video.py index 8375fd8952..ca0ffda2b8 100644 --- a/pyrogram/types/inline_mode/inline_query_result_video.py +++ b/pyrogram/types/inline_mode/inline_query_result_video.py @@ -16,18 +16,17 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Optional +from typing import Optional, List -from pyrogram import raw -from pyrogram import types -from pyrogram.parser import Parser +import pyrogram +from pyrogram import raw, types, utils from .inline_query_result import InlineQueryResult class InlineQueryResultVideo(InlineQueryResult): - """Link to a video. + """Link to a page containing an embedded video player or a video file. - By default, this video will be sent by the user with optional caption. + By default, this video file will be sent by the user with an optional caption. Alternatively, you can use *input_message_content* to send a message with the specified content instead of the video. @@ -38,21 +37,31 @@ class InlineQueryResultVideo(InlineQueryResult): thumb_url (``str``): URL of the thumbnail (jpeg only) for the video. + title (``str``): + Title for the result. + id (``str``, *optional*): Unique identifier for this result, 1-64 bytes. Defaults to a randomly generated UUID4. - title (``str``, *optional*): - Title for the result. + mime_type (``str``): + Mime type of the content of video url, "text/html" or "video/mp4". + Defaults to "video/mp4". + + video_width (``int``): + Video width. - mime_type (``str``, *optional*): - Mime type of the content of video url, “text/html” or “video/mp4”. + video_height (``int``): + Video height. + + video_duration (``int``): + Video duration in seconds. description (``str``, *optional*): Short description of the result. caption (``str``, *optional*): - Caption of the video to be sent, 0-1024 characters. + Caption of the photo to be sent, 0-1024 characters. parse_mode (``str``, *optional*): By default, texts are parsed using both Markdown and HTML styles. @@ -61,8 +70,11 @@ class InlineQueryResultVideo(InlineQueryResult): Pass "html" to enable HTML-style parsing only. Pass None to completely disable style parsing. + caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): + List of special entities that appear in the caption, which can be specified instead of *parse_mode*. + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*): - An InlineKeyboardMarkup object. + Inline keyboard attached to the message input_message_content (:obj:`~pyrogram.types.InputMessageContent`): Content of the message to be sent instead of the video. This field is required if InlineQueryResultVideo is @@ -73,42 +85,43 @@ def __init__( self, video_url: str, thumb_url: str, + title: str, id: str = None, - title: str = None, - mime_type: str = None, + mime_type: str = "video/mp4", + video_width: int = 0, + video_height: int = 0, + video_duration: int = 0, description: str = None, caption: str = "", parse_mode: Optional[str] = object, + caption_entities: List["types.MessageEntity"] = None, reply_markup: "types.InlineKeyboardMarkup" = None, input_message_content: "types.InputMessageContent" = None ): super().__init__("video", id, input_message_content, reply_markup) self.video_url = video_url - self.thumb_url = thumb_url self.title = title - - if mime_type != "text/html" and mime_type != "video/mp4": - raise ValueError("Invalid mime type") - - self.mime_type = mime_type + self.video_width = video_width + self.video_height = video_height + self.video_duration = video_duration self.description = description self.caption = caption self.parse_mode = parse_mode - self.reply_markup = reply_markup - - if mime_type == "text/html" and input_message_content is None: - raise ValueError("input_message_content is required for videos with `text/html` mime type") - - self.input_message_content = input_message_content + self.caption_entities = caption_entities + self.mime_type = mime_type - async def write(self): + async def write(self, client: "pyrogram.Client"): video = raw.types.InputWebDocument( url=self.video_url, size=0, mime_type=self.mime_type, - attributes=[] + attributes=[raw.types.DocumentAttributeVideo( + duration=self.video_duration, + w=self.video_width, + h=self.video_height + )] ) thumb = raw.types.InputWebDocument( @@ -118,6 +131,10 @@ async def write(self): attributes=[] ) + message, entities = (await utils.parse_text_entities( + client, self.caption, self.parse_mode, self.caption_entities + )).values() + return raw.types.InputBotInlineResult( id=self.id, type=self.type, @@ -126,11 +143,12 @@ async def write(self): thumb=thumb, content=video, send_message=( - await self.input_message_content.write(self.reply_markup) + await self.input_message_content.write(client, self.reply_markup) if self.input_message_content else raw.types.InputBotInlineMessageMediaAuto( - reply_markup=self.reply_markup.write() if self.reply_markup else None, - **await(Parser(None)).parse(self.caption, self.parse_mode) + reply_markup=await self.reply_markup.write(client) if self.reply_markup else None, + message=message, + entities=entities ) ) ) From a4bb2a3fdf18ef67dd555d6904e334f48528301f Mon Sep 17 00:00:00 2001 From: Gaetano <64538010+skrtdev@users.noreply.github.com> Date: Wed, 12 May 2021 09:13:19 +0200 Subject: [PATCH 0594/1185] Fix typo in send_video examples (#679) --- pyrogram/methods/messages/send_video.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/methods/messages/send_video.py b/pyrogram/methods/messages/send_video.py index a46439ea49..d42846eda4 100644 --- a/pyrogram/methods/messages/send_video.py +++ b/pyrogram/methods/messages/send_video.py @@ -162,7 +162,7 @@ async def send_video( app.send_video("me", "video.mp4", caption="recording") # Send self-destructing video - app.send_photo("me", "video.mp4", ttl_seconds=10) + app.send_video("me", "video.mp4", ttl_seconds=10) # Keep track of the progress while uploading def progress(current, total): From 29701a3a55b00b127c0588791c8dbc4d092bc8a6 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 12 May 2021 09:27:39 +0200 Subject: [PATCH 0595/1185] Fix import errors --- pyrogram/types/inline_mode/inline_query_result_audio.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/types/inline_mode/inline_query_result_audio.py b/pyrogram/types/inline_mode/inline_query_result_audio.py index 46e9a3600a..dde8b5e64a 100644 --- a/pyrogram/types/inline_mode/inline_query_result_audio.py +++ b/pyrogram/types/inline_mode/inline_query_result_audio.py @@ -20,7 +20,7 @@ import pyrogram from pyrogram import raw, types, utils -from pyrogram.types import InlineQueryResult +from .inline_query_result import InlineQueryResult class InlineQueryResultAudio(InlineQueryResult): From dd32854db45a129fd0cfc8c759cedb53d83f710e Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 12 May 2021 09:39:51 +0200 Subject: [PATCH 0596/1185] Squashed commit of the following: commit d6dcf98d7445cbdc2a036deca57207c14bc354fc Author: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed May 12 09:35:18 2021 +0200 Rename get_chat_onlines to get_chat_online_count commit 21ff2a39d856ebc939ce9b15810198c82a9c23c6 Merge: 808c629f 29701a3a Author: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed May 12 09:32:59 2021 +0200 Merge branch 'master' into get-chat-online-count commit 808c629f43b185bc0df8337a82f5ecc860bbdb94 Author: Andriel Rodrigues Date: Wed May 12 04:28:53 2021 -0300 Add get_chat_online_count method (todo) (#654) --- compiler/docs/compiler.py | 1 + pyrogram/methods/chats/__init__.py | 4 +- .../methods/chats/get_chat_online_count.py | 46 +++++++++++++++++++ 3 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 pyrogram/methods/chats/get_chat_online_count.py diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index a5ce87e41a..be726c44eb 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -222,6 +222,7 @@ def get_title_list(s: str) -> list: set_slow_mode mark_chat_unread get_chat_event_log + get_chat_online_count """, users=""" Users diff --git a/pyrogram/methods/chats/__init__.py b/pyrogram/methods/chats/__init__.py index 8aeb5fe42c..31ffe4fd2f 100644 --- a/pyrogram/methods/chats/__init__.py +++ b/pyrogram/methods/chats/__init__.py @@ -53,6 +53,7 @@ from .unpin_all_chat_messages import UnpinAllChatMessages from .unpin_chat_message import UnpinChatMessage from .update_chat_username import UpdateChatUsername +from .get_chat_online_count import GetChatOnlineCount class Chats( @@ -92,6 +93,7 @@ class Chats( DeleteUserHistory, UnpinAllChatMessages, MarkChatUnread, - GetChatEventLog + GetChatEventLog, + GetChatOnlineCount ): pass diff --git a/pyrogram/methods/chats/get_chat_online_count.py b/pyrogram/methods/chats/get_chat_online_count.py new file mode 100644 index 0000000000..5d3d861f87 --- /dev/null +++ b/pyrogram/methods/chats/get_chat_online_count.py @@ -0,0 +1,46 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan +# +# 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 Union + +from pyrogram import raw +from pyrogram.scaffold import Scaffold + + +class GetChatOnlineCount(Scaffold): + async def get_chat_online_count(self, chat_id: Union[int, str]) -> int: + """Get the number of members that are currently online in a chat. + + Parameters: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + + Returns: + ``int``: On success, the chat members online count is returned. + + Example: + .. code-block:: python + + online = app.get_chat_online_count(chat_id) + print(online) + """ + return (await self.send( + raw.functions.messages.GetOnlines( + peer=await self.resolve_peer(chat_id) + ) + )).onlines From aef0cce8d973ffb3a7a5c7e1970fe714214a34f7 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 14 May 2021 17:31:36 +0200 Subject: [PATCH 0597/1185] Add more RPC errors --- compiler/errors/source/400_BAD_REQUEST.tsv | 5 +++++ compiler/errors/source/500_INTERNAL_SERVER_ERROR.tsv | 7 ++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/compiler/errors/source/400_BAD_REQUEST.tsv b/compiler/errors/source/400_BAD_REQUEST.tsv index 7dbf64d1db..ab23069722 100644 --- a/compiler/errors/source/400_BAD_REQUEST.tsv +++ b/compiler/errors/source/400_BAD_REQUEST.tsv @@ -290,6 +290,8 @@ STICKER_ID_INVALID The provided sticker id is invalid STICKER_INVALID The provided sticker is invalid STICKER_PNG_DIMENSIONS The sticker png dimensions are invalid STICKER_PNG_NOPNG Stickers must be png files but the provided image was not a png +STICKER_TGS_NOTGS A tgs sticker file was expected, but something else was provided +STICKER_THUMB_PNG_NOPNG A png sticker thumbnail file was expected, but something else was provided TAKEOUT_INVALID The takeout id is invalid TAKEOUT_REQUIRED The method must be invoked inside a takeout session TEMP_AUTH_KEY_EMPTY The temporary auth key provided is empty @@ -298,6 +300,7 @@ THEME_FORMAT_INVALID Invalid theme format provided THEME_INVALID Invalid theme provided THEME_MIME_INVALID You cannot create this theme because the mime-type is invalid TMP_PASSWORD_DISABLED The temporary password is disabled +TMP_PASSWORD_INVALID The temporary password is invalid TOKEN_INVALID The provided token is invalid TTL_DAYS_INVALID The provided TTL days is invalid TTL_MEDIA_INVALID The media does not support self-destruction @@ -310,6 +313,7 @@ USERNAME_INVALID The username is invalid USERNAME_NOT_MODIFIED The username was not modified because you tried to edit it using the same one USERNAME_NOT_OCCUPIED The username is not occupied by anyone USERNAME_OCCUPIED The username is already in use by someone else +USERPIC_UPLOAD_REQUIRED You are required to upload a profile picture for this action USERS_TOO_FEW Not enough users (to create a chat, for example) USERS_TOO_MUCH The maximum number of users has been exceeded (to create a chat, for example) USER_ADMIN_INVALID The action requires admin privileges. Probably you tried to edit admin privileges on someone you don't have rights to @@ -333,6 +337,7 @@ VIDEO_FILE_INVALID The video file is invalid VOLUME_LOC_NOT_FOUND The volume location can't be found WALLPAPER_FILE_INVALID The provided file cannot be used as a wallpaper WALLPAPER_INVALID The input wallpaper was not valid +WALLPAPER_MIME_INVALID The wallpaper mime type is invalid WC_CONVERT_URL_INVALID WC convert URL invalid WEBDOCUMENT_INVALID The web document is invalid WEBDOCUMENT_MIME_INVALID The web document mime type is invalid diff --git a/compiler/errors/source/500_INTERNAL_SERVER_ERROR.tsv b/compiler/errors/source/500_INTERNAL_SERVER_ERROR.tsv index 97d0984cc1..9982fc662f 100644 --- a/compiler/errors/source/500_INTERNAL_SERVER_ERROR.tsv +++ b/compiler/errors/source/500_INTERNAL_SERVER_ERROR.tsv @@ -9,6 +9,7 @@ CHP_CALL_FAIL Telegram is having internal problems. Please try again later ENCRYPTION_OCCUPY_ADMIN_FAILED Failed occupying memory for admin info due to Telegram having internal problems. Please try again later ENCRYPTION_OCCUPY_FAILED Internal server error while accepting secret chat FOLDER_DEAC_AUTOFIX_ALL Telegram is having internal problems. Please try again later +GROUPCALL_ADD_PARTICIPANTS_FAILED Failure while adding voice chat member due to Telegram having internal problems. Please try again later GROUPED_ID_OCCUPY_FAILED Telegram is having internal problems. Please try again later HISTORY_GET_FAILED The chat history couldn't be retrieved due to Telegram having internal problems. Please try again later IMAGE_ENGINE_DOWN Image engine down due to Telegram having internal problems. Please try again later @@ -23,8 +24,10 @@ MSG_RANGE_UNSYNC Message range unsynchronized due to Telegram having internal pr MT_SEND_QUEUE_TOO_LONG The MTProto send queue has grown too much due to Telegram having internal problems. Please try again later NEED_CHAT_INVALID The provided chat is invalid NEED_MEMBER_INVALID The provided member is invalid or does not exist +No workers running The Telegram server is restarting its workers. Try again later. PARTICIPANT_CALL_FAILED Failure while making call due to Telegram having internal problems. Please try again later PERSISTENT_TIMESTAMP_OUTDATED The persistent timestamp is outdated due to Telegram having internal problems. Please try again later +PHOTO_CREATE_FAILED The creation of the photo failed due to Telegram having internal problems. Please try again later POSTPONED_TIMEOUT Telegram is having internal problems. Please try again later PTS_CHANGE_EMPTY No PTS change RANDOM_ID_DUPLICATE You provided a random ID that was already used @@ -39,6 +42,4 @@ UNKNOWN_METHOD The method you tried to call cannot be called on non-CDN DCs UPLOAD_NO_VOLUME Telegram is having internal problems. Please try again later VOLUME_LOC_NOT_FOUND Telegram is having internal problems. Please try again later WORKER_BUSY_TOO_LONG_RETRY Server workers are too busy right now due to Telegram having internal problems. Please try again later -WP_ID_GENERATE_FAILED Telegram is having internal problems. Please try again later -GROUPCALL_ADD_PARTICIPANTS_FAILED Failure while adding voice chat member due to Telegram having internal problems. Please try again later -No workers running The Telegram server is restarting its workers. Try again later. \ No newline at end of file +WP_ID_GENERATE_FAILED Telegram is having internal problems. Please try again later \ No newline at end of file From be83f8e25ea909a92b64f6145ed96557cdf8ee39 Mon Sep 17 00:00:00 2001 From: Ripe <42308266+Ripeey@users.noreply.github.com> Date: Sat, 15 May 2021 10:36:37 +0530 Subject: [PATCH 0598/1185] Actually fix inline message id invalid (for inline message queries) (#683) * Fix Inline Message ID Invalid * Update inline_session.py * Update inline_session.py Co-authored-by: Dan <14043624+delivrance@users.noreply.github.com> --- pyrogram/methods/messages/inline_session.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pyrogram/methods/messages/inline_session.py b/pyrogram/methods/messages/inline_session.py index 3ea863696c..bfc323a22e 100644 --- a/pyrogram/methods/messages/inline_session.py +++ b/pyrogram/methods/messages/inline_session.py @@ -25,7 +25,7 @@ from pyrogram.session.auth import Auth lock = Lock() -session = None +sessions = {} async def get_session(client: "pyrogram.Client", dc_id: int): @@ -33,12 +33,12 @@ async def get_session(client: "pyrogram.Client", dc_id: int): return client async with lock: - global session + global sessions - if session is not None: - return session + if sessions.get(dc_id): + return sessions[dc_id] - session = Session( + session = sessions[dc_id] = Session( client, dc_id, await Auth(client, dc_id, False).create(), False, is_media=True From 7c9f1cb8ef27ed55dd3b1a4fdf874833e7b832c7 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 15 May 2021 18:14:27 +0200 Subject: [PATCH 0599/1185] Improve the HTML parser Fixes #682 --- pyrogram/parser/html.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/parser/html.py b/pyrogram/parser/html.py index e73cb3aa8a..f966a59252 100644 --- a/pyrogram/parser/html.py +++ b/pyrogram/parser/html.py @@ -111,7 +111,7 @@ def __init__(self, client: Optional["pyrogram.Client"]): async def parse(self, text: str): # Strip whitespace characters from the end of the message, but preserve closing tags - text = re.sub(r"\s+()\s*$", r"\1", text) + text = re.sub(r"\s*()\s*$", r"\1", text) parser = Parser(self.client) parser.feed(utils.add_surrogates(text)) From 317685cf540c1a36bbb05990066907e4a23d48a6 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 15 May 2021 18:35:57 +0200 Subject: [PATCH 0600/1185] Further improve the HTML parser Related to #682 --- pyrogram/parser/html.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/parser/html.py b/pyrogram/parser/html.py index f966a59252..f3f55195c2 100644 --- a/pyrogram/parser/html.py +++ b/pyrogram/parser/html.py @@ -111,7 +111,7 @@ def __init__(self, client: Optional["pyrogram.Client"]): async def parse(self, text: str): # Strip whitespace characters from the end of the message, but preserve closing tags - text = re.sub(r"\s*()\s*$", r"\1", text) + text = re.sub(r"\s*(]*>)\s*$", r"\1", text) parser = Parser(self.client) parser.feed(utils.add_surrogates(text)) From 0d12d8c1bb92ce7473218361ed3742e677d6c0aa Mon Sep 17 00:00:00 2001 From: Christy Roys Date: Sat, 22 May 2021 13:30:23 +0530 Subject: [PATCH 0601/1185] Add support for in-memory uploads in send_media_group (#519) * Add support for in-memory uploads for send_media_group * update input_media_photo docs * update type hints Co-authored-by: Dan <14043624+delivrance@users.noreply.github.com> --- pyrogram/methods/messages/send_media_group.py | 5 +++-- pyrogram/types/input_media/input_media_photo.py | 7 ++++--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/pyrogram/methods/messages/send_media_group.py b/pyrogram/methods/messages/send_media_group.py index d591c99b55..1669ce32c1 100644 --- a/pyrogram/methods/messages/send_media_group.py +++ b/pyrogram/methods/messages/send_media_group.py @@ -19,6 +19,7 @@ import logging import os import re +import io from typing import Union, List from pyrogram import raw @@ -87,7 +88,7 @@ async def send_media_group( for i in media: if isinstance(i, types.InputMediaPhoto): - if os.path.isfile(i.media): + if os.path.isfile(i.media) or isinstance(i.media, io.IOBase): media = await self.send( raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), @@ -124,7 +125,7 @@ async def send_media_group( else: media = utils.get_input_media_from_file_id(i.media, FileType.PHOTO) elif isinstance(i, types.InputMediaVideo): - if os.path.isfile(i.media): + if os.path.isfile(i.media) or isinstance(i.media, io.IOBase): media = await self.send( raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), diff --git a/pyrogram/types/input_media/input_media_photo.py b/pyrogram/types/input_media/input_media_photo.py index a821354054..e84a7616c0 100644 --- a/pyrogram/types/input_media/input_media_photo.py +++ b/pyrogram/types/input_media/input_media_photo.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Optional, List +from typing import Optional, List, Union, BinaryIO from .input_media import InputMedia from ..messages_and_media import MessageEntity @@ -27,10 +27,11 @@ class InputMediaPhoto(InputMedia): It is intended to be used with :obj:`~pyrogram.Client.send_media_group`. Parameters: - media (``str``): + media (``str`` | ``BinaryIO``): Photo to send. Pass a file_id as string to send a photo that exists on the Telegram servers or pass a file path as string to upload a new photo that exists on your local machine or + pass a binary file-like object with its attribute “.name” set for in-memory uploads or pass an HTTP URL as a string for Telegram to get a photo from the Internet. caption (``str``, *optional*): @@ -50,7 +51,7 @@ class InputMediaPhoto(InputMedia): def __init__( self, - media: str, + media: Union[str, BinaryIO], caption: str = "", parse_mode: Optional[str] = object, caption_entities: List[MessageEntity] = None From 6cb3e92838205377db94c302f2f647926718ae7f Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 22 May 2021 10:01:46 +0200 Subject: [PATCH 0602/1185] Reword a note --- pyrogram/types/inline_mode/chosen_inline_result.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/pyrogram/types/inline_mode/chosen_inline_result.py b/pyrogram/types/inline_mode/chosen_inline_result.py index 8da825eacb..578ef64a1b 100644 --- a/pyrogram/types/inline_mode/chosen_inline_result.py +++ b/pyrogram/types/inline_mode/chosen_inline_result.py @@ -29,6 +29,11 @@ class ChosenInlineResult(Object, Update): """A :doc:`result ` of an inline query chosen by the user and sent to their chat partner. + .. note:: + + In order to receive these updates, your bot must have "inline feedback" enabled. You can enable this feature + with `@BotFather `_. + Parameters: result_id (``str``): The unique identifier for the result that was chosen. @@ -46,11 +51,6 @@ class ChosenInlineResult(Object, Update): Identifier of the sent inline message. Available only if there is an :doc:`inline keyboard ` attached to the message. Will be also received in :doc:`callback queries ` and can be used to edit the message. - - .. note:: - - It is necessary to enable inline feedback via `@Botfather `_ in order to receive these - objects in updates. """ def __init__( From c9d0c5d75a7646b43cd7e8c1f45715fd1630f797 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 31 May 2021 20:23:32 +0200 Subject: [PATCH 0603/1185] Sync chat permissions with official clients --- .../methods/chats/set_chat_permissions.py | 15 ++-- .../types/user_and_chats/chat_permissions.py | 68 +++++++------------ 2 files changed, 30 insertions(+), 53 deletions(-) diff --git a/pyrogram/methods/chats/set_chat_permissions.py b/pyrogram/methods/chats/set_chat_permissions.py index d8a102420a..fe208818b1 100644 --- a/pyrogram/methods/chats/set_chat_permissions.py +++ b/pyrogram/methods/chats/set_chat_permissions.py @@ -52,17 +52,16 @@ async def set_chat_permissions( # Completely restrict chat app.set_chat_permissions(chat_id, ChatPermissions()) - # Chat members can only send text messages, media, stickers and GIFs + # Chat members can only send text messages and media messages app.set_chat_permissions( chat_id, ChatPermissions( can_send_messages=True, - can_send_media_messages=True, - can_send_stickers=True, - can_send_animations=True + can_send_media_messages=True ) ) """ + r = await self.send( raw.functions.messages.EditChatDefaultBannedRights( peer=await self.resolve_peer(chat_id), @@ -70,10 +69,10 @@ async def set_chat_permissions( until_date=0, send_messages=True if not permissions.can_send_messages else None, send_media=True if not permissions.can_send_media_messages else None, - send_stickers=True if not permissions.can_send_stickers else None, - send_gifs=True if not permissions.can_send_animations else None, - send_games=True if not permissions.can_send_games else None, - send_inline=True if not permissions.can_use_inline_bots else None, + send_stickers=permissions.can_send_other_messages, + send_gifs=permissions.can_send_other_messages, + send_games=permissions.can_send_other_messages, + send_inline=permissions.can_send_other_messages, embed_links=True if not permissions.can_add_web_page_previews else None, send_polls=True if not permissions.can_send_polls else None, change_info=True if not permissions.can_change_info else None, diff --git a/pyrogram/types/user_and_chats/chat_permissions.py b/pyrogram/types/user_and_chats/chat_permissions.py index 150f8321a6..a5bc3011b1 100644 --- a/pyrogram/types/user_and_chats/chat_permissions.py +++ b/pyrogram/types/user_and_chats/chat_permissions.py @@ -21,49 +21,31 @@ class ChatPermissions(Object): - """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. - - .. note:: - - Pyrogram's chat permission are much more detailed. In particular, you can restrict sending stickers, animations, - games and inline bot results individually, allowing a finer control. - - If you wish to have the same permissions as seen in official apps or in bot API's *"can_send_other_messages"* - simply set these arguments to True: ``can_send_stickers``, ``can_send_animations``, ``can_send_games`` and - ``can_use_inline_bots``. + """Describes actions that a non-administrator user is allowed to take in a chat. Parameters: can_send_messages (``bool``, *optional*): True, if the user is allowed to send text messages, contacts, locations and venues. can_send_media_messages (``bool``, *optional*): - True, if the user is allowed to send audios, documents, photos, videos, video notes and voice notes, implies - can_send_messages. + True, if the user is allowed to send audios, documents, photos, videos, video notes and voice notes. + Implies *can_send_messages*. - can_send_stickers (``bool``, *optional*): - True, if the user is allowed to send stickers, implies can_send_media_messages. + can_send_other_messages (``bool``, *optional*): + True, if the user is allowed to send animations, games, stickers and use inline bots. + Implies *can_send_media_messages*. - can_send_animations (``bool``, *optional*): - True, if the user is allowed to send animations (GIFs), implies can_send_media_messages. - - can_send_games (``bool``, *optional*): - True, if the user is allowed to send games, implies can_send_media_messages. - - can_use_inline_bots (``bool``, *optional*): - True, if the user is allowed to use inline bots, implies can_send_media_messages. + can_send_polls (``bool``, *optional*): + True, if the user is allowed to send polls. + Implies can_send_messages can_add_web_page_previews (``bool``, *optional*): - True, if the user is allowed to add web page previews to their messages, implies can_send_media_messages. - - can_send_polls (``bool``, *optional*): - True, if the user is allowed to send polls, implies can_send_messages. + True, if the user is allowed to add web page previews to their messages. + Implies *can_send_media_messages*. can_change_info (``bool``, *optional*): True, if the user is allowed to change the chat title, photo and other settings. - Ignored in public supergroups. + Ignored in public supergroups can_invite_users (``bool``, *optional*): True, if the user is allowed to invite new users to the chat. @@ -77,13 +59,10 @@ def __init__( self, *, can_send_messages: bool = None, # Text, contacts, locations and venues - can_send_media_messages: bool = None, # Audios, documents, photos, videos, video notes and voice notes - can_send_stickers: bool = None, - can_send_animations: bool = None, - can_send_games: bool = None, - can_use_inline_bots: bool = None, - can_add_web_page_previews: bool = None, + can_send_media_messages: bool = None, # Audio files, documents, photos, videos, video notes and voice notes + can_send_other_messages: bool = None, # Stickers, animations, games, inline bots can_send_polls: bool = None, + can_add_web_page_previews: bool = None, can_change_info: bool = None, can_invite_users: bool = None, can_pin_messages: bool = None @@ -92,12 +71,9 @@ def __init__( self.can_send_messages = can_send_messages self.can_send_media_messages = can_send_media_messages - self.can_send_stickers = can_send_stickers - self.can_send_animations = can_send_animations - self.can_send_games = can_send_games - self.can_use_inline_bots = can_use_inline_bots - self.can_add_web_page_previews = can_add_web_page_previews + self.can_send_other_messages = can_send_other_messages self.can_send_polls = can_send_polls + self.can_add_web_page_previews = can_add_web_page_previews self.can_change_info = can_change_info self.can_invite_users = can_invite_users self.can_pin_messages = can_pin_messages @@ -108,10 +84,12 @@ def _parse(denied_permissions: "raw.types.ChatBannedRights") -> "ChatPermissions return ChatPermissions( can_send_messages=not denied_permissions.send_messages, can_send_media_messages=not denied_permissions.send_media, - can_send_stickers=not denied_permissions.send_stickers, - can_send_animations=not denied_permissions.send_gifs, - can_send_games=not denied_permissions.send_games, - can_use_inline_bots=not denied_permissions.send_inline, + can_send_other_messages=any([ + not denied_permissions.send_stickers, + not denied_permissions.send_gifs, + not denied_permissions.send_games, + not denied_permissions.send_inline + ]), can_add_web_page_previews=not denied_permissions.embed_links, can_send_polls=not denied_permissions.send_polls, can_change_info=not denied_permissions.change_info, From 523ed3e7cb7a0e90e64bedd641973460fe7b4dfb Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 1 Jun 2021 13:57:31 +0200 Subject: [PATCH 0604/1185] Add support for in-memory uploads in send_media_group (#519) * Add support for in-memory uploads for send_media_group * update input_media_photo docs * update type hints Co-authored-by: Dan <14043624+delivrance@users.noreply.github.com> --- pyrogram/methods/advanced/save_file.py | 86 +++--- pyrogram/methods/messages/send_media_group.py | 251 ++++++++++++------ .../input_media/input_media_animation.py | 5 +- .../types/input_media/input_media_audio.py | 5 +- .../types/input_media/input_media_document.py | 5 +- .../types/input_media/input_media_video.py | 5 +- 6 files changed, 232 insertions(+), 125 deletions(-) diff --git a/pyrogram/methods/advanced/save_file.py b/pyrogram/methods/advanced/save_file.py index bc99b859b0..8436156584 100644 --- a/pyrogram/methods/advanced/save_file.py +++ b/pyrogram/methods/advanced/save_file.py @@ -116,7 +116,7 @@ async def worker(session): else: raise ValueError("Invalid file. Expected a file path as string or a binary (not text) file pointer") - file_name = fp.name + file_name = getattr(fp, "name", "file.jpg") fp.seek(0, os.SEEK_END) file_size = fp.tell() @@ -148,53 +148,52 @@ async def worker(session): for session in pool: await session.start() - with fp: - fp.seek(part_size * file_part) + fp.seek(part_size * file_part) - while True: - chunk = fp.read(part_size) + while True: + chunk = fp.read(part_size) - if not chunk: - if not is_big and not is_missing_part: - md5_sum = "".join([hex(i)[2:].zfill(2) for i in md5_sum.digest()]) - break + if not chunk: + if not is_big and not is_missing_part: + md5_sum = "".join([hex(i)[2:].zfill(2) for i in md5_sum.digest()]) + break + + if is_big: + rpc = raw.functions.upload.SaveBigFilePart( + file_id=file_id, + file_part=file_part, + file_total_parts=file_total_parts, + bytes=chunk + ) + else: + rpc = raw.functions.upload.SaveFilePart( + file_id=file_id, + file_part=file_part, + bytes=chunk + ) + + await queue.put(rpc) + + if is_missing_part: + return - if is_big: - rpc = raw.functions.upload.SaveBigFilePart( - file_id=file_id, - file_part=file_part, - file_total_parts=file_total_parts, - bytes=chunk - ) - else: - rpc = raw.functions.upload.SaveFilePart( - file_id=file_id, - file_part=file_part, - bytes=chunk - ) + if not is_big and not is_missing_part: + md5_sum.update(chunk) - await queue.put(rpc) + file_part += 1 - if is_missing_part: - return + if progress: + func = functools.partial( + progress, + min(file_part * part_size, file_size), + file_size, + *progress_args + ) - if not is_big and not is_missing_part: - md5_sum.update(chunk) - - file_part += 1 - - if progress: - func = functools.partial( - progress, - min(file_part * part_size, file_size), - file_size, - *progress_args - ) - - if inspect.iscoroutinefunction(progress): - await func() - else: - await self.loop.run_in_executor(self.executor, func) + if inspect.iscoroutinefunction(progress): + await func() + else: + await self.loop.run_in_executor(self.executor, func) except StopTransmission: raise except Exception as e: @@ -222,3 +221,6 @@ async def worker(session): for session in pool: await session.stop() + + if isinstance(path, (str, PurePath)): + fp.close() diff --git a/pyrogram/methods/messages/send_media_group.py b/pyrogram/methods/messages/send_media_group.py index 1669ce32c1..4073ddecaa 100644 --- a/pyrogram/methods/messages/send_media_group.py +++ b/pyrogram/methods/messages/send_media_group.py @@ -19,7 +19,6 @@ import logging import os import re -import io from typing import Union, List from pyrogram import raw @@ -88,7 +87,44 @@ async def send_media_group( for i in media: if isinstance(i, types.InputMediaPhoto): - if os.path.isfile(i.media) or isinstance(i.media, io.IOBase): + if isinstance(i.media, str): + if os.path.isfile(i.media): + media = await self.send( + raw.functions.messages.UploadMedia( + peer=await self.resolve_peer(chat_id), + media=raw.types.InputMediaUploadedPhoto( + file=await self.save_file(i.media) + ) + ) + ) + + media = raw.types.InputMediaPhoto( + id=raw.types.InputPhoto( + id=media.photo.id, + access_hash=media.photo.access_hash, + file_reference=media.photo.file_reference + ) + ) + elif re.match("^https?://", i.media): + media = await self.send( + raw.functions.messages.UploadMedia( + peer=await self.resolve_peer(chat_id), + media=raw.types.InputMediaPhotoExternal( + url=i.media + ) + ) + ) + + media = raw.types.InputMediaPhoto( + id=raw.types.InputPhoto( + id=media.photo.id, + access_hash=media.photo.access_hash, + file_reference=media.photo.file_reference + ) + ) + else: + media = utils.get_input_media_from_file_id(i.media, FileType.PHOTO) + else: media = await self.send( raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), @@ -105,34 +141,63 @@ async def send_media_group( file_reference=media.photo.file_reference ) ) - elif re.match("^https?://", i.media): - media = await self.send( - raw.functions.messages.UploadMedia( - peer=await self.resolve_peer(chat_id), - media=raw.types.InputMediaPhotoExternal( - url=i.media + elif isinstance(i, types.InputMediaVideo): + if isinstance(i.media, str): + if os.path.isfile(i.media): + media = await self.send( + raw.functions.messages.UploadMedia( + peer=await self.resolve_peer(chat_id), + media=raw.types.InputMediaUploadedDocument( + file=await self.save_file(i.media), + thumb=await self.save_file(i.thumb), + mime_type=self.guess_mime_type(i.media) or "video/mp4", + attributes=[ + raw.types.DocumentAttributeVideo( + supports_streaming=i.supports_streaming or None, + duration=i.duration, + w=i.width, + h=i.height + ), + raw.types.DocumentAttributeFilename(file_name=os.path.basename(i.media)) + ] + ) ) ) - ) - media = raw.types.InputMediaPhoto( - id=raw.types.InputPhoto( - id=media.photo.id, - access_hash=media.photo.access_hash, - file_reference=media.photo.file_reference + media = raw.types.InputMediaDocument( + id=raw.types.InputDocument( + id=media.document.id, + access_hash=media.document.access_hash, + file_reference=media.document.file_reference + ) ) - ) + elif re.match("^https?://", i.media): + media = await self.send( + raw.functions.messages.UploadMedia( + peer=await self.resolve_peer(chat_id), + media=raw.types.InputMediaDocumentExternal( + url=i.media + ) + ) + ) + + media = raw.types.InputMediaDocument( + id=raw.types.InputDocument( + id=media.document.id, + access_hash=media.document.access_hash, + file_reference=media.document.file_reference + ) + ) + else: + media = utils.get_input_media_from_file_id(i.media, FileType.VIDEO) else: - media = utils.get_input_media_from_file_id(i.media, FileType.PHOTO) - elif isinstance(i, types.InputMediaVideo): - if os.path.isfile(i.media) or isinstance(i.media, io.IOBase): media = await self.send( raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), media=raw.types.InputMediaUploadedDocument( file=await self.save_file(i.media), thumb=await self.save_file(i.thumb), - mime_type=self.guess_mime_type(i.media) or "video/mp4", + mime_type=self.guess_mime_type(getattr(i.media, "name", "video.mp4")) or "video/mp4", attributes=[ raw.types.DocumentAttributeVideo( supports_streaming=i.supports_streaming or None, @@ -140,7 +205,7 @@ async def send_media_group( w=i.width, h=i.height ), - raw.types.DocumentAttributeFilename(file_name=os.path.basename(i.media)) + raw.types.DocumentAttributeFilename(file_name=getattr(i.media, "name", "video.mp4")) ] ) ) @@ -153,32 +218,60 @@ async def send_media_group( file_reference=media.document.file_reference ) ) - elif re.match("^https?://", i.media): - media = await self.send( - raw.functions.messages.UploadMedia( - peer=await self.resolve_peer(chat_id), - media=raw.types.InputMediaDocumentExternal( - url=i.media + elif isinstance(i, types.InputMediaAudio): + if isinstance(i.media, str): + if os.path.isfile(i.media): + media = await self.send( + raw.functions.messages.UploadMedia( + peer=await self.resolve_peer(chat_id), + media=raw.types.InputMediaUploadedDocument( + mime_type=self.guess_mime_type(i.media) or "audio/mpeg", + file=await self.save_file(i.media), + thumb=await self.save_file(i.thumb), + attributes=[ + raw.types.DocumentAttributeAudio( + duration=i.duration, + performer=i.performer, + title=i.title + ), + raw.types.DocumentAttributeFilename(file_name=os.path.basename(i.media)) + ] + ) ) ) - ) - media = raw.types.InputMediaDocument( - id=raw.types.InputDocument( - id=media.document.id, - access_hash=media.document.access_hash, - file_reference=media.document.file_reference + media = raw.types.InputMediaDocument( + id=raw.types.InputDocument( + id=media.document.id, + access_hash=media.document.access_hash, + file_reference=media.document.file_reference + ) ) - ) + elif re.match("^https?://", i.media): + media = await self.send( + raw.functions.messages.UploadMedia( + peer=await self.resolve_peer(chat_id), + media=raw.types.InputMediaDocumentExternal( + url=i.media + ) + ) + ) + + media = raw.types.InputMediaDocument( + id=raw.types.InputDocument( + id=media.document.id, + access_hash=media.document.access_hash, + file_reference=media.document.file_reference + ) + ) + else: + media = utils.get_input_media_from_file_id(i.media, FileType.AUDIO) else: - media = utils.get_input_media_from_file_id(i.media, FileType.VIDEO) - elif isinstance(i, types.InputMediaAudio): - if os.path.isfile(i.media): media = await self.send( raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), media=raw.types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(i.media) or "audio/mpeg", + mime_type=self.guess_mime_type(getattr(i.media, "name", "audio.mp3")) or "audio/mpeg", file=await self.save_file(i.media), thumb=await self.save_file(i.thumb), attributes=[ @@ -187,7 +280,7 @@ async def send_media_group( performer=i.performer, title=i.title ), - raw.types.DocumentAttributeFilename(file_name=os.path.basename(i.media)) + raw.types.DocumentAttributeFilename(file_name=getattr(i.media, "name", "audio.mp3")) ] ) ) @@ -200,36 +293,61 @@ async def send_media_group( file_reference=media.document.file_reference ) ) - elif re.match("^https?://", i.media): - media = await self.send( - raw.functions.messages.UploadMedia( - peer=await self.resolve_peer(chat_id), - media=raw.types.InputMediaDocumentExternal( - url=i.media + elif isinstance(i, types.InputMediaDocument): + if isinstance(i.media, str): + if os.path.isfile(i.media): + media = await self.send( + raw.functions.messages.UploadMedia( + peer=await self.resolve_peer(chat_id), + media=raw.types.InputMediaUploadedDocument( + mime_type=self.guess_mime_type(i.media) or "application/zip", + file=await self.save_file(i.media), + thumb=await self.save_file(i.thumb), + attributes=[ + raw.types.DocumentAttributeFilename(file_name=os.path.basename(i.media)) + ] + ) ) ) - ) - media = raw.types.InputMediaDocument( - id=raw.types.InputDocument( - id=media.document.id, - access_hash=media.document.access_hash, - file_reference=media.document.file_reference + media = raw.types.InputMediaDocument( + id=raw.types.InputDocument( + id=media.document.id, + access_hash=media.document.access_hash, + file_reference=media.document.file_reference + ) ) - ) + elif re.match("^https?://", i.media): + media = await self.send( + raw.functions.messages.UploadMedia( + peer=await self.resolve_peer(chat_id), + media=raw.types.InputMediaDocumentExternal( + url=i.media + ) + ) + ) + + media = raw.types.InputMediaDocument( + id=raw.types.InputDocument( + id=media.document.id, + access_hash=media.document.access_hash, + file_reference=media.document.file_reference + ) + ) + else: + media = utils.get_input_media_from_file_id(i.media, FileType.DOCUMENT) else: - media = utils.get_input_media_from_file_id(i.media, FileType.AUDIO) - elif isinstance(i, types.InputMediaDocument): - if os.path.isfile(i.media): media = await self.send( raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), media=raw.types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(i.media) or "application/zip", + mime_type=self.guess_mime_type( + getattr(i.media, "name", "file.zip") + ) or "application/zip", file=await self.save_file(i.media), thumb=await self.save_file(i.thumb), attributes=[ - raw.types.DocumentAttributeFilename(file_name=os.path.basename(i.media)) + raw.types.DocumentAttributeFilename(file_name=getattr(i.media, "name", "file.zip")) ] ) ) @@ -242,25 +360,8 @@ async def send_media_group( file_reference=media.document.file_reference ) ) - elif re.match("^https?://", i.media): - media = await self.send( - raw.functions.messages.UploadMedia( - peer=await self.resolve_peer(chat_id), - media=raw.types.InputMediaDocumentExternal( - url=i.media - ) - ) - ) - - media = raw.types.InputMediaDocument( - id=raw.types.InputDocument( - id=media.document.id, - access_hash=media.document.access_hash, - file_reference=media.document.file_reference - ) - ) - else: - media = utils.get_input_media_from_file_id(i.media, FileType.DOCUMENT) + else: + raise ValueError(f"{i.__class__.__name__} is not a supported type for send_media_group") multi_media.append( raw.types.InputSingleMedia( diff --git a/pyrogram/types/input_media/input_media_animation.py b/pyrogram/types/input_media/input_media_animation.py index e39a148e82..9c5767fe78 100644 --- a/pyrogram/types/input_media/input_media_animation.py +++ b/pyrogram/types/input_media/input_media_animation.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Optional, List +from typing import Optional, List, Union, BinaryIO from .input_media import InputMedia from ..messages_and_media import MessageEntity @@ -30,6 +30,7 @@ class InputMediaAnimation(InputMedia): Animation to send. Pass a file_id as string to send a file that exists on the Telegram servers or pass a file path as string to upload a new file that exists on your local machine or + pass a binary file-like object with its attribute “.name” set for in-memory uploads or pass an HTTP URL as a string for Telegram to get an animation from the Internet. thumb (``str``, *optional*): @@ -64,7 +65,7 @@ class InputMediaAnimation(InputMedia): def __init__( self, - media: str, + media: Union[str, BinaryIO], thumb: str = None, caption: str = "", parse_mode: Optional[str] = object, diff --git a/pyrogram/types/input_media/input_media_audio.py b/pyrogram/types/input_media/input_media_audio.py index e47bc8185f..3a66b5ee58 100644 --- a/pyrogram/types/input_media/input_media_audio.py +++ b/pyrogram/types/input_media/input_media_audio.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Optional, List +from typing import Optional, List, BinaryIO, Union from .input_media import InputMedia from ..messages_and_media import MessageEntity @@ -32,6 +32,7 @@ class InputMediaAudio(InputMedia): Audio to send. Pass a file_id as string to send an audio that exists on the Telegram servers or pass a file path as string to upload a new audio that exists on your local machine or + pass a binary file-like object with its attribute “.name” set for in-memory uploads or pass an HTTP URL as a string for Telegram to get an audio file from the Internet. thumb (``str``, *optional*): @@ -66,7 +67,7 @@ class InputMediaAudio(InputMedia): def __init__( self, - media: str, + media: Union[str, BinaryIO], thumb: str = None, caption: str = "", parse_mode: Optional[str] = object, diff --git a/pyrogram/types/input_media/input_media_document.py b/pyrogram/types/input_media/input_media_document.py index 12b9fc940d..3146006048 100644 --- a/pyrogram/types/input_media/input_media_document.py +++ b/pyrogram/types/input_media/input_media_document.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Optional, List +from typing import Optional, List, Union, BinaryIO from .input_media import InputMedia from ..messages_and_media import MessageEntity @@ -30,6 +30,7 @@ class InputMediaDocument(InputMedia): File to send. Pass a file_id as string to send a file that exists on the Telegram servers or pass a file path as string to upload a new file that exists on your local machine or + pass a binary file-like object with its attribute “.name” set for in-memory uploads or pass an HTTP URL as a string for Telegram to get a file from the Internet. thumb (``str``): @@ -55,7 +56,7 @@ class InputMediaDocument(InputMedia): def __init__( self, - media: str, + media: Union[str, BinaryIO], thumb: str = None, caption: str = "", parse_mode: Optional[str] = object, diff --git a/pyrogram/types/input_media/input_media_video.py b/pyrogram/types/input_media/input_media_video.py index b0c368307d..1199d86265 100644 --- a/pyrogram/types/input_media/input_media_video.py +++ b/pyrogram/types/input_media/input_media_video.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Optional, List +from typing import Optional, List, Union, BinaryIO from .input_media import InputMedia from ..messages_and_media import MessageEntity @@ -31,6 +31,7 @@ class InputMediaVideo(InputMedia): Video to send. Pass a file_id as string to send a video that exists on the Telegram servers or pass a file path as string to upload a new video that exists on your local machine or + pass a binary file-like object with its attribute “.name” set for in-memory uploads or pass an HTTP URL as a string for Telegram to get a video from the Internet. thumb (``str``): @@ -68,7 +69,7 @@ class InputMediaVideo(InputMedia): def __init__( self, - media: str, + media: Union[str, BinaryIO], thumb: str = None, caption: str = "", parse_mode: Optional[str] = object, From c90e5a419c66b0a1e7325d86d9176a8a30b9f499 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 1 Jun 2021 20:21:50 +0200 Subject: [PATCH 0605/1185] Update issue templates --- .github/CODE_OF_CONDUCT.md | 76 ----------------------- .github/CONTRIBUTING.md | 1 - .github/ISSUE_TEMPLATE/bug_report.md | 20 ++++-- .github/ISSUE_TEMPLATE/config.yml | 8 +++ .github/ISSUE_TEMPLATE/feature_request.md | 2 +- .github/ISSUE_TEMPLATE/question.md | 15 ----- 6 files changed, 23 insertions(+), 99 deletions(-) delete mode 100644 .github/CODE_OF_CONDUCT.md delete mode 100644 .github/CONTRIBUTING.md create mode 100644 .github/ISSUE_TEMPLATE/config.yml delete mode 100644 .github/ISSUE_TEMPLATE/question.md diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md deleted file mode 100644 index c3b6928415..0000000000 --- a/.github/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,76 +0,0 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -In the interest of fostering an open and welcoming environment, we as -contributors and maintainers pledge to making participation in our project and -our community a harassment-free experience for everyone, regardless of age, body -size, disability, ethnicity, sex characteristics, gender identity and expression, -level of experience, education, socio-economic status, nationality, personal -appearance, race, religion, or sexual identity and orientation. - -## Our Standards - -Examples of behavior that contributes to creating a positive environment -include: - -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members - -Examples of unacceptable behavior by participants include: - -* The use of sexualized language or imagery and unwelcome sexual attention or - advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic - address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Our Responsibilities - -Project maintainers are responsible for clarifying the standards of acceptable -behavior and are expected to take appropriate and fair corrective action in -response to any instances of unacceptable behavior. - -Project maintainers have the right and responsibility to remove, edit, or -reject comments, commits, code, wiki edits, issues, and other contributions -that are not aligned to this Code of Conduct, or to ban temporarily or -permanently any contributor for other behaviors that they deem inappropriate, -threatening, offensive, or harmful. - -## Scope - -This Code of Conduct applies both within project spaces and in public spaces -when an individual is representing the project or its community. Examples of -representing a project or community include using an official project e-mail -address, posting via an official social media account, or acting as an appointed -representative at an online or offline event. Representation of a project may be -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 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. -Further details of specific enforcement policies may be posted separately. - -Project maintainers who do not follow or enforce the Code of Conduct in good -faith may face temporary or permanent repercussions as determined by other -members of the project's leadership. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, -available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html - -[homepage]: https://www.contributor-covenant.org - -For answers to common questions about this code of conduct, see -https://www.contributor-covenant.org/faq diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md deleted file mode 100644 index e721fd9829..0000000000 --- a/.github/CONTRIBUTING.md +++ /dev/null @@ -1 +0,0 @@ -# How to Contribute diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 15fc4fa38b..7efd4ca160 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,9 +1,9 @@ --- name: Bug Report -about: Create a bug report affecting the library +about: Create a bug report affecting the library or the documentation --- - + ## Checklist - [ ] I am sure the error is coming from Pyrogram's code and not elsewhere. @@ -11,10 +11,18 @@ about: Create a bug report affecting the library - [ ] I ran `pip3 install -U https://github.com/pyrogram/pyrogram/archive/master.zip` and reproduced the issue using the latest development version. ## Description -A **clear** and **concise** description of the problem. Code snippets must be formatted properly. +A **clear** and **concise** description of the problem. Code snippets must be +[minimal, reproducible](https://stackoverflow.com/help/minimal-reproducible-example) and properly formatted. -## Steps to Reproduce -[A minimal, reproducible example](https://stackoverflow.com/help/minimal-reproducible-example). +``` python +from pyrogram import Client +... +``` ## Traceback -The full traceback (if applicable). \ No newline at end of file +The full traceback (if applicable). + +``` +Traceback (most recent call last): + File "main.py", line 1, in +``` \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000..37e0cd33be --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Ask Pyrogram related questions + url: https://stackoverflow.com/questions/tagged/pyrogram + about: This place is for issues about Pyrogram. If you'd like to ask a question, please do so at StackOverflow. + - name: Join the Telegram community + url: https://t.me/pyrogram + about: Join the official channel to stay tuned for news and updates. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 279af3e4d5..854db44d9c 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -4,7 +4,7 @@ about: Suggest ideas, new features or enhancements labels: "enhancement" --- - + ## Checklist - [ ] I believe the idea is awesome and would benefit the library. diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md deleted file mode 100644 index 883503c446..0000000000 --- a/.github/ISSUE_TEMPLATE/question.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -name: Ask Question -about: Ask a Pyrogram related question -title: For Q&A purposes, please read this template body -labels: "invalid" ---- - - - -# Important -This place is for issues about Pyrogram, it's **not a forum**. - -If you'd like to post a question, please move to https://stackoverflow.com or join the Telegram community at https://t.me/pyrogram. Useful information on how to ask good questions can be found here: https://stackoverflow.com/help/how-to-ask. - -Thanks. From f7713d135e072d3e21a5d242de361b64af84fd6e Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 1 Jun 2021 21:03:17 +0200 Subject: [PATCH 0606/1185] Use a cleaner link --- .github/FUNDING.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index f34f615a8c..13e0071eb0 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,2 +1,2 @@ github: delivrance -custom: https://docs.pyrogram.org/support-pyrogram +custom: https://docs.pyrogram.org/support From ab63abe6fdc739c51fde404523e1e2211b03b1c0 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 6 Jun 2021 11:52:01 +0200 Subject: [PATCH 0607/1185] Update API schema to Layer 129 --- compiler/api/source/main_api.tl | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl index 8cd2235c71..30740228b5 100644 --- a/compiler/api/source/main_api.tl +++ b/compiler/api/source/main_api.tl @@ -46,7 +46,7 @@ inputMediaVenue#c13d1c11 geo_point:InputGeoPoint title:string address:string pro inputMediaPhotoExternal#e5bbfe1a flags:# url:string ttl_seconds:flags.0?int = InputMedia; inputMediaDocumentExternal#fb52dc99 flags:# url:string ttl_seconds:flags.0?int = InputMedia; inputMediaGame#d33f43f3 id:InputGame = InputMedia; -inputMediaInvoice#f4e096c3 flags:# multiple_allowed:flags.1?true can_forward:flags.2?true title:string description:string photo:flags.0?InputWebDocument invoice:Invoice payload:bytes provider:string provider_data:DataJSON start_param:string = InputMedia; +inputMediaInvoice#d9799874 flags:# title:string description:string photo:flags.0?InputWebDocument invoice:Invoice payload:bytes provider:string provider_data:DataJSON start_param:flags.1?string = InputMedia; inputMediaGeoLive#971fa843 flags:# stopped:flags.0?true geo_point:InputGeoPoint heading:flags.2?int period:flags.1?int proximity_notification_radius:flags.3?int = InputMedia; inputMediaPoll#f94e5f1 flags:# poll:Poll correct_answers:flags.0?Vector solution:flags.1?string solution_entities:flags.1?Vector = InputMedia; inputMediaDice#e66fbf7b emoticon:string = InputMedia; @@ -334,7 +334,7 @@ updateDeleteScheduledMessages#90866cee peer:Peer messages:Vector = Update; updateTheme#8216fba3 theme:Theme = Update; updateGeoLiveViewed#871fb939 peer:Peer msg_id:int = Update; updateLoginToken#564fe691 = Update; -updateMessagePollVote#42f88f2c poll_id:long user_id:int options:Vector = Update; +updateMessagePollVote#37f69f0b poll_id:long user_id:int options:Vector qts:int = Update; updateDialogFilter#26ffde7d flags:# id:int filter:flags.0?DialogFilter = Update; updateDialogFilterOrder#a5d72105 order:Vector = Update; updateDialogFilters#3504914f = Update; @@ -353,6 +353,7 @@ updatePeerHistoryTTL#bb9bb9a5 flags:# peer:Peer ttl_period:flags.0?int = Update; updateChatParticipant#f3b3781f flags:# chat_id:int date:int actor_id:int user_id:int prev_participant:flags.0?ChatParticipant new_participant:flags.1?ChatParticipant invite:flags.2?ExportedChatInvite qts:int = Update; updateChannelParticipant#7fecb1ec flags:# channel_id:int date:int actor_id:int user_id:int prev_participant:flags.0?ChannelParticipant new_participant:flags.1?ChannelParticipant invite:flags.2?ExportedChatInvite qts:int = Update; updateBotStopped#7f9488a user_id:int date:int stopped:Bool qts:int = Update; +updateGroupCallConnection#b783982 flags:# presentation:flags.0?true params:DataJSON = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -383,7 +384,7 @@ config#330b4067 flags:# phonecalls_enabled:flags.1?true default_p2p_contacts:fla nearestDc#8e1a1775 country:string this_dc:int nearest_dc:int = NearestDc; -help.appUpdate#1da7158f flags:# can_not_skip:flags.0?true id:int version:string text:string entities:Vector document:flags.1?Document url:flags.2?string = help.AppUpdate; +help.appUpdate#ccbbce30 flags:# can_not_skip:flags.0?true id:int version:string text:string entities:Vector document:flags.1?Document url:flags.2?string sticker:flags.3?Document = help.AppUpdate; help.noAppUpdate#c45a6536 = help.AppUpdate; help.inviteText#18cb9f78 message:string = help.InviteText; @@ -627,7 +628,7 @@ inputBotInlineMessageMediaGeo#96929a85 flags:# geo_point:InputGeoPoint heading:f inputBotInlineMessageMediaVenue#417bbf11 flags:# geo_point:InputGeoPoint title:string address:string provider:string venue_id:string venue_type:string reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; inputBotInlineMessageMediaContact#a6edbffd flags:# phone_number:string first_name:string last_name:string vcard:string reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; inputBotInlineMessageGame#4b425864 flags:# reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; -inputBotInlineMessageMediaInvoice#d5348d85 flags:# multiple_allowed:flags.1?true can_forward:flags.3?true title:string description:string photo:flags.0?InputWebDocument invoice:Invoice payload:bytes provider:string provider_data:DataJSON start_param:string reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; +inputBotInlineMessageMediaInvoice#d7e78225 flags:# title:string description:string photo:flags.0?InputWebDocument invoice:Invoice payload:bytes provider:string provider_data:DataJSON reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; inputBotInlineResult#88bf9319 flags:# id:string type:string title:flags.1?string description:flags.2?string url:flags.3?string thumb:flags.4?InputWebDocument content:flags.5?InputWebDocument send_message:InputBotInlineMessage = InputBotInlineResult; inputBotInlineResultPhoto#a8d864a7 id:string type:string photo:InputPhoto send_message:InputBotInlineMessage = InputBotInlineResult; @@ -1054,7 +1055,7 @@ account.wallPapers#702b65a9 hash:int wallpapers:Vector = account.Wall codeSettings#debebe83 flags:# allow_flashcall:flags.0?true current_number:flags.1?true allow_app_hash:flags.4?true = CodeSettings; -wallPaperSettings#5086cf8 flags:# blur:flags.1?true motion:flags.2?true background_color:flags.0?int second_background_color:flags.4?int intensity:flags.3?int rotation:flags.4?int = WallPaperSettings; +wallPaperSettings#1dc1bca4 flags:# blur:flags.1?true motion:flags.2?true background_color:flags.0?int second_background_color:flags.4?int third_background_color:flags.5?int fourth_background_color:flags.6?int intensity:flags.3?int rotation:flags.4?int = WallPaperSettings; autoDownloadSettings#e04232f3 flags:# disabled:flags.0?true video_preload_large:flags.1?true audio_preload_next:flags.2?true phonecalls_less_data:flags.3?true photo_size_max:int video_size_max:int file_size_max:int video_upload_maxbitrate:int = AutoDownloadSettings; @@ -1182,11 +1183,11 @@ peerBlocked#e8fd8014 peer_id:Peer date:int = PeerBlocked; stats.messageStats#8999f295 views_graph:StatsGraph = stats.MessageStats; groupCallDiscarded#7780bcb4 id:long access_hash:long duration:int = GroupCall; -groupCall#c95c6654 flags:# join_muted:flags.1?true can_change_join_muted:flags.2?true join_date_asc:flags.6?true schedule_start_subscribed:flags.8?true id:long access_hash:long participants_count:int params:flags.0?DataJSON title:flags.3?string stream_dc_id:flags.4?int record_start_date:flags.5?int schedule_date:flags.7?int version:int = GroupCall; +groupCall#653dbaad flags:# join_muted:flags.1?true can_change_join_muted:flags.2?true join_date_asc:flags.6?true schedule_start_subscribed:flags.8?true can_start_video:flags.9?true id:long access_hash:long participants_count:int title:flags.3?string stream_dc_id:flags.4?int record_start_date:flags.5?int schedule_date:flags.7?int version:int = GroupCall; inputGroupCall#d8aa840f id:long access_hash:long = InputGroupCall; -groupCallParticipant#b96b25ee flags:# muted:flags.0?true left:flags.1?true can_self_unmute:flags.2?true just_joined:flags.4?true versioned:flags.5?true min:flags.8?true muted_by_you:flags.9?true volume_by_admin:flags.10?true self:flags.12?true peer:Peer date:int active_date:flags.3?int source:int volume:flags.7?int about:flags.11?string raise_hand_rating:flags.13?long params:flags.6?DataJSON = GroupCallParticipant; +groupCallParticipant#a8ba51a7 flags:# muted:flags.0?true left:flags.1?true can_self_unmute:flags.2?true just_joined:flags.4?true versioned:flags.5?true min:flags.8?true muted_by_you:flags.9?true volume_by_admin:flags.10?true self:flags.12?true peer:Peer date:int active_date:flags.3?int source:int volume:flags.7?int about:flags.11?string raise_hand_rating:flags.13?long video:flags.6?DataJSON presentation:flags.14?DataJSON = GroupCallParticipant; phone.groupCall#9e727aad call:GroupCall participants:Vector participants_next_offset:string chats:Vector users:Vector = phone.GroupCall; @@ -1595,22 +1596,24 @@ phone.setCallRating#59ead627 flags:# user_initiative:flags.0?true peer:InputPhon phone.saveCallDebug#277add7e peer:InputPhoneCall debug:DataJSON = Bool; phone.sendSignalingData#ff7a9383 peer:InputPhoneCall data:bytes = Bool; phone.createGroupCall#48cdc6d8 flags:# peer:InputPeer random_id:int title:flags.0?string schedule_date:flags.1?int = Updates; -phone.joinGroupCall#b132ff7b flags:# muted:flags.0?true call:InputGroupCall join_as:InputPeer invite_hash:flags.1?string params:DataJSON = Updates; +phone.joinGroupCall#b132ff7b flags:# muted:flags.0?true video_muted:flags.2?true call:InputGroupCall join_as:InputPeer invite_hash:flags.1?string params:DataJSON = Updates; phone.leaveGroupCall#500377f9 call:InputGroupCall source:int = Updates; phone.inviteToGroupCall#7b393160 call:InputGroupCall users:Vector = Updates; phone.discardGroupCall#7a777135 call:InputGroupCall = Updates; phone.toggleGroupCallSettings#74bbb43d flags:# reset_invite_hash:flags.1?true call:InputGroupCall join_muted:flags.0?Bool = Updates; phone.getGroupCall#c7cb017 call:InputGroupCall = phone.GroupCall; phone.getGroupParticipants#c558d8ab call:InputGroupCall ids:Vector sources:Vector offset:string limit:int = phone.GroupParticipants; -phone.checkGroupCall#b74a7bea call:InputGroupCall source:int = Bool; +phone.checkGroupCall#b59cf977 call:InputGroupCall sources:Vector = Vector; phone.toggleGroupCallRecord#c02a66d7 flags:# start:flags.0?true call:InputGroupCall title:flags.1?string = Updates; -phone.editGroupCallParticipant#d975eb80 flags:# muted:flags.0?true call:InputGroupCall participant:InputPeer volume:flags.1?int raise_hand:flags.2?Bool = Updates; +phone.editGroupCallParticipant#aec610e4 flags:# call:InputGroupCall participant:InputPeer muted:flags.0?Bool volume:flags.1?int raise_hand:flags.2?Bool video_muted:flags.3?Bool = Updates; phone.editGroupCallTitle#1ca6ac0a call:InputGroupCall title:string = Updates; phone.getGroupCallJoinAs#ef7c213a peer:InputPeer = phone.JoinAsPeers; phone.exportGroupCallInvite#e6aa647f flags:# can_self_unmute:flags.0?true call:InputGroupCall = phone.ExportedGroupCallInvite; phone.toggleGroupCallStartSubscription#219c34e6 call:InputGroupCall subscribed:Bool = Updates; phone.startScheduledGroupCall#5680e342 call:InputGroupCall = Updates; phone.saveDefaultGroupCallJoinAs#575e1f8c peer:InputPeer join_as:InputPeer = Bool; +phone.joinGroupCallPresentation#cbea6bc4 call:InputGroupCall params:DataJSON = Updates; +phone.leaveGroupCallPresentation#1c50d144 call:InputGroupCall = Updates; langpack.getLangPack#f2f2330a lang_pack:string lang_code:string = LangPackDifference; langpack.getStrings#efea3803 lang_pack:string lang_code:string keys:Vector = Vector; @@ -1627,4 +1630,4 @@ stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats; -// LAYER 128 \ No newline at end of file +// LAYER 129 \ No newline at end of file From 3dde55db39af59445e66a0a1e73807779adff2c4 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 20 Jun 2021 08:48:38 +0200 Subject: [PATCH 0608/1185] Update API schema to L129 (patch) --- compiler/api/source/main_api.tl | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl index 30740228b5..1f4c1c1b0f 100644 --- a/compiler/api/source/main_api.tl +++ b/compiler/api/source/main_api.tl @@ -201,7 +201,7 @@ peerNotifySettings#af509d20 flags:# show_previews:flags.0?Bool silent:flags.1?Bo peerSettings#733f2961 flags:# report_spam:flags.0?true add_contact:flags.1?true block_contact:flags.2?true share_contact:flags.3?true need_contacts_exception:flags.4?true report_geo:flags.5?true autoarchived:flags.7?true invite_members:flags.8?true geo_distance:flags.6?int = PeerSettings; wallPaper#a437c3ed id:long flags:# creator:flags.0?true default:flags.1?true pattern:flags.3?true dark:flags.4?true access_hash:long slug:string document:Document settings:flags.2?WallPaperSettings = WallPaper; -wallPaperNoFile#8af40b25 flags:# default:flags.1?true dark:flags.4?true settings:flags.2?WallPaperSettings = WallPaper; +wallPaperNoFile#e0804116 id:long flags:# default:flags.1?true dark:flags.4?true settings:flags.2?WallPaperSettings = WallPaper; inputReportReasonSpam#58dbcab8 = ReportReason; inputReportReasonViolence#1e22c78d = ReportReason; @@ -1048,7 +1048,7 @@ chatBannedRights#9f120418 flags:# view_messages:flags.0?true send_messages:flags inputWallPaper#e630b979 id:long access_hash:long = InputWallPaper; inputWallPaperSlug#72091c80 slug:string = InputWallPaper; -inputWallPaperNoFile#8427bbac = InputWallPaper; +inputWallPaperNoFile#967a462e id:long = InputWallPaper; account.wallPapersNotModified#1c199183 = account.WallPapers; account.wallPapers#702b65a9 hash:int wallpapers:Vector = account.WallPapers; @@ -1187,7 +1187,7 @@ groupCall#653dbaad flags:# join_muted:flags.1?true can_change_join_muted:flags.2 inputGroupCall#d8aa840f id:long access_hash:long = InputGroupCall; -groupCallParticipant#a8ba51a7 flags:# muted:flags.0?true left:flags.1?true can_self_unmute:flags.2?true just_joined:flags.4?true versioned:flags.5?true min:flags.8?true muted_by_you:flags.9?true volume_by_admin:flags.10?true self:flags.12?true peer:Peer date:int active_date:flags.3?int source:int volume:flags.7?int about:flags.11?string raise_hand_rating:flags.13?long video:flags.6?DataJSON presentation:flags.14?DataJSON = GroupCallParticipant; +groupCallParticipant#eba636fe flags:# muted:flags.0?true left:flags.1?true can_self_unmute:flags.2?true just_joined:flags.4?true versioned:flags.5?true min:flags.8?true muted_by_you:flags.9?true volume_by_admin:flags.10?true self:flags.12?true video_joined:flags.15?true peer:Peer date:int active_date:flags.3?int source:int volume:flags.7?int about:flags.11?string raise_hand_rating:flags.13?long video:flags.6?GroupCallParticipantVideo presentation:flags.14?GroupCallParticipantVideo = GroupCallParticipant; phone.groupCall#9e727aad call:GroupCall participants:Vector participants_next_offset:string chats:Vector users:Vector = phone.GroupCall; @@ -1224,6 +1224,10 @@ phone.joinAsPeers#afe5623f peers:Vector chats:Vector users:Vector = GroupCallParticipantVideoSourceGroup; + +groupCallParticipantVideo#78e41663 flags:# paused:flags.0?true endpoint:string source_groups:Vector = GroupCallParticipantVideo; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -1596,7 +1600,7 @@ phone.setCallRating#59ead627 flags:# user_initiative:flags.0?true peer:InputPhon phone.saveCallDebug#277add7e peer:InputPhoneCall debug:DataJSON = Bool; phone.sendSignalingData#ff7a9383 peer:InputPhoneCall data:bytes = Bool; phone.createGroupCall#48cdc6d8 flags:# peer:InputPeer random_id:int title:flags.0?string schedule_date:flags.1?int = Updates; -phone.joinGroupCall#b132ff7b flags:# muted:flags.0?true video_muted:flags.2?true call:InputGroupCall join_as:InputPeer invite_hash:flags.1?string params:DataJSON = Updates; +phone.joinGroupCall#b132ff7b flags:# muted:flags.0?true video_stopped:flags.2?true call:InputGroupCall join_as:InputPeer invite_hash:flags.1?string params:DataJSON = Updates; phone.leaveGroupCall#500377f9 call:InputGroupCall source:int = Updates; phone.inviteToGroupCall#7b393160 call:InputGroupCall users:Vector = Updates; phone.discardGroupCall#7a777135 call:InputGroupCall = Updates; @@ -1605,7 +1609,7 @@ phone.getGroupCall#c7cb017 call:InputGroupCall = phone.GroupCall; phone.getGroupParticipants#c558d8ab call:InputGroupCall ids:Vector sources:Vector offset:string limit:int = phone.GroupParticipants; phone.checkGroupCall#b59cf977 call:InputGroupCall sources:Vector = Vector; phone.toggleGroupCallRecord#c02a66d7 flags:# start:flags.0?true call:InputGroupCall title:flags.1?string = Updates; -phone.editGroupCallParticipant#aec610e4 flags:# call:InputGroupCall participant:InputPeer muted:flags.0?Bool volume:flags.1?int raise_hand:flags.2?Bool video_muted:flags.3?Bool = Updates; +phone.editGroupCallParticipant#a5273abf flags:# call:InputGroupCall participant:InputPeer muted:flags.0?Bool volume:flags.1?int raise_hand:flags.2?Bool video_stopped:flags.3?Bool video_paused:flags.4?Bool presentation_paused:flags.5?Bool = Updates; phone.editGroupCallTitle#1ca6ac0a call:InputGroupCall title:string = Updates; phone.getGroupCallJoinAs#ef7c213a peer:InputPeer = phone.JoinAsPeers; phone.exportGroupCallInvite#e6aa647f flags:# can_self_unmute:flags.0?true call:InputGroupCall = phone.ExportedGroupCallInvite; From a65f8e2655e8ef7e4eefb4383f9a4306d9e9f1f1 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 20 Jun 2021 08:52:11 +0200 Subject: [PATCH 0609/1185] Clearer error message --- pyrogram/crypto/mtproto.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/crypto/mtproto.py b/pyrogram/crypto/mtproto.py index dad30cd398..803db29704 100644 --- a/pyrogram/crypto/mtproto.py +++ b/pyrogram/crypto/mtproto.py @@ -72,7 +72,7 @@ def unpack(b: BytesIO, session_id: bytes, auth_key: bytes, auth_key_id: bytes) - left = [[left[i:i + 8] for i in range(0, len(left), 8)] for left in left] left = "\n".join(" ".join(x for x in left) for left in left) - raise ValueError(f"Unknown constructor found: {hex(e.args[0])}\n{left}") + raise ValueError(f"The server sent an unknown constructor: {hex(e.args[0])}\n{left}") # https://core.telegram.org/mtproto/security_guidelines#checking-sha256-hash-value-of-msg-key # https://core.telegram.org/mtproto/security_guidelines#checking-message-length From 685b9328f85c27c1a3d1584132876e8f7cc2d867 Mon Sep 17 00:00:00 2001 From: Shrimadhav U K Date: Tue, 22 Jun 2021 14:10:13 +0530 Subject: [PATCH 0610/1185] Add missing parameters in reply_document (#700) --- pyrogram/types/messages_and_media/message.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index f349d1d76d..5ecb6c21c5 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -1401,8 +1401,11 @@ async def reply_document( caption: str = "", parse_mode: Optional[str] = object, caption_entities: List["types.MessageEntity"] = None, + file_name: str = None, + force_document: bool = None, disable_notification: bool = None, reply_to_message_id: int = None, + schedule_date: int = None, reply_markup: Union[ "types.InlineKeyboardMarkup", "types.ReplyKeyboardMarkup", @@ -1458,6 +1461,15 @@ async def reply_document( caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): List of special entities that appear in the caption, which can be specified instead of *parse_mode*. + + file_name (``str``, *optional*): + File name of the document sent. + Defaults to file's path basename. + + force_document (``bool``, *optional*): + Pass True to force sending files as document. Useful for video files that need to be sent as + document messages instead of video messages. + Defaults to False. disable_notification (``bool``, *optional*): Sends the message silently. @@ -1465,6 +1477,9 @@ async def reply_document( reply_to_message_id (``int``, *optional*): If the message is a reply, ID of the original message. + + schedule_date (``int``, *optional*): + Date when the message will be automatically sent. Unix time. reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, @@ -1513,8 +1528,11 @@ async def reply_document( caption=caption, parse_mode=parse_mode, caption_entities=caption_entities, + file_name=file_name, + force_document=force_document, disable_notification=disable_notification, reply_to_message_id=reply_to_message_id, + schedule_date=schedule_date, reply_markup=reply_markup, progress=progress, progress_args=progress_args From 8845b976ab1ac8cb7acc875a1d484683ecd98c3d Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 23 Jun 2021 17:01:06 +0200 Subject: [PATCH 0611/1185] Fix docs (#699) (#703) * Fix docs (#699) * typo fixing * Update inline_query_result_video.py * layer update 129 (commits) * fixes * Update main_api.tl * Update main_api.tl Co-authored-by: BelgenOp <76117511+BelgenOp@users.noreply.github.com> --- compiler/api/source/main_api.tl | 2 +- pyrogram/types/inline_mode/inline_query_result_audio.py | 4 ++-- pyrogram/types/inline_mode/inline_query_result_video.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl index 1f4c1c1b0f..332eb5f21c 100644 --- a/compiler/api/source/main_api.tl +++ b/compiler/api/source/main_api.tl @@ -1634,4 +1634,4 @@ stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats; -// LAYER 129 \ No newline at end of file +// LAYER 129 diff --git a/pyrogram/types/inline_mode/inline_query_result_audio.py b/pyrogram/types/inline_mode/inline_query_result_audio.py index dde8b5e64a..6ca8c34c19 100644 --- a/pyrogram/types/inline_mode/inline_query_result_audio.py +++ b/pyrogram/types/inline_mode/inline_query_result_audio.py @@ -48,7 +48,7 @@ class InlineQueryResultAudio(InlineQueryResult): Audio duration in seconds. caption (``str``, *optional*): - Caption of the photo to be sent, 0-1024 characters. + Caption of the audio to be sent, 0-1024 characters. parse_mode (``str``, *optional*): By default, texts are parsed using both Markdown and HTML styles. @@ -64,7 +64,7 @@ class InlineQueryResultAudio(InlineQueryResult): Inline keyboard attached to the message. input_message_content (:obj:`~pyrogram.types.InputMessageContent`, *optional*): - Content of the message to be sent instead of the photo. + Content of the message to be sent instead of the audio. """ def __init__( diff --git a/pyrogram/types/inline_mode/inline_query_result_video.py b/pyrogram/types/inline_mode/inline_query_result_video.py index ca0ffda2b8..cf035408df 100644 --- a/pyrogram/types/inline_mode/inline_query_result_video.py +++ b/pyrogram/types/inline_mode/inline_query_result_video.py @@ -61,7 +61,7 @@ class InlineQueryResultVideo(InlineQueryResult): Short description of the result. caption (``str``, *optional*): - Caption of the photo to be sent, 0-1024 characters. + Caption of the video to be sent, 0-1024 characters. parse_mode (``str``, *optional*): By default, texts are parsed using both Markdown and HTML styles. From 3d6ec570f3d0cfc8e663ed4733b4bf8bc6e0d442 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 10 Jul 2021 09:43:14 +0200 Subject: [PATCH 0612/1185] Update API schema to Layer 130 --- compiler/api/source/main_api.tl | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl index 332eb5f21c..e75d137546 100644 --- a/compiler/api/source/main_api.tl +++ b/compiler/api/source/main_api.tl @@ -557,8 +557,8 @@ keyboardButtonRequestPoll#bbc7515d flags:# quiz:flags.0?Bool text:string = Keybo keyboardButtonRow#77608b83 buttons:Vector = KeyboardButtonRow; replyKeyboardHide#a03e5b85 flags:# selective:flags.2?true = ReplyMarkup; -replyKeyboardForceReply#f4108aa0 flags:# single_use:flags.1?true selective:flags.2?true = ReplyMarkup; -replyKeyboardMarkup#3502758c flags:# resize:flags.0?true single_use:flags.1?true selective:flags.2?true rows:Vector = ReplyMarkup; +replyKeyboardForceReply#86b40b08 flags:# single_use:flags.1?true selective:flags.2?true placeholder:flags.3?string = ReplyMarkup; +replyKeyboardMarkup#85dd99d1 flags:# resize:flags.0?true single_use:flags.1?true selective:flags.2?true rows:Vector placeholder:flags.3?string = ReplyMarkup; replyInlineMarkup#48a30254 rows:Vector = ReplyMarkup; messageEntityUnknown#bb92ba95 offset:int length:int = MessageEntity; @@ -1228,6 +1228,16 @@ groupCallParticipantVideoSourceGroup#dcb118b7 semantics:string sources:Vector = GroupCallParticipantVideo; +stickers.suggestedShortName#85fea03f short_name:string = stickers.SuggestedShortName; + +botCommandScopeDefault#2f6cb2ab = BotCommandScope; +botCommandScopeUsers#3c4f04d8 = BotCommandScope; +botCommandScopeChats#6fe1a881 = BotCommandScope; +botCommandScopeChatAdmins#b9aa606a = BotCommandScope; +botCommandScopePeer#db9d897d peer:InputPeer = BotCommandScope; +botCommandScopePeerAdmins#3fd863d1 peer:InputPeer = BotCommandScope; +botCommandScopePeerUser#a1321f3 peer:InputPeer user_id:InputUser = BotCommandScope; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -1574,7 +1584,9 @@ channels.convertToGigagroup#b290c69 channel:InputChannel = Updates; bots.sendCustomRequest#aa2769ed custom_method:string params:DataJSON = DataJSON; bots.answerWebhookJSONQuery#e6213f4d query_id:long data:DataJSON = Bool; -bots.setBotCommands#805d46f6 commands:Vector = Bool; +bots.setBotCommands#517165a scope:BotCommandScope lang_code:string commands:Vector = Bool; +bots.resetBotCommands#3d8de0f9 scope:BotCommandScope lang_code:string = Bool; +bots.getBotCommands#e34c0dd6 scope:BotCommandScope lang_code:string = Vector; payments.getPaymentForm#8a333c8d flags:# peer:InputPeer msg_id:int theme_params:flags.0?DataJSON = payments.PaymentForm; payments.getPaymentReceipt#2478d1cc peer:InputPeer msg_id:int = payments.PaymentReceipt; @@ -1584,11 +1596,13 @@ payments.getSavedInfo#227d824b = payments.SavedInfo; payments.clearSavedInfo#d83d70c1 flags:# credentials:flags.0?true info:flags.1?true = Bool; payments.getBankCardData#2e79d779 number:string = payments.BankCardData; -stickers.createStickerSet#f1036780 flags:# masks:flags.0?true animated:flags.1?true user_id:InputUser title:string short_name:string thumb:flags.2?InputDocument stickers:Vector = messages.StickerSet; +stickers.createStickerSet#9021ab67 flags:# masks:flags.0?true animated:flags.1?true user_id:InputUser title:string short_name:string thumb:flags.2?InputDocument stickers:Vector software:flags.3?string = messages.StickerSet; stickers.removeStickerFromSet#f7760f51 sticker:InputDocument = messages.StickerSet; stickers.changeStickerPosition#ffb6d4ca sticker:InputDocument position:int = messages.StickerSet; stickers.addStickerToSet#8653febe stickerset:InputStickerSet sticker:InputStickerSetItem = messages.StickerSet; stickers.setStickerSetThumb#9a364e30 stickerset:InputStickerSet thumb:InputDocument = messages.StickerSet; +stickers.checkShortName#284b3639 short_name:string = Bool; +stickers.suggestShortName#4dafc503 title:string = stickers.SuggestedShortName; phone.getCallConfig#55451fa9 = DataJSON; phone.requestCall#42ff96ed flags:# video:flags.0?true user_id:InputUser random_id:int g_a_hash:bytes protocol:PhoneCallProtocol = phone.PhoneCall; @@ -1634,4 +1648,4 @@ stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats; -// LAYER 129 +// LAYER 130 \ No newline at end of file From 6745c9d815fa2ee8c82593f103349f1bdc012a44 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 25 Jul 2021 12:46:39 +0200 Subject: [PATCH 0613/1185] Update API schema to Layer 131 --- compiler/api/source/main_api.tl | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl index e75d137546..68f8922364 100644 --- a/compiler/api/source/main_api.tl +++ b/compiler/api/source/main_api.tl @@ -354,6 +354,7 @@ updateChatParticipant#f3b3781f flags:# chat_id:int date:int actor_id:int user_id updateChannelParticipant#7fecb1ec flags:# channel_id:int date:int actor_id:int user_id:int prev_participant:flags.0?ChannelParticipant new_participant:flags.1?ChannelParticipant invite:flags.2?ExportedChatInvite qts:int = Update; updateBotStopped#7f9488a user_id:int date:int stopped:Bool qts:int = Update; updateGroupCallConnection#b783982 flags:# presentation:flags.0?true params:DataJSON = Update; +updateBotCommands#cf7e0873 peer:Peer bot_id:int commands:Vector = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -512,7 +513,7 @@ authorization#ad01d61d flags:# current:flags.0?true official_app:flags.1?true pa account.authorizations#1250abde authorizations:Vector = account.Authorizations; -account.password#ad2641f8 flags:# has_recovery:flags.0?true has_secure_values:flags.1?true has_password:flags.2?true current_algo:flags.2?PasswordKdfAlgo srp_B:flags.2?bytes srp_id:flags.2?long hint:flags.3?string email_unconfirmed_pattern:flags.4?string new_algo:PasswordKdfAlgo new_secure_algo:SecurePasswordKdfAlgo secure_random:bytes = account.Password; +account.password#185b184f flags:# has_recovery:flags.0?true has_secure_values:flags.1?true has_password:flags.2?true current_algo:flags.2?PasswordKdfAlgo srp_B:flags.2?bytes srp_id:flags.2?long hint:flags.3?string email_unconfirmed_pattern:flags.4?string new_algo:PasswordKdfAlgo new_secure_algo:SecurePasswordKdfAlgo secure_random:bytes pending_reset_date:flags.5?int = account.Password; account.passwordSettings#9a5c33e5 flags:# email:flags.0?string secure_settings:flags.1?SecureSecretSettings = account.PasswordSettings; @@ -1183,7 +1184,7 @@ peerBlocked#e8fd8014 peer_id:Peer date:int = PeerBlocked; stats.messageStats#8999f295 views_graph:StatsGraph = stats.MessageStats; groupCallDiscarded#7780bcb4 id:long access_hash:long duration:int = GroupCall; -groupCall#653dbaad flags:# join_muted:flags.1?true can_change_join_muted:flags.2?true join_date_asc:flags.6?true schedule_start_subscribed:flags.8?true can_start_video:flags.9?true id:long access_hash:long participants_count:int title:flags.3?string stream_dc_id:flags.4?int record_start_date:flags.5?int schedule_date:flags.7?int version:int = GroupCall; +groupCall#d597650c flags:# join_muted:flags.1?true can_change_join_muted:flags.2?true join_date_asc:flags.6?true schedule_start_subscribed:flags.8?true can_start_video:flags.9?true id:long access_hash:long participants_count:int title:flags.3?string stream_dc_id:flags.4?int record_start_date:flags.5?int schedule_date:flags.7?int unmuted_video_count:flags.10?int unmuted_video_limit:int version:int = GroupCall; inputGroupCall#d8aa840f id:long access_hash:long = InputGroupCall; @@ -1226,7 +1227,7 @@ phone.exportedGroupCallInvite#204bd158 link:string = phone.ExportedGroupCallInvi groupCallParticipantVideoSourceGroup#dcb118b7 semantics:string sources:Vector = GroupCallParticipantVideoSourceGroup; -groupCallParticipantVideo#78e41663 flags:# paused:flags.0?true endpoint:string source_groups:Vector = GroupCallParticipantVideo; +groupCallParticipantVideo#67753ac8 flags:# paused:flags.0?true endpoint:string source_groups:Vector audio_source:flags.1?int = GroupCallParticipantVideo; stickers.suggestedShortName#85fea03f short_name:string = stickers.SuggestedShortName; @@ -1238,6 +1239,10 @@ botCommandScopePeer#db9d897d peer:InputPeer = BotCommandScope; botCommandScopePeerAdmins#3fd863d1 peer:InputPeer = BotCommandScope; botCommandScopePeerUser#a1321f3 peer:InputPeer user_id:InputUser = BotCommandScope; +account.resetPasswordFailedWait#e3779861 retry_date:int = account.ResetPasswordResult; +account.resetPasswordRequestedWait#e9effc7d until_date:int = account.ResetPasswordResult; +account.resetPasswordOk#e926d63e = account.ResetPasswordResult; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -1259,13 +1264,14 @@ auth.bindTempAuthKey#cdd42a05 perm_auth_key_id:long nonce:long expires_at:int en auth.importBotAuthorization#67a3ff2c flags:int api_id:int api_hash:string bot_auth_token:string = auth.Authorization; auth.checkPassword#d18b4d16 password:InputCheckPasswordSRP = auth.Authorization; auth.requestPasswordRecovery#d897bc66 = auth.PasswordRecovery; -auth.recoverPassword#4ea56e92 code:string = auth.Authorization; +auth.recoverPassword#37096c70 flags:# code:string new_settings:flags.0?account.PasswordInputSettings = auth.Authorization; auth.resendCode#3ef1a9bf phone_number:string phone_code_hash:string = auth.SentCode; auth.cancelCode#1f040578 phone_number:string phone_code_hash:string = Bool; auth.dropTempAuthKeys#8e48a188 except_auth_keys:Vector = Bool; auth.exportLoginToken#b1b41517 api_id:int api_hash:string except_ids:Vector = auth.LoginToken; auth.importLoginToken#95ac5ce4 token:bytes = auth.LoginToken; auth.acceptLoginToken#e894ad4d token:bytes = Authorization; +auth.checkRecoveryPassword#d36bf79 code:string = Bool; account.registerDevice#68976c6f flags:# no_muted:flags.0?true token_type:int token:string app_sandbox:Bool secret:bytes other_uids:Vector = Bool; account.unregisterDevice#3076c4bf token_type:int token:string other_uids:Vector = Bool; @@ -1335,6 +1341,8 @@ account.getMultiWallPapers#65ad71dc wallpapers:Vector = Vector = Vector; users.getFullUser#ca30a5b1 id:InputUser = UserFull; @@ -1648,4 +1656,4 @@ stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats; -// LAYER 130 \ No newline at end of file +// LAYER 131 \ No newline at end of file From 4d933b80f96810b7a652a328dcc74f2645935362 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A4=85=E5=96=B5?= Date: Sat, 28 Aug 2021 16:01:12 +0800 Subject: [PATCH 0614/1185] Fix TL schema naming conflicts (#690) * Avoid variable conflicts with Telegram TL schema * Fix game button with no data attached to button * Update combinator.txt * Update compiler.py * Update tl_object.py Co-authored-by: Dan <14043624+delivrance@users.noreply.github.com> --- compiler/api/compiler.py | 28 +++++++++---------- compiler/api/template/combinator.txt | 8 +++--- pyrogram/raw/core/tl_object.py | 4 +-- .../bots_and_keyboards/callback_query.py | 2 +- 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/compiler/api/compiler.py b/compiler/api/compiler.py index 307a9d498c..d79fc80160 100644 --- a/compiler/api/compiler.py +++ b/compiler/api/compiler.py @@ -416,11 +416,11 @@ def start(format: bool = False): write_flags = "\n ".join([ "flags = 0", "\n ".join(write_flags), - "data.write(Int(flags))\n " + "b.write(Int(flags))\n " ]) write_types += write_flags - read_types += "flags = Int.read(data)\n " + read_types += "flags = Int.read(b)\n " continue @@ -433,55 +433,55 @@ def start(format: bool = False): elif flag_type in CORE_TYPES: write_types += "\n " write_types += f"if self.{arg_name} is not None:\n " - write_types += f"data.write({flag_type.title()}(self.{arg_name}))\n " + write_types += f"b.write({flag_type.title()}(self.{arg_name}))\n " read_types += "\n " - read_types += f"{arg_name} = {flag_type.title()}.read(data) if flags & (1 << {index}) else None" + read_types += f"{arg_name} = {flag_type.title()}.read(b) if flags & (1 << {index}) else None" elif "vector" in flag_type.lower(): sub_type = arg_type.split("<")[1][:-1] write_types += "\n " write_types += f"if self.{arg_name} is not None:\n " - write_types += "data.write(Vector(self.{}{}))\n ".format( + write_types += "b.write(Vector(self.{}{}))\n ".format( arg_name, f", {sub_type.title()}" if sub_type in CORE_TYPES else "" ) read_types += "\n " - read_types += "{} = TLObject.read(data{}) if flags & (1 << {}) else []\n ".format( + read_types += "{} = TLObject.read(b{}) if flags & (1 << {}) else []\n ".format( arg_name, f", {sub_type.title()}" if sub_type in CORE_TYPES else "", index ) else: write_types += "\n " write_types += f"if self.{arg_name} is not None:\n " - write_types += f"data.write(self.{arg_name}.write())\n " + write_types += f"b.write(self.{arg_name}.write())\n " read_types += "\n " - read_types += f"{arg_name} = TLObject.read(data) if flags & (1 << {index}) else None\n " + read_types += f"{arg_name} = TLObject.read(b) if flags & (1 << {index}) else None\n " else: if arg_type in CORE_TYPES: write_types += "\n " - write_types += f"data.write({arg_type.title()}(self.{arg_name}))\n " + write_types += f"b.write({arg_type.title()}(self.{arg_name}))\n " read_types += "\n " - read_types += f"{arg_name} = {arg_type.title()}.read(data)\n " + read_types += f"{arg_name} = {arg_type.title()}.read(b)\n " elif "vector" in arg_type.lower(): sub_type = arg_type.split("<")[1][:-1] write_types += "\n " - write_types += "data.write(Vector(self.{}{}))\n ".format( + write_types += "b.write(Vector(self.{}{}))\n ".format( arg_name, f", {sub_type.title()}" if sub_type in CORE_TYPES else "" ) read_types += "\n " - read_types += "{} = TLObject.read(data{})\n ".format( + read_types += "{} = TLObject.read(b{})\n ".format( arg_name, f", {sub_type.title()}" if sub_type in CORE_TYPES else "" ) else: write_types += "\n " - write_types += f"data.write(self.{arg_name}.write())\n " + write_types += f"b.write(self.{arg_name}.write())\n " read_types += "\n " - read_types += f"{arg_name} = TLObject.read(data)\n " + read_types += f"{arg_name} = TLObject.read(b)\n " slots = ", ".join([f'"{i[0]}"' for i in sorted_args]) return_arguments = ", ".join([f"{i[0]}={i[0]}" for i in sorted_args]) diff --git a/compiler/api/template/combinator.txt b/compiler/api/template/combinator.txt index 318052a7a9..e0275dd104 100644 --- a/compiler/api/template/combinator.txt +++ b/compiler/api/template/combinator.txt @@ -23,13 +23,13 @@ class {name}(TLObject): # type: ignore {fields} @staticmethod - def read(data: BytesIO, *args: Any) -> "{name}": + def read(b: BytesIO, *args: Any) -> "{name}": {read_types} return {name}({return_arguments}) def write(self) -> bytes: - data = BytesIO() - data.write(Int(self.ID, False)) + b = BytesIO() + b.write(Int(self.ID, False)) {write_types} - return data.getvalue() + return b.getvalue() diff --git a/pyrogram/raw/core/tl_object.py b/pyrogram/raw/core/tl_object.py index 3b87d72c76..f0dd596a3f 100644 --- a/pyrogram/raw/core/tl_object.py +++ b/pyrogram/raw/core/tl_object.py @@ -29,8 +29,8 @@ class TLObject: QUALNAME = "Base" @classmethod - def read(cls, data: BytesIO, *args: Any) -> Any: - return cast(TLObject, objects[int.from_bytes(data.read(4), "little")]).read(data, *args) + def read(cls, b: BytesIO, *args: Any) -> Any: + return cast(TLObject, objects[int.from_bytes(b.read(4), "little")]).read(b, *args) def write(self, *args: Any) -> bytes: pass diff --git a/pyrogram/types/bots_and_keyboards/callback_query.py b/pyrogram/types/bots_and_keyboards/callback_query.py index 11e749d135..8887594eb7 100644 --- a/pyrogram/types/bots_and_keyboards/callback_query.py +++ b/pyrogram/types/bots_and_keyboards/callback_query.py @@ -110,7 +110,7 @@ async def _parse(client, callback_query, users) -> "CallbackQuery": # ignoring/replacing errors, this way, button clicks will still work. try: data = callback_query.data.decode() - except UnicodeDecodeError: + except (UnicodeDecodeError, AttributeError): data = callback_query.data return CallbackQuery( From 99e152a67cdfe81b7ae021fe5ffb7e8c5e01392b Mon Sep 17 00:00:00 2001 From: Jonathan <49692607+jonatan1609@users.noreply.github.com> Date: Sat, 28 Aug 2021 15:40:10 +0300 Subject: [PATCH 0615/1185] Fix accessing non-existent attribute (#747) This should solve the error `AttributeError: 'ChatParticipantsForbidden' object has no attribute 'participants'` and apply the commit https://github.com/pyrogram/pyrogram/commit/062a6ce6dd8e9cbc9ee18d6c7c43bae5607ed215 on this file, too. --- pyrogram/methods/chats/get_chat_members.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/methods/chats/get_chat_members.py b/pyrogram/methods/chats/get_chat_members.py index 1f891b4657..64abf1e5e2 100644 --- a/pyrogram/methods/chats/get_chat_members.py +++ b/pyrogram/methods/chats/get_chat_members.py @@ -112,7 +112,7 @@ async def get_chat_members( ) ) - members = r.full_chat.participants.participants + members = getattr(r.full_chat.participants, "participants", []) users = {i.id: i for i in r.users} return types.List(types.ChatMember._parse(self, member, users, {}) for member in members) From 9e9a944e86b2d4ea1a5f85111011b44f3b3d2c87 Mon Sep 17 00:00:00 2001 From: Gautam Kumar Date: Sat, 28 Aug 2021 18:12:16 +0530 Subject: [PATCH 0616/1185] Fix parameter name being in plural (#724) --- pyrogram/types/messages_and_media/message.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index 5ecb6c21c5..403411fceb 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -2909,7 +2909,7 @@ async def copy( client.copy_message( chat_id=chat_id, from_chat_id=message.chat.id, - message_ids=message.message_id + message_id=message.message_id ) Example: From aea1ffc46f236e07129bc54f42954a531cf086f0 Mon Sep 17 00:00:00 2001 From: DatDraggy Date: Sun, 29 Aug 2021 08:38:32 +0200 Subject: [PATCH 0617/1185] Fix old non existing can_send_[sticker...] with can_send_other_messages (#746) https://github.com/pyrogram/pyrogram/commit/c9d0c5d75a7646b43cd7e8c1f45715fd1630f797 --- pyrogram/methods/chats/restrict_chat_member.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyrogram/methods/chats/restrict_chat_member.py b/pyrogram/methods/chats/restrict_chat_member.py index f8a05a0f15..d0cc933831 100644 --- a/pyrogram/methods/chats/restrict_chat_member.py +++ b/pyrogram/methods/chats/restrict_chat_member.py @@ -79,10 +79,10 @@ async def restrict_chat_member( until_date=until_date, send_messages=True if not permissions.can_send_messages else None, send_media=True if not permissions.can_send_media_messages else None, - send_stickers=True if not permissions.can_send_stickers else None, - send_gifs=True if not permissions.can_send_animations else None, - send_games=True if not permissions.can_send_games else None, - send_inline=True if not permissions.can_use_inline_bots else None, + send_stickers=True if not permissions.can_send_other_messages else None, + send_gifs=True if not permissions.can_send_other_messages else None, + send_games=True if not permissions.can_send_other_messages else None, + send_inline=True if not permissions.can_send_other_messages else None, embed_links=True if not permissions.can_add_web_page_previews else None, send_polls=True if not permissions.can_send_polls else None, change_info=True if not permissions.can_change_info else None, From e68da74e89f198ba1276a91975e16237d221a6c9 Mon Sep 17 00:00:00 2001 From: "Md. Hasibul Kabir" <46620128+HasibulKabir@users.noreply.github.com> Date: Sun, 29 Aug 2021 14:49:47 +0600 Subject: [PATCH 0618/1185] Further improve the session-switching mechanism for inline bots (#739) * Ability to run multiple bot. Global session creates a problem for other bot. As that session was generated for another bot can't be used by this bot. * Use the existing media session lock Co-authored-by: Dan <14043624+delivrance@users.noreply.github.com> --- pyrogram/methods/messages/inline_session.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/pyrogram/methods/messages/inline_session.py b/pyrogram/methods/messages/inline_session.py index bfc323a22e..354cb70f69 100644 --- a/pyrogram/methods/messages/inline_session.py +++ b/pyrogram/methods/messages/inline_session.py @@ -16,29 +16,22 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from asyncio import Lock - import pyrogram from pyrogram import raw from pyrogram.errors import AuthBytesInvalid from pyrogram.session import Session from pyrogram.session.auth import Auth -lock = Lock() -sessions = {} - async def get_session(client: "pyrogram.Client", dc_id: int): if dc_id == await client.storage.dc_id(): return client - async with lock: - global sessions - - if sessions.get(dc_id): - return sessions[dc_id] + async with client.media_sessions_lock: + if client.media_sessions.get(dc_id): + return client.media_sessions[dc_id] - session = sessions[dc_id] = Session( + session = client.media_sessions[dc_id] = Session( client, dc_id, await Auth(client, dc_id, False).create(), False, is_media=True From bacc7c004bd7235c6728d951b9711998d9b7673b Mon Sep 17 00:00:00 2001 From: rking32 <52490534+rking32@users.noreply.github.com> Date: Sun, 29 Aug 2021 15:35:48 +0530 Subject: [PATCH 0619/1185] Improve the async-to-sync wrapper (#744) * improved async to sync wrapper * Create a new loop in non-main threads & improve readability * Do not run_coroutine_threadsafe unless it's outside the loop Co-authored-by: Dan <14043624+delivrance@users.noreply.github.com> --- pyrogram/sync.py | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/pyrogram/sync.py b/pyrogram/sync.py index 51742945ec..d94d490c41 100644 --- a/pyrogram/sync.py +++ b/pyrogram/sync.py @@ -40,23 +40,33 @@ def async_to_sync_wrap(*args, **kwargs): try: loop = asyncio.get_event_loop() except RuntimeError: - loop = main_loop + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) - if loop.is_running(): - if threading.current_thread() is threading.main_thread(): + if threading.current_thread() is threading.main_thread(): + if loop.is_running(): return coroutine else: if inspect.iscoroutine(coroutine): - return asyncio.run_coroutine_threadsafe(coroutine, loop).result() + return loop.run_until_complete(coroutine) if inspect.isasyncgen(coroutine): - return asyncio.run_coroutine_threadsafe(consume_generator(coroutine), loop).result() - - if inspect.iscoroutine(coroutine): - return loop.run_until_complete(coroutine) - - if inspect.isasyncgen(coroutine): - return loop.run_until_complete(consume_generator(coroutine)) + return loop.run_until_complete(consume_generator(coroutine)) + else: + if inspect.iscoroutine(coroutine): + if loop.is_running(): + async def coro_wrapper(): + return await asyncio.wrap_future(asyncio.run_coroutine_threadsafe(coroutine, main_loop)) + + return coro_wrapper() + else: + return asyncio.run_coroutine_threadsafe(coroutine, main_loop).result() + + if inspect.isasyncgen(coroutine): + if loop.is_running(): + return coroutine + else: + return asyncio.run_coroutine_threadsafe(consume_generator(coroutine), main_loop).result() setattr(obj, name, async_to_sync_wrap) From 320696b4b23c688d0488a6dbd2b1c9ca32ba4517 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 31 Aug 2021 21:59:23 +0200 Subject: [PATCH 0620/1185] Update API schema to Layer 132 --- compiler/api/source/main_api.tl | 41 ++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl index 68f8922364..e9ba464b8c 100644 --- a/compiler/api/source/main_api.tl +++ b/compiler/api/source/main_api.tl @@ -70,7 +70,7 @@ inputPhotoFileLocation#40181ffe id:long access_hash:long file_reference:bytes th inputPhotoLegacyFileLocation#d83466f3 id:long access_hash:long file_reference:bytes volume_id:long local_id:int secret:long = InputFileLocation; inputPeerPhotoFileLocation#37257e99 flags:# big:flags.0?true peer:InputPeer photo_id:long = InputFileLocation; inputStickerSetThumb#9d84f3db stickerset:InputStickerSet thumb_version:int = InputFileLocation; -inputGroupCallStream#bba51639 call:InputGroupCall time_ms:long scale:int = InputFileLocation; +inputGroupCallStream#598a92a flags:# call:InputGroupCall time_ms:long scale:int video_channel:flags.0?int video_quality:flags.0?int = InputFileLocation; peerUser#9db1bc6d user_id:int = Peer; peerChat#bad0e5bb chat_id:int = Peer; @@ -106,8 +106,8 @@ chatForbidden#7328bdb id:int title:string = Chat; channel#d31a961e flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true fake:flags.25?true gigagroup:flags.26?true id:int access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int version:int restriction_reason:flags.9?Vector admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int = Chat; channelForbidden#289da732 flags:# broadcast:flags.5?true megagroup:flags.8?true id:int access_hash:long title:string until_date:flags.16?int = Chat; -chatFull#8a1e2983 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:int about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer = ChatFull; -channelFull#548c3f93 flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true id:int about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?int migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?int location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector groupcall_default_join_as:flags.26?Peer = ChatFull; +chatFull#49a0a5d9 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:int about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer theme_emoticon:flags.16?string = ChatFull; +channelFull#2f532f3c flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true id:int about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?int migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?int location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string = ChatFull; chatParticipant#c8d7493e user_id:int inviter_id:int date:int = ChatParticipant; chatParticipantCreator#da13538a user_id:int = ChatParticipant; @@ -165,6 +165,7 @@ messageActionGroupCall#7a0d7f42 flags:# call:InputGroupCall duration:flags.0?int messageActionInviteToGroupCall#76b9f11a call:InputGroupCall users:Vector = MessageAction; messageActionSetMessagesTTL#aa1afbfd period:int = MessageAction; messageActionGroupCallScheduled#b3a07661 call:InputGroupCall schedule_date:int = MessageAction; +messageActionSetChatTheme#aa786345 emoticon:string = MessageAction; dialog#2c171f72 flags:# pinned:flags.2?true unread_mark:flags.3?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int = Dialog; dialogFolder#71bd134c flags:# pinned:flags.2?true folder:Folder peer:Peer top_message:int unread_muted_peers_count:int unread_unmuted_peers_count:int unread_muted_messages_count:int unread_unmuted_messages_count:int = Dialog; @@ -212,7 +213,7 @@ inputReportReasonCopyright#9b89f93a = ReportReason; inputReportReasonGeoIrrelevant#dbd4feed = ReportReason; inputReportReasonFake#f5ddd6e7 = ReportReason; -userFull#139a9a77 flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true user:User about:flags.1?string settings:PeerSettings profile_photo:flags.2?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int ttl_period:flags.14?int = UserFull; +userFull#d697ff05 flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true user:User about:flags.1?string settings:PeerSettings profile_photo:flags.2?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int ttl_period:flags.14?int theme_emoticon:flags.15?string = UserFull; contact#f911c994 user_id:int mutual:Bool = Contact; @@ -443,6 +444,7 @@ sendMessageRecordRoundAction#88f27fbc = SendMessageAction; sendMessageUploadRoundAction#243e1c66 progress:int = SendMessageAction; speakingInGroupCallAction#d92c2285 = SendMessageAction; sendMessageHistoryImportAction#dbda9246 progress:int = SendMessageAction; +sendMessageChooseStickerAction#b05ac6b1 = SendMessageAction; contacts.found#b3134d9d my_results:Vector results:Vector chats:Vector users:Vector = contacts.Found; @@ -1094,7 +1096,7 @@ restrictionReason#d072acb4 platform:string reason:string text:string = Restricti inputTheme#3c5693e9 id:long access_hash:long = InputTheme; inputThemeSlug#f5890df1 slug:string = InputTheme; -theme#28f1114 flags:# creator:flags.0?true default:flags.1?true id:long access_hash:long slug:string title:string document:flags.2?Document settings:flags.3?ThemeSettings installs_count:int = Theme; +theme#e802b8dc flags:# creator:flags.0?true default:flags.1?true for_chat:flags.5?true id:long access_hash:long slug:string title:string document:flags.2?Document settings:flags.3?ThemeSettings installs_count:flags.4?int = Theme; account.themesNotModified#f41eb622 = account.Themes; account.themes#7f676421 hash:int themes:Vector = account.Themes; @@ -1113,9 +1115,9 @@ baseThemeNight#b7b31ea8 = BaseTheme; baseThemeTinted#6d5f77ee = BaseTheme; baseThemeArctic#5b11125a = BaseTheme; -inputThemeSettings#bd507cd1 flags:# base_theme:BaseTheme accent_color:int message_top_color:flags.0?int message_bottom_color:flags.0?int wallpaper:flags.1?InputWallPaper wallpaper_settings:flags.1?WallPaperSettings = InputThemeSettings; +inputThemeSettings#ff38f912 flags:# message_colors_animated:flags.2?true base_theme:BaseTheme accent_color:int message_colors:flags.0?Vector wallpaper:flags.1?InputWallPaper wallpaper_settings:flags.1?WallPaperSettings = InputThemeSettings; -themeSettings#9c14984a flags:# base_theme:BaseTheme accent_color:int message_top_color:flags.0?int message_bottom_color:flags.0?int wallpaper:flags.1?WallPaper = ThemeSettings; +themeSettings#8db4e76c flags:# message_colors_animated:flags.2?true base_theme:BaseTheme accent_color:int message_colors:flags.0?Vector wallpaper:flags.1?WallPaper = ThemeSettings; webPageAttributeTheme#54b56617 flags:# documents:flags.0?Vector settings:flags.1?ThemeSettings = WebPageAttribute; @@ -1173,7 +1175,7 @@ messageViews#455b853d flags:# views:flags.0?int forwards:flags.1?int replies:fla messages.messageViews#b6c4f543 views:Vector chats:Vector users:Vector = messages.MessageViews; -messages.discussionMessage#f5dd8f9d flags:# messages:Vector max_id:flags.0?int read_inbox_max_id:flags.1?int read_outbox_max_id:flags.2?int chats:Vector users:Vector = messages.DiscussionMessage; +messages.discussionMessage#a6341782 flags:# messages:Vector max_id:flags.0?int read_inbox_max_id:flags.1?int read_outbox_max_id:flags.2?int unread_count:int chats:Vector users:Vector = messages.DiscussionMessage; messageReplyHeader#a6d57763 flags:# reply_to_msg_id:int reply_to_peer_id:flags.0?Peer reply_to_top_id:flags.1?int = MessageReplyHeader; @@ -1184,7 +1186,7 @@ peerBlocked#e8fd8014 peer_id:Peer date:int = PeerBlocked; stats.messageStats#8999f295 views_graph:StatsGraph = stats.MessageStats; groupCallDiscarded#7780bcb4 id:long access_hash:long duration:int = GroupCall; -groupCall#d597650c flags:# join_muted:flags.1?true can_change_join_muted:flags.2?true join_date_asc:flags.6?true schedule_start_subscribed:flags.8?true can_start_video:flags.9?true id:long access_hash:long participants_count:int title:flags.3?string stream_dc_id:flags.4?int record_start_date:flags.5?int schedule_date:flags.7?int unmuted_video_count:flags.10?int unmuted_video_limit:int version:int = GroupCall; +groupCall#d597650c flags:# join_muted:flags.1?true can_change_join_muted:flags.2?true join_date_asc:flags.6?true schedule_start_subscribed:flags.8?true can_start_video:flags.9?true record_video_active:flags.11?true id:long access_hash:long participants_count:int title:flags.3?string stream_dc_id:flags.4?int record_start_date:flags.5?int schedule_date:flags.7?int unmuted_video_count:flags.10?int unmuted_video_limit:int version:int = GroupCall; inputGroupCall#d8aa840f id:long access_hash:long = InputGroupCall; @@ -1243,6 +1245,15 @@ account.resetPasswordFailedWait#e3779861 retry_date:int = account.ResetPasswordR account.resetPasswordRequestedWait#e9effc7d until_date:int = account.ResetPasswordResult; account.resetPasswordOk#e926d63e = account.ResetPasswordResult; +chatTheme#ed0b5c33 emoticon:string theme:Theme dark_theme:Theme = ChatTheme; + +account.chatThemesNotModified#e011e1c4 = account.ChatThemes; +account.chatThemes#fe4cbebd hash:int themes:Vector = account.ChatThemes; + +sponsoredMessage#f671f0d1 flags:# random_id:bytes peer_id:Peer from_id:Peer message:string media:flags.0?MessageMedia entities:flags.1?Vector = SponsoredMessage; + +messages.sponsoredMessages#65a4c7d5 messages:Vector chats:Vector users:Vector = messages.SponsoredMessages; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -1343,6 +1354,7 @@ account.setGlobalPrivacySettings#1edaaac2 settings:GlobalPrivacySettings = Globa account.reportProfilePhoto#fa8cc6f5 peer:InputPeer photo_id:InputPhoto reason:ReportReason message:string = Bool; account.resetPassword#9308ce1b = account.ResetPasswordResult; account.declinePasswordReset#4c9409f6 = Bool; +account.getChatThemes#d6d71d7b hash:int = account.ChatThemes; users.getUsers#d91a548 id:Vector = Vector; users.getFullUser#ca30a5b1 id:InputUser = UserFull; @@ -1380,7 +1392,7 @@ messages.receivedMessages#5a954c0 max_id:int = Vector; messages.setTyping#58943ee2 flags:# peer:InputPeer top_msg_id:flags.0?int action:SendMessageAction = Bool; messages.sendMessage#520c3870 flags:# no_webpage:flags.1?true silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int = Updates; messages.sendMedia#3491eba9 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int media:InputMedia message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int = Updates; -messages.forwardMessages#d9fee60e flags:# silent:flags.5?true background:flags.6?true with_my_score:flags.8?true from_peer:InputPeer id:Vector random_id:Vector to_peer:InputPeer schedule_date:flags.10?int = Updates; +messages.forwardMessages#d9fee60e flags:# silent:flags.5?true background:flags.6?true with_my_score:flags.8?true drop_author:flags.11?true drop_media_captions:flags.12?true from_peer:InputPeer id:Vector random_id:Vector to_peer:InputPeer schedule_date:flags.10?int = Updates; messages.reportSpam#cf1592db peer:InputPeer = Bool; messages.getPeerSettings#3672e09c peer:InputPeer = PeerSettings; messages.report#8953ab4e peer:InputPeer id:Vector reason:ReportReason message:string = Bool; @@ -1511,6 +1523,7 @@ messages.getAdminsWithInvites#3920e6ef peer:InputPeer = messages.ChatAdminsWithI messages.getChatInviteImporters#26fb7289 peer:InputPeer link:string offset_date:int offset_user:InputUser limit:int = messages.ChatInviteImporters; messages.setHistoryTTL#b80e5fe4 peer:InputPeer period:int = Updates; messages.checkHistoryImportPeer#5dc60f03 peer:InputPeer = messages.CheckedHistoryImportPeer; +messages.setChatTheme#e63be13f peer:InputPeer emoticon:string = Updates; updates.getState#edd4882a = updates.State; updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference; @@ -1589,6 +1602,8 @@ channels.editLocation#58e63f6d channel:InputChannel geo_point:InputGeoPoint addr channels.toggleSlowMode#edd49ef0 channel:InputChannel seconds:int = Updates; channels.getInactiveChannels#11e831ee = messages.InactiveChats; channels.convertToGigagroup#b290c69 channel:InputChannel = Updates; +channels.viewSponsoredMessage#beaedb94 channel:InputChannel random_id:bytes = Bool; +channels.getSponsoredMessages#ec210fbf channel:InputChannel = messages.SponsoredMessages; bots.sendCustomRequest#aa2769ed custom_method:string params:DataJSON = DataJSON; bots.answerWebhookJSONQuery#e6213f4d query_id:long data:DataJSON = Bool; @@ -1627,10 +1642,10 @@ phone.leaveGroupCall#500377f9 call:InputGroupCall source:int = Updates; phone.inviteToGroupCall#7b393160 call:InputGroupCall users:Vector = Updates; phone.discardGroupCall#7a777135 call:InputGroupCall = Updates; phone.toggleGroupCallSettings#74bbb43d flags:# reset_invite_hash:flags.1?true call:InputGroupCall join_muted:flags.0?Bool = Updates; -phone.getGroupCall#c7cb017 call:InputGroupCall = phone.GroupCall; +phone.getGroupCall#41845db call:InputGroupCall limit:int = phone.GroupCall; phone.getGroupParticipants#c558d8ab call:InputGroupCall ids:Vector sources:Vector offset:string limit:int = phone.GroupParticipants; phone.checkGroupCall#b59cf977 call:InputGroupCall sources:Vector = Vector; -phone.toggleGroupCallRecord#c02a66d7 flags:# start:flags.0?true call:InputGroupCall title:flags.1?string = Updates; +phone.toggleGroupCallRecord#f128c708 flags:# start:flags.0?true video:flags.2?true call:InputGroupCall title:flags.1?string video_portrait:flags.2?Bool = Updates; phone.editGroupCallParticipant#a5273abf flags:# call:InputGroupCall participant:InputPeer muted:flags.0?Bool volume:flags.1?int raise_hand:flags.2?Bool video_stopped:flags.3?Bool video_paused:flags.4?Bool presentation_paused:flags.5?Bool = Updates; phone.editGroupCallTitle#1ca6ac0a call:InputGroupCall title:string = Updates; phone.getGroupCallJoinAs#ef7c213a peer:InputPeer = phone.JoinAsPeers; @@ -1656,4 +1671,4 @@ stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats; -// LAYER 131 \ No newline at end of file +// LAYER 132 From eec7ec39470e474ef012c47a94ee65d20e8f2cce Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 10 Sep 2021 19:46:38 +0200 Subject: [PATCH 0621/1185] Update API schema to Layer 133 --- compiler/api/source/main_api.tl | 339 ++++++++++++++++---------------- 1 file changed, 172 insertions(+), 167 deletions(-) diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl index e9ba464b8c..8b3e4964c5 100644 --- a/compiler/api/source/main_api.tl +++ b/compiler/api/source/main_api.tl @@ -19,16 +19,16 @@ inputPeerEmpty#7f3b18ea = InputPeer; inputPeerSelf#7da07ec9 = InputPeer; -inputPeerChat#179be863 chat_id:int = InputPeer; -inputPeerUser#7b8e7de6 user_id:int access_hash:long = InputPeer; -inputPeerChannel#20adaef8 channel_id:int access_hash:long = InputPeer; -inputPeerUserFromMessage#17bae2e6 peer:InputPeer msg_id:int user_id:int = InputPeer; -inputPeerChannelFromMessage#9c95f7bb peer:InputPeer msg_id:int channel_id:int = InputPeer; +inputPeerChat#35a95cb9 chat_id:long = InputPeer; +inputPeerUser#dde8a54c user_id:long access_hash:long = InputPeer; +inputPeerChannel#27bcbbfc channel_id:long access_hash:long = InputPeer; +inputPeerUserFromMessage#a87b0a1c peer:InputPeer msg_id:int user_id:long = InputPeer; +inputPeerChannelFromMessage#bd2a0840 peer:InputPeer msg_id:int channel_id:long = InputPeer; inputUserEmpty#b98886cf = InputUser; inputUserSelf#f7c1b13f = InputUser; -inputUser#d8292816 user_id:int access_hash:long = InputUser; -inputUserFromMessage#2d117597 peer:InputPeer msg_id:int user_id:int = InputUser; +inputUser#f21158c6 user_id:long access_hash:long = InputUser; +inputUserFromMessage#1da448e2 peer:InputPeer msg_id:int user_id:long = InputUser; inputPhoneContact#f392b7f4 client_id:long phone:string first_name:string last_name:string = InputContact; @@ -72,9 +72,9 @@ inputPeerPhotoFileLocation#37257e99 flags:# big:flags.0?true peer:InputPeer phot inputStickerSetThumb#9d84f3db stickerset:InputStickerSet thumb_version:int = InputFileLocation; inputGroupCallStream#598a92a flags:# call:InputGroupCall time_ms:long scale:int video_channel:flags.0?int video_quality:flags.0?int = InputFileLocation; -peerUser#9db1bc6d user_id:int = Peer; -peerChat#bad0e5bb chat_id:int = Peer; -peerChannel#bddde532 channel_id:int = Peer; +peerUser#59511722 user_id:long = Peer; +peerChat#36c6019a chat_id:long = Peer; +peerChannel#a2a5371e channel_id:long = Peer; storage.fileUnknown#aa963b05 = storage.FileType; storage.filePartial#40bc6f52 = storage.FileType; @@ -87,8 +87,8 @@ storage.fileMov#4b09ebbc = storage.FileType; storage.fileMp4#b3cea0e4 = storage.FileType; storage.fileWebp#1081464c = storage.FileType; -userEmpty#200250ba id:int = User; -user#938458c1 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true apply_min_photo:flags.25?true fake:flags.26?true id:int access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector bot_inline_placeholder:flags.19?string lang_code:flags.22?string = User; +userEmpty#d3bc4b7a id:long = User; +user#3ff6ecb0 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true apply_min_photo:flags.25?true fake:flags.26?true id:long access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector bot_inline_placeholder:flags.19?string lang_code:flags.22?string = User; userProfilePhotoEmpty#4f11bae1 = UserProfilePhoto; userProfilePhoto#82d1f706 flags:# has_video:flags.0?true photo_id:long stripped_thumb:flags.1?bytes dc_id:int = UserProfilePhoto; @@ -100,33 +100,33 @@ userStatusRecently#e26f42f1 = UserStatus; userStatusLastWeek#7bf09fc = UserStatus; userStatusLastMonth#77ebc742 = UserStatus; -chatEmpty#9ba2d800 id:int = Chat; -chat#3bda1bde flags:# creator:flags.0?true kicked:flags.1?true left:flags.2?true deactivated:flags.5?true call_active:flags.23?true call_not_empty:flags.24?true id:int title:string photo:ChatPhoto participants_count:int date:int version:int migrated_to:flags.6?InputChannel admin_rights:flags.14?ChatAdminRights default_banned_rights:flags.18?ChatBannedRights = Chat; -chatForbidden#7328bdb id:int title:string = Chat; -channel#d31a961e flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true fake:flags.25?true gigagroup:flags.26?true id:int access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int version:int restriction_reason:flags.9?Vector admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int = Chat; -channelForbidden#289da732 flags:# broadcast:flags.5?true megagroup:flags.8?true id:int access_hash:long title:string until_date:flags.16?int = Chat; +chatEmpty#29562865 id:long = Chat; +chat#41cbf256 flags:# creator:flags.0?true kicked:flags.1?true left:flags.2?true deactivated:flags.5?true call_active:flags.23?true call_not_empty:flags.24?true id:long title:string photo:ChatPhoto participants_count:int date:int version:int migrated_to:flags.6?InputChannel admin_rights:flags.14?ChatAdminRights default_banned_rights:flags.18?ChatBannedRights = Chat; +chatForbidden#6592a1a7 id:long title:string = Chat; +channel#8261ac61 flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true fake:flags.25?true gigagroup:flags.26?true id:long access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int restriction_reason:flags.9?Vector admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int = Chat; +channelForbidden#17d493d5 flags:# broadcast:flags.5?true megagroup:flags.8?true id:long access_hash:long title:string until_date:flags.16?int = Chat; -chatFull#49a0a5d9 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:int about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer theme_emoticon:flags.16?string = ChatFull; -channelFull#2f532f3c flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true id:int about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?int migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?int location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string = ChatFull; +chatFull#4dbdc099 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:long about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer theme_emoticon:flags.16?string = ChatFull; +channelFull#e9b27a17 flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string = ChatFull; -chatParticipant#c8d7493e user_id:int inviter_id:int date:int = ChatParticipant; -chatParticipantCreator#da13538a user_id:int = ChatParticipant; -chatParticipantAdmin#e2d6e436 user_id:int inviter_id:int date:int = ChatParticipant; +chatParticipant#c02d4007 user_id:long inviter_id:long date:int = ChatParticipant; +chatParticipantCreator#e46bcee4 user_id:long = ChatParticipant; +chatParticipantAdmin#a0933f5b user_id:long inviter_id:long date:int = ChatParticipant; -chatParticipantsForbidden#fc900c2b flags:# chat_id:int self_participant:flags.0?ChatParticipant = ChatParticipants; -chatParticipants#3f460fed chat_id:int participants:Vector version:int = ChatParticipants; +chatParticipantsForbidden#8763d3e1 flags:# chat_id:long self_participant:flags.0?ChatParticipant = ChatParticipants; +chatParticipants#3cbc93f8 chat_id:long participants:Vector version:int = ChatParticipants; chatPhotoEmpty#37c1011c = ChatPhoto; chatPhoto#1c6e1c11 flags:# has_video:flags.0?true photo_id:long stripped_thumb:flags.1?bytes dc_id:int = ChatPhoto; messageEmpty#90a6ca84 flags:# id:int peer_id:flags.0?Peer = Message; -message#bce383d2 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true id:int from_id:flags.8?Peer peer_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long restriction_reason:flags.22?Vector ttl_period:flags.25?int = Message; +message#85d6cbe2 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true id:int from_id:flags.8?Peer peer_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?long reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long restriction_reason:flags.22?Vector ttl_period:flags.25?int = Message; messageService#2b085862 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true legacy:flags.19?true id:int from_id:flags.8?Peer peer_id:Peer reply_to:flags.3?MessageReplyHeader date:int action:MessageAction ttl_period:flags.25?int = Message; messageMediaEmpty#3ded6320 = MessageMedia; messageMediaPhoto#695150d7 flags:# photo:flags.0?Photo ttl_seconds:flags.2?int = MessageMedia; messageMediaGeo#56e0d474 geo:GeoPoint = MessageMedia; -messageMediaContact#cbf24940 phone_number:string first_name:string last_name:string vcard:string user_id:int = MessageMedia; +messageMediaContact#70322949 phone_number:string first_name:string last_name:string vcard:string user_id:long = MessageMedia; messageMediaUnsupported#9f84f49e = MessageMedia; messageMediaDocument#9cb070d7 flags:# document:flags.0?Document ttl_seconds:flags.2?int = MessageMedia; messageMediaWebPage#a32dd600 webpage:WebPage = MessageMedia; @@ -138,16 +138,16 @@ messageMediaPoll#4bd6e798 poll:Poll results:PollResults = MessageMedia; messageMediaDice#3f7ee58b value:int emoticon:string = MessageMedia; messageActionEmpty#b6aef7b0 = MessageAction; -messageActionChatCreate#a6638b9a title:string users:Vector = MessageAction; +messageActionChatCreate#bd47cbad title:string users:Vector = MessageAction; messageActionChatEditTitle#b5a1ce5a title:string = MessageAction; messageActionChatEditPhoto#7fcb13a8 photo:Photo = MessageAction; messageActionChatDeletePhoto#95e3fbef = MessageAction; -messageActionChatAddUser#488a7337 users:Vector = MessageAction; -messageActionChatDeleteUser#b2ae9b0c user_id:int = MessageAction; -messageActionChatJoinedByLink#f89cf5e8 inviter_id:int = MessageAction; +messageActionChatAddUser#15cefd00 users:Vector = MessageAction; +messageActionChatDeleteUser#a43f30cc user_id:long = MessageAction; +messageActionChatJoinedByLink#31224c3 inviter_id:long = MessageAction; messageActionChannelCreate#95d2ac92 title:string = MessageAction; -messageActionChatMigrateTo#51bdb021 channel_id:int = MessageAction; -messageActionChannelMigrateFrom#b055eaee title:string chat_id:int = MessageAction; +messageActionChatMigrateTo#e1037f92 channel_id:long = MessageAction; +messageActionChannelMigrateFrom#ea3948e9 title:string chat_id:long = MessageAction; messageActionPinMessage#94bd38ed = MessageAction; messageActionHistoryClear#9fbab604 = MessageAction; messageActionGameScore#92a72876 game_id:long score:int = MessageAction; @@ -162,7 +162,7 @@ messageActionSecureValuesSent#d95c6154 types:Vector = MessageAc messageActionContactSignUp#f3f25f76 = MessageAction; messageActionGeoProximityReached#98e0d697 from_id:Peer to_id:Peer distance:int = MessageAction; messageActionGroupCall#7a0d7f42 flags:# call:InputGroupCall duration:flags.0?int = MessageAction; -messageActionInviteToGroupCall#76b9f11a call:InputGroupCall users:Vector = MessageAction; +messageActionInviteToGroupCall#502f92f7 call:InputGroupCall users:Vector = MessageAction; messageActionSetMessagesTTL#aa1afbfd period:int = MessageAction; messageActionGroupCallScheduled#b3a07661 call:InputGroupCall schedule_date:int = MessageAction; messageActionSetChatTheme#aa786345 emoticon:string = MessageAction; @@ -188,7 +188,7 @@ auth.sentCode#5e002502 flags:# type:auth.SentCodeType phone_code_hash:string nex auth.authorization#cd050916 flags:# tmp_sessions:flags.0?int user:User = auth.Authorization; auth.authorizationSignUpRequired#44747e9a flags:# terms_of_service:flags.0?help.TermsOfService = auth.Authorization; -auth.exportedAuthorization#df969c2d id:int bytes:bytes = auth.ExportedAuthorization; +auth.exportedAuthorization#b434e2b8 id:long bytes:bytes = auth.ExportedAuthorization; inputNotifyPeer#b8bc5b0c peer:InputPeer = InputNotifyPeer; inputNotifyUsers#193b4417 = InputNotifyPeer; @@ -215,11 +215,11 @@ inputReportReasonFake#f5ddd6e7 = ReportReason; userFull#d697ff05 flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true user:User about:flags.1?string settings:PeerSettings profile_photo:flags.2?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int ttl_period:flags.14?int theme_emoticon:flags.15?string = UserFull; -contact#f911c994 user_id:int mutual:Bool = Contact; +contact#145ade0b user_id:long mutual:Bool = Contact; -importedContact#d0028438 user_id:int client_id:long = ImportedContact; +importedContact#c13e3c50 user_id:long client_id:long = ImportedContact; -contactStatus#d3680c61 user_id:int status:UserStatus = ContactStatus; +contactStatus#16d9703b user_id:long status:UserStatus = ContactStatus; contacts.contactsNotModified#b74ba9d2 = contacts.Contacts; contacts.contacts#eae87e42 contacts:Vector saved_count:int users:Vector = contacts.Contacts; @@ -266,64 +266,64 @@ inputMessagesFilterPinned#1bb00451 = MessagesFilter; updateNewMessage#1f2b0afd message:Message pts:int pts_count:int = Update; updateMessageID#4e90bfd6 id:int random_id:long = Update; updateDeleteMessages#a20db0e5 messages:Vector pts:int pts_count:int = Update; -updateUserTyping#5c486927 user_id:int action:SendMessageAction = Update; -updateChatUserTyping#86cadb6c chat_id:int from_id:Peer action:SendMessageAction = Update; +updateUserTyping#c01e857f user_id:long action:SendMessageAction = Update; +updateChatUserTyping#83487af0 chat_id:long from_id:Peer action:SendMessageAction = Update; updateChatParticipants#7761198 participants:ChatParticipants = Update; -updateUserStatus#1bfbd823 user_id:int status:UserStatus = Update; -updateUserName#a7332b73 user_id:int first_name:string last_name:string username:string = Update; -updateUserPhoto#95313b0c user_id:int date:int photo:UserProfilePhoto previous:Bool = Update; +updateUserStatus#e5bdf8de user_id:long status:UserStatus = Update; +updateUserName#c3f202e0 user_id:long first_name:string last_name:string username:string = Update; +updateUserPhoto#f227868c user_id:long date:int photo:UserProfilePhoto previous:Bool = Update; updateNewEncryptedMessage#12bcbd9a message:EncryptedMessage qts:int = Update; updateEncryptedChatTyping#1710f156 chat_id:int = Update; updateEncryption#b4a2e88d chat:EncryptedChat date:int = Update; updateEncryptedMessagesRead#38fe25b7 chat_id:int max_date:int date:int = Update; -updateChatParticipantAdd#ea4b0e5c chat_id:int user_id:int inviter_id:int date:int version:int = Update; -updateChatParticipantDelete#6e5f8c22 chat_id:int user_id:int version:int = Update; +updateChatParticipantAdd#3dda5451 chat_id:long user_id:long inviter_id:long date:int version:int = Update; +updateChatParticipantDelete#e32f3d77 chat_id:long user_id:long version:int = Update; updateDcOptions#8e5e9873 dc_options:Vector = Update; updateNotifySettings#bec268ef peer:NotifyPeer notify_settings:PeerNotifySettings = Update; updateServiceNotification#ebe46819 flags:# popup:flags.0?true inbox_date:flags.1?int type:string message:string media:MessageMedia entities:Vector = Update; updatePrivacy#ee3b272a key:PrivacyKey rules:Vector = Update; -updateUserPhone#12b9417b user_id:int phone:string = Update; +updateUserPhone#5492a13 user_id:long phone:string = Update; updateReadHistoryInbox#9c974fdf flags:# folder_id:flags.0?int peer:Peer max_id:int still_unread_count:int pts:int pts_count:int = Update; updateReadHistoryOutbox#2f2f21bf peer:Peer max_id:int pts:int pts_count:int = Update; updateWebPage#7f891213 webpage:WebPage pts:int pts_count:int = Update; updateReadMessagesContents#68c13933 messages:Vector pts:int pts_count:int = Update; -updateChannelTooLong#eb0467fb flags:# channel_id:int pts:flags.0?int = Update; -updateChannel#b6d45656 channel_id:int = Update; +updateChannelTooLong#108d941f flags:# channel_id:long pts:flags.0?int = Update; +updateChannel#635b4c09 channel_id:long = Update; updateNewChannelMessage#62ba04d9 message:Message pts:int pts_count:int = Update; -updateReadChannelInbox#330b5424 flags:# folder_id:flags.0?int channel_id:int max_id:int still_unread_count:int pts:int = Update; -updateDeleteChannelMessages#c37521c9 channel_id:int messages:Vector pts:int pts_count:int = Update; -updateChannelMessageViews#98a12b4b channel_id:int id:int views:int = Update; -updateChatParticipantAdmin#b6901959 chat_id:int user_id:int is_admin:Bool version:int = Update; +updateReadChannelInbox#922e6e10 flags:# folder_id:flags.0?int channel_id:long max_id:int still_unread_count:int pts:int = Update; +updateDeleteChannelMessages#c32d5b12 channel_id:long messages:Vector pts:int pts_count:int = Update; +updateChannelMessageViews#f226ac08 channel_id:long id:int views:int = Update; +updateChatParticipantAdmin#d7ca61a2 chat_id:long user_id:long is_admin:Bool version:int = Update; updateNewStickerSet#688a30aa stickerset:messages.StickerSet = Update; updateStickerSetsOrder#bb2d201 flags:# masks:flags.0?true order:Vector = Update; updateStickerSets#43ae3dec = Update; updateSavedGifs#9375341e = Update; -updateBotInlineQuery#3f2038db flags:# query_id:long user_id:int query:string geo:flags.0?GeoPoint peer_type:flags.1?InlineQueryPeerType offset:string = Update; -updateBotInlineSend#e48f964 flags:# user_id:int query:string geo:flags.0?GeoPoint id:string msg_id:flags.1?InputBotInlineMessageID = Update; +updateBotInlineQuery#496f379c flags:# query_id:long user_id:long query:string geo:flags.0?GeoPoint peer_type:flags.1?InlineQueryPeerType offset:string = Update; +updateBotInlineSend#12f12a07 flags:# user_id:long query:string geo:flags.0?GeoPoint id:string msg_id:flags.1?InputBotInlineMessageID = Update; updateEditChannelMessage#1b3f4df7 message:Message pts:int pts_count:int = Update; -updateBotCallbackQuery#e73547e1 flags:# query_id:long user_id:int peer:Peer msg_id:int chat_instance:long data:flags.0?bytes game_short_name:flags.1?string = Update; +updateBotCallbackQuery#b9cfc48d flags:# query_id:long user_id:long peer:Peer msg_id:int chat_instance:long data:flags.0?bytes game_short_name:flags.1?string = Update; updateEditMessage#e40370a3 message:Message pts:int pts_count:int = Update; -updateInlineBotCallbackQuery#f9d27a5a flags:# query_id:long user_id:int msg_id:InputBotInlineMessageID chat_instance:long data:flags.0?bytes game_short_name:flags.1?string = Update; -updateReadChannelOutbox#25d6c9c7 channel_id:int max_id:int = Update; +updateInlineBotCallbackQuery#691e9052 flags:# query_id:long user_id:long msg_id:InputBotInlineMessageID chat_instance:long data:flags.0?bytes game_short_name:flags.1?string = Update; +updateReadChannelOutbox#b75f99a9 channel_id:long max_id:int = Update; updateDraftMessage#ee2bb969 peer:Peer draft:DraftMessage = Update; updateReadFeaturedStickers#571d2742 = Update; updateRecentStickers#9a422c20 = Update; updateConfig#a229dd06 = Update; updatePtsChanged#3354678f = Update; -updateChannelWebPage#40771900 channel_id:int webpage:WebPage pts:int pts_count:int = Update; +updateChannelWebPage#2f2ba99f channel_id:long webpage:WebPage pts:int pts_count:int = Update; updateDialogPinned#6e6fe51c flags:# pinned:flags.0?true folder_id:flags.1?int peer:DialogPeer = Update; updatePinnedDialogs#fa0f3ca2 flags:# folder_id:flags.1?int order:flags.0?Vector = Update; updateBotWebhookJSON#8317c0c3 data:DataJSON = Update; updateBotWebhookJSONQuery#9b9240a6 query_id:long data:DataJSON timeout:int = Update; -updateBotShippingQuery#e0cdc940 query_id:long user_id:int payload:bytes shipping_address:PostAddress = Update; -updateBotPrecheckoutQuery#5d2f3aa9 flags:# query_id:long user_id:int payload:bytes info:flags.0?PaymentRequestedInfo shipping_option_id:flags.1?string currency:string total_amount:long = Update; +updateBotShippingQuery#b5aefd7d query_id:long user_id:long payload:bytes shipping_address:PostAddress = Update; +updateBotPrecheckoutQuery#8caa9a96 flags:# query_id:long user_id:long payload:bytes info:flags.0?PaymentRequestedInfo shipping_option_id:flags.1?string currency:string total_amount:long = Update; updatePhoneCall#ab0f6b1e phone_call:PhoneCall = Update; updateLangPackTooLong#46560264 lang_code:string = Update; updateLangPack#56022f4d difference:LangPackDifference = Update; updateFavedStickers#e511996d = Update; -updateChannelReadMessagesContents#89893b45 channel_id:int messages:Vector = Update; +updateChannelReadMessagesContents#44bdd535 channel_id:long messages:Vector = Update; updateContactsReset#7084a7be = Update; -updateChannelAvailableMessages#70db6837 channel_id:int available_min_id:int = Update; +updateChannelAvailableMessages#b23fc698 channel_id:long available_min_id:int = Update; updateDialogUnreadMark#e16459c3 flags:# unread:flags.0?true peer:DialogPeer = Update; updateMessagePoll#aca1657b flags:# poll_id:long poll:flags.0?Poll results:PollResults = Update; updateChatDefaultBannedRights#54c01850 peer:Peer default_banned_rights:ChatBannedRights version:int = Update; @@ -335,27 +335,27 @@ updateDeleteScheduledMessages#90866cee peer:Peer messages:Vector = Update; updateTheme#8216fba3 theme:Theme = Update; updateGeoLiveViewed#871fb939 peer:Peer msg_id:int = Update; updateLoginToken#564fe691 = Update; -updateMessagePollVote#37f69f0b poll_id:long user_id:int options:Vector qts:int = Update; +updateMessagePollVote#106395c9 poll_id:long user_id:long options:Vector qts:int = Update; updateDialogFilter#26ffde7d flags:# id:int filter:flags.0?DialogFilter = Update; updateDialogFilterOrder#a5d72105 order:Vector = Update; updateDialogFilters#3504914f = Update; updatePhoneCallSignalingData#2661bf09 phone_call_id:long data:bytes = Update; -updateChannelMessageForwards#6e8a84df channel_id:int id:int forwards:int = Update; -updateReadChannelDiscussionInbox#1cc7de54 flags:# channel_id:int top_msg_id:int read_max_id:int broadcast_id:flags.0?int broadcast_post:flags.0?int = Update; -updateReadChannelDiscussionOutbox#4638a26c channel_id:int top_msg_id:int read_max_id:int = Update; +updateChannelMessageForwards#d29a27f4 channel_id:long id:int forwards:int = Update; +updateReadChannelDiscussionInbox#d6b19546 flags:# channel_id:long top_msg_id:int read_max_id:int broadcast_id:flags.0?long broadcast_post:flags.0?int = Update; +updateReadChannelDiscussionOutbox#695c9e7c channel_id:long top_msg_id:int read_max_id:int = Update; updatePeerBlocked#246a4b22 peer_id:Peer blocked:Bool = Update; -updateChannelUserTyping#6b171718 flags:# channel_id:int top_msg_id:flags.0?int from_id:Peer action:SendMessageAction = Update; +updateChannelUserTyping#8c88c923 flags:# channel_id:long top_msg_id:flags.0?int from_id:Peer action:SendMessageAction = Update; updatePinnedMessages#ed85eab5 flags:# pinned:flags.0?true peer:Peer messages:Vector pts:int pts_count:int = Update; -updatePinnedChannelMessages#8588878b flags:# pinned:flags.0?true channel_id:int messages:Vector pts:int pts_count:int = Update; -updateChat#1330a196 chat_id:int = Update; +updatePinnedChannelMessages#5bb98608 flags:# pinned:flags.0?true channel_id:long messages:Vector pts:int pts_count:int = Update; +updateChat#f89a6a4e chat_id:long = Update; updateGroupCallParticipants#f2ebdb4e call:InputGroupCall participants:Vector version:int = Update; -updateGroupCall#a45eb99b chat_id:int call:GroupCall = Update; +updateGroupCall#14b24500 chat_id:long call:GroupCall = Update; updatePeerHistoryTTL#bb9bb9a5 flags:# peer:Peer ttl_period:flags.0?int = Update; -updateChatParticipant#f3b3781f flags:# chat_id:int date:int actor_id:int user_id:int prev_participant:flags.0?ChatParticipant new_participant:flags.1?ChatParticipant invite:flags.2?ExportedChatInvite qts:int = Update; -updateChannelParticipant#7fecb1ec flags:# channel_id:int date:int actor_id:int user_id:int prev_participant:flags.0?ChannelParticipant new_participant:flags.1?ChannelParticipant invite:flags.2?ExportedChatInvite qts:int = Update; -updateBotStopped#7f9488a user_id:int date:int stopped:Bool qts:int = Update; +updateChatParticipant#d087663a flags:# chat_id:long date:int actor_id:long user_id:long prev_participant:flags.0?ChatParticipant new_participant:flags.1?ChatParticipant invite:flags.2?ExportedChatInvite qts:int = Update; +updateChannelParticipant#985d3abb flags:# channel_id:long date:int actor_id:long user_id:long prev_participant:flags.0?ChannelParticipant new_participant:flags.1?ChannelParticipant invite:flags.2?ExportedChatInvite qts:int = Update; +updateBotStopped#c4870a49 user_id:long date:int stopped:Bool qts:int = Update; updateGroupCallConnection#b783982 flags:# presentation:flags.0?true params:DataJSON = Update; -updateBotCommands#cf7e0873 peer:Peer bot_id:int commands:Vector = Update; +updateBotCommands#4d712f2e peer:Peer bot_id:long commands:Vector = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -365,8 +365,8 @@ updates.differenceSlice#a8fb1981 new_messages:Vector new_encrypted_mess updates.differenceTooLong#4afe8f6d pts:int = updates.Difference; updatesTooLong#e317af7e = Updates; -updateShortMessage#faeff833 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true id:int user_id:int message:string pts:int pts_count:int date:int fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to:flags.3?MessageReplyHeader entities:flags.7?Vector ttl_period:flags.25?int = Updates; -updateShortChatMessage#1157b858 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true id:int from_id:int chat_id:int message:string pts:int pts_count:int date:int fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?int reply_to:flags.3?MessageReplyHeader entities:flags.7?Vector ttl_period:flags.25?int = Updates; +updateShortMessage#313bc7f8 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true id:int user_id:long message:string pts:int pts_count:int date:int fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?long reply_to:flags.3?MessageReplyHeader entities:flags.7?Vector ttl_period:flags.25?int = Updates; +updateShortChatMessage#4d6deea5 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true id:int from_id:long chat_id:long message:string pts:int pts_count:int date:int fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?long reply_to:flags.3?MessageReplyHeader entities:flags.7?Vector ttl_period:flags.25?int = Updates; updateShort#78d4dec1 update:Update date:int = Updates; updatesCombined#725b04c3 updates:Vector users:Vector chats:Vector date:int seq_start:int seq:int = Updates; updates#74ae4240 updates:Vector users:Vector chats:Vector date:int seq:int = Updates; @@ -392,9 +392,9 @@ help.noAppUpdate#c45a6536 = help.AppUpdate; help.inviteText#18cb9f78 message:string = help.InviteText; encryptedChatEmpty#ab7ec0a0 id:int = EncryptedChat; -encryptedChatWaiting#3bf703dc id:int access_hash:long date:int admin_id:int participant_id:int = EncryptedChat; -encryptedChatRequested#62718a82 flags:# folder_id:flags.0?int id:int access_hash:long date:int admin_id:int participant_id:int g_a:bytes = EncryptedChat; -encryptedChat#fa56ce36 id:int access_hash:long date:int admin_id:int participant_id:int g_a_or_b:bytes key_fingerprint:long = EncryptedChat; +encryptedChatWaiting#66b25953 id:int access_hash:long date:int admin_id:long participant_id:long = EncryptedChat; +encryptedChatRequested#48f1d94c flags:# folder_id:flags.0?int id:int access_hash:long date:int admin_id:long participant_id:long g_a:bytes = EncryptedChat; +encryptedChat#61f0d4c7 id:int access_hash:long date:int admin_id:long participant_id:long g_a_or_b:bytes key_fingerprint:long = EncryptedChat; encryptedChatDiscarded#1e1c7c45 flags:# history_deleted:flags.0?true id:int = EncryptedChat; inputEncryptedChat#f141b5e1 chat_id:int access_hash:long = InputEncryptedChat; @@ -445,6 +445,8 @@ sendMessageUploadRoundAction#243e1c66 progress:int = SendMessageAction; speakingInGroupCallAction#d92c2285 = SendMessageAction; sendMessageHistoryImportAction#dbda9246 progress:int = SendMessageAction; sendMessageChooseStickerAction#b05ac6b1 = SendMessageAction; +sendMessageEmojiInteraction#6a3233b6 emoticon:string interaction:DataJSON = SendMessageAction; +sendMessageEmojiInteractionSeen#b665902e emoticon:string = SendMessageAction; contacts.found#b3134d9d my_results:Vector results:Vector chats:Vector users:Vector = contacts.Found; @@ -472,17 +474,17 @@ inputPrivacyValueAllowUsers#131cc67f users:Vector = InputPrivacyRule; inputPrivacyValueDisallowContacts#ba52007 = InputPrivacyRule; inputPrivacyValueDisallowAll#d66b66c9 = InputPrivacyRule; inputPrivacyValueDisallowUsers#90110467 users:Vector = InputPrivacyRule; -inputPrivacyValueAllowChatParticipants#4c81c1ba chats:Vector = InputPrivacyRule; -inputPrivacyValueDisallowChatParticipants#d82363af chats:Vector = InputPrivacyRule; +inputPrivacyValueAllowChatParticipants#840649cf chats:Vector = InputPrivacyRule; +inputPrivacyValueDisallowChatParticipants#e94f0f86 chats:Vector = InputPrivacyRule; privacyValueAllowContacts#fffe1bac = PrivacyRule; privacyValueAllowAll#65427b82 = PrivacyRule; -privacyValueAllowUsers#4d5bbe0c users:Vector = PrivacyRule; +privacyValueAllowUsers#b8905fb2 users:Vector = PrivacyRule; privacyValueDisallowContacts#f888fa1a = PrivacyRule; privacyValueDisallowAll#8b73e763 = PrivacyRule; -privacyValueDisallowUsers#c7f49b7 users:Vector = PrivacyRule; -privacyValueAllowChatParticipants#18be796b chats:Vector = PrivacyRule; -privacyValueDisallowChatParticipants#acae0690 chats:Vector = PrivacyRule; +privacyValueDisallowUsers#e4621141 users:Vector = PrivacyRule; +privacyValueAllowChatParticipants#6b134e8e chats:Vector = PrivacyRule; +privacyValueDisallowChatParticipants#41c87565 chats:Vector = PrivacyRule; account.privacyRules#50a04e45 rules:Vector chats:Vector users:Vector = account.PrivacyRules; @@ -497,12 +499,12 @@ documentAttributeFilename#15590068 file_name:string = DocumentAttribute; documentAttributeHasStickers#9801d2f7 = DocumentAttribute; messages.stickersNotModified#f1749a22 = messages.Stickers; -messages.stickers#e4599bbd hash:int stickers:Vector = messages.Stickers; +messages.stickers#30a6ec7e hash:long stickers:Vector = messages.Stickers; stickerPack#12b299d4 emoticon:string documents:Vector = StickerPack; messages.allStickersNotModified#e86602c3 = messages.AllStickers; -messages.allStickers#edfd405f hash:int sets:Vector = messages.AllStickers; +messages.allStickers#cdbbcebb hash:long sets:Vector = messages.AllStickers; messages.affectedMessages#84d19185 pts:int pts_count:int = messages.AffectedMessages; @@ -525,7 +527,7 @@ auth.passwordRecovery#137948a5 email_pattern:string = auth.PasswordRecovery; receivedNotifyMessage#a384b779 id:int flags:int = ReceivedNotifyMessage; -chatInviteExported#6e24fc9d flags:# revoked:flags.0?true permanent:flags.5?true link:string admin_id:int date:int start_date:flags.4?int expire_date:flags.1?int usage_limit:flags.2?int usage:flags.3?int = ExportedChatInvite; +chatInviteExported#b18105e8 flags:# revoked:flags.0?true permanent:flags.5?true link:string admin_id:long date:int start_date:flags.4?int expire_date:flags.1?int usage_limit:flags.2?int usage:flags.3?int = ExportedChatInvite; chatInviteAlready#5a686d7c chat:Chat = ChatInvite; chatInvite#dfc2f58e flags:# channel:flags.0?true broadcast:flags.1?true public:flags.2?true megagroup:flags.3?true title:string photo:Photo participants_count:int participants:flags.4?Vector = ChatInvite; @@ -543,7 +545,7 @@ messages.stickerSet#b60a24a6 set:StickerSet packs:Vector documents: botCommand#c27ac8c7 command:string description:string = BotCommand; -botInfo#98e81d3a user_id:int description:string commands:Vector = BotInfo; +botInfo#1b74b335 user_id:long description:string commands:Vector = BotInfo; keyboardButton#a2fa4880 text:string = KeyboardButton; keyboardButtonUrl#258aff05 text:string url:string = KeyboardButton; @@ -575,7 +577,7 @@ messageEntityItalic#826f8b60 offset:int length:int = MessageEntity; messageEntityCode#28a20571 offset:int length:int = MessageEntity; messageEntityPre#73924be0 offset:int length:int language:string = MessageEntity; messageEntityTextUrl#76a6d327 offset:int length:int url:string = MessageEntity; -messageEntityMentionName#352dca58 offset:int length:int user_id:int = MessageEntity; +messageEntityMentionName#dc7b1140 offset:int length:int user_id:long = MessageEntity; inputMessageEntityMentionName#208e68c9 offset:int length:int user_id:InputUser = MessageEntity; messageEntityPhone#9b69e34b offset:int length:int = MessageEntity; messageEntityCashtag#4c4e743f offset:int length:int = MessageEntity; @@ -585,8 +587,8 @@ messageEntityBlockquote#20df5d0 offset:int length:int = MessageEntity; messageEntityBankCard#761e6af4 offset:int length:int = MessageEntity; inputChannelEmpty#ee8c1e86 = InputChannel; -inputChannel#afeb712e channel_id:int access_hash:long = InputChannel; -inputChannelFromMessage#2a286531 peer:InputPeer msg_id:int channel_id:int = InputChannel; +inputChannel#f35aec28 channel_id:long access_hash:long = InputChannel; +inputChannelFromMessage#5b934f9d peer:InputPeer msg_id:int channel_id:long = InputChannel; contacts.resolvedPeer#7f077ad9 peer:Peer chats:Vector users:Vector = contacts.ResolvedPeer; @@ -599,11 +601,11 @@ updates.channelDifference#2064674e flags:# final:flags.0?true pts:int timeout:fl channelMessagesFilterEmpty#94d42ee7 = ChannelMessagesFilter; channelMessagesFilter#cd77d957 flags:# exclude_new_messages:flags.1?true ranges:Vector = ChannelMessagesFilter; -channelParticipant#15ebac1d user_id:int date:int = ChannelParticipant; -channelParticipantSelf#a3289a6d user_id:int inviter_id:int date:int = ChannelParticipant; -channelParticipantCreator#447dca4b flags:# user_id:int admin_rights:ChatAdminRights rank:flags.0?string = ChannelParticipant; -channelParticipantAdmin#ccbebbaf flags:# can_edit:flags.0?true self:flags.1?true user_id:int inviter_id:flags.1?int promoted_by:int date:int admin_rights:ChatAdminRights rank:flags.2?string = ChannelParticipant; -channelParticipantBanned#50a1dfd6 flags:# left:flags.0?true peer:Peer kicked_by:int date:int banned_rights:ChatBannedRights = ChannelParticipant; +channelParticipant#c00c07c0 user_id:long date:int = ChannelParticipant; +channelParticipantSelf#28a8bc67 user_id:long inviter_id:long date:int = ChannelParticipant; +channelParticipantCreator#2fe601d3 flags:# user_id:long admin_rights:ChatAdminRights rank:flags.0?string = ChannelParticipant; +channelParticipantAdmin#34c3bb53 flags:# can_edit:flags.0?true self:flags.1?true user_id:long inviter_id:flags.1?long promoted_by:long date:int admin_rights:ChatAdminRights rank:flags.2?string = ChannelParticipant; +channelParticipantBanned#6df8014e flags:# left:flags.0?true peer:Peer kicked_by:long date:int banned_rights:ChatBannedRights = ChannelParticipant; channelParticipantLeft#1b03f006 peer:Peer = ChannelParticipant; channelParticipantsRecent#de3f3c79 = ChannelParticipantsFilter; @@ -623,7 +625,7 @@ channels.channelParticipant#dfb80317 participant:ChannelParticipant chats:Vector help.termsOfService#780a0310 flags:# popup:flags.0?true id:DataJSON text:string entities:Vector min_age_confirm:flags.1?int = help.TermsOfService; messages.savedGifsNotModified#e8025ca2 = messages.SavedGifs; -messages.savedGifs#2e0709a5 hash:int gifs:Vector = messages.SavedGifs; +messages.savedGifs#84a02a0d hash:long gifs:Vector = messages.SavedGifs; inputBotInlineMessageMediaAuto#3380c786 flags:# message:string entities:flags.1?Vector reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; inputBotInlineMessageText#3dcd7a87 flags:# no_webpage:flags.0?true message:string entities:flags.1?Vector reply_markup:flags.2?ReplyMarkup = InputBotInlineMessage; @@ -668,6 +670,7 @@ messages.botCallbackAnswer#36585ea4 flags:# alert:flags.1?true has_url:flags.3?t messages.messageEditData#26b5dde6 flags:# caption:flags.0?true = messages.MessageEditData; inputBotInlineMessageID#890c3d89 dc_id:int id:long access_hash:long = InputBotInlineMessageID; +inputBotInlineMessageID64#b6d915d7 dc_id:int owner_id:long id:int access_hash:long = InputBotInlineMessageID; inlineBotSwitchPM#3c20629f text:string start_param:string = InlineBotSwitchPM; @@ -694,10 +697,10 @@ draftMessageEmpty#1b0c841a flags:# date:flags.0?int = DraftMessage; draftMessage#fd8e711f flags:# no_webpage:flags.1?true reply_to_msg_id:flags.0?int message:string entities:flags.3?Vector date:int = DraftMessage; messages.featuredStickersNotModified#c6dc0c66 count:int = messages.FeaturedStickers; -messages.featuredStickers#b6abc341 hash:int count:int sets:Vector unread:Vector = messages.FeaturedStickers; +messages.featuredStickers#84c02310 hash:long count:int sets:Vector unread:Vector = messages.FeaturedStickers; messages.recentStickersNotModified#b17f890 = messages.RecentStickers; -messages.recentStickers#22f3afb3 hash:int packs:Vector stickers:Vector dates:Vector = messages.RecentStickers; +messages.recentStickers#88d37c56 hash:long packs:Vector stickers:Vector dates:Vector = messages.RecentStickers; messages.archivedStickers#4fcba9c8 count:int sets:Vector = messages.ArchivedStickers; @@ -717,7 +720,7 @@ game#bdf9653b flags:# id:long access_hash:long short_name:string title:string de inputGameID#32c3e77 id:long access_hash:long = InputGame; inputGameShortName#c331e80a bot_id:InputUser short_name:string = InputGame; -highScore#58fffcd0 pos:int user_id:int score:int = HighScore; +highScore#73a379eb pos:int user_id:long score:int = HighScore; messages.highScores#9a3bfd99 scores:Vector users:Vector = messages.HighScores; @@ -797,14 +800,14 @@ inputWebFileGeoPointLocation#9f2221c9 geo_point:InputGeoPoint access_hash:long w upload.webFile#21e753bc size:int mime_type:string file_type:storage.FileType mtime:int bytes:bytes = upload.WebFile; -payments.paymentForm#8d0b2415 flags:# can_save_credentials:flags.2?true password_missing:flags.3?true form_id:long bot_id:int invoice:Invoice provider_id:int url:string native_provider:flags.4?string native_params:flags.4?DataJSON saved_info:flags.0?PaymentRequestedInfo saved_credentials:flags.1?PaymentSavedCredentials users:Vector = payments.PaymentForm; +payments.paymentForm#1694761b flags:# can_save_credentials:flags.2?true password_missing:flags.3?true form_id:long bot_id:long invoice:Invoice provider_id:long url:string native_provider:flags.4?string native_params:flags.4?DataJSON saved_info:flags.0?PaymentRequestedInfo saved_credentials:flags.1?PaymentSavedCredentials users:Vector = payments.PaymentForm; payments.validatedRequestedInfo#d1451883 flags:# id:flags.0?string shipping_options:flags.1?Vector = payments.ValidatedRequestedInfo; payments.paymentResult#4e5f810d updates:Updates = payments.PaymentResult; payments.paymentVerificationNeeded#d8411139 url:string = payments.PaymentResult; -payments.paymentReceipt#10b555d0 flags:# date:int bot_id:int provider_id:int title:string description:string photo:flags.2?WebDocument invoice:Invoice info:flags.0?PaymentRequestedInfo shipping:flags.1?ShippingOption tip_amount:flags.3?long currency:string total_amount:long credentials_title:string users:Vector = payments.PaymentReceipt; +payments.paymentReceipt#70c4fe03 flags:# date:int bot_id:long provider_id:long title:string description:string photo:flags.2?WebDocument invoice:Invoice info:flags.0?PaymentRequestedInfo shipping:flags.1?ShippingOption tip_amount:flags.3?long currency:string total_amount:long credentials_title:string users:Vector = payments.PaymentReceipt; payments.savedInfo#fb8fe43c flags:# has_saved_credentials:flags.1?true saved_info:flags.0?PaymentRequestedInfo = payments.SavedInfo; @@ -822,10 +825,10 @@ inputStickerSetItem#ffa0a496 flags:# document:InputDocument emoji:string mask_co inputPhoneCall#1e36fded id:long access_hash:long = InputPhoneCall; phoneCallEmpty#5366c915 id:long = PhoneCall; -phoneCallWaiting#1b8f4ad1 flags:# video:flags.6?true id:long access_hash:long date:int admin_id:int participant_id:int protocol:PhoneCallProtocol receive_date:flags.0?int = PhoneCall; -phoneCallRequested#87eabb53 flags:# video:flags.6?true id:long access_hash:long date:int admin_id:int participant_id:int g_a_hash:bytes protocol:PhoneCallProtocol = PhoneCall; -phoneCallAccepted#997c454a flags:# video:flags.6?true id:long access_hash:long date:int admin_id:int participant_id:int g_b:bytes protocol:PhoneCallProtocol = PhoneCall; -phoneCall#8742ae7f flags:# p2p_allowed:flags.5?true video:flags.6?true id:long access_hash:long date:int admin_id:int participant_id:int g_a_or_b:bytes key_fingerprint:long protocol:PhoneCallProtocol connections:Vector start_date:int = PhoneCall; +phoneCallWaiting#c5226f17 flags:# video:flags.6?true id:long access_hash:long date:int admin_id:long participant_id:long protocol:PhoneCallProtocol receive_date:flags.0?int = PhoneCall; +phoneCallRequested#14b0ed0c flags:# video:flags.6?true id:long access_hash:long date:int admin_id:long participant_id:long g_a_hash:bytes protocol:PhoneCallProtocol = PhoneCall; +phoneCallAccepted#3660c311 flags:# video:flags.6?true id:long access_hash:long date:int admin_id:long participant_id:long g_b:bytes protocol:PhoneCallProtocol = PhoneCall; +phoneCall#967f7c67 flags:# p2p_allowed:flags.5?true video:flags.6?true id:long access_hash:long date:int admin_id:long participant_id:long g_a_or_b:bytes key_fingerprint:long protocol:PhoneCallProtocol connections:Vector start_date:int = PhoneCall; phoneCallDiscarded#50ca4de1 flags:# need_rating:flags.2?true need_debug:flags.3?true video:flags.6?true id:long reason:flags.0?PhoneCallDiscardReason duration:flags.1?int = PhoneCall; phoneConnection#9d4c17c0 id:long ip:string ipv6:string port:int peer_tag:bytes = PhoneConnection; @@ -868,7 +871,7 @@ channelAdminLogEventActionChangeStickerSet#b1c3caa7 prev_stickerset:InputSticker channelAdminLogEventActionTogglePreHistoryHidden#5f5c95f1 new_value:Bool = ChannelAdminLogEventAction; channelAdminLogEventActionDefaultBannedRights#2df5fc0a prev_banned_rights:ChatBannedRights new_banned_rights:ChatBannedRights = ChannelAdminLogEventAction; channelAdminLogEventActionStopPoll#8f079643 message:Message = ChannelAdminLogEventAction; -channelAdminLogEventActionChangeLinkedChat#a26f881b prev_value:int new_value:int = ChannelAdminLogEventAction; +channelAdminLogEventActionChangeLinkedChat#50c7ac8 prev_value:long new_value:long = ChannelAdminLogEventAction; channelAdminLogEventActionChangeLocation#e6b76ae prev_value:ChannelLocation new_value:ChannelLocation = ChannelAdminLogEventAction; channelAdminLogEventActionToggleSlowMode#53909779 prev_value:int new_value:int = ChannelAdminLogEventAction; channelAdminLogEventActionStartGroupCall#23209745 call:InputGroupCall = ChannelAdminLogEventAction; @@ -882,8 +885,9 @@ channelAdminLogEventActionExportedInviteRevoke#410a134e invite:ExportedChatInvit channelAdminLogEventActionExportedInviteEdit#e90ebb59 prev_invite:ExportedChatInvite new_invite:ExportedChatInvite = ChannelAdminLogEventAction; channelAdminLogEventActionParticipantVolume#3e7f6847 participant:GroupCallParticipant = ChannelAdminLogEventAction; channelAdminLogEventActionChangeHistoryTTL#6e941a38 prev_value:int new_value:int = ChannelAdminLogEventAction; +channelAdminLogEventActionChangeTheme#fe69018d prev_value:string new_value:string = ChannelAdminLogEventAction; -channelAdminLogEvent#3b5a3e40 id:long date:int user_id:int action:ChannelAdminLogEventAction = ChannelAdminLogEvent; +channelAdminLogEvent#1fad68cd id:long date:int user_id:long action:ChannelAdminLogEventAction = ChannelAdminLogEvent; channels.adminLogResults#ed8af74d events:Vector chats:Vector users:Vector = channels.AdminLogResults; @@ -892,11 +896,11 @@ channelAdminLogEventsFilter#ea107ae4 flags:# join:flags.0?true leave:flags.1?tru popularContact#5ce14175 client_id:long importers:int = PopularContact; messages.favedStickersNotModified#9e8fa6d3 = messages.FavedStickers; -messages.favedStickers#f37f2f16 hash:int packs:Vector stickers:Vector = messages.FavedStickers; +messages.favedStickers#2cb51097 hash:long packs:Vector stickers:Vector = messages.FavedStickers; recentMeUrlUnknown#46e1d13d url:string = RecentMeUrl; -recentMeUrlUser#8dbc3336 url:string user_id:int = RecentMeUrl; -recentMeUrlChat#a01b22f9 url:string chat_id:int = RecentMeUrl; +recentMeUrlUser#b92c09e2 url:string user_id:long = RecentMeUrl; +recentMeUrlChat#b2da71d2 url:string chat_id:long = RecentMeUrl; recentMeUrlChatInvite#eb49081d url:string chat_invite:ChatInvite = RecentMeUrl; recentMeUrlStickerSet#bc0a57dc url:string set:StickerSetCovered = RecentMeUrl; @@ -904,7 +908,7 @@ help.recentMeUrls#e0310d7 urls:Vector chats:Vector users:Vect inputSingleMedia#1cc6e91f flags:# media:InputMedia random_id:long message:string entities:flags.0?Vector = InputSingleMedia; -webAuthorization#cac943f2 hash:long bot_id:int domain:string browser:string platform:string date_created:int date_active:int ip:string region:string = WebAuthorization; +webAuthorization#a6f8f452 hash:long bot_id:long domain:string browser:string platform:string date_created:int date_active:int ip:string region:string = WebAuthorization; account.webAuthorizations#ed56c9fc authorizations:Vector users:Vector = account.WebAuthorizations; @@ -920,7 +924,7 @@ dialogPeer#e56dbf05 peer:Peer = DialogPeer; dialogPeerFolder#514519e2 folder_id:int = DialogPeer; messages.foundStickerSetsNotModified#d54b65d = messages.FoundStickerSets; -messages.foundStickerSets#5108d648 hash:int sets:Vector = messages.FoundStickerSets; +messages.foundStickerSets#8af09dd2 hash:long sets:Vector = messages.FoundStickerSets; fileHash#6242c773 offset:int limit:int hash:bytes = FileHash; @@ -1039,7 +1043,7 @@ poll#86e18161 id:long flags:# closed:flags.0?true public_voters:flags.1?true mul pollAnswerVoters#3b6ddad2 flags:# chosen:flags.0?true correct:flags.1?true option:bytes voters:int = PollAnswerVoters; -pollResults#badcc1a3 flags:# min:flags.0?true results:flags.1?Vector total_voters:flags.2?int recent_voters:flags.3?Vector solution:flags.4?string solution_entities:flags.4?Vector = PollResults; +pollResults#dcb82ea3 flags:# min:flags.0?true results:flags.1?Vector total_voters:flags.2?int recent_voters:flags.3?Vector solution:flags.4?string solution_entities:flags.4?Vector = PollResults; chatOnlines#f041e250 onlines:int = ChatOnlines; @@ -1054,7 +1058,7 @@ inputWallPaperSlug#72091c80 slug:string = InputWallPaper; inputWallPaperNoFile#967a462e id:long = InputWallPaper; account.wallPapersNotModified#1c199183 = account.WallPapers; -account.wallPapers#702b65a9 hash:int wallpapers:Vector = account.WallPapers; +account.wallPapers#cdc3858c hash:long wallpapers:Vector = account.WallPapers; codeSettings#debebe83 flags:# allow_flashcall:flags.0?true current_number:flags.1?true allow_app_hash:flags.4?true = CodeSettings; @@ -1099,7 +1103,7 @@ inputThemeSlug#f5890df1 slug:string = InputTheme; theme#e802b8dc flags:# creator:flags.0?true default:flags.1?true for_chat:flags.5?true id:long access_hash:long slug:string title:string document:flags.2?Document settings:flags.3?ThemeSettings installs_count:flags.4?int = Theme; account.themesNotModified#f41eb622 = account.Themes; -account.themes#7f676421 hash:int themes:Vector = account.Themes; +account.themes#9a3d8c6d hash:long themes:Vector = account.Themes; auth.loginToken#629f1980 expires:int token:bytes = auth.LoginToken; auth.loginTokenMigrateTo#68e9916 dc_id:int token:bytes = auth.LoginToken; @@ -1115,15 +1119,15 @@ baseThemeNight#b7b31ea8 = BaseTheme; baseThemeTinted#6d5f77ee = BaseTheme; baseThemeArctic#5b11125a = BaseTheme; -inputThemeSettings#ff38f912 flags:# message_colors_animated:flags.2?true base_theme:BaseTheme accent_color:int message_colors:flags.0?Vector wallpaper:flags.1?InputWallPaper wallpaper_settings:flags.1?WallPaperSettings = InputThemeSettings; +inputThemeSettings#8fde504f flags:# message_colors_animated:flags.2?true base_theme:BaseTheme accent_color:int outbox_accent_color:flags.3?int message_colors:flags.0?Vector wallpaper:flags.1?InputWallPaper wallpaper_settings:flags.1?WallPaperSettings = InputThemeSettings; -themeSettings#8db4e76c flags:# message_colors_animated:flags.2?true base_theme:BaseTheme accent_color:int message_colors:flags.0?Vector wallpaper:flags.1?WallPaper = ThemeSettings; +themeSettings#fa58b6d4 flags:# message_colors_animated:flags.2?true base_theme:BaseTheme accent_color:int outbox_accent_color:flags.3?int message_colors:flags.0?Vector wallpaper:flags.1?WallPaper = ThemeSettings; webPageAttributeTheme#54b56617 flags:# documents:flags.0?Vector settings:flags.1?ThemeSettings = WebPageAttribute; -messageUserVote#a28e5559 user_id:int option:bytes date:int = MessageUserVote; -messageUserVoteInputOption#36377430 user_id:int date:int = MessageUserVote; -messageUserVoteMultiple#e8fe0de user_id:int options:Vector date:int = MessageUserVote; +messageUserVote#34d247b4 user_id:long option:bytes date:int = MessageUserVote; +messageUserVoteInputOption#3ca5b0ec user_id:long date:int = MessageUserVote; +messageUserVoteMultiple#8a65e557 user_id:long options:Vector date:int = MessageUserVote; messages.votesList#823f649 flags:# count:int votes:Vector users:Vector next_offset:flags.0?string = messages.VotesList; @@ -1154,11 +1158,11 @@ help.promoData#8c39793f flags:# proxy:flags.0?true expires:int peer:Peer chats:V videoSize#de33b094 flags:# type:string w:int h:int size:int video_start_ts:flags.0?double = VideoSize; -statsGroupTopPoster#18f3d0f7 user_id:int messages:int avg_chars:int = StatsGroupTopPoster; +statsGroupTopPoster#9d04af9b user_id:long messages:int avg_chars:int = StatsGroupTopPoster; -statsGroupTopAdmin#6014f412 user_id:int deleted:int kicked:int banned:int = StatsGroupTopAdmin; +statsGroupTopAdmin#d7584c87 user_id:long deleted:int kicked:int banned:int = StatsGroupTopAdmin; -statsGroupTopInviter#31962a4c user_id:int invitations:int = StatsGroupTopInviter; +statsGroupTopInviter#535f779d user_id:long invitations:int = StatsGroupTopInviter; stats.megagroupStats#ef7ff916 period:StatsDateRangeDays members:StatsAbsValueAndPrev messages:StatsAbsValueAndPrev viewers:StatsAbsValueAndPrev posters:StatsAbsValueAndPrev growth_graph:StatsGraph members_graph:StatsGraph new_members_by_source_graph:StatsGraph languages_graph:StatsGraph messages_graph:StatsGraph actions_graph:StatsGraph top_hours_graph:StatsGraph weekdays_graph:StatsGraph top_posters:Vector top_admins:Vector top_inviters:Vector users:Vector = stats.MegagroupStats; @@ -1179,7 +1183,7 @@ messages.discussionMessage#a6341782 flags:# messages:Vector max_id:flag messageReplyHeader#a6d57763 flags:# reply_to_msg_id:int reply_to_peer_id:flags.0?Peer reply_to_top_id:flags.1?int = MessageReplyHeader; -messageReplies#4128faac flags:# comments:flags.0?true replies:int replies_pts:int recent_repliers:flags.1?Vector channel_id:flags.0?int max_id:flags.2?int read_max_id:flags.3?int = MessageReplies; +messageReplies#83d60fc2 flags:# comments:flags.0?true replies:int replies_pts:int recent_repliers:flags.1?Vector channel_id:flags.0?long max_id:flags.2?int read_max_id:flags.3?int = MessageReplies; peerBlocked#e8fd8014 peer_id:Peer date:int = PeerBlocked; @@ -1208,7 +1212,7 @@ messages.historyImportParsed#5e0fb7b9 flags:# pm:flags.0?true group:flags.1?true messages.affectedFoundMessages#ef8d3e6c pts:int pts_count:int offset:int messages:Vector = messages.AffectedFoundMessages; -chatInviteImporter#1e3e6680 user_id:int date:int = ChatInviteImporter; +chatInviteImporter#b5cd5f4 user_id:long date:int = ChatInviteImporter; messages.exportedChatInvites#bdc62dcc count:int invites:Vector users:Vector = messages.ExportedChatInvites; @@ -1217,7 +1221,7 @@ messages.exportedChatInviteReplaced#222600ef invite:ExportedChatInvite new_invit messages.chatInviteImporters#81b6b00a count:int importers:Vector users:Vector = messages.ChatInviteImporters; -chatAdminWithInvites#dfd2330f admin_id:int invites_count:int revoked_invites_count:int = ChatAdminWithInvites; +chatAdminWithInvites#f2ecef23 admin_id:long invites_count:int revoked_invites_count:int = ChatAdminWithInvites; messages.chatAdminsWithInvites#b69b72d7 admins:Vector users:Vector = messages.ChatAdminsWithInvites; @@ -1250,7 +1254,7 @@ chatTheme#ed0b5c33 emoticon:string theme:Theme dark_theme:Theme = ChatTheme; account.chatThemesNotModified#e011e1c4 = account.ChatThemes; account.chatThemes#fe4cbebd hash:int themes:Vector = account.ChatThemes; -sponsoredMessage#f671f0d1 flags:# random_id:bytes peer_id:Peer from_id:Peer message:string media:flags.0?MessageMedia entities:flags.1?Vector = SponsoredMessage; +sponsoredMessage#2a3c381f flags:# random_id:bytes from_id:Peer start_param:flags.0?string message:string entities:flags.1?Vector = SponsoredMessage; messages.sponsoredMessages#65a4c7d5 messages:Vector chats:Vector users:Vector = messages.SponsoredMessages; @@ -1270,7 +1274,7 @@ auth.signIn#bcd51581 phone_number:string phone_code_hash:string phone_code:strin auth.logOut#5717da40 = Bool; auth.resetAuthorizations#9fab0d1a = Bool; auth.exportAuthorization#e5bfffcd dc_id:int = auth.ExportedAuthorization; -auth.importAuthorization#e3ef9613 id:int bytes:bytes = auth.Authorization; +auth.importAuthorization#a57a7dad id:long bytes:bytes = auth.Authorization; auth.bindTempAuthKey#cdd42a05 perm_auth_key_id:long nonce:long expires_at:int encrypted_message:bytes = Bool; auth.importBotAuthorization#67a3ff2c flags:int api_id:int api_hash:string bot_auth_token:string = auth.Authorization; auth.checkPassword#d18b4d16 password:InputCheckPasswordSRP = auth.Authorization; @@ -1279,19 +1283,19 @@ auth.recoverPassword#37096c70 flags:# code:string new_settings:flags.0?account.P auth.resendCode#3ef1a9bf phone_number:string phone_code_hash:string = auth.SentCode; auth.cancelCode#1f040578 phone_number:string phone_code_hash:string = Bool; auth.dropTempAuthKeys#8e48a188 except_auth_keys:Vector = Bool; -auth.exportLoginToken#b1b41517 api_id:int api_hash:string except_ids:Vector = auth.LoginToken; +auth.exportLoginToken#b7e085fe api_id:int api_hash:string except_ids:Vector = auth.LoginToken; auth.importLoginToken#95ac5ce4 token:bytes = auth.LoginToken; auth.acceptLoginToken#e894ad4d token:bytes = Authorization; auth.checkRecoveryPassword#d36bf79 code:string = Bool; -account.registerDevice#68976c6f flags:# no_muted:flags.0?true token_type:int token:string app_sandbox:Bool secret:bytes other_uids:Vector = Bool; -account.unregisterDevice#3076c4bf token_type:int token:string other_uids:Vector = Bool; +account.registerDevice#ec86017a flags:# no_muted:flags.0?true token_type:int token:string app_sandbox:Bool secret:bytes other_uids:Vector = Bool; +account.unregisterDevice#6a0d3206 token_type:int token:string other_uids:Vector = Bool; account.updateNotifySettings#84be5b93 peer:InputNotifyPeer settings:InputPeerNotifySettings = Bool; account.getNotifySettings#12b3ad31 peer:InputNotifyPeer = PeerNotifySettings; account.resetNotifySettings#db7e1747 = Bool; account.updateProfile#78515775 flags:# first_name:flags.0?string last_name:flags.1?string about:flags.2?string = User; account.updateStatus#6628562c offline:Bool = Bool; -account.getWallPapers#aabb1763 hash:int = account.WallPapers; +account.getWallPapers#7967d36 hash:long = account.WallPapers; account.reportPeer#c5ba3d86 peer:InputPeer reason:ReportReason message:string = Bool; account.checkUsername#2714d86c username:string = Bool; account.updateUsername#3e0bdd7c username:string = User; @@ -1318,8 +1322,8 @@ account.getAllSecureValues#b288bc7d = Vector; account.getSecureValue#73665bc2 types:Vector = Vector; account.saveSecureValue#899fe31d value:InputSecureValue secure_secret_id:long = SecureValue; account.deleteSecureValue#b880bc4b types:Vector = Bool; -account.getAuthorizationForm#b86ba8e1 bot_id:int scope:string public_key:string = account.AuthorizationForm; -account.acceptAuthorization#e7027c94 bot_id:int scope:string public_key:string value_hashes:Vector credentials:SecureCredentialsEncrypted = Bool; +account.getAuthorizationForm#a929597a bot_id:long scope:string public_key:string = account.AuthorizationForm; +account.acceptAuthorization#f3ed4c73 bot_id:long scope:string public_key:string value_hashes:Vector credentials:SecureCredentialsEncrypted = Bool; account.sendVerifyPhoneCode#a5a356f9 phone_number:string settings:CodeSettings = auth.SentCode; account.verifyPhone#4dd3a7f6 phone_number:string phone_code_hash:string phone_code:string = Bool; account.sendVerifyEmailCode#7011509f email:string = account.SentEmailCode; @@ -1345,7 +1349,7 @@ account.updateTheme#5cb367d5 flags:# format:string theme:InputTheme slug:flags.0 account.saveTheme#f257106c theme:InputTheme unsave:Bool = Bool; account.installTheme#7ae43737 flags:# dark:flags.0?true format:flags.1?string theme:flags.1?InputTheme = Bool; account.getTheme#8d9d742b format:string theme:InputTheme document_id:long = Theme; -account.getThemes#285946f8 format:string hash:int = account.Themes; +account.getThemes#7206e458 format:string hash:long = account.Themes; account.setContentSettings#b574b16b flags:# sensitive_enabled:flags.0?true = Bool; account.getContentSettings#8b9b4dae = account.ContentSettings; account.getMultiWallPapers#65ad71dc wallpapers:Vector = Vector; @@ -1360,9 +1364,9 @@ users.getUsers#d91a548 id:Vector = Vector; users.getFullUser#ca30a5b1 id:InputUser = UserFull; users.setSecureValueErrors#90c894b5 id:InputUser errors:Vector = Bool; -contacts.getContactIDs#2caa4a42 hash:int = Vector; +contacts.getContactIDs#7adc669d hash:long = Vector; contacts.getStatuses#c4a353ee = Vector; -contacts.getContacts#c023849f hash:int = contacts.Contacts; +contacts.getContacts#5dd69e12 hash:long = contacts.Contacts; contacts.importContacts#2c800be5 contacts:Vector = contacts.ImportedContacts; contacts.deleteContacts#96a0e00 id:Vector = Updates; contacts.deleteByPhones#1013fd9e phones:Vector = Bool; @@ -1371,7 +1375,7 @@ contacts.unblock#bea65d50 id:InputPeer = Bool; contacts.getBlocked#f57c350f offset:int limit:int = contacts.Blocked; contacts.search#11f812d8 q:string limit:int = contacts.Found; contacts.resolveUsername#f93ccba3 username:string = contacts.ResolvedPeer; -contacts.getTopPeers#d4982db5 flags:# correspondents:flags.0?true bots_pm:flags.1?true bots_inline:flags.2?true phone_calls:flags.3?true forward_users:flags.4?true forward_chats:flags.5?true groups:flags.10?true channels:flags.15?true offset:int limit:int hash:int = contacts.TopPeers; +contacts.getTopPeers#973478b6 flags:# correspondents:flags.0?true bots_pm:flags.1?true bots_inline:flags.2?true phone_calls:flags.3?true forward_users:flags.4?true forward_chats:flags.5?true groups:flags.10?true channels:flags.15?true offset:int limit:int hash:long = contacts.TopPeers; contacts.resetTopPeerRating#1ae373ac category:TopPeerCategory peer:InputPeer = Bool; contacts.resetSaved#879537f1 = Bool; contacts.getSaved#82f1e39f = Vector; @@ -1382,9 +1386,9 @@ contacts.getLocated#d348bc44 flags:# background:flags.1?true geo_point:InputGeoP contacts.blockFromReplies#29a8962c flags:# delete_message:flags.0?true delete_history:flags.1?true report_spam:flags.2?true msg_id:int = Updates; messages.getMessages#63c66506 id:Vector = messages.Messages; -messages.getDialogs#a0ee3b73 flags:# exclude_pinned:flags.0?true folder_id:flags.1?int offset_date:int offset_id:int offset_peer:InputPeer limit:int hash:int = messages.Dialogs; -messages.getHistory#dcbb8260 peer:InputPeer offset_id:int offset_date:int add_offset:int limit:int max_id:int min_id:int hash:int = messages.Messages; -messages.search#c352eec flags:# peer:InputPeer q:string from_id:flags.0?InputPeer top_msg_id:flags.1?int filter:MessagesFilter min_date:int max_date:int offset_id:int add_offset:int limit:int max_id:int min_id:int hash:int = messages.Messages; +messages.getDialogs#a0f4cb4f flags:# exclude_pinned:flags.0?true folder_id:flags.1?int offset_date:int offset_id:int offset_peer:InputPeer limit:int hash:long = messages.Dialogs; +messages.getHistory#4423e6c5 peer:InputPeer offset_id:int offset_date:int add_offset:int limit:int max_id:int min_id:int hash:long = messages.Messages; +messages.search#a0fda762 flags:# peer:InputPeer q:string from_id:flags.0?InputPeer top_msg_id:flags.1?int filter:MessagesFilter min_date:int max_date:int offset_id:int add_offset:int limit:int max_id:int min_id:int hash:long = messages.Messages; messages.readHistory#e306d3a peer:InputPeer max_id:int = messages.AffectedMessages; messages.deleteHistory#1c015b09 flags:# just_clear:flags.0?true revoke:flags.1?true peer:InputPeer max_id:int = messages.AffectedHistory; messages.deleteMessages#e58e95d2 flags:# revoke:flags.0?true id:Vector = messages.AffectedMessages; @@ -1396,12 +1400,12 @@ messages.forwardMessages#d9fee60e flags:# silent:flags.5?true background:flags.6 messages.reportSpam#cf1592db peer:InputPeer = Bool; messages.getPeerSettings#3672e09c peer:InputPeer = PeerSettings; messages.report#8953ab4e peer:InputPeer id:Vector reason:ReportReason message:string = Bool; -messages.getChats#3c6aa187 id:Vector = messages.Chats; -messages.getFullChat#3b831c66 chat_id:int = messages.ChatFull; -messages.editChatTitle#dc452855 chat_id:int title:string = Updates; -messages.editChatPhoto#ca4c79d8 chat_id:int photo:InputChatPhoto = Updates; -messages.addChatUser#f9a0aa09 chat_id:int user_id:InputUser fwd_limit:int = Updates; -messages.deleteChatUser#c534459a flags:# revoke_history:flags.0?true chat_id:int user_id:InputUser = Updates; +messages.getChats#49e9528f id:Vector = messages.Chats; +messages.getFullChat#aeb00b34 chat_id:long = messages.ChatFull; +messages.editChatTitle#73783ffd chat_id:long title:string = Updates; +messages.editChatPhoto#35ddd674 chat_id:long photo:InputChatPhoto = Updates; +messages.addChatUser#f24753e3 chat_id:long user_id:InputUser fwd_limit:int = Updates; +messages.deleteChatUser#a2185cab flags:# revoke_history:flags.0?true chat_id:long user_id:InputUser = Updates; messages.createChat#9cb126e users:Vector title:string = Updates; messages.getDhConfig#26cf8950 version:int random_length:int = messages.DhConfig; messages.requestEncryption#f64daf43 user_id:InputUser random_id:int g_a:bytes = EncryptedChat; @@ -1415,8 +1419,8 @@ messages.sendEncryptedService#32d439a4 peer:InputEncryptedChat random_id:long da messages.receivedQueue#55a5bb66 max_qts:int = Vector; messages.reportEncryptedSpam#4b0c8c0f peer:InputEncryptedChat = Bool; messages.readMessageContents#36a73f77 id:Vector = messages.AffectedMessages; -messages.getStickers#43d4f2c emoticon:string hash:int = messages.Stickers; -messages.getAllStickers#1c9618b1 hash:int = messages.AllStickers; +messages.getStickers#d5a5d3a1 emoticon:string hash:long = messages.Stickers; +messages.getAllStickers#b8a0a1a8 hash:long = messages.AllStickers; messages.getWebPagePreview#8b68b0cc flags:# message:string entities:flags.3?Vector = MessageMedia; messages.exportChatInvite#14b9bcd7 flags:# legacy_revoke_permanent:flags.2?true peer:InputPeer expire_date:flags.0?int usage_limit:flags.1?int = ExportedChatInvite; messages.checkChatInvite#3eadb1bb hash:string = ChatInvite; @@ -1426,12 +1430,12 @@ messages.installStickerSet#c78fe460 stickerset:InputStickerSet archived:Bool = m messages.uninstallStickerSet#f96e55de stickerset:InputStickerSet = Bool; messages.startBot#e6df7378 bot:InputUser peer:InputPeer random_id:long start_param:string = Updates; messages.getMessagesViews#5784d3e1 peer:InputPeer id:Vector increment:Bool = messages.MessageViews; -messages.editChatAdmin#a9e69f2e chat_id:int user_id:InputUser is_admin:Bool = Bool; -messages.migrateChat#15a3b8e3 chat_id:int = Updates; +messages.editChatAdmin#a85bd1c2 chat_id:long user_id:InputUser is_admin:Bool = Bool; +messages.migrateChat#a2875319 chat_id:long = Updates; messages.searchGlobal#4bc6589a flags:# folder_id:flags.0?int q:string filter:MessagesFilter min_date:int max_date:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; messages.reorderStickerSets#78337739 flags:# masks:flags.0?true order:Vector = Bool; messages.getDocumentByHash#338e2464 sha256:bytes size:int mime_type:string = Document; -messages.getSavedGifs#83bf3d52 hash:int = messages.SavedGifs; +messages.getSavedGifs#5cf09635 hash:long = messages.SavedGifs; messages.saveGif#327a30cb id:InputDocument unsave:Bool = Bool; messages.getInlineBotResults#514e999d flags:# bot:InputUser peer:InputPeer geo_point:flags.0?InputGeoPoint query:string offset:string = messages.BotResults; messages.setInlineBotResults#eb5ea206 flags:# gallery:flags.0?true private:flags.1?true query_id:long results:Vector cache_time:int next_offset:flags.2?string switch_pm:flags.3?InlineBotSwitchPM = Bool; @@ -1444,20 +1448,20 @@ messages.setBotCallbackAnswer#d58f130a flags:# alert:flags.1?true query_id:long messages.getPeerDialogs#e470bcfd peers:Vector = messages.PeerDialogs; messages.saveDraft#bc39e14b flags:# no_webpage:flags.1?true reply_to_msg_id:flags.0?int peer:InputPeer message:string entities:flags.3?Vector = Bool; messages.getAllDrafts#6a3f8d65 = Updates; -messages.getFeaturedStickers#2dacca4f hash:int = messages.FeaturedStickers; +messages.getFeaturedStickers#64780b14 hash:long = messages.FeaturedStickers; messages.readFeaturedStickers#5b118126 id:Vector = Bool; -messages.getRecentStickers#5ea192c9 flags:# attached:flags.0?true hash:int = messages.RecentStickers; +messages.getRecentStickers#9da9403b flags:# attached:flags.0?true hash:long = messages.RecentStickers; messages.saveRecentSticker#392718f8 flags:# attached:flags.0?true id:InputDocument unsave:Bool = Bool; messages.clearRecentStickers#8999602d flags:# attached:flags.0?true = Bool; messages.getArchivedStickers#57f17692 flags:# masks:flags.0?true offset_id:long limit:int = messages.ArchivedStickers; -messages.getMaskStickers#65b8c79f hash:int = messages.AllStickers; +messages.getMaskStickers#640f82b8 hash:long = messages.AllStickers; messages.getAttachedStickers#cc5b67cc media:InputStickeredMedia = Vector; messages.setGameScore#8ef8ecc0 flags:# edit_message:flags.0?true force:flags.1?true peer:InputPeer id:int user_id:InputUser score:int = Updates; messages.setInlineGameScore#15ad9f64 flags:# edit_message:flags.0?true force:flags.1?true id:InputBotInlineMessageID user_id:InputUser score:int = Bool; messages.getGameHighScores#e822649d peer:InputPeer id:int user_id:InputUser = messages.HighScores; messages.getInlineGameHighScores#f635e1b id:InputBotInlineMessageID user_id:InputUser = messages.HighScores; -messages.getCommonChats#d0a48c4 user_id:InputUser max_id:int limit:int = messages.Chats; -messages.getAllChats#eba80ff0 except_ids:Vector = messages.Chats; +messages.getCommonChats#e40ca104 user_id:InputUser max_id:long limit:int = messages.Chats; +messages.getAllChats#875f74be except_ids:Vector = messages.Chats; messages.getWebPage#32ca8f91 url:string hash:int = WebPage; messages.toggleDialogPin#a731e257 flags:# pinned:flags.0?true peer:InputDialogPeer = Bool; messages.reorderPinnedDialogs#3b1adf37 flags:# force:flags.0?true folder_id:int order:Vector = Bool; @@ -1466,14 +1470,14 @@ messages.setBotShippingResults#e5f672fa flags:# query_id:long error:flags.0?stri messages.setBotPrecheckoutResults#9c2dd95 flags:# success:flags.1?true query_id:long error:flags.0?string = Bool; messages.uploadMedia#519bc2b1 peer:InputPeer media:InputMedia = MessageMedia; messages.sendScreenshotNotification#c97df020 peer:InputPeer reply_to_msg_id:int random_id:long = Updates; -messages.getFavedStickers#21ce0b0e hash:int = messages.FavedStickers; +messages.getFavedStickers#4f1aaa9 hash:long = messages.FavedStickers; messages.faveSticker#b9ffc55b id:InputDocument unfave:Bool = Bool; messages.getUnreadMentions#46578472 peer:InputPeer offset_id:int add_offset:int limit:int max_id:int min_id:int = messages.Messages; messages.readMentions#f0189d3 peer:InputPeer = messages.AffectedHistory; -messages.getRecentLocations#bbc45b09 peer:InputPeer limit:int hash:int = messages.Messages; +messages.getRecentLocations#702a40e0 peer:InputPeer limit:int hash:long = messages.Messages; messages.sendMultiMedia#cc0110cb flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int multi_media:Vector schedule_date:flags.10?int = Updates; messages.uploadEncryptedFile#5057c497 peer:InputEncryptedChat file:InputEncryptedFile = EncryptedFile; -messages.searchStickerSets#c2b7d08b flags:# exclude_featured:flags.0?true q:string hash:int = messages.FoundStickerSets; +messages.searchStickerSets#35705b8a flags:# exclude_featured:flags.0?true q:string hash:long = messages.FoundStickerSets; messages.getSplitRanges#1cff7e08 = Vector; messages.markDialogUnread#c286d98f flags:# unread:flags.0?true peer:InputDialogPeer = Bool; messages.getDialogUnreadMarks#22e24e22 = Vector; @@ -1493,7 +1497,7 @@ messages.getSearchCounters#732eef00 peer:InputPeer filters:Vector = messages.Messages; messages.sendScheduledMessages#bd38850a peer:InputPeer id:Vector = Updates; messages.deleteScheduledMessages#59ae2b16 peer:InputPeer id:Vector = Updates; @@ -1503,12 +1507,12 @@ messages.getDialogFilters#f19ed96d = Vector; messages.getSuggestedDialogFilters#a29cd42c = Vector; messages.updateDialogFilter#1ad4a04a flags:# id:int filter:flags.0?DialogFilter = Bool; messages.updateDialogFiltersOrder#c563c1e4 order:Vector = Bool; -messages.getOldFeaturedStickers#5fe7025b offset:int limit:int hash:int = messages.FeaturedStickers; -messages.getReplies#24b581ba peer:InputPeer msg_id:int offset_id:int offset_date:int add_offset:int limit:int max_id:int min_id:int hash:int = messages.Messages; +messages.getOldFeaturedStickers#7ed094a1 offset:int limit:int hash:long = messages.FeaturedStickers; +messages.getReplies#22ddd30c peer:InputPeer msg_id:int offset_id:int offset_date:int add_offset:int limit:int max_id:int min_id:int hash:long = messages.Messages; messages.getDiscussionMessage#446972fd peer:InputPeer msg_id:int = messages.DiscussionMessage; messages.readDiscussion#f731a9f4 peer:InputPeer msg_id:int read_max_id:int = Bool; messages.unpinAllMessages#f025bc8b peer:InputPeer = messages.AffectedHistory; -messages.deleteChat#83247d11 chat_id:int = Bool; +messages.deleteChat#5bd0ee50 chat_id:long = Bool; messages.deletePhoneCallHistory#f9cbe409 flags:# revoke:flags.0?true = messages.AffectedFoundMessages; messages.checkHistoryImport#43fe19f3 import_head:string = messages.HistoryImportParsed; messages.initHistoryImport#34090c3b peer:InputPeer file:InputFile media_count:int = messages.HistoryImport; @@ -1524,6 +1528,7 @@ messages.getChatInviteImporters#26fb7289 peer:InputPeer link:string offset_date: messages.setHistoryTTL#b80e5fe4 peer:InputPeer period:int = Updates; messages.checkHistoryImportPeer#5dc60f03 peer:InputPeer = messages.CheckedHistoryImportPeer; messages.setChatTheme#e63be13f peer:InputPeer emoticon:string = Updates; +messages.getMessageReadParticipants#2c6f97b7 peer:InputPeer msg_id:int = Vector; updates.getState#edd4882a = updates.State; updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference; @@ -1571,7 +1576,7 @@ channels.deleteMessages#84c1fd4e channel:InputChannel id:Vector = messages. channels.deleteUserHistory#d10dd71b channel:InputChannel user_id:InputUser = messages.AffectedHistory; channels.reportSpam#fe087810 channel:InputChannel user_id:InputUser id:Vector = Bool; channels.getMessages#ad8c9a23 channel:InputChannel id:Vector = messages.Messages; -channels.getParticipants#123e05e9 channel:InputChannel filter:ChannelParticipantsFilter offset:int limit:int hash:int = channels.ChannelParticipants; +channels.getParticipants#77ced9d0 channel:InputChannel filter:ChannelParticipantsFilter offset:int limit:int hash:long = channels.ChannelParticipants; channels.getParticipant#a0ab6cc6 channel:InputChannel participant:InputPeer = channels.ChannelParticipant; channels.getChannels#a7f6bbb id:Vector = messages.Chats; channels.getFullChannel#8736a09 channel:InputChannel = messages.ChatFull; @@ -1671,4 +1676,4 @@ stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats; -// LAYER 132 +// LAYER 133 From c3953c18caa3364a2f11b2626349172fe93b1160 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 10 Sep 2021 20:57:06 +0200 Subject: [PATCH 0622/1185] Fix reading vectors of bare longs (#752) --- pyrogram/raw/core/primitives/vector.py | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/pyrogram/raw/core/primitives/vector.py b/pyrogram/raw/core/primitives/vector.py index 5b2f9ba6f5..bfcd1b282e 100644 --- a/pyrogram/raw/core/primitives/vector.py +++ b/pyrogram/raw/core/primitives/vector.py @@ -19,7 +19,7 @@ from io import BytesIO from typing import cast, Union, Any -from .int import Int +from .int import Int, Long from ..list import List from ..tl_object import TLObject @@ -30,19 +30,29 @@ class Vector(bytes, TLObject): # Method added to handle the special case when a query returns a bare Vector (of Ints); # i.e., RpcResult body starts with 0x1cb5c415 (Vector Id) - e.g., messages.GetMessagesViews. @staticmethod - def _read(b: BytesIO) -> Union[int, Any]: + def read_bare(b: BytesIO, n: int) -> Union[int, Any]: + left = len(b.read()) + size = left // n + b.seek(-left, 1) + try: return TLObject.read(b) - except ValueError: + except KeyError: b.seek(-4, 1) - return Int.read(b) + + if size == 4: + return Int.read(b) + else: + return Long.read(b) @classmethod def read(cls, data: BytesIO, t: Any = None, *args: Any) -> List: + count = Int.read(data) + return List( t.read(data) if t - else Vector._read(data) - for _ in range(Int.read(data)) + else Vector.read_bare(data, count) + for _ in range(count) ) def __new__(cls, value: list, t: Any = None) -> bytes: # type: ignore From acd92b100b2f38671bdd2466cbb9632ed76983c8 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 11 Sep 2021 14:32:05 +0200 Subject: [PATCH 0623/1185] Actually fix reading vectors of bare longs --- pyrogram/raw/core/primitives/vector.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/pyrogram/raw/core/primitives/vector.py b/pyrogram/raw/core/primitives/vector.py index bfcd1b282e..b46a75420f 100644 --- a/pyrogram/raw/core/primitives/vector.py +++ b/pyrogram/raw/core/primitives/vector.py @@ -30,11 +30,7 @@ class Vector(bytes, TLObject): # Method added to handle the special case when a query returns a bare Vector (of Ints); # i.e., RpcResult body starts with 0x1cb5c415 (Vector Id) - e.g., messages.GetMessagesViews. @staticmethod - def read_bare(b: BytesIO, n: int) -> Union[int, Any]: - left = len(b.read()) - size = left // n - b.seek(-left, 1) - + def read_bare(b: BytesIO, size: int) -> Union[int, Any]: try: return TLObject.read(b) except KeyError: @@ -48,10 +44,13 @@ def read_bare(b: BytesIO, n: int) -> Union[int, Any]: @classmethod def read(cls, data: BytesIO, t: Any = None, *args: Any) -> List: count = Int.read(data) + left = len(data.read()) + size = left // count + data.seek(-left, 1) return List( t.read(data) if t - else Vector.read_bare(data, count) + else Vector.read_bare(data, size) for _ in range(count) ) From fdbab8cc87ea6363e1c3358647abd758b5717e90 Mon Sep 17 00:00:00 2001 From: Davide Galilei <43778739+DavideGalilei@users.noreply.github.com> Date: Sat, 11 Sep 2021 14:52:34 +0200 Subject: [PATCH 0624/1185] Final fix for reading vectors of bare longs (#754) --- pyrogram/raw/core/primitives/vector.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/raw/core/primitives/vector.py b/pyrogram/raw/core/primitives/vector.py index b46a75420f..c51bbaf3f5 100644 --- a/pyrogram/raw/core/primitives/vector.py +++ b/pyrogram/raw/core/primitives/vector.py @@ -45,7 +45,7 @@ def read_bare(b: BytesIO, size: int) -> Union[int, Any]: def read(cls, data: BytesIO, t: Any = None, *args: Any) -> List: count = Int.read(data) left = len(data.read()) - size = left // count + size = (left // count) if count else 0 data.seek(-left, 1) return List( From 02a3969101729f8a34b26e4fcc315eac7ed63095 Mon Sep 17 00:00:00 2001 From: Alisson Lauffer Date: Tue, 14 Sep 2021 13:33:54 -0300 Subject: [PATCH 0625/1185] Fix Message.command when case is different (#757) * Fix Message.command when case is different * Update test_command.py Co-authored-by: Dan <14043624+delivrance@users.noreply.github.com> --- pyrogram/filters.py | 3 ++- tests/filters/test_command.py | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/pyrogram/filters.py b/pyrogram/filters.py index d969026be6..b6d27087e7 100644 --- a/pyrogram/filters.py +++ b/pyrogram/filters.py @@ -794,7 +794,8 @@ async def func(flt, client: pyrogram.Client, message: Message): flags=re.IGNORECASE if not flt.case_sensitive else 0): continue - without_command = re.sub(rf"{cmd}(?:@?{username})?\s?", "", without_prefix, count=1) + without_command = re.sub(rf"{cmd}(?:@?{username})?\s?", "", without_prefix, count=1, + flags=re.IGNORECASE if not flt.case_sensitive else 0) # match.groups are 1-indexed, group(1) is the quote, group(2) is the text # between the quotes, group(3) is unquoted, whitespace-split text diff --git a/tests/filters/test_command.py b/tests/filters/test_command.py index d9a7490e1a..19e21d9dd5 100644 --- a/tests/filters/test_command.py +++ b/tests/filters/test_command.py @@ -107,6 +107,10 @@ async def test_with_args(): await f(c, m) assert m.command == ["start"] + m = Message("/StArT") + await f(c, m) + assert m.command == ["start"] + m = Message("/start@username") await f(c, m) assert m.command == ["start"] From 09c82892597988d1ea7f8dd645cc9b8be300c226 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 15 Sep 2021 17:55:01 +0200 Subject: [PATCH 0626/1185] Update iter_dialogs.py Closes #749 #750 #756 --- pyrogram/methods/chats/iter_dialogs.py | 59 +++++++++++++++++--------- 1 file changed, 38 insertions(+), 21 deletions(-) diff --git a/pyrogram/methods/chats/iter_dialogs.py b/pyrogram/methods/chats/iter_dialogs.py index 2ee8860062..124eff52f1 100644 --- a/pyrogram/methods/chats/iter_dialogs.py +++ b/pyrogram/methods/chats/iter_dialogs.py @@ -18,15 +18,14 @@ from typing import AsyncGenerator, Optional -from pyrogram import types +from pyrogram import types, raw, utils from pyrogram.scaffold import Scaffold class IterDialogs(Scaffold): async def iter_dialogs( self, - limit: int = 0, - offset_date: int = 0 + limit: int = 0 ) -> Optional[AsyncGenerator["types.Dialog", None]]: """Iterate through a user's dialogs sequentially. @@ -39,10 +38,6 @@ async def iter_dialogs( 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:`~pyrogram.types.Dialog`. - Defaults to 0 (most recent dialog). - Returns: ``Generator``: A generator yielding :obj:`~pyrogram.types.Dialog` objects. @@ -57,28 +52,50 @@ async def iter_dialogs( total = limit or (1 << 31) - 1 limit = min(100, total) - pinned_dialogs = await self.get_dialogs( - pinned_only=True - ) + offset_date = 0 + offset_id = 0 + offset_peer = raw.types.InputPeerEmpty() - for dialog in pinned_dialogs: - yield dialog + while True: + r = (await self.send( + raw.functions.messages.GetDialogs( + offset_date=offset_date, + offset_id=offset_id, + offset_peer=offset_peer, + limit=limit, + hash=0 + ), + sleep_threshold=60 + )) - current += 1 + users = {i.id: i for i in r.users} + chats = {i.id: i for i in r.chats} - if current >= total: - return + messages = {} - while True: - dialogs = await self.get_dialogs( - offset_date=offset_date, - limit=limit - ) + for message in r.messages: + if isinstance(message, raw.types.MessageEmpty): + continue + + chat_id = utils.get_peer_id(message.peer_id) + messages[chat_id] = await types.Message._parse(self, message, users, chats) + + dialogs = [] + + for dialog in r.dialogs: + if not isinstance(dialog, raw.types.Dialog): + continue + + dialogs.append(types.Dialog._parse(self, dialog, messages, users, chats)) if not dialogs: return - offset_date = dialogs[-1].top_message.date + last = dialogs[-1] + + offset_id = last.top_message.message_id + offset_date = last.top_message.date + offset_peer = await self.resolve_peer(last.chat.id) for dialog in dialogs: yield dialog From 362441a74a2b0e1a88187a60be783279dc87ff33 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 25 Sep 2021 10:02:19 +0200 Subject: [PATCH 0627/1185] Update API schema to Layer 133 (patch) --- compiler/api/source/main_api.tl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl index 8b3e4964c5..3daf7da31a 100644 --- a/compiler/api/source/main_api.tl +++ b/compiler/api/source/main_api.tl @@ -445,7 +445,7 @@ sendMessageUploadRoundAction#243e1c66 progress:int = SendMessageAction; speakingInGroupCallAction#d92c2285 = SendMessageAction; sendMessageHistoryImportAction#dbda9246 progress:int = SendMessageAction; sendMessageChooseStickerAction#b05ac6b1 = SendMessageAction; -sendMessageEmojiInteraction#6a3233b6 emoticon:string interaction:DataJSON = SendMessageAction; +sendMessageEmojiInteraction#25972bcb emoticon:string msg_id:int interaction:DataJSON = SendMessageAction; sendMessageEmojiInteractionSeen#b665902e emoticon:string = SendMessageAction; contacts.found#b3134d9d my_results:Vector results:Vector chats:Vector users:Vector = contacts.Found; @@ -538,6 +538,7 @@ inputStickerSetID#9de7a269 id:long access_hash:long = InputStickerSet; inputStickerSetShortName#861cc8a0 short_name:string = InputStickerSet; inputStickerSetAnimatedEmoji#28703c8 = InputStickerSet; inputStickerSetDice#e67f520e emoticon:string = InputStickerSet; +inputStickerSetAnimatedEmojiAnimations#cde3739 = InputStickerSet; stickerSet#d7df217a flags:# archived:flags.1?true official:flags.2?true masks:flags.3?true animated:flags.5?true installed_date:flags.0?int id:long access_hash:long title:string short_name:string thumbs:flags.4?Vector thumb_dc_id:flags.4?int thumb_version:flags.4?int count:int hash:int = StickerSet; @@ -885,7 +886,6 @@ channelAdminLogEventActionExportedInviteRevoke#410a134e invite:ExportedChatInvit channelAdminLogEventActionExportedInviteEdit#e90ebb59 prev_invite:ExportedChatInvite new_invite:ExportedChatInvite = ChannelAdminLogEventAction; channelAdminLogEventActionParticipantVolume#3e7f6847 participant:GroupCallParticipant = ChannelAdminLogEventAction; channelAdminLogEventActionChangeHistoryTTL#6e941a38 prev_value:int new_value:int = ChannelAdminLogEventAction; -channelAdminLogEventActionChangeTheme#fe69018d prev_value:string new_value:string = ChannelAdminLogEventAction; channelAdminLogEvent#1fad68cd id:long date:int user_id:long action:ChannelAdminLogEventAction = ChannelAdminLogEvent; From 428cbf56a6b0b77dbe1f16aa301c9d5ecb02c058 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 25 Sep 2021 10:12:12 +0200 Subject: [PATCH 0628/1185] Read integers first when size matches --- pyrogram/raw/core/primitives/vector.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/pyrogram/raw/core/primitives/vector.py b/pyrogram/raw/core/primitives/vector.py index c51bbaf3f5..48b2eed91c 100644 --- a/pyrogram/raw/core/primitives/vector.py +++ b/pyrogram/raw/core/primitives/vector.py @@ -31,21 +31,19 @@ class Vector(bytes, TLObject): # i.e., RpcResult body starts with 0x1cb5c415 (Vector Id) - e.g., messages.GetMessagesViews. @staticmethod def read_bare(b: BytesIO, size: int) -> Union[int, Any]: - try: - return TLObject.read(b) - except KeyError: - b.seek(-4, 1) + if size == 4: + return Int.read(b) + + if size == 8: + return Long.read(b) - if size == 4: - return Int.read(b) - else: - return Long.read(b) + return TLObject.read(b) @classmethod def read(cls, data: BytesIO, t: Any = None, *args: Any) -> List: count = Int.read(data) left = len(data.read()) - size = (left // count) if count else 0 + size = (left / count) if count else 0 data.seek(-left, 1) return List( From 6efd01937fa89441d381b4ad98b839268ca2d149 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 6 Oct 2021 17:12:49 +0200 Subject: [PATCH 0629/1185] Fix issues with Python 3.10 (#766) --- pyrogram/types/messages_and_media/sticker.py | 27 +++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/pyrogram/types/messages_and_media/sticker.py b/pyrogram/types/messages_and_media/sticker.py index 87dd2f7e41..5b63a2fe06 100644 --- a/pyrogram/types/messages_and_media/sticker.py +++ b/pyrogram/types/messages_and_media/sticker.py @@ -18,8 +18,6 @@ from typing import List -from async_lru import alru_cache - import pyrogram from pyrogram import raw from pyrogram import types @@ -105,18 +103,35 @@ def __init__( self.thumbs = thumbs # self.mask_position = mask_position + cache = {} + @staticmethod - @alru_cache(maxsize=256) async def _get_sticker_set_name(send, input_sticker_set_id): try: - return (await send( + set_id = input_sticker_set_id[0] + set_access_hash = input_sticker_set_id[1] + + name = Sticker.cache.get((set_id, set_access_hash), None) + + if name is not None: + return name + + name = (await send( raw.functions.messages.GetStickerSet( stickerset=raw.types.InputStickerSetID( - id=input_sticker_set_id[0], - access_hash=input_sticker_set_id[1] + id=set_id, + access_hash=set_access_hash ) ) )).set.short_name + + Sticker.cache[(set_id, set_access_hash)] = name + + if len(Sticker.cache) > 250: + for i in range(50): + Sticker.cache.pop(next(iter(Sticker.cache))) + + return name except StickersetInvalid: return None From 0f204e1d8c6260b0d70edd383cfb34941e60bd1c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 6 Oct 2021 17:13:30 +0200 Subject: [PATCH 0630/1185] Update requirements.txt --- requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 72ed1aa51a..bf6641539c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,2 @@ pyaes==1.6.1 pysocks==1.7.1 -async_lru==1.0.2 \ No newline at end of file From 2265d282f5efc1c06ee230b294d43cbc9f22cd8f Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 11 Oct 2021 14:27:31 +0200 Subject: [PATCH 0631/1185] Add search_global_count and search_messages_count methods (#768) --- pyrogram/methods/messages/__init__.py | 8 +- .../methods/messages/search_global_count.py | 82 ++++++++++++++ .../methods/messages/search_messages_count.py | 107 ++++++++++++++++++ 3 files changed, 195 insertions(+), 2 deletions(-) create mode 100644 pyrogram/methods/messages/search_global_count.py create mode 100644 pyrogram/methods/messages/search_messages_count.py diff --git a/pyrogram/methods/messages/__init__.py b/pyrogram/methods/messages/__init__.py index 5ebedfde28..45cccb7e4e 100644 --- a/pyrogram/methods/messages/__init__.py +++ b/pyrogram/methods/messages/__init__.py @@ -16,8 +16,8 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from .copy_message import CopyMessage from .copy_media_group import CopyMediaGroup +from .copy_message import CopyMessage from .delete_messages import DeleteMessages from .download_media import DownloadMedia from .edit_inline_caption import EditInlineCaption @@ -37,7 +37,9 @@ from .read_history import ReadHistory from .retract_vote import RetractVote from .search_global import SearchGlobal +from .search_global_count import SearchGlobalCount from .search_messages import SearchMessages +from .search_messages_count import SearchMessagesCount from .send_animation import SendAnimation from .send_audio import SendAudio from .send_cached_media import SendCachedMedia @@ -100,6 +102,8 @@ class Messages( SearchMessages, SearchGlobal, CopyMessage, - CopyMediaGroup + CopyMediaGroup, + SearchMessagesCount, + SearchGlobalCount ): pass diff --git a/pyrogram/methods/messages/search_global_count.py b/pyrogram/methods/messages/search_global_count.py new file mode 100644 index 0000000000..113ef81362 --- /dev/null +++ b/pyrogram/methods/messages/search_global_count.py @@ -0,0 +1,82 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan +# +# 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 pyrogram import raw +from pyrogram.scaffold import Scaffold +from .search_messages import Filters, POSSIBLE_VALUES + + +class SearchGlobalCount(Scaffold): + async def search_global_count( + self, + query: str = "", + filter: str = "empty", + ) -> int: + """Get the count of messages resulting from a global search. + + If you want to get the actual messages, see :meth:`~pyrogram.Client.search_global`. + + Parameters: + query (``str``, *optional*): + Text query string. + Use "@" to search for mentions. + + filter (``str``, *optional*): + Pass a filter in order to search for specific kind of messages only: + + - ``"empty"``: Search for all kind of messages (default). + - ``"photo"``: Search for photos. + - ``"video"``: Search for video. + - ``"photo_video"``: Search for either photo or video. + - ``"document"``: Search for documents (generic files). + - ``"url"``: Search for messages containing URLs (web links). + - ``"animation"``: Search for animations (GIFs). + - ``"voice_note"``: Search for voice notes. + - ``"audio"``: Search for audio files (music). + - ``"chat_photo"``: Search for chat photos. + - ``"audio_video_note"``: Search for either audio or video notes. + - ``"video_note"``: Search for video notes. + - ``"location"``: Search for location messages. + - ``"contact"``: Search for contact messages. + + Returns: + ``int``: On success, the messages count is returned. + """ + try: + filter = Filters.__dict__[filter.upper()] + except KeyError: + raise ValueError('Invalid filter "{}". Possible values are: {}'.format( + filter, ", ".join(f'"{v}"' for v in POSSIBLE_VALUES))) from None + + r = await self.send( + raw.functions.messages.SearchGlobal( + q=query, + filter=filter, + min_date=0, + max_date=0, + offset_rate=0, + offset_peer=raw.types.InputPeerEmpty(), + offset_id=0, + limit=1 + ) + ) + + if hasattr(r, "count"): + return r.count + else: + return len(r.messages) diff --git a/pyrogram/methods/messages/search_messages_count.py b/pyrogram/methods/messages/search_messages_count.py new file mode 100644 index 0000000000..02e0c80366 --- /dev/null +++ b/pyrogram/methods/messages/search_messages_count.py @@ -0,0 +1,107 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan +# +# 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 Union + +from pyrogram import raw +from pyrogram.scaffold import Scaffold +from .search_messages import Filters, POSSIBLE_VALUES + + +class SearchMessagesCount(Scaffold): + async def search_messages_count( + self, + chat_id: Union[int, str], + query: str = "", + filter: str = "empty", + from_user: Union[int, str] = None + ) -> int: + """Get the count of messages resulting from a search inside a chat. + + If you want to get the actual messages, see :meth:`~pyrogram.Client.search_messages`. + + Parameters: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + For your personal cloud (Saved Messages) you can simply use "me" or "self". + For a contact that exists in your Telegram address book you can use his phone number (str). + + query (``str``, *optional*): + Text query string. + Required for text-only messages, optional for media messages (see the ``filter`` argument). + When passed while searching for media messages, the query will be applied to captions. + Defaults to "" (empty string). + + filter (``str``, *optional*): + Pass a filter in order to search for specific kind of messages only: + + - ``"empty"``: Search for all kind of messages (default). + - ``"photo"``: Search for photos. + - ``"video"``: Search for video. + - ``"photo_video"``: Search for either photo or video. + - ``"document"``: Search for documents (generic files). + - ``"url"``: Search for messages containing URLs (web links). + - ``"animation"``: Search for animations (GIFs). + - ``"voice_note"``: Search for voice notes. + - ``"audio"``: Search for audio files (music). + - ``"chat_photo"``: Search for chat photos. + - ``"phone_call"``: Search for phone calls. + - ``"audio_video_note"``: Search for either audio or video notes. + - ``"video_note"``: Search for video notes. + - ``"mention"``: Search for messages containing mentions to yourself. + - ``"location"``: Search for location messages. + - ``"contact"``: Search for contact messages. + - ``"pinned"``: Search for pinned messages. + + from_user (``int`` | ``str``, *optional*): + Unique identifier (int) or username (str) of the target user you want to search for messages from. + + Returns: + ``int``: On success, the messages count is returned. + """ + try: + filter = Filters.__dict__[filter.upper()] + except KeyError: + raise ValueError('Invalid filter "{}". Possible values are: {}'.format( + filter, ", ".join(f'"{v}"' for v in POSSIBLE_VALUES))) from None + + r = await self.send( + raw.functions.messages.Search( + peer=await self.resolve_peer(chat_id), + q=query, + filter=filter, + min_date=0, + max_date=0, + offset_id=0, + add_offset=0, + limit=1, + min_id=0, + max_id=0, + from_id=( + await self.resolve_peer(from_user) + if from_user + else None + ), + hash=0 + ) + ) + + if hasattr(r, "count"): + return r.count + else: + return len(r.messages) From e27f30dade75b7acc62a95e8b508a065a7aa036f Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 11 Oct 2021 14:31:00 +0200 Subject: [PATCH 0632/1185] Enable Actions on Python 3.10 --- .github/workflows/python.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 5bece5fc7a..77cea3d8f6 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -1,6 +1,6 @@ name: Pyrogram -on: [ push, pull_request ] +on: [push, pull_request] jobs: build: @@ -8,8 +8,8 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ ubuntu-latest, macos-latest, windows-latest ] - python-version: [ 3.6, 3.7, 3.8, 3.9 ] + os: [ubuntu-latest, macos-latest, windows-latest] + python-version: [3.6, 3.7, 3.8, 3.9, 3.10] steps: - uses: actions/checkout@v2 From aba5304fd69d913176b80b818b9c334775036c62 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 11 Oct 2021 14:34:05 +0200 Subject: [PATCH 0633/1185] Fix Actions on Python 3.10 --- .github/workflows/python.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 77cea3d8f6..89ed8532ce 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -9,7 +9,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] - python-version: [3.6, 3.7, 3.8, 3.9, 3.10] + python-version: [3.6, 3.7, 3.8, 3.9, "3.10"] steps: - uses: actions/checkout@v2 From a460d12a824ff61f1c96842b13f1fdd2ab0a7419 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 10 Nov 2021 10:23:10 +0100 Subject: [PATCH 0634/1185] Update API schema to Layer 134 --- compiler/api/source/main_api.tl | 53 +++++++++++++++++++-------------- 1 file changed, 31 insertions(+), 22 deletions(-) diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl index 3daf7da31a..df250439e5 100644 --- a/compiler/api/source/main_api.tl +++ b/compiler/api/source/main_api.tl @@ -106,8 +106,8 @@ chatForbidden#6592a1a7 id:long title:string = Chat; channel#8261ac61 flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true fake:flags.25?true gigagroup:flags.26?true id:long access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int restriction_reason:flags.9?Vector admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int = Chat; channelForbidden#17d493d5 flags:# broadcast:flags.5?true megagroup:flags.8?true id:long access_hash:long title:string until_date:flags.16?int = Chat; -chatFull#4dbdc099 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:long about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer theme_emoticon:flags.16?string = ChatFull; -channelFull#e9b27a17 flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string = ChatFull; +chatFull#46a6ffb4 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:long about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer theme_emoticon:flags.16?string requests_pending:flags.17?int recent_requesters:flags.17?Vector = ChatFull; +channelFull#59cff963 flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector = ChatFull; chatParticipant#c02d4007 user_id:long inviter_id:long date:int = ChatParticipant; chatParticipantCreator#e46bcee4 user_id:long = ChatParticipant; @@ -166,6 +166,7 @@ messageActionInviteToGroupCall#502f92f7 call:InputGroupCall users:Vector = messageActionSetMessagesTTL#aa1afbfd period:int = MessageAction; messageActionGroupCallScheduled#b3a07661 call:InputGroupCall schedule_date:int = MessageAction; messageActionSetChatTheme#aa786345 emoticon:string = MessageAction; +messageActionChatJoinedByRequest#ebbca3cb = MessageAction; dialog#2c171f72 flags:# pinned:flags.2?true unread_mark:flags.3?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int = Dialog; dialogFolder#71bd134c flags:# pinned:flags.2?true folder:Folder peer:Peer top_message:int unread_muted_peers_count:int unread_unmuted_peers_count:int unread_muted_messages_count:int unread_unmuted_messages_count:int = Dialog; @@ -356,6 +357,8 @@ updateChannelParticipant#985d3abb flags:# channel_id:long date:int actor_id:long updateBotStopped#c4870a49 user_id:long date:int stopped:Bool qts:int = Update; updateGroupCallConnection#b783982 flags:# presentation:flags.0?true params:DataJSON = Update; updateBotCommands#4d712f2e peer:Peer bot_id:long commands:Vector = Update; +updatePendingJoinRequests#7063c3db peer:Peer requests_pending:int recent_requesters:Vector = Update; +updateBotChatInviteRequester#11dfa986 peer:Peer date:int user_id:long about:string invite:ExportedChatInvite qts:int = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -527,10 +530,10 @@ auth.passwordRecovery#137948a5 email_pattern:string = auth.PasswordRecovery; receivedNotifyMessage#a384b779 id:int flags:int = ReceivedNotifyMessage; -chatInviteExported#b18105e8 flags:# revoked:flags.0?true permanent:flags.5?true link:string admin_id:long date:int start_date:flags.4?int expire_date:flags.1?int usage_limit:flags.2?int usage:flags.3?int = ExportedChatInvite; +chatInviteExported#ab4a819 flags:# revoked:flags.0?true permanent:flags.5?true request_needed:flags.6?true link:string admin_id:long date:int start_date:flags.4?int expire_date:flags.1?int usage_limit:flags.2?int usage:flags.3?int requested:flags.7?int title:flags.8?string = ExportedChatInvite; chatInviteAlready#5a686d7c chat:Chat = ChatInvite; -chatInvite#dfc2f58e flags:# channel:flags.0?true broadcast:flags.1?true public:flags.2?true megagroup:flags.3?true title:string photo:Photo participants_count:int participants:flags.4?Vector = ChatInvite; +chatInvite#300c44c1 flags:# channel:flags.0?true broadcast:flags.1?true public:flags.2?true megagroup:flags.3?true request_needed:flags.6?true title:string about:flags.5?string photo:Photo participants_count:int participants:flags.4?Vector = ChatInvite; chatInvitePeek#61695cb0 chat:Chat expires:int = ChatInvite; inputStickerSetEmpty#ffb62b95 = InputStickerSet; @@ -603,7 +606,7 @@ channelMessagesFilterEmpty#94d42ee7 = ChannelMessagesFilter; channelMessagesFilter#cd77d957 flags:# exclude_new_messages:flags.1?true ranges:Vector = ChannelMessagesFilter; channelParticipant#c00c07c0 user_id:long date:int = ChannelParticipant; -channelParticipantSelf#28a8bc67 user_id:long inviter_id:long date:int = ChannelParticipant; +channelParticipantSelf#35a8bfa7 flags:# via_invite:flags.0?true user_id:long inviter_id:long date:int = ChannelParticipant; channelParticipantCreator#2fe601d3 flags:# user_id:long admin_rights:ChatAdminRights rank:flags.0?string = ChannelParticipant; channelParticipantAdmin#34c3bb53 flags:# can_edit:flags.0?true self:flags.1?true user_id:long inviter_id:flags.1?long promoted_by:long date:int admin_rights:ChatAdminRights rank:flags.2?string = ChannelParticipant; channelParticipantBanned#6df8014e flags:# left:flags.0?true peer:Peer kicked_by:long date:int banned_rights:ChatBannedRights = ChannelParticipant; @@ -886,6 +889,7 @@ channelAdminLogEventActionExportedInviteRevoke#410a134e invite:ExportedChatInvit channelAdminLogEventActionExportedInviteEdit#e90ebb59 prev_invite:ExportedChatInvite new_invite:ExportedChatInvite = ChannelAdminLogEventAction; channelAdminLogEventActionParticipantVolume#3e7f6847 participant:GroupCallParticipant = ChannelAdminLogEventAction; channelAdminLogEventActionChangeHistoryTTL#6e941a38 prev_value:int new_value:int = ChannelAdminLogEventAction; +channelAdminLogEventActionParticipantJoinByRequest#afb6144a invite:ExportedChatInvite approved_by:long = ChannelAdminLogEventAction; channelAdminLogEvent#1fad68cd id:long date:int user_id:long action:ChannelAdminLogEventAction = ChannelAdminLogEvent; @@ -1100,7 +1104,7 @@ restrictionReason#d072acb4 platform:string reason:string text:string = Restricti inputTheme#3c5693e9 id:long access_hash:long = InputTheme; inputThemeSlug#f5890df1 slug:string = InputTheme; -theme#e802b8dc flags:# creator:flags.0?true default:flags.1?true for_chat:flags.5?true id:long access_hash:long slug:string title:string document:flags.2?Document settings:flags.3?ThemeSettings installs_count:flags.4?int = Theme; +theme#a00e67d6 flags:# creator:flags.0?true default:flags.1?true for_chat:flags.5?true id:long access_hash:long slug:string title:string document:flags.2?Document settings:flags.3?Vector emoticon:flags.6?string installs_count:flags.4?int = Theme; account.themesNotModified#f41eb622 = account.Themes; account.themes#9a3d8c6d hash:long themes:Vector = account.Themes; @@ -1212,7 +1216,7 @@ messages.historyImportParsed#5e0fb7b9 flags:# pm:flags.0?true group:flags.1?true messages.affectedFoundMessages#ef8d3e6c pts:int pts_count:int offset:int messages:Vector = messages.AffectedFoundMessages; -chatInviteImporter#b5cd5f4 user_id:long date:int = ChatInviteImporter; +chatInviteImporter#8c5adfd9 flags:# requested:flags.0?true user_id:long date:int about:flags.2?string approved_by:flags.1?long = ChatInviteImporter; messages.exportedChatInvites#bdc62dcc count:int invites:Vector users:Vector = messages.ExportedChatInvites; @@ -1249,14 +1253,17 @@ account.resetPasswordFailedWait#e3779861 retry_date:int = account.ResetPasswordR account.resetPasswordRequestedWait#e9effc7d until_date:int = account.ResetPasswordResult; account.resetPasswordOk#e926d63e = account.ResetPasswordResult; -chatTheme#ed0b5c33 emoticon:string theme:Theme dark_theme:Theme = ChatTheme; +sponsoredMessage#d151e19a flags:# random_id:bytes from_id:Peer channel_post:flags.2?int start_param:flags.0?string message:string entities:flags.1?Vector = SponsoredMessage; -account.chatThemesNotModified#e011e1c4 = account.ChatThemes; -account.chatThemes#fe4cbebd hash:int themes:Vector = account.ChatThemes; +messages.sponsoredMessages#65a4c7d5 messages:Vector chats:Vector users:Vector = messages.SponsoredMessages; -sponsoredMessage#2a3c381f flags:# random_id:bytes from_id:Peer start_param:flags.0?string message:string entities:flags.1?Vector = SponsoredMessage; +searchResultsCalendarPeriod#c9b0539f date:int min_msg_id:int max_msg_id:int count:int = SearchResultsCalendarPeriod; -messages.sponsoredMessages#65a4c7d5 messages:Vector chats:Vector users:Vector = messages.SponsoredMessages; +messages.searchResultsCalendar#147ee23c flags:# inexact:flags.0?true count:int min_date:int min_msg_id:int offset_id_offset:flags.1?int periods:Vector messages:Vector chats:Vector users:Vector = messages.SearchResultsCalendar; + +searchResultPosition#7f648b67 msg_id:int date:int offset:int = SearchResultsPosition; + +messages.searchResultsPositions#53b22baf count:int positions:Vector = messages.SearchResultsPositions; ---functions--- @@ -1344,10 +1351,10 @@ account.resetWallPapers#bb3b9804 = Bool; account.getAutoDownloadSettings#56da0b3f = account.AutoDownloadSettings; account.saveAutoDownloadSettings#76f36233 flags:# low:flags.0?true high:flags.1?true settings:AutoDownloadSettings = Bool; account.uploadTheme#1c3db333 flags:# file:InputFile thumb:flags.0?InputFile file_name:string mime_type:string = Document; -account.createTheme#8432c21f flags:# slug:string title:string document:flags.2?InputDocument settings:flags.3?InputThemeSettings = Theme; -account.updateTheme#5cb367d5 flags:# format:string theme:InputTheme slug:flags.0?string title:flags.1?string document:flags.2?InputDocument settings:flags.3?InputThemeSettings = Theme; +account.createTheme#652e4400 flags:# slug:string title:string document:flags.2?InputDocument settings:flags.3?Vector = Theme; +account.updateTheme#2bf40ccc flags:# format:string theme:InputTheme slug:flags.0?string title:flags.1?string document:flags.2?InputDocument settings:flags.3?Vector = Theme; account.saveTheme#f257106c theme:InputTheme unsave:Bool = Bool; -account.installTheme#7ae43737 flags:# dark:flags.0?true format:flags.1?string theme:flags.1?InputTheme = Bool; +account.installTheme#c727bb3b flags:# dark:flags.0?true theme:flags.1?InputTheme format:flags.2?string base_theme:flags.3?BaseTheme = Bool; account.getTheme#8d9d742b format:string theme:InputTheme document_id:long = Theme; account.getThemes#7206e458 format:string hash:long = account.Themes; account.setContentSettings#b574b16b flags:# sensitive_enabled:flags.0?true = Bool; @@ -1358,7 +1365,7 @@ account.setGlobalPrivacySettings#1edaaac2 settings:GlobalPrivacySettings = Globa account.reportProfilePhoto#fa8cc6f5 peer:InputPeer photo_id:InputPhoto reason:ReportReason message:string = Bool; account.resetPassword#9308ce1b = account.ResetPasswordResult; account.declinePasswordReset#4c9409f6 = Bool; -account.getChatThemes#d6d71d7b hash:int = account.ChatThemes; +account.getChatThemes#d638de89 hash:long = account.Themes; users.getUsers#d91a548 id:Vector = Vector; users.getFullUser#ca30a5b1 id:InputUser = UserFull; @@ -1390,7 +1397,7 @@ messages.getDialogs#a0f4cb4f flags:# exclude_pinned:flags.0?true folder_id:flags messages.getHistory#4423e6c5 peer:InputPeer offset_id:int offset_date:int add_offset:int limit:int max_id:int min_id:int hash:long = messages.Messages; messages.search#a0fda762 flags:# peer:InputPeer q:string from_id:flags.0?InputPeer top_msg_id:flags.1?int filter:MessagesFilter min_date:int max_date:int offset_id:int add_offset:int limit:int max_id:int min_id:int hash:long = messages.Messages; messages.readHistory#e306d3a peer:InputPeer max_id:int = messages.AffectedMessages; -messages.deleteHistory#1c015b09 flags:# just_clear:flags.0?true revoke:flags.1?true peer:InputPeer max_id:int = messages.AffectedHistory; +messages.deleteHistory#b08f922a flags:# just_clear:flags.0?true revoke:flags.1?true peer:InputPeer max_id:int min_date:flags.2?int max_date:flags.3?int = messages.AffectedHistory; messages.deleteMessages#e58e95d2 flags:# revoke:flags.0?true id:Vector = messages.AffectedMessages; messages.receivedMessages#5a954c0 max_id:int = Vector; messages.setTyping#58943ee2 flags:# peer:InputPeer top_msg_id:flags.0?int action:SendMessageAction = Bool; @@ -1422,7 +1429,7 @@ messages.readMessageContents#36a73f77 id:Vector = messages.AffectedMessages messages.getStickers#d5a5d3a1 emoticon:string hash:long = messages.Stickers; messages.getAllStickers#b8a0a1a8 hash:long = messages.AllStickers; messages.getWebPagePreview#8b68b0cc flags:# message:string entities:flags.3?Vector = MessageMedia; -messages.exportChatInvite#14b9bcd7 flags:# legacy_revoke_permanent:flags.2?true peer:InputPeer expire_date:flags.0?int usage_limit:flags.1?int = ExportedChatInvite; +messages.exportChatInvite#a02ce5d5 flags:# legacy_revoke_permanent:flags.2?true request_needed:flags.3?true peer:InputPeer expire_date:flags.0?int usage_limit:flags.1?int title:flags.4?string = ExportedChatInvite; messages.checkChatInvite#3eadb1bb hash:string = ChatInvite; messages.importChatInvite#6c50051c hash:string = Updates; messages.getStickerSet#2619a90e stickerset:InputStickerSet = messages.StickerSet; @@ -1486,7 +1493,6 @@ messages.updatePinnedMessage#d2aaf7ec flags:# silent:flags.0?true unpin:flags.1? messages.sendVote#10ea6184 peer:InputPeer msg_id:int options:Vector = Updates; messages.getPollResults#73bb643b peer:InputPeer msg_id:int = Updates; messages.getOnlines#6e2be050 peer:InputPeer = ChatOnlines; -messages.getStatsURL#812c2ae6 flags:# dark:flags.0?true peer:InputPeer params:string = StatsURL; messages.editChatAbout#def60797 peer:InputPeer about:string = Bool; messages.editChatDefaultBannedRights#a5866b41 peer:InputPeer banned_rights:ChatBannedRights = Updates; messages.getEmojiKeywords#35a0e062 lang_code:string = EmojiKeywordsDifference; @@ -1520,15 +1526,18 @@ messages.uploadImportedMedia#2a862092 peer:InputPeer import_id:long file_name:st messages.startHistoryImport#b43df344 peer:InputPeer import_id:long = Bool; messages.getExportedChatInvites#a2b5a3f6 flags:# revoked:flags.3?true peer:InputPeer admin_id:InputUser offset_date:flags.2?int offset_link:flags.2?string limit:int = messages.ExportedChatInvites; messages.getExportedChatInvite#73746f5c peer:InputPeer link:string = messages.ExportedChatInvite; -messages.editExportedChatInvite#2e4ffbe flags:# revoked:flags.2?true peer:InputPeer link:string expire_date:flags.0?int usage_limit:flags.1?int = messages.ExportedChatInvite; +messages.editExportedChatInvite#bdca2f75 flags:# revoked:flags.2?true peer:InputPeer link:string expire_date:flags.0?int usage_limit:flags.1?int request_needed:flags.3?Bool title:flags.4?string = messages.ExportedChatInvite; messages.deleteRevokedExportedChatInvites#56987bd5 peer:InputPeer admin_id:InputUser = Bool; messages.deleteExportedChatInvite#d464a42b peer:InputPeer link:string = Bool; messages.getAdminsWithInvites#3920e6ef peer:InputPeer = messages.ChatAdminsWithInvites; -messages.getChatInviteImporters#26fb7289 peer:InputPeer link:string offset_date:int offset_user:InputUser limit:int = messages.ChatInviteImporters; +messages.getChatInviteImporters#df04dd4e flags:# requested:flags.0?true peer:InputPeer link:flags.1?string q:flags.2?string offset_date:int offset_user:InputUser limit:int = messages.ChatInviteImporters; messages.setHistoryTTL#b80e5fe4 peer:InputPeer period:int = Updates; messages.checkHistoryImportPeer#5dc60f03 peer:InputPeer = messages.CheckedHistoryImportPeer; messages.setChatTheme#e63be13f peer:InputPeer emoticon:string = Updates; messages.getMessageReadParticipants#2c6f97b7 peer:InputPeer msg_id:int = Vector; +messages.getSearchResultsCalendar#49f0bde9 peer:InputPeer filter:MessagesFilter offset_id:int offset_date:int = messages.SearchResultsCalendar; +messages.getSearchResultsPositions#6e9583a3 peer:InputPeer filter:MessagesFilter offset_id:int limit:int = messages.SearchResultsPositions; +messages.hideChatJoinRequest#7fe7e815 flags:# approved:flags.0?true peer:InputPeer user_id:InputUser = Updates; updates.getState#edd4882a = updates.State; updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference; @@ -1676,4 +1685,4 @@ stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats; -// LAYER 133 +// LAYER 134 \ No newline at end of file From ca29ea1db43b024015971d2e23a4da6ec916b747 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 26 Nov 2021 18:03:17 +0100 Subject: [PATCH 0635/1185] Update API schema to Layer 135 --- compiler/api/source/main_api.tl | 69 +++++++++++++++++++++------------ 1 file changed, 45 insertions(+), 24 deletions(-) diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl index df250439e5..bc5b1bf365 100644 --- a/compiler/api/source/main_api.tl +++ b/compiler/api/source/main_api.tl @@ -101,13 +101,13 @@ userStatusLastWeek#7bf09fc = UserStatus; userStatusLastMonth#77ebc742 = UserStatus; chatEmpty#29562865 id:long = Chat; -chat#41cbf256 flags:# creator:flags.0?true kicked:flags.1?true left:flags.2?true deactivated:flags.5?true call_active:flags.23?true call_not_empty:flags.24?true id:long title:string photo:ChatPhoto participants_count:int date:int version:int migrated_to:flags.6?InputChannel admin_rights:flags.14?ChatAdminRights default_banned_rights:flags.18?ChatBannedRights = Chat; +chat#41cbf256 flags:# creator:flags.0?true kicked:flags.1?true left:flags.2?true deactivated:flags.5?true call_active:flags.23?true call_not_empty:flags.24?true noforwards:flags.25?true id:long title:string photo:ChatPhoto participants_count:int date:int version:int migrated_to:flags.6?InputChannel admin_rights:flags.14?ChatAdminRights default_banned_rights:flags.18?ChatBannedRights = Chat; chatForbidden#6592a1a7 id:long title:string = Chat; -channel#8261ac61 flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true fake:flags.25?true gigagroup:flags.26?true id:long access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int restriction_reason:flags.9?Vector admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int = Chat; +channel#8261ac61 flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true fake:flags.25?true gigagroup:flags.26?true noforwards:flags.27?true id:long access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int restriction_reason:flags.9?Vector admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int = Chat; channelForbidden#17d493d5 flags:# broadcast:flags.5?true megagroup:flags.8?true id:long access_hash:long title:string until_date:flags.16?int = Chat; chatFull#46a6ffb4 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:long about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer theme_emoticon:flags.16?string requests_pending:flags.17?int recent_requesters:flags.17?Vector = ChatFull; -channelFull#59cff963 flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector = ChatFull; +channelFull#56662e2e flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector default_send_as:flags.29?Peer = ChatFull; chatParticipant#c02d4007 user_id:long inviter_id:long date:int = ChatParticipant; chatParticipantCreator#e46bcee4 user_id:long = ChatParticipant; @@ -120,7 +120,7 @@ chatPhotoEmpty#37c1011c = ChatPhoto; chatPhoto#1c6e1c11 flags:# has_video:flags.0?true photo_id:long stripped_thumb:flags.1?bytes dc_id:int = ChatPhoto; messageEmpty#90a6ca84 flags:# id:int peer_id:flags.0?Peer = Message; -message#85d6cbe2 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true id:int from_id:flags.8?Peer peer_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?long reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long restriction_reason:flags.22?Vector ttl_period:flags.25?int = Message; +message#85d6cbe2 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true noforwards:flags.26?true id:int from_id:flags.8?Peer peer_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?long reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long restriction_reason:flags.22?Vector ttl_period:flags.25?int = Message; messageService#2b085862 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true legacy:flags.19?true id:int from_id:flags.8?Peer peer_id:Peer reply_to:flags.3?MessageReplyHeader date:int action:MessageAction ttl_period:flags.25?int = Message; messageMediaEmpty#3ded6320 = MessageMedia; @@ -186,7 +186,7 @@ geoPoint#b2a2f663 flags:# long:double lat:double access_hash:long accuracy_radiu auth.sentCode#5e002502 flags:# type:auth.SentCodeType phone_code_hash:string next_type:flags.1?auth.CodeType timeout:flags.2?int = auth.SentCode; -auth.authorization#cd050916 flags:# tmp_sessions:flags.0?int user:User = auth.Authorization; +auth.authorization#33fb7bb8 flags:# setup_password_required:flags.1?true otherwise_relogin_days:flags.1?int tmp_sessions:flags.0?int user:User = auth.Authorization; auth.authorizationSignUpRequired#44747e9a flags:# terms_of_service:flags.0?help.TermsOfService = auth.Authorization; auth.exportedAuthorization#b434e2b8 id:long bytes:bytes = auth.ExportedAuthorization; @@ -200,7 +200,7 @@ inputPeerNotifySettings#9c3d198e flags:# show_previews:flags.0?Bool silent:flags peerNotifySettings#af509d20 flags:# show_previews:flags.0?Bool silent:flags.1?Bool mute_until:flags.2?int sound:flags.3?string = PeerNotifySettings; -peerSettings#733f2961 flags:# report_spam:flags.0?true add_contact:flags.1?true block_contact:flags.2?true share_contact:flags.3?true need_contacts_exception:flags.4?true report_geo:flags.5?true autoarchived:flags.7?true invite_members:flags.8?true geo_distance:flags.6?int = PeerSettings; +peerSettings#a518110d flags:# report_spam:flags.0?true add_contact:flags.1?true block_contact:flags.2?true share_contact:flags.3?true need_contacts_exception:flags.4?true report_geo:flags.5?true autoarchived:flags.7?true invite_members:flags.8?true request_chat_broadcast:flags.10?true geo_distance:flags.6?int request_chat_title:flags.9?string request_chat_date:flags.9?int = PeerSettings; wallPaper#a437c3ed id:long flags:# creator:flags.0?true default:flags.1?true pattern:flags.3?true dark:flags.4?true access_hash:long slug:string document:Document settings:flags.2?WallPaperSettings = WallPaper; wallPaperNoFile#e0804116 id:long flags:# default:flags.1?true dark:flags.4?true settings:flags.2?WallPaperSettings = WallPaper; @@ -214,7 +214,7 @@ inputReportReasonCopyright#9b89f93a = ReportReason; inputReportReasonGeoIrrelevant#dbd4feed = ReportReason; inputReportReasonFake#f5ddd6e7 = ReportReason; -userFull#d697ff05 flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true user:User about:flags.1?string settings:PeerSettings profile_photo:flags.2?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int ttl_period:flags.14?int theme_emoticon:flags.15?string = UserFull; +userFull#cf366521 flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true id:long about:flags.1?string settings:PeerSettings profile_photo:flags.2?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int ttl_period:flags.14?int theme_emoticon:flags.15?string private_forward_name:flags.16?string = UserFull; contact#145ade0b user_id:long mutual:Bool = Contact; @@ -516,9 +516,9 @@ webPagePending#c586da1c id:long date:int = WebPage; webPage#e89c45b2 flags:# id:long url:string display_url:string hash:int type:flags.0?string site_name:flags.1?string title:flags.2?string description:flags.3?string photo:flags.4?Photo embed_url:flags.5?string embed_type:flags.5?string embed_width:flags.6?int embed_height:flags.6?int duration:flags.7?int author:flags.8?string document:flags.9?Document cached_page:flags.10?Page attributes:flags.12?Vector = WebPage; webPageNotModified#7311ca11 flags:# cached_page_views:flags.0?int = WebPage; -authorization#ad01d61d flags:# current:flags.0?true official_app:flags.1?true password_pending:flags.2?true hash:long device_model:string platform:string system_version:string api_id:int app_name:string app_version:string date_created:int date_active:int ip:string country:string region:string = Authorization; +authorization#ad01d61d flags:# current:flags.0?true official_app:flags.1?true password_pending:flags.2?true encrypted_requests_disabled:flags.3?true call_requests_disabled:flags.4?true hash:long device_model:string platform:string system_version:string api_id:int app_name:string app_version:string date_created:int date_active:int ip:string country:string region:string = Authorization; -account.authorizations#1250abde authorizations:Vector = account.Authorizations; +account.authorizations#4bff8ea0 authorization_ttl_days:int authorizations:Vector = account.Authorizations; account.password#185b184f flags:# has_recovery:flags.0?true has_secure_values:flags.1?true has_password:flags.2?true current_algo:flags.2?PasswordKdfAlgo srp_B:flags.2?bytes srp_id:flags.2?long hint:flags.3?string email_unconfirmed_pattern:flags.4?string new_algo:PasswordKdfAlgo new_secure_algo:SecurePasswordKdfAlgo secure_random:bytes pending_reset_date:flags.5?int = account.Password; @@ -546,6 +546,7 @@ inputStickerSetAnimatedEmojiAnimations#cde3739 = InputStickerSet; stickerSet#d7df217a flags:# archived:flags.1?true official:flags.2?true masks:flags.3?true animated:flags.5?true installed_date:flags.0?int id:long access_hash:long title:string short_name:string thumbs:flags.4?Vector thumb_dc_id:flags.4?int thumb_version:flags.4?int count:int hash:int = StickerSet; messages.stickerSet#b60a24a6 set:StickerSet packs:Vector documents:Vector = messages.StickerSet; +messages.stickerSetNotModified#d3f924eb = messages.StickerSet; botCommand#c27ac8c7 command:string description:string = BotCommand; @@ -562,6 +563,8 @@ keyboardButtonBuy#afd93fbb text:string = KeyboardButton; keyboardButtonUrlAuth#10b78d29 flags:# text:string fwd_text:flags.0?string url:string button_id:int = KeyboardButton; inputKeyboardButtonUrlAuth#d02e7fd4 flags:# request_write_access:flags.0?true text:string fwd_text:flags.1?string url:string bot:InputUser = KeyboardButton; keyboardButtonRequestPoll#bbc7515d flags:# quiz:flags.0?Bool text:string = KeyboardButton; +inputKeyboardButtonUserProfile#e988037b text:string user_id:InputUser = KeyboardButton; +keyboardButtonUserProfile#308660c1 text:string user_id:long = KeyboardButton; keyboardButtonRow#77608b83 buttons:Vector = KeyboardButtonRow; @@ -606,7 +609,7 @@ channelMessagesFilterEmpty#94d42ee7 = ChannelMessagesFilter; channelMessagesFilter#cd77d957 flags:# exclude_new_messages:flags.1?true ranges:Vector = ChannelMessagesFilter; channelParticipant#c00c07c0 user_id:long date:int = ChannelParticipant; -channelParticipantSelf#35a8bfa7 flags:# via_invite:flags.0?true user_id:long inviter_id:long date:int = ChannelParticipant; +channelParticipantSelf#35a8bfa7 flags:# via_request:flags.0?true user_id:long inviter_id:long date:int = ChannelParticipant; channelParticipantCreator#2fe601d3 flags:# user_id:long admin_rights:ChatAdminRights rank:flags.0?string = ChannelParticipant; channelParticipantAdmin#34c3bb53 flags:# can_edit:flags.0?true self:flags.1?true user_id:long inviter_id:flags.1?long promoted_by:long date:int admin_rights:ChatAdminRights rank:flags.2?string = ChannelParticipant; channelParticipantBanned#6df8014e flags:# left:flags.0?true peer:Peer kicked_by:long date:int banned_rights:ChatBannedRights = ChannelParticipant; @@ -663,11 +666,13 @@ messageFwdHeader#5f777dce flags:# imported:flags.7?true from_id:flags.0?Peer fro auth.codeTypeSms#72a3158c = auth.CodeType; auth.codeTypeCall#741cd3e3 = auth.CodeType; auth.codeTypeFlashCall#226ccefb = auth.CodeType; +auth.codeTypeMissedCall#d61ad6ee = auth.CodeType; auth.sentCodeTypeApp#3dbb5986 length:int = auth.SentCodeType; auth.sentCodeTypeSms#c000bba2 length:int = auth.SentCodeType; auth.sentCodeTypeCall#5353e5a7 length:int = auth.SentCodeType; auth.sentCodeTypeFlashCall#ab03c6d9 pattern:string = auth.SentCodeType; +auth.sentCodeTypeMissedCall#82006484 prefix:string length:int = auth.SentCodeType; messages.botCallbackAnswer#36585ea4 flags:# alert:flags.1?true has_url:flags.3?true native_ui:flags.4?true message:flags.0?string url:flags.2?string cache_time:int = messages.BotCallbackAnswer; @@ -890,12 +895,14 @@ channelAdminLogEventActionExportedInviteEdit#e90ebb59 prev_invite:ExportedChatIn channelAdminLogEventActionParticipantVolume#3e7f6847 participant:GroupCallParticipant = ChannelAdminLogEventAction; channelAdminLogEventActionChangeHistoryTTL#6e941a38 prev_value:int new_value:int = ChannelAdminLogEventAction; channelAdminLogEventActionParticipantJoinByRequest#afb6144a invite:ExportedChatInvite approved_by:long = ChannelAdminLogEventAction; +channelAdminLogEventActionToggleNoForwards#cb2ac766 new_value:Bool = ChannelAdminLogEventAction; +channelAdminLogEventActionSendMessage#278f2868 message:Message = ChannelAdminLogEventAction; channelAdminLogEvent#1fad68cd id:long date:int user_id:long action:ChannelAdminLogEventAction = ChannelAdminLogEvent; channels.adminLogResults#ed8af74d events:Vector chats:Vector users:Vector = channels.AdminLogResults; -channelAdminLogEventsFilter#ea107ae4 flags:# join:flags.0?true leave:flags.1?true invite:flags.2?true ban:flags.3?true unban:flags.4?true kick:flags.5?true unkick:flags.6?true promote:flags.7?true demote:flags.8?true info:flags.9?true settings:flags.10?true pinned:flags.11?true edit:flags.12?true delete:flags.13?true group_call:flags.14?true invites:flags.15?true = ChannelAdminLogEventsFilter; +channelAdminLogEventsFilter#ea107ae4 flags:# join:flags.0?true leave:flags.1?true invite:flags.2?true ban:flags.3?true unban:flags.4?true kick:flags.5?true unkick:flags.6?true promote:flags.7?true demote:flags.8?true info:flags.9?true settings:flags.10?true pinned:flags.11?true edit:flags.12?true delete:flags.13?true group_call:flags.14?true invites:flags.15?true send:flags.16?true = ChannelAdminLogEventsFilter; popularContact#5ce14175 client_id:long importers:int = PopularContact; @@ -1064,7 +1071,7 @@ inputWallPaperNoFile#967a462e id:long = InputWallPaper; account.wallPapersNotModified#1c199183 = account.WallPapers; account.wallPapers#cdc3858c hash:long wallpapers:Vector = account.WallPapers; -codeSettings#debebe83 flags:# allow_flashcall:flags.0?true current_number:flags.1?true allow_app_hash:flags.4?true = CodeSettings; +codeSettings#8a6469c2 flags:# allow_flashcall:flags.0?true current_number:flags.1?true allow_app_hash:flags.4?true allow_missed_call:flags.5?true logout_tokens:flags.6?Vector = CodeSettings; wallPaperSettings#1dc1bca4 flags:# blur:flags.1?true motion:flags.2?true background_color:flags.0?int second_background_color:flags.4?int third_background_color:flags.5?int fourth_background_color:flags.6?int intensity:flags.3?int rotation:flags.4?int = WallPaperSettings; @@ -1265,6 +1272,14 @@ searchResultPosition#7f648b67 msg_id:int date:int offset:int = SearchResultsPosi messages.searchResultsPositions#53b22baf count:int positions:Vector = messages.SearchResultsPositions; +channels.sendAsPeers#8356cda9 peers:Vector chats:Vector users:Vector = channels.SendAsPeers; + +users.userFull#3b6d152e full_user:UserFull chats:Vector users:Vector = users.UserFull; + +messages.peerSettings#6880b94d settings:PeerSettings chats:Vector users:Vector = messages.PeerSettings; + +auth.loggedOut#c3a2835f flags:# future_auth_token:flags.0?bytes = auth.LoggedOut; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -1278,7 +1293,7 @@ invokeWithTakeout#aca9fd2e {X:Type} takeout_id:long query:!X = X; auth.sendCode#a677244f phone_number:string api_id:int api_hash:string settings:CodeSettings = auth.SentCode; auth.signUp#80eee427 phone_number:string phone_code_hash:string first_name:string last_name:string = auth.Authorization; auth.signIn#bcd51581 phone_number:string phone_code_hash:string phone_code:string = auth.Authorization; -auth.logOut#5717da40 = Bool; +auth.logOut#3e72ba19 = auth.LoggedOut; auth.resetAuthorizations#9fab0d1a = Bool; auth.exportAuthorization#e5bfffcd dc_id:int = auth.ExportedAuthorization; auth.importAuthorization#a57a7dad id:long bytes:bytes = auth.Authorization; @@ -1366,9 +1381,11 @@ account.reportProfilePhoto#fa8cc6f5 peer:InputPeer photo_id:InputPhoto reason:Re account.resetPassword#9308ce1b = account.ResetPasswordResult; account.declinePasswordReset#4c9409f6 = Bool; account.getChatThemes#d638de89 hash:long = account.Themes; +account.setAuthorizationTTL#bf899aa0 authorization_ttl_days:int = Bool; +account.changeAuthorizationSettings#40f48462 flags:# hash:long encrypted_requests_disabled:flags.0?Bool call_requests_disabled:flags.1?Bool = Bool; users.getUsers#d91a548 id:Vector = Vector; -users.getFullUser#ca30a5b1 id:InputUser = UserFull; +users.getFullUser#b60f5918 id:InputUser = users.UserFull; users.setSecureValueErrors#90c894b5 id:InputUser errors:Vector = Bool; contacts.getContactIDs#7adc669d hash:long = Vector; @@ -1401,11 +1418,11 @@ messages.deleteHistory#b08f922a flags:# just_clear:flags.0?true revoke:flags.1?t messages.deleteMessages#e58e95d2 flags:# revoke:flags.0?true id:Vector = messages.AffectedMessages; messages.receivedMessages#5a954c0 max_id:int = Vector; messages.setTyping#58943ee2 flags:# peer:InputPeer top_msg_id:flags.0?int action:SendMessageAction = Bool; -messages.sendMessage#520c3870 flags:# no_webpage:flags.1?true silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int = Updates; -messages.sendMedia#3491eba9 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int media:InputMedia message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int = Updates; -messages.forwardMessages#d9fee60e flags:# silent:flags.5?true background:flags.6?true with_my_score:flags.8?true drop_author:flags.11?true drop_media_captions:flags.12?true from_peer:InputPeer id:Vector random_id:Vector to_peer:InputPeer schedule_date:flags.10?int = Updates; +messages.sendMessage#d9d75a4 flags:# no_webpage:flags.1?true silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates; +messages.sendMedia#e25ff8e0 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int media:InputMedia message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates; +messages.forwardMessages#cc30290b flags:# silent:flags.5?true background:flags.6?true with_my_score:flags.8?true drop_author:flags.11?true drop_media_captions:flags.12?true from_peer:InputPeer id:Vector random_id:Vector to_peer:InputPeer schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates; messages.reportSpam#cf1592db peer:InputPeer = Bool; -messages.getPeerSettings#3672e09c peer:InputPeer = PeerSettings; +messages.getPeerSettings#efd9a6a2 peer:InputPeer = messages.PeerSettings; messages.report#8953ab4e peer:InputPeer id:Vector reason:ReportReason message:string = Bool; messages.getChats#49e9528f id:Vector = messages.Chats; messages.getFullChat#aeb00b34 chat_id:long = messages.ChatFull; @@ -1432,7 +1449,7 @@ messages.getWebPagePreview#8b68b0cc flags:# message:string entities:flags.3?Vect messages.exportChatInvite#a02ce5d5 flags:# legacy_revoke_permanent:flags.2?true request_needed:flags.3?true peer:InputPeer expire_date:flags.0?int usage_limit:flags.1?int title:flags.4?string = ExportedChatInvite; messages.checkChatInvite#3eadb1bb hash:string = ChatInvite; messages.importChatInvite#6c50051c hash:string = Updates; -messages.getStickerSet#2619a90e stickerset:InputStickerSet = messages.StickerSet; +messages.getStickerSet#c8a0ec74 stickerset:InputStickerSet hash:int = messages.StickerSet; messages.installStickerSet#c78fe460 stickerset:InputStickerSet archived:Bool = messages.StickerSetInstallResult; messages.uninstallStickerSet#f96e55de stickerset:InputStickerSet = Bool; messages.startBot#e6df7378 bot:InputUser peer:InputPeer random_id:long start_param:string = Updates; @@ -1446,7 +1463,7 @@ messages.getSavedGifs#5cf09635 hash:long = messages.SavedGifs; messages.saveGif#327a30cb id:InputDocument unsave:Bool = Bool; messages.getInlineBotResults#514e999d flags:# bot:InputUser peer:InputPeer geo_point:flags.0?InputGeoPoint query:string offset:string = messages.BotResults; messages.setInlineBotResults#eb5ea206 flags:# gallery:flags.0?true private:flags.1?true query_id:long results:Vector cache_time:int next_offset:flags.2?string switch_pm:flags.3?InlineBotSwitchPM = Bool; -messages.sendInlineBotResult#220815b0 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true hide_via:flags.11?true peer:InputPeer reply_to_msg_id:flags.0?int random_id:long query_id:long id:string schedule_date:flags.10?int = Updates; +messages.sendInlineBotResult#7aa11297 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true hide_via:flags.11?true peer:InputPeer reply_to_msg_id:flags.0?int random_id:long query_id:long id:string schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates; messages.getMessageEditData#fda68d36 peer:InputPeer id:int = messages.MessageEditData; messages.editMessage#48f71778 flags:# no_webpage:flags.1?true peer:InputPeer id:int message:flags.11?string media:flags.14?InputMedia reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.15?int = Updates; messages.editInlineBotMessage#83557dba flags:# no_webpage:flags.1?true id:InputBotInlineMessageID message:flags.11?string media:flags.14?InputMedia reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector = Bool; @@ -1482,7 +1499,7 @@ messages.faveSticker#b9ffc55b id:InputDocument unfave:Bool = Bool; messages.getUnreadMentions#46578472 peer:InputPeer offset_id:int add_offset:int limit:int max_id:int min_id:int = messages.Messages; messages.readMentions#f0189d3 peer:InputPeer = messages.AffectedHistory; messages.getRecentLocations#702a40e0 peer:InputPeer limit:int hash:long = messages.Messages; -messages.sendMultiMedia#cc0110cb flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int multi_media:Vector schedule_date:flags.10?int = Updates; +messages.sendMultiMedia#f803138f flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int multi_media:Vector schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates; messages.uploadEncryptedFile#5057c497 peer:InputEncryptedChat file:InputEncryptedFile = EncryptedFile; messages.searchStickerSets#35705b8a flags:# exclude_featured:flags.0?true q:string hash:long = messages.FoundStickerSets; messages.getSplitRanges#1cff7e08 = Vector; @@ -1538,6 +1555,9 @@ messages.getMessageReadParticipants#2c6f97b7 peer:InputPeer msg_id:int = Vector< messages.getSearchResultsCalendar#49f0bde9 peer:InputPeer filter:MessagesFilter offset_id:int offset_date:int = messages.SearchResultsCalendar; messages.getSearchResultsPositions#6e9583a3 peer:InputPeer filter:MessagesFilter offset_id:int limit:int = messages.SearchResultsPositions; messages.hideChatJoinRequest#7fe7e815 flags:# approved:flags.0?true peer:InputPeer user_id:InputUser = Updates; +messages.hideAllChatJoinRequests#e085f4ea flags:# approved:flags.0?true peer:InputPeer link:flags.1?string = Updates; +messages.toggleNoForwards#b11eafa2 peer:InputPeer enabled:Bool = Updates; +messages.saveDefaultSendAs#ccfddf96 peer:InputPeer send_as:InputPeer = Bool; updates.getState#edd4882a = updates.State; updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference; @@ -1582,8 +1602,7 @@ help.getCountriesList#735787a8 lang_code:string hash:int = help.CountriesList; channels.readHistory#cc104937 channel:InputChannel max_id:int = Bool; channels.deleteMessages#84c1fd4e channel:InputChannel id:Vector = messages.AffectedMessages; -channels.deleteUserHistory#d10dd71b channel:InputChannel user_id:InputUser = messages.AffectedHistory; -channels.reportSpam#fe087810 channel:InputChannel user_id:InputUser id:Vector = Bool; +channels.reportSpam#f44a8315 channel:InputChannel participant:InputPeer id:Vector = Bool; channels.getMessages#ad8c9a23 channel:InputChannel id:Vector = messages.Messages; channels.getParticipants#77ced9d0 channel:InputChannel filter:ChannelParticipantsFilter offset:int limit:int hash:long = channels.ChannelParticipants; channels.getParticipant#a0ab6cc6 channel:InputChannel participant:InputPeer = channels.ChannelParticipant; @@ -1618,6 +1637,8 @@ channels.getInactiveChannels#11e831ee = messages.InactiveChats; channels.convertToGigagroup#b290c69 channel:InputChannel = Updates; channels.viewSponsoredMessage#beaedb94 channel:InputChannel random_id:bytes = Bool; channels.getSponsoredMessages#ec210fbf channel:InputChannel = messages.SponsoredMessages; +channels.getSendAs#dc770ee peer:InputPeer = channels.SendAsPeers; +channels.deleteParticipantHistory#367544db channel:InputChannel participant:InputPeer = messages.AffectedHistory; bots.sendCustomRequest#aa2769ed custom_method:string params:DataJSON = DataJSON; bots.answerWebhookJSONQuery#e6213f4d query_id:long data:DataJSON = Bool; @@ -1685,4 +1706,4 @@ stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats; -// LAYER 134 \ No newline at end of file +// LAYER 135 \ No newline at end of file From 804d00fa0f9ee8ea36874cc4ff575fafdec6fa47 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 26 Nov 2021 18:07:28 +0100 Subject: [PATCH 0636/1185] Update get_me.py --- pyrogram/methods/users/get_me.py | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/pyrogram/methods/users/get_me.py b/pyrogram/methods/users/get_me.py index 0c54572e11..3fab57e34d 100644 --- a/pyrogram/methods/users/get_me.py +++ b/pyrogram/methods/users/get_me.py @@ -34,11 +34,12 @@ async def get_me(self) -> "types.User": me = app.get_me() print(me) """ - return types.User._parse( - self, - (await self.send( - raw.functions.users.GetFullUser( - id=raw.types.InputUserSelf() - ) - )).user + r = await self.send( + raw.functions.users.GetFullUser( + id=raw.types.InputUserSelf() + ) ) + + users = {u.id: u for u in r.users} + + return types.User._parse(self, users[r.full_user.id]) From 2e964b3fa91a42fa4e9d683eaf5f8b8574c68cf0 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 4 Dec 2021 18:39:35 +0100 Subject: [PATCH 0637/1185] Update MAX_USER_ID --- pyrogram/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/utils.py b/pyrogram/utils.py index 236d9c7b2a..e5b15aad63 100644 --- a/pyrogram/utils.py +++ b/pyrogram/utils.py @@ -159,7 +159,7 @@ def unpack_inline_message_id(inline_message_id: str) -> "raw.types.InputBotInlin MIN_CHANNEL_ID = -1002147483647 MAX_CHANNEL_ID = -1000000000000 MIN_CHAT_ID = -2147483647 -MAX_USER_ID = 2147483647 +MAX_USER_ID = 999999999999 def get_raw_peer_id(peer: raw.base.Peer) -> Optional[int]: From f7db10307112aa606a533af9d0b823fc34c563f2 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 4 Dec 2021 18:40:22 +0100 Subject: [PATCH 0638/1185] Fix missing argument --- pyrogram/types/messages_and_media/sticker.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyrogram/types/messages_and_media/sticker.py b/pyrogram/types/messages_and_media/sticker.py index 5b63a2fe06..b7e4ecb5f4 100644 --- a/pyrogram/types/messages_and_media/sticker.py +++ b/pyrogram/types/messages_and_media/sticker.py @@ -121,7 +121,8 @@ async def _get_sticker_set_name(send, input_sticker_set_id): stickerset=raw.types.InputStickerSetID( id=set_id, access_hash=set_access_hash - ) + ), + hash=0 ) )).set.short_name From 34b6002c689273d7233ca1a0976da009a3aafe09 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 6 Dec 2021 14:16:49 +0100 Subject: [PATCH 0639/1185] Fix joinchat links regex --- pyrogram/scaffold.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/scaffold.py b/pyrogram/scaffold.py index a003359981..2bd2308690 100644 --- a/pyrogram/scaffold.py +++ b/pyrogram/scaffold.py @@ -41,7 +41,7 @@ class Scaffold: PARENT_DIR = Path(sys.argv[0]).parent - INVITE_LINK_RE = re.compile(r"^(?:https?://)?(?:www\.)?(?:t(?:elegram)?\.(?:org|me|dog)/joinchat/)([\w-]+)$") + INVITE_LINK_RE = re.compile(r"^(?:https?://)?(?:www\.)?(?:t(?:elegram)?\.(?:org|me|dog)/(?:joinchat/|\+))([\w-]+)$") WORKERS = min(32, os.cpu_count() + 4) WORKDIR = PARENT_DIR CONFIG_FILE = PARENT_DIR / "config.ini" From 85ee6c378560e5c9d035c5e68a81f1e7db485051 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 7 Dec 2021 19:40:06 +0100 Subject: [PATCH 0640/1185] Fetch user details in case they are missing --- pyrogram/types/messages_and_media/message.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index 403411fceb..6f2539b8a9 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -433,6 +433,16 @@ async def _parse( is_scheduled: bool = False, replies: int = 1 ): + user_id = utils.get_raw_peer_id(message.from_id) or utils.get_raw_peer_id(message.peer_id) + if user_id not in users: + r = (await client.send( + raw.functions.users.GetUsers( + id=[await client.resolve_peer(user_id)] + ) + ))[0] + + users[r.id] = r + if isinstance(message, raw.types.MessageEmpty): return Message(message_id=message.id, empty=True, client=client) @@ -499,8 +509,7 @@ async def _parse( voice_chat_members_invited = types.VoiceChatMembersInvited._parse(client, action, users) service_type = "voice_chat_members_invited" - user = utils.get_raw_peer_id(message.from_id) or utils.get_raw_peer_id(message.peer_id) - from_user = types.User._parse(client, users.get(user, None)) + from_user = types.User._parse(client, users.get(user_id, None)) sender_chat = types.Chat._parse(client, message, users, chats) if not from_user else None parsed_message = Message( @@ -554,8 +563,6 @@ async def _parse( except MessageIdsEmpty: pass - - return parsed_message if isinstance(message, raw.types.Message): @@ -696,8 +703,7 @@ async def _parse( else: reply_markup = None - user = utils.get_raw_peer_id(message.from_id) or utils.get_raw_peer_id(message.peer_id) - from_user = types.User._parse(client, users.get(user, None)) + from_user = types.User._parse(client, users.get(user_id, None)) sender_chat = types.Chat._parse(client, message, users, chats) if not from_user else None parsed_message = Message( From 8d03850d038e06b6d26d55b0c9ac456fafc81722 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 7 Dec 2021 20:22:29 +0100 Subject: [PATCH 0641/1185] Don't propagate errors when fetching missing user details --- pyrogram/types/messages_and_media/message.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index 6f2539b8a9..897cf26d9f 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -24,7 +24,7 @@ from pyrogram import raw from pyrogram import types from pyrogram import utils -from pyrogram.errors import MessageIdsEmpty +from pyrogram.errors import MessageIdsEmpty, PeerIdInvalid from pyrogram.parser import utils as parser_utils, Parser from ..object import Object from ..update import Update @@ -435,13 +435,16 @@ async def _parse( ): user_id = utils.get_raw_peer_id(message.from_id) or utils.get_raw_peer_id(message.peer_id) if user_id not in users: - r = (await client.send( - raw.functions.users.GetUsers( - id=[await client.resolve_peer(user_id)] - ) - ))[0] - - users[r.id] = r + try: + r = (await client.send( + raw.functions.users.GetUsers( + id=[await client.resolve_peer(user_id)] + ) + ))[0] + except PeerIdInvalid: + pass + else: + users[r.id] = r if isinstance(message, raw.types.MessageEmpty): return Message(message_id=message.id, empty=True, client=client) From e2cce1b1e6dfa109ab7f0b34ed5a5f2fce44c692 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 7 Dec 2021 20:29:46 +0100 Subject: [PATCH 0642/1185] Check empty messages first of all --- pyrogram/types/messages_and_media/message.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index 897cf26d9f..aaab0af004 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -433,6 +433,9 @@ async def _parse( is_scheduled: bool = False, replies: int = 1 ): + if isinstance(message, raw.types.MessageEmpty): + return Message(message_id=message.id, empty=True, client=client) + user_id = utils.get_raw_peer_id(message.from_id) or utils.get_raw_peer_id(message.peer_id) if user_id not in users: try: @@ -446,9 +449,6 @@ async def _parse( else: users[r.id] = r - if isinstance(message, raw.types.MessageEmpty): - return Message(message_id=message.id, empty=True, client=client) - if isinstance(message, raw.types.MessageService): action = message.action From 439935b4012eb03ad632c46191055227e9e7c566 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 7 Dec 2021 22:39:56 +0100 Subject: [PATCH 0643/1185] Fix wrong sender_chat --- pyrogram/types/messages_and_media/message.py | 8 ++++---- pyrogram/types/user_and_chats/chat.py | 17 +++++++++++++---- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index aaab0af004..85db6842b0 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -513,12 +513,12 @@ async def _parse( service_type = "voice_chat_members_invited" from_user = types.User._parse(client, users.get(user_id, None)) - sender_chat = types.Chat._parse(client, message, users, chats) if not from_user else None + sender_chat = types.Chat._parse(client, message, users, chats, is_chat=False) if not from_user else None parsed_message = Message( message_id=message.id, date=message.date, - chat=types.Chat._parse(client, message, users, chats), + chat=types.Chat._parse(client, message, users, chats, is_chat=True), from_user=from_user, sender_chat=sender_chat, service=service_type, @@ -707,12 +707,12 @@ async def _parse( reply_markup = None from_user = types.User._parse(client, users.get(user_id, None)) - sender_chat = types.Chat._parse(client, message, users, chats) if not from_user else None + sender_chat = types.Chat._parse(client, message, users, chats, is_chat=False) if not from_user else None parsed_message = Message( message_id=message.id, date=message.date, - chat=types.Chat._parse(client, message, users, chats), + chat=types.Chat._parse(client, message, users, chats, is_chat=True), from_user=from_user, sender_chat=sender_chat, text=( diff --git a/pyrogram/types/user_and_chats/chat.py b/pyrogram/types/user_and_chats/chat.py index 8930bf3afb..59db7a6260 100644 --- a/pyrogram/types/user_and_chats/chat.py +++ b/pyrogram/types/user_and_chats/chat.py @@ -238,14 +238,23 @@ def _parse_channel_chat(client, channel: raw.types.Channel) -> "Chat": ) @staticmethod - def _parse(client, message: Union[raw.types.Message, raw.types.MessageService], users: dict, chats: dict) -> "Chat": + def _parse( + client, + message: Union[raw.types.Message, raw.types.MessageService], + users: dict, + chats: dict, + is_chat: bool + ) -> "Chat": if isinstance(message.peer_id, raw.types.PeerUser): - return Chat._parse_user_chat(client, users[message.peer_id.user_id]) + user_id = message.peer_id.user_id if is_chat else message.from_id.user_id + return Chat._parse_user_chat(client, users[user_id]) if isinstance(message.peer_id, raw.types.PeerChat): - return Chat._parse_chat_chat(client, chats[message.peer_id.chat_id]) + chat_id = message.peer_id.chat_id if is_chat else message.from_id.chat_id + return Chat._parse_chat_chat(client, chats[chat_id]) - return Chat._parse_channel_chat(client, chats[message.peer_id.channel_id]) + channel_id = message.peer_id.channel_id if is_chat else message.from_id.channel_id + return Chat._parse_channel_chat(client, chats[channel_id]) @staticmethod def _parse_dialog(client, peer, users: dict, chats: dict): From 8a08dcb92a63cb27fb71df1812c57cb71319ccc5 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 8 Dec 2021 07:43:02 +0100 Subject: [PATCH 0644/1185] Update UserFull according to the new schema --- pyrogram/types/user_and_chats/chat.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pyrogram/types/user_and_chats/chat.py b/pyrogram/types/user_and_chats/chat.py index 59db7a6260..40bf2023bb 100644 --- a/pyrogram/types/user_and_chats/chat.py +++ b/pyrogram/types/user_and_chats/chat.py @@ -266,15 +266,15 @@ def _parse_dialog(client, peer, users: dict, chats: dict): return Chat._parse_channel_chat(client, chats[peer.channel_id]) @staticmethod - async def _parse_full(client, chat_full: Union[raw.types.messages.ChatFull, raw.types.UserFull]) -> "Chat": - if isinstance(chat_full, raw.types.UserFull): - parsed_chat = Chat._parse_user_chat(client, chat_full.user) - parsed_chat.bio = chat_full.about + async def _parse_full(client, chat_full: Union[raw.types.messages.ChatFull, raw.types.users.UserFull]) -> "Chat": + if isinstance(chat_full, raw.types.users.UserFull): + parsed_chat = Chat._parse_user_chat(client, chat_full.users[0]) + parsed_chat.bio = chat_full.full_user.about - if chat_full.pinned_msg_id: + if chat_full.full_user.pinned_msg_id: parsed_chat.pinned_message = await client.get_messages( parsed_chat.id, - message_ids=chat_full.pinned_msg_id + message_ids=chat_full.full_user.pinned_msg_id ) else: full_chat = chat_full.full_chat From a73f990667ce2b2b396f7af2a46203a418da47da Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 8 Dec 2021 07:46:41 +0100 Subject: [PATCH 0645/1185] Improve parsing of Chat --- pyrogram/types/user_and_chats/chat.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pyrogram/types/user_and_chats/chat.py b/pyrogram/types/user_and_chats/chat.py index 40bf2023bb..cf607c66df 100644 --- a/pyrogram/types/user_and_chats/chat.py +++ b/pyrogram/types/user_and_chats/chat.py @@ -245,16 +245,17 @@ def _parse( chats: dict, is_chat: bool ) -> "Chat": + from_id = utils.get_raw_peer_id(message.from_id) + peer_id = utils.get_raw_peer_id(message.peer_id) + chat_id = (peer_id or from_id) if is_chat else (from_id or peer_id) + if isinstance(message.peer_id, raw.types.PeerUser): - user_id = message.peer_id.user_id if is_chat else message.from_id.user_id - return Chat._parse_user_chat(client, users[user_id]) + return Chat._parse_user_chat(client, users[chat_id]) if isinstance(message.peer_id, raw.types.PeerChat): - chat_id = message.peer_id.chat_id if is_chat else message.from_id.chat_id return Chat._parse_chat_chat(client, chats[chat_id]) - channel_id = message.peer_id.channel_id if is_chat else message.from_id.channel_id - return Chat._parse_channel_chat(client, chats[channel_id]) + return Chat._parse_channel_chat(client, chats[chat_id]) @staticmethod def _parse_dialog(client, peer, users: dict, chats: dict): From bf9e1864141bdab1a4f20393c7d10fcf8c522f63 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 9 Dec 2021 20:12:13 +0100 Subject: [PATCH 0646/1185] Also fetch peer_id user details --- pyrogram/types/messages_and_media/message.py | 29 ++++++++++++-------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index 85db6842b0..2fa21c4eb7 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -436,18 +436,25 @@ async def _parse( if isinstance(message, raw.types.MessageEmpty): return Message(message_id=message.id, empty=True, client=client) - user_id = utils.get_raw_peer_id(message.from_id) or utils.get_raw_peer_id(message.peer_id) - if user_id not in users: - try: - r = (await client.send( - raw.functions.users.GetUsers( - id=[await client.resolve_peer(user_id)] + from_id = utils.get_raw_peer_id(message.from_id) + peer_id = utils.get_raw_peer_id(message.peer_id) + user_id = from_id or peer_id + + if isinstance(message.from_id, raw.types.PeerUser) and isinstance(message.peer_id, raw.types.PeerUser): + if from_id not in users or peer_id not in users: + try: + r = await client.send( + raw.functions.users.GetUsers( + id=[ + await client.resolve_peer(from_id), + await client.resolve_peer(peer_id) + ] + ) ) - ))[0] - except PeerIdInvalid: - pass - else: - users[r.id] = r + except PeerIdInvalid: + pass + else: + users.update({i.id: i for i in r}) if isinstance(message, raw.types.MessageService): action = message.action From cebecb99c0e53d4de22a335540d84919658e5521 Mon Sep 17 00:00:00 2001 From: Abir Hasan Date: Mon, 13 Dec 2021 18:20:55 +0600 Subject: [PATCH 0647/1185] Fix renamed raw API function (#810) `raw.functions.channels.DeleteUserHistory` to `raw.functions.channels.DeleteParticipantHistory` --- pyrogram/methods/chats/delete_user_history.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/methods/chats/delete_user_history.py b/pyrogram/methods/chats/delete_user_history.py index e2f780f9fc..8043432de3 100644 --- a/pyrogram/methods/chats/delete_user_history.py +++ b/pyrogram/methods/chats/delete_user_history.py @@ -42,9 +42,9 @@ async def delete_user_history( """ r = await self.send( - raw.functions.channels.DeleteUserHistory( + raw.functions.channels.DeleteParticipantHistory( channel=await self.resolve_peer(chat_id), - user_id=await self.resolve_peer(user_id) + participant=await self.resolve_peer(user_id) ) ) From cd027b8c1c252149cc7304b95b6ec8da53bd01fa Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 15 Dec 2021 13:18:13 +0100 Subject: [PATCH 0648/1185] Implement missing MTProto checks --- pyrogram/crypto/mtproto.py | 44 ++++++++++++++++++++++++++++--- pyrogram/raw/core/future_salt.py | 9 +++++++ pyrogram/raw/core/future_salts.py | 14 ++++++++++ pyrogram/session/session.py | 8 +++++- 4 files changed, 71 insertions(+), 4 deletions(-) diff --git a/pyrogram/crypto/mtproto.py b/pyrogram/crypto/mtproto.py index 803db29704..1eec7b7a3e 100644 --- a/pyrogram/crypto/mtproto.py +++ b/pyrogram/crypto/mtproto.py @@ -19,9 +19,11 @@ from hashlib import sha256 from io import BytesIO from os import urandom +from typing import Optional, List from pyrogram.raw.core import Message, Long from . import aes +from ..session.internals import MsgId def kdf(auth_key: bytes, msg_key: bytes, outgoing: bool) -> tuple: @@ -49,13 +51,19 @@ 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) -def unpack(b: BytesIO, 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, + stored_msg_ids: List[int] +) -> Optional[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)) - data.read(8) + data.read(8) # Salt # https://core.telegram.org/mtproto/security_guidelines#checking-session-id assert data.read(8) == session_id @@ -75,11 +83,41 @@ def unpack(b: BytesIO, session_id: bytes, auth_key: bytes, auth_key_id: bytes) - raise ValueError(f"The server sent an unknown constructor: {hex(e.args[0])}\n{left}") # 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-message-length + data.seek(32) # Get to the payload, skip salt (8) + session_id (8) + msg_id (8) + seq_no (4) + length (4) + payload = data.read() + padding = payload[message.length:] + assert 12 <= len(padding) <= 1024 + assert len(payload) % 4 == 0 + # https://core.telegram.org/mtproto/security_guidelines#checking-msg-id assert message.msg_id % 2 != 0 + if len(stored_msg_ids) > 200: + stored_msg_ids = stored_msg_ids[50:] + + if stored_msg_ids: + # Ignored message: msg_id is lower than all of the stored values + if message.msg_id < stored_msg_ids[0]: + return None + + # Ignored message: msg_id is equal to any of the stored values + if message.msg_id in stored_msg_ids: + return None + + time_diff = (message.msg_id - MsgId()) / 2 ** 32 + + # Ignored message: msg_id belongs over 30 seconds in the future + if time_diff > 30: + return None + + # Ignored message: msg_id belongs over 300 seconds in the past + if time_diff < -300: + return None + + stored_msg_ids.append(message.msg_id) + return message diff --git a/pyrogram/raw/core/future_salt.py b/pyrogram/raw/core/future_salt.py index 85303d1203..54a1296361 100644 --- a/pyrogram/raw/core/future_salt.py +++ b/pyrogram/raw/core/future_salt.py @@ -42,3 +42,12 @@ def read(data: BytesIO, *args: Any) -> "FutureSalt": salt = Long.read(data) return FutureSalt(valid_since, valid_until, salt) + + def write(self, *args: Any) -> bytes: + b = BytesIO() + + b.write(Int(self.valid_since)) + b.write(Int(self.valid_until)) + b.write(Long(self.salt)) + + return b.getvalue() diff --git a/pyrogram/raw/core/future_salts.py b/pyrogram/raw/core/future_salts.py index faa4b74163..9fa2f8e97e 100644 --- a/pyrogram/raw/core/future_salts.py +++ b/pyrogram/raw/core/future_salts.py @@ -45,3 +45,17 @@ def read(data: BytesIO, *args: Any) -> "FutureSalts": salts = [FutureSalt.read(data) for _ in range(count)] return FutureSalts(req_msg_id, now, salts) + + def write(self, *args: Any) -> bytes: + b = BytesIO() + + b.write(Long(self.req_msg_id)) + b.write(Int(self.now)) + + count = len(self.salts) + b.write(Int(count)) + + for salt in self.salts: + b.write(salt.write()) + + return b.getvalue() diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index 721586a01c..39fe605ca3 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -102,6 +102,8 @@ def __init__( self.results = {} + self.stored_msg_ids = [] + self.ping_task = None self.ping_task_event = asyncio.Event() @@ -224,9 +226,13 @@ async def handle_packet(self, packet): BytesIO(packet), self.session_id, self.auth_key, - self.auth_key_id + self.auth_key_id, + self.stored_msg_ids ) + if data is None: + return + messages = ( data.body.messages if isinstance(data.body, MsgContainer) From bc420da0e2ec34506263d0a8abef7019e1770194 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 15 Dec 2021 15:04:44 +0100 Subject: [PATCH 0649/1185] Maintain a sorted list of stored_msg_ids --- pyrogram/crypto/mtproto.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyrogram/crypto/mtproto.py b/pyrogram/crypto/mtproto.py index 1eec7b7a3e..81cf56f4a8 100644 --- a/pyrogram/crypto/mtproto.py +++ b/pyrogram/crypto/mtproto.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 bisect from hashlib import sha256 from io import BytesIO from os import urandom @@ -118,6 +119,6 @@ def unpack( if time_diff < -300: return None - stored_msg_ids.append(message.msg_id) + bisect.insort(stored_msg_ids, message.msg_id) return message From 2a1af2b8e9afb54cc8f96bffb95317e52fbafcd3 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 15 Dec 2021 16:02:39 +0100 Subject: [PATCH 0650/1185] Close and reestablish the TCP connection in case of mismatch --- pyrogram/crypto/mtproto.py | 14 +++++++------- pyrogram/session/session.py | 25 +++++++++++++++---------- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/pyrogram/crypto/mtproto.py b/pyrogram/crypto/mtproto.py index 81cf56f4a8..a22693fe49 100644 --- a/pyrogram/crypto/mtproto.py +++ b/pyrogram/crypto/mtproto.py @@ -20,7 +20,7 @@ from hashlib import sha256 from io import BytesIO from os import urandom -from typing import Optional, List +from typing import List, Tuple from pyrogram.raw.core import Message, Long from . import aes @@ -58,7 +58,7 @@ def unpack( auth_key: bytes, auth_key_id: bytes, stored_msg_ids: List[int] -) -> Optional[Message]: +) -> Tuple[Message, bool]: assert b.read(8) == auth_key_id, b.getvalue() msg_key = b.read(16) @@ -103,22 +103,22 @@ def unpack( if stored_msg_ids: # Ignored message: msg_id is lower than all of the stored values if message.msg_id < stored_msg_ids[0]: - return None + return message, False # Ignored message: msg_id is equal to any of the stored values if message.msg_id in stored_msg_ids: - return None + return message, False time_diff = (message.msg_id - MsgId()) / 2 ** 32 # Ignored message: msg_id belongs over 30 seconds in the future if time_diff > 30: - return None + return message, False # Ignored message: msg_id belongs over 300 seconds in the past if time_diff < -300: - return None + return message, False bisect.insort(stored_msg_ids, message.msg_id) - return message + return message, True diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index 39fe605ca3..b3875f3c9e 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -220,17 +220,22 @@ async def restart(self): await self.start() async def handle_packet(self, packet): - data = await self.loop.run_in_executor( - pyrogram.crypto_executor, - mtproto.unpack, - BytesIO(packet), - self.session_id, - self.auth_key, - self.auth_key_id, - self.stored_msg_ids - ) + try: + data, ok = await self.loop.run_in_executor( + pyrogram.crypto_executor, + mtproto.unpack, + BytesIO(packet), + self.session_id, + self.auth_key, + self.auth_key_id, + self.stored_msg_ids + ) + except AssertionError: + self.connection.close() + return - if data is None: + if not ok: + self.connection.close() return messages = ( From c2a29c8c302feff24e84e7974b7618f4437db105 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 15 Dec 2021 16:53:17 +0100 Subject: [PATCH 0651/1185] Tune stored_msg_ids max size --- pyrogram/crypto/mtproto.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pyrogram/crypto/mtproto.py b/pyrogram/crypto/mtproto.py index a22693fe49..2b4f71af1b 100644 --- a/pyrogram/crypto/mtproto.py +++ b/pyrogram/crypto/mtproto.py @@ -26,6 +26,8 @@ from . import aes from ..session.internals import MsgId +STORED_MSG_IDS_MAX_SIZE = 1000 * 2 + def kdf(auth_key: bytes, msg_key: bytes, outgoing: bool) -> tuple: # https://core.telegram.org/mtproto/description#defining-aes-key-and-initialization-vector @@ -97,8 +99,8 @@ def unpack( # https://core.telegram.org/mtproto/security_guidelines#checking-msg-id assert message.msg_id % 2 != 0 - if len(stored_msg_ids) > 200: - stored_msg_ids = stored_msg_ids[50:] + if len(stored_msg_ids) > STORED_MSG_IDS_MAX_SIZE: + del stored_msg_ids[:STORED_MSG_IDS_MAX_SIZE // 2] if stored_msg_ids: # Ignored message: msg_id is lower than all of the stored values From ed9c7e4694f770f2ef90adc87152889c8530fc01 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 15 Dec 2021 19:26:54 +0100 Subject: [PATCH 0652/1185] Simplify the error handling a bit --- pyrogram/crypto/mtproto.py | 16 ++++++++-------- pyrogram/session/session.py | 6 +----- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/pyrogram/crypto/mtproto.py b/pyrogram/crypto/mtproto.py index 2b4f71af1b..f9b885f66b 100644 --- a/pyrogram/crypto/mtproto.py +++ b/pyrogram/crypto/mtproto.py @@ -20,7 +20,7 @@ from hashlib import sha256 from io import BytesIO from os import urandom -from typing import List, Tuple +from typing import List from pyrogram.raw.core import Message, Long from . import aes @@ -60,8 +60,8 @@ def unpack( auth_key: bytes, auth_key_id: bytes, stored_msg_ids: List[int] -) -> Tuple[Message, bool]: - assert b.read(8) == auth_key_id, b.getvalue() +) -> Message: + assert b.read(8) == auth_key_id msg_key = b.read(16) aes_key, aes_iv = kdf(auth_key, msg_key, False) @@ -105,22 +105,22 @@ def unpack( if stored_msg_ids: # Ignored message: msg_id is lower than all of the stored values if message.msg_id < stored_msg_ids[0]: - return message, False + assert False # Ignored message: msg_id is equal to any of the stored values if message.msg_id in stored_msg_ids: - return message, False + assert False time_diff = (message.msg_id - MsgId()) / 2 ** 32 # Ignored message: msg_id belongs over 30 seconds in the future if time_diff > 30: - return message, False + assert False # Ignored message: msg_id belongs over 300 seconds in the past if time_diff < -300: - return message, False + assert False bisect.insort(stored_msg_ids, message.msg_id) - return message, True + return message diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index b3875f3c9e..3504c3adcf 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -221,7 +221,7 @@ async def restart(self): async def handle_packet(self, packet): try: - data, ok = await self.loop.run_in_executor( + data = await self.loop.run_in_executor( pyrogram.crypto_executor, mtproto.unpack, BytesIO(packet), @@ -234,10 +234,6 @@ async def handle_packet(self, packet): self.connection.close() return - if not ok: - self.connection.close() - return - messages = ( data.body.messages if isinstance(data.body, MsgContainer) From a720726479bd1f9b6804b837a7679bc4b9f682b1 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 16 Dec 2021 21:05:01 +0100 Subject: [PATCH 0653/1185] Remove unneeded assertion --- pyrogram/methods/auth/accept_terms_of_service.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/pyrogram/methods/auth/accept_terms_of_service.py b/pyrogram/methods/auth/accept_terms_of_service.py index b5abab8613..c8cfd36d71 100644 --- a/pyrogram/methods/auth/accept_terms_of_service.py +++ b/pyrogram/methods/auth/accept_terms_of_service.py @@ -36,6 +36,4 @@ async def accept_terms_of_service(self, terms_of_service_id: str) -> bool: ) ) - assert r - - return True + return bool(r) From 8aa358129c3484a2cd35454dc82228e34663be13 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 16 Dec 2021 21:38:24 +0100 Subject: [PATCH 0654/1185] Use specialized exceptions for handling security checks --- pyrogram/client.py | 3 ++- pyrogram/crypto/mtproto.py | 21 +++++++++++---------- pyrogram/errors/__init__.py | 24 ++++++++++++++++++++++++ pyrogram/session/auth.py | 25 +++++++++++++------------ pyrogram/session/session.py | 7 +++++-- 5 files changed, 55 insertions(+), 25 deletions(-) diff --git a/pyrogram/client.py b/pyrogram/client.py index b54794fad2..8867d2f90e 100644 --- a/pyrogram/client.py +++ b/pyrogram/client.py @@ -35,6 +35,7 @@ from pyrogram import raw from pyrogram import utils from pyrogram.crypto import aes +from pyrogram.errors import CDNFileHashMismatch from pyrogram.errors import ( SessionPasswordNeeded, VolumeLocNotFound, ChannelPrivate, @@ -1009,7 +1010,7 @@ async def get_file( # https://core.telegram.org/cdn#verifying-files for i, h in enumerate(hashes): cdn_chunk = decrypted_chunk[h.limit * i: h.limit * (i + 1)] - assert h.hash == sha256(cdn_chunk).digest(), f"Invalid CDN hash part {i}" + CDNFileHashMismatch.check(h.hash == sha256(cdn_chunk).digest()) f.write(decrypted_chunk) diff --git a/pyrogram/crypto/mtproto.py b/pyrogram/crypto/mtproto.py index f9b885f66b..ccea119c83 100644 --- a/pyrogram/crypto/mtproto.py +++ b/pyrogram/crypto/mtproto.py @@ -22,6 +22,7 @@ from os import urandom from typing import List +from pyrogram.errors import SecurityCheckMismatch from pyrogram.raw.core import Message, Long from . import aes from ..session.internals import MsgId @@ -61,7 +62,7 @@ def unpack( auth_key_id: bytes, stored_msg_ids: List[int] ) -> Message: - assert b.read(8) == auth_key_id + SecurityCheckMismatch.check(b.read(8) == auth_key_id) msg_key = b.read(16) aes_key, aes_iv = kdf(auth_key, msg_key, False) @@ -69,7 +70,7 @@ def unpack( data.read(8) # Salt # https://core.telegram.org/mtproto/security_guidelines#checking-session-id - assert data.read(8) == session_id + SecurityCheckMismatch.check(data.read(8) == session_id) try: message = Message.read(data) @@ -87,17 +88,17 @@ def unpack( # https://core.telegram.org/mtproto/security_guidelines#checking-sha256-hash-value-of-msg-key # 96 = 88 + 8 (incoming message) - assert msg_key == sha256(auth_key[96:96 + 32] + data.getvalue()).digest()[8:24] + SecurityCheckMismatch.check(msg_key == sha256(auth_key[96:96 + 32] + data.getvalue()).digest()[8:24]) # https://core.telegram.org/mtproto/security_guidelines#checking-message-length data.seek(32) # Get to the payload, skip salt (8) + session_id (8) + msg_id (8) + seq_no (4) + length (4) payload = data.read() padding = payload[message.length:] - assert 12 <= len(padding) <= 1024 - assert len(payload) % 4 == 0 + SecurityCheckMismatch.check(12 <= len(padding) <= 1024) + SecurityCheckMismatch.check(len(payload) % 4 == 0) # https://core.telegram.org/mtproto/security_guidelines#checking-msg-id - assert message.msg_id % 2 != 0 + SecurityCheckMismatch.check(message.msg_id % 2 != 0) if len(stored_msg_ids) > STORED_MSG_IDS_MAX_SIZE: del stored_msg_ids[:STORED_MSG_IDS_MAX_SIZE // 2] @@ -105,21 +106,21 @@ def unpack( if stored_msg_ids: # Ignored message: msg_id is lower than all of the stored values if message.msg_id < stored_msg_ids[0]: - assert False + SecurityCheckMismatch.check(False) # Ignored message: msg_id is equal to any of the stored values if message.msg_id in stored_msg_ids: - assert False + SecurityCheckMismatch.check(False) time_diff = (message.msg_id - MsgId()) / 2 ** 32 # Ignored message: msg_id belongs over 30 seconds in the future if time_diff > 30: - assert False + SecurityCheckMismatch.check(False) # Ignored message: msg_id belongs over 300 seconds in the past if time_diff < -300: - assert False + SecurityCheckMismatch.check(False) bisect.insort(stored_msg_ids, message.msg_id) diff --git a/pyrogram/errors/__init__.py b/pyrogram/errors/__init__.py index 1b94700faa..5011b08020 100644 --- a/pyrogram/errors/__init__.py +++ b/pyrogram/errors/__init__.py @@ -18,3 +18,27 @@ from .exceptions import * from .rpc_error import UnknownError + + +class SecurityError(Exception): + """Generic security error.""" + + @classmethod + def check(cls, cond: bool): + """Raises this exception if the condition is false""" + if not cond: + raise cls + + +class SecurityCheckMismatch(SecurityError): + """Raised when a security check mismatch occurs.""" + + def __init__(self): + super().__init__("A security check mismatch has occurred.") + + +class CDNFileHashMismatch(SecurityError): + """Raised when a CDN file hash mismatch occurs.""" + + def __init__(self): + super().__init__("A CDN file hash mismatch has occurred.") diff --git a/pyrogram/session/auth.py b/pyrogram/session/auth.py index a3e87ff41c..6b1ad9530d 100644 --- a/pyrogram/session/auth.py +++ b/pyrogram/session/auth.py @@ -27,6 +27,7 @@ from pyrogram import raw from pyrogram.connection import Connection from pyrogram.crypto import aes, rsa, prime +from pyrogram.errors import SecurityCheckMismatch from pyrogram.raw.core import TLObject, Long, Int from .internals import MsgId @@ -210,33 +211,33 @@ async def create(self): # Security checks ####################### - assert dh_prime == prime.CURRENT_DH_PRIME + SecurityCheckMismatch.check(dh_prime == prime.CURRENT_DH_PRIME) log.debug("DH parameters check: OK") # https://core.telegram.org/mtproto/security_guidelines#g-a-and-g-b-validation g_b = int.from_bytes(g_b, "big") - assert 1 < g < dh_prime - 1 - assert 1 < g_a < dh_prime - 1 - assert 1 < g_b < dh_prime - 1 - assert 2 ** (2048 - 64) < g_a < dh_prime - 2 ** (2048 - 64) - assert 2 ** (2048 - 64) < g_b < dh_prime - 2 ** (2048 - 64) + SecurityCheckMismatch.check(1 < g < dh_prime - 1) + SecurityCheckMismatch.check(1 < g_a < dh_prime - 1) + SecurityCheckMismatch.check(1 < g_b < dh_prime - 1) + SecurityCheckMismatch.check(2 ** (2048 - 64) < g_a < dh_prime - 2 ** (2048 - 64)) + SecurityCheckMismatch.check(2 ** (2048 - 64) < g_b < dh_prime - 2 ** (2048 - 64)) log.debug("g_a and g_b validation: OK") # https://core.telegram.org/mtproto/security_guidelines#checking-sha1-hash-values answer = server_dh_inner_data.write() # Call .write() to remove padding - assert answer_with_hash[:20] == sha1(answer).digest() + SecurityCheckMismatch.check(answer_with_hash[:20] == sha1(answer).digest()) log.debug("SHA1 hash values check: OK") # https://core.telegram.org/mtproto/security_guidelines#checking-nonce-server-nonce-and-new-nonce-fields # 1st message - assert nonce == res_pq.nonce + SecurityCheckMismatch.check(nonce == res_pq.nonce) # 2nd message server_nonce = int.from_bytes(server_nonce, "little", signed=True) - assert nonce == server_dh_params.nonce - assert server_nonce == server_dh_params.server_nonce + SecurityCheckMismatch.check(nonce == server_dh_params.nonce) + SecurityCheckMismatch.check(server_nonce == server_dh_params.server_nonce) # 3rd message - assert nonce == set_client_dh_params_answer.nonce - assert server_nonce == set_client_dh_params_answer.server_nonce + SecurityCheckMismatch.check(nonce == set_client_dh_params_answer.nonce) + SecurityCheckMismatch.check(server_nonce == set_client_dh_params_answer.server_nonce) server_nonce = server_nonce.to_bytes(16, "little", signed=True) log.debug("Nonce fields check: OK") diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index 3504c3adcf..72f2762172 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -29,7 +29,10 @@ from pyrogram import raw from pyrogram.connection import Connection from pyrogram.crypto import mtproto -from pyrogram.errors import RPCError, InternalServerError, AuthKeyDuplicated, FloodWait, ServiceUnavailable +from pyrogram.errors import ( + RPCError, InternalServerError, AuthKeyDuplicated, FloodWait, + ServiceUnavailable, SecurityCheckMismatch +) from pyrogram.raw.all import layer from pyrogram.raw.core import TLObject, MsgContainer, Int, FutureSalt, FutureSalts from .internals import MsgId, MsgFactory @@ -230,7 +233,7 @@ async def handle_packet(self, packet): self.auth_key_id, self.stored_msg_ids ) - except AssertionError: + except SecurityCheckMismatch: self.connection.close() return From ea3281b5f62caa916ea6f1ebb2f406bb66a2b36f Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 16 Dec 2021 21:39:52 +0100 Subject: [PATCH 0655/1185] Raise directly when not checking a boolean expression --- pyrogram/crypto/mtproto.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyrogram/crypto/mtproto.py b/pyrogram/crypto/mtproto.py index ccea119c83..2fc3b9f848 100644 --- a/pyrogram/crypto/mtproto.py +++ b/pyrogram/crypto/mtproto.py @@ -106,21 +106,21 @@ def unpack( if stored_msg_ids: # Ignored message: msg_id is lower than all of the stored values if message.msg_id < stored_msg_ids[0]: - SecurityCheckMismatch.check(False) + raise SecurityCheckMismatch # Ignored message: msg_id is equal to any of the stored values if message.msg_id in stored_msg_ids: - SecurityCheckMismatch.check(False) + raise SecurityCheckMismatch time_diff = (message.msg_id - MsgId()) / 2 ** 32 # Ignored message: msg_id belongs over 30 seconds in the future if time_diff > 30: - SecurityCheckMismatch.check(False) + raise SecurityCheckMismatch # Ignored message: msg_id belongs over 300 seconds in the past if time_diff < -300: - SecurityCheckMismatch.check(False) + raise SecurityCheckMismatch bisect.insort(stored_msg_ids, message.msg_id) From 6fb427fb9a61bfcd31494f65c948bcb3b9d1d676 Mon Sep 17 00:00:00 2001 From: Adek Date: Fri, 17 Dec 2021 16:41:39 +0700 Subject: [PATCH 0656/1185] Add new chat actions (#751) * Add new chat action * Update send_chat_action.py Co-authored-by: Dan <14043624+delivrance@users.noreply.github.com> --- pyrogram/methods/messages/send_chat_action.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyrogram/methods/messages/send_chat_action.py b/pyrogram/methods/messages/send_chat_action.py index 586d1ea746..f618670d7e 100644 --- a/pyrogram/methods/messages/send_chat_action.py +++ b/pyrogram/methods/messages/send_chat_action.py @@ -37,6 +37,8 @@ class ChatAction: PLAYING = raw.types.SendMessageGamePlayAction CHOOSE_CONTACT = raw.types.SendMessageChooseContactAction SPEAKING = raw.types.SpeakingInGroupCallAction + IMPORT_HISTORY = raw.types.SendMessageHistoryImportAction + CHOOSE_STICKER = raw.types.SendMessageChooseStickerAction CANCEL = raw.types.SendMessageCancelAction @@ -59,6 +61,7 @@ async def send_chat_action(self, chat_id: Union[int, str], action: str) -> bool: *"record_audio"* or *"upload_audio"* for audio files, *"upload_document"* for general files, *"find_location"* for location data, *"record_video_note"* or *"upload_video_note"* for video notes, *"choose_contact"* for contacts, *"playing"* for games, *"speaking"* for speaking in group calls or + *"import_history"* for importing history, *"choose_sticker"* for stickers or *"cancel"* to cancel any chat action currently displayed. Returns: @@ -89,7 +92,7 @@ async def send_chat_action(self, chat_id: Union[int, str], action: str) -> bool: raise ValueError("Invalid chat action '{}'. Possible values are: {}".format( action, json.dumps(POSSIBLE_VALUES, indent=4))) from None - if "Upload" in action.__name__: + if "Upload" in action.__name__ or "History" in action.__name__: action = action(progress=0) else: action = action() From 9b28a120e2a70711446b9c0489a596b9f3be66b8 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 17 Dec 2021 11:49:53 +0100 Subject: [PATCH 0657/1185] Fix megagroup attribute on ChatForbidden objects --- pyrogram/types/user_and_chats/chat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/types/user_and_chats/chat.py b/pyrogram/types/user_and_chats/chat.py index cf607c66df..4042c0739a 100644 --- a/pyrogram/types/user_and_chats/chat.py +++ b/pyrogram/types/user_and_chats/chat.py @@ -221,7 +221,7 @@ def _parse_channel_chat(client, channel: raw.types.Channel) -> "Chat": return Chat( id=peer_id, - type="supergroup" if channel.megagroup else "channel", + type="supergroup" if getattr(channel, "megagroup", None) else "channel", is_verified=getattr(channel, "verified", None), is_restricted=getattr(channel, "restricted", None), is_creator=getattr(channel, "creator", None), From ef6125b57ad22aa2aff83365216b6e90970f54df Mon Sep 17 00:00:00 2001 From: AduchiMergen Date: Tue, 21 Dec 2021 02:22:56 +0700 Subject: [PATCH 0658/1185] Fix -503 Timeout errors #664 (#812) --- pyrogram/client.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyrogram/client.py b/pyrogram/client.py index b54794fad2..61722ac3bc 100644 --- a/pyrogram/client.py +++ b/pyrogram/client.py @@ -916,9 +916,6 @@ async def get_file( while True: chunk = r.bytes - if not chunk: - break - f.write(chunk) offset += limit @@ -938,6 +935,9 @@ async def get_file( else: await self.loop.run_in_executor(self.executor, func) + if len(chunk) < limit: + break + r = await session.send( raw.functions.upload.GetFile( location=location, From fe764e0e2b26e1222f8ba7d45d756c41378d75f3 Mon Sep 17 00:00:00 2001 From: Sam Date: Tue, 21 Dec 2021 03:34:57 +0800 Subject: [PATCH 0659/1185] Update session string format (#818) --- pyrogram/storage/memory_storage.py | 3 ++- pyrogram/storage/storage.py | 9 +++++++-- pyrogram/utils.py | 1 + 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/pyrogram/storage/memory_storage.py b/pyrogram/storage/memory_storage.py index 4934354051..5f097a1c50 100644 --- a/pyrogram/storage/memory_storage.py +++ b/pyrogram/storage/memory_storage.py @@ -36,7 +36,8 @@ async def open(self): if self.name != ":memory:": dc_id, test_mode, auth_key, user_id, is_bot = struct.unpack( - self.SESSION_STRING_FORMAT, + (self.SESSION_STRING_FORMAT if len(self.name) == MemoryStorage.SESSION_STRING_SIZE else + self.SESSION_STRING_FORMAT_64), base64.urlsafe_b64decode( self.name + "=" * (-len(self.name) % 4) ) diff --git a/pyrogram/storage/storage.py b/pyrogram/storage/storage.py index c506c8ca04..3690cafd73 100644 --- a/pyrogram/storage/storage.py +++ b/pyrogram/storage/storage.py @@ -20,10 +20,14 @@ import struct from typing import List, Tuple +from pyrogram import utils + class Storage: SESSION_STRING_FORMAT = ">B?256sI?" + SESSION_STRING_FORMAT_64 = ">B?256sQ?" SESSION_STRING_SIZE = 351 + SESSION_STRING_SIZE_64 = 356 def __init__(self, name: str): self.name = name @@ -71,13 +75,14 @@ async def is_bot(self, value: bool = object): raise NotImplementedError async def export_session_string(self): + user_id = await self.user_id() return base64.urlsafe_b64encode( struct.pack( - self.SESSION_STRING_FORMAT, + self.SESSION_STRING_FORMAT if user_id < utils.MAX_USER_ID_OLD else self.SESSION_STRING_FORMAT_64, await self.dc_id(), await self.test_mode(), await self.auth_key(), - await self.user_id(), + user_id, await self.is_bot() ) ).decode().rstrip("=") diff --git a/pyrogram/utils.py b/pyrogram/utils.py index e5b15aad63..9d53d8bfba 100644 --- a/pyrogram/utils.py +++ b/pyrogram/utils.py @@ -159,6 +159,7 @@ def unpack_inline_message_id(inline_message_id: str) -> "raw.types.InputBotInlin MIN_CHANNEL_ID = -1002147483647 MAX_CHANNEL_ID = -1000000000000 MIN_CHAT_ID = -2147483647 +MAX_USER_ID_OLD = 2147483647 MAX_USER_ID = 999999999999 From 4bb57a4da347f2c07e3a932521bca5b39f3d8acb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sch=C3=BCtz?= Date: Mon, 20 Dec 2021 19:37:08 +0000 Subject: [PATCH 0660/1185] Include tests in sdist (#819) --- MANIFEST.in | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/MANIFEST.in b/MANIFEST.in index 4a750253e2..b1f5bc04f3 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,10 +1,11 @@ ## Include include README.md COPYING COPYING.lesser NOTICE requirements.txt recursive-include compiler *.py *.tl *.tsv *.txt +recursive-include tests *.py ## Exclude prune pyrogram/errors/exceptions prune pyrogram/raw/functions prune pyrogram/raw/types prune pyrogram/raw/base -exclude pyrogram/raw/all.py \ No newline at end of file +exclude pyrogram/raw/all.py From 419ecb1af5427a1a0bae8f1b782daf0dfbc40228 Mon Sep 17 00:00:00 2001 From: David Hu <81270448+D4v1dH03@users.noreply.github.com> Date: Mon, 20 Dec 2021 11:40:32 -0800 Subject: [PATCH 0661/1185] Add RPC Error: CHAT_FORWARDS_RESTRICTED (#825) * Add RPC Error: CHAT_FORWARD_RESTRICTED * Fix the typo in the previous commit Sorry, made a typo in the error name * Update 400_BAD_REQUEST.tsv Co-authored-by: Dan <14043624+delivrance@users.noreply.github.com> --- compiler/errors/source/400_BAD_REQUEST.tsv | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/errors/source/400_BAD_REQUEST.tsv b/compiler/errors/source/400_BAD_REQUEST.tsv index ab23069722..e1d29f3803 100644 --- a/compiler/errors/source/400_BAD_REQUEST.tsv +++ b/compiler/errors/source/400_BAD_REQUEST.tsv @@ -52,6 +52,7 @@ CHANNEL_TOO_LARGE The channel is too large to be deleted; this error is issued w CHAT_ABOUT_NOT_MODIFIED The chat about text was not modified because you tried to edit it using the same content CHAT_ABOUT_TOO_LONG The chat about text is too long CHAT_ADMIN_REQUIRED The method requires chat admin privileges +CHAT_FORWARDS_RESTRICTED The chat restricts forwarding content CHAT_ID_EMPTY The provided chat id is empty CHAT_ID_INVALID The chat id being used is invalid or not known yet. Make sure you see the chat before interacting with it CHAT_INVALID The chat is invalid @@ -346,4 +347,4 @@ WEBDOCUMENT_URL_EMPTY The web document URL is empty WEBDOCUMENT_URL_INVALID The web document URL is invalid WEBPAGE_CURL_FAILED Telegram server could not fetch the provided URL WEBPAGE_MEDIA_EMPTY The URL doesn't contain any valid media -YOU_BLOCKED_USER You blocked this user \ No newline at end of file +YOU_BLOCKED_USER You blocked this user From 56e7e110372aa3bdd8de37a90d79cfe34deba7e4 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 22 Dec 2021 14:01:05 +0100 Subject: [PATCH 0662/1185] Use a specialized exception for handling BadMsgNotification --- pyrogram/errors/__init__.py | 21 +++++++++++++++++++++ pyrogram/session/session.py | 30 ++++++------------------------ 2 files changed, 27 insertions(+), 24 deletions(-) diff --git a/pyrogram/errors/__init__.py b/pyrogram/errors/__init__.py index 1b94700faa..514e7a1280 100644 --- a/pyrogram/errors/__init__.py +++ b/pyrogram/errors/__init__.py @@ -18,3 +18,24 @@ from .exceptions import * from .rpc_error import UnknownError + + +class BadMsgNotification(Exception): + descriptions = { + 16: "The msg_id is too low, the client time has to be synchronized.", + 17: "The msg_id is too high, the client time has to be synchronized.", + 18: "Incorrect two lower order of the msg_id bits, the server expects the client message " + "msg_id to be divisible by 4.", + 19: "The container msg_id is the same as the msg_id of a previously received message.", + 20: "The message is too old, it cannot be verified by the server.", + 32: "The msg_seqno is too low.", + 33: "The msg_seqno is too high.", + 34: "An even msg_seqno was expected, but an odd one was received.", + 35: "An odd msg_seqno was expected, but an even one was received.", + 48: "Incorrect server salt.", + 64: "Invalid container." + } + + def __init__(self, code): + description = self.descriptions.get(code, "Unknown error code") + super().__init__(f"[{code}] {description}") diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index 721586a01c..06df212594 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -29,7 +29,9 @@ from pyrogram import raw from pyrogram.connection import Connection from pyrogram.crypto import mtproto -from pyrogram.errors import RPCError, InternalServerError, AuthKeyDuplicated, FloodWait, ServiceUnavailable +from pyrogram.errors import ( + RPCError, InternalServerError, AuthKeyDuplicated, FloodWait, ServiceUnavailable, BadMsgNotification +) from pyrogram.raw.all import layer from pyrogram.raw.core import TLObject, MsgContainer, Int, FutureSalt, FutureSalts from .internals import MsgId, MsgFactory @@ -44,7 +46,6 @@ def __init__(self): class Session: - INITIAL_SALT = 0x616e67656c696361 START_TIMEOUT = 1 WAIT_TIMEOUT = 15 SLEEP_THRESHOLD = 10 @@ -54,20 +55,6 @@ class Session: 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, client: "pyrogram.Client", @@ -129,7 +116,7 @@ async def start(self): self.network_task = self.loop.create_task(self.network_worker()) - self.current_salt = FutureSalt(0, 0, Session.INITIAL_SALT) + self.current_salt = FutureSalt(0, 0, 0) self.current_salt = FutureSalt( 0, 0, (await self._send( @@ -243,9 +230,7 @@ async def handle_packet(self, packet): MsgId.set_server_time(msg.msg_id / (2 ** 32)) if msg.seq_no % 2 != 0: - if msg.msg_id in self.pending_acks: - continue - else: + if msg.msg_id not in self.pending_acks: self.pending_acks.add(msg.msg_id) if isinstance(msg.body, (raw.types.MsgDetailedInfo, raw.types.MsgNewDetailedInfo)): @@ -395,10 +380,7 @@ async def _send(self, data: TLObject, wait_response: bool = True, timeout: float RPCError.raise_it(result, type(data)) elif isinstance(result, raw.types.BadMsgNotification): - raise Exception(self.BAD_MSG_DESCRIPTION.get( - result.error_code, - f"Error code {result.error_code}" - )) + raise BadMsgNotification(result.error_code) else: return result From 29b4615848591eaf4c982a0be722879671ad026f Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 22 Dec 2021 14:08:24 +0100 Subject: [PATCH 0663/1185] Update create/edit_chat_invite_link --- .../methods/invite_links/create_chat_invite_link.py | 11 +++++++++++ .../methods/invite_links/edit_chat_invite_link.py | 11 +++++++++++ 2 files changed, 22 insertions(+) diff --git a/pyrogram/methods/invite_links/create_chat_invite_link.py b/pyrogram/methods/invite_links/create_chat_invite_link.py index 006e7e1445..b90b9b1524 100644 --- a/pyrogram/methods/invite_links/create_chat_invite_link.py +++ b/pyrogram/methods/invite_links/create_chat_invite_link.py @@ -27,8 +27,10 @@ class CreateChatInviteLink(Scaffold): async def create_chat_invite_link( self, chat_id: Union[int, str], + name: str = None, expire_date: int = None, member_limit: int = None, + creates_join_request: bool = None ) -> "types.ChatInviteLink": """Create an additional invite link for a chat. @@ -41,6 +43,9 @@ async def create_chat_invite_link( Unique identifier for the target chat or username of the target channel/supergroup (in the format @username). + name (``str``, *optional*): + Invite link name. + expire_date (``int``, *optional*): Point in time (Unix timestamp) when the link will expire. Defaults to None (no expiration date). @@ -50,6 +55,10 @@ async def create_chat_invite_link( this invite link; 1-99999. Defaults to None (no member limit). + creates_join_request (``bool``, *optional*): + True, if users joining the chat via the link need to be approved by chat administrators. + If True, member_limit can't be specified. + Returns: :obj:`~pyrogram.types.ChatInviteLink`: On success, the new invite link is returned. @@ -67,6 +76,8 @@ async def create_chat_invite_link( peer=await self.resolve_peer(chat_id), expire_date=expire_date, usage_limit=member_limit, + title=name, + request_needed=creates_join_request ) ) diff --git a/pyrogram/methods/invite_links/edit_chat_invite_link.py b/pyrogram/methods/invite_links/edit_chat_invite_link.py index 07c84d3c4d..ea4be32f55 100644 --- a/pyrogram/methods/invite_links/edit_chat_invite_link.py +++ b/pyrogram/methods/invite_links/edit_chat_invite_link.py @@ -28,8 +28,10 @@ async def edit_chat_invite_link( self, chat_id: Union[int, str], invite_link: str, + name: str = None, expire_date: int = None, member_limit: int = None, + creates_join_request: bool = None ) -> "types.ChatInviteLink": """Edit a non-primary invite link. @@ -43,6 +45,9 @@ async def edit_chat_invite_link( invite_link (``str``): The invite link to edit + name (``str``, *optional*): + Invite link name. + expire_date (``int``, *optional*): Point in time (Unix timestamp) when the link will expire. Defaults to None (no change), pass 0 to set no expiration date. @@ -52,6 +57,10 @@ async def edit_chat_invite_link( invite link; 1-99999. Defaults to None (no change), pass 0 to set no member limit. + creates_join_request (``bool``, *optional*): + True, if users joining the chat via the link need to be approved by chat administrators. + If True, member_limit can't be specified. + Returns: :obj:`~pyrogram.types.ChatInviteLink`: On success, the new invite link is returned @@ -70,6 +79,8 @@ async def edit_chat_invite_link( link=invite_link, expire_date=expire_date, usage_limit=member_limit, + title=name, + request_needed=creates_join_request ) ) From 7d917f43e5c0ba0cd0aa442d5143c0e163d1660f Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 22 Dec 2021 14:12:57 +0100 Subject: [PATCH 0664/1185] Add approve/decline_chat_join_request --- compiler/docs/compiler.py | 2 + pyrogram/methods/invite_links/__init__.py | 12 ++-- .../invite_links/approve_chat_join_request.py | 55 +++++++++++++++++++ .../invite_links/decline_chat_join_request.py | 55 +++++++++++++++++++ 4 files changed, 120 insertions(+), 4 deletions(-) create mode 100644 pyrogram/methods/invite_links/approve_chat_join_request.py create mode 100644 pyrogram/methods/invite_links/decline_chat_join_request.py diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index be726c44eb..84fc4f2051 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -253,6 +253,8 @@ def get_title_list(s: str) -> list: get_chat_admin_invite_links_count get_chat_admins_with_invite_links delete_chat_admin_invite_links + approve_chat_join_request + decline_chat_join_request """, contacts=""" Contacts diff --git a/pyrogram/methods/invite_links/__init__.py b/pyrogram/methods/invite_links/__init__.py index 198ce1bd8f..ccdad31297 100644 --- a/pyrogram/methods/invite_links/__init__.py +++ b/pyrogram/methods/invite_links/__init__.py @@ -17,17 +17,19 @@ # along with Pyrogram. If not, see . +from .approve_chat_join_request import ApproveChatJoinRequest from .create_chat_invite_link import CreateChatInviteLink +from .decline_chat_join_request import DeclineChatJoinRequest from .delete_chat_admin_invite_links import DeleteChatAdminInviteLinks from .delete_chat_invite_link import DeleteChatInviteLink from .edit_chat_invite_link import EditChatInviteLink from .export_chat_invite_link import ExportChatInviteLink +from .get_chat_admin_invite_links import GetChatAdminInviteLinks +from .get_chat_admin_invite_links_count import GetChatAdminInviteLinksCount from .get_chat_admins_with_invite_links import GetChatAdminsWithInviteLinks from .get_chat_invite_link import GetChatInviteLink from .get_chat_invite_link_members import GetChatInviteLinkMembers from .get_chat_invite_link_members_count import GetChatInviteLinkMembersCount -from .get_chat_admin_invite_links import GetChatAdminInviteLinks -from .get_chat_admin_invite_links_count import GetChatAdminInviteLinksCount from .revoke_chat_invite_link import RevokeChatInviteLink @@ -43,6 +45,8 @@ class InviteLinks( DeleteChatAdminInviteLinks, GetChatAdminInviteLinksCount, GetChatAdminsWithInviteLinks, - GetChatInviteLink + GetChatInviteLink, + ApproveChatJoinRequest, + DeclineChatJoinRequest ): - pass \ No newline at end of file + pass diff --git a/pyrogram/methods/invite_links/approve_chat_join_request.py b/pyrogram/methods/invite_links/approve_chat_join_request.py new file mode 100644 index 0000000000..013b64feaf --- /dev/null +++ b/pyrogram/methods/invite_links/approve_chat_join_request.py @@ -0,0 +1,55 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan +# +# 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 Union + +from pyrogram import raw +from pyrogram.scaffold import Scaffold + + +class ApproveChatJoinRequest(Scaffold): + async def approve_chat_join_request( + self, + chat_id: Union[int, str], + user_id: int, + ) -> bool: + """Approve a chat join request. + + The bot must be an administrator in the chat for this to work and must have the *can_invite_users* administrator + right. + + Parameters: + chat_id (``int`` | ``str``): + Unique identifier for the target chat or username of the target channel/supergroup + (in the format @username). + + user_id (``int``): + Unique identifier of the target user. + + Returns: + ``bool``: True on success. + """ + await self.send( + raw.functions.messages.HideChatJoinRequest( + peer=await self.resolve_peer(chat_id), + user_id=await self.resolve_peer(user_id), + approved=True + ) + ) + + return True diff --git a/pyrogram/methods/invite_links/decline_chat_join_request.py b/pyrogram/methods/invite_links/decline_chat_join_request.py new file mode 100644 index 0000000000..5a0f942c1c --- /dev/null +++ b/pyrogram/methods/invite_links/decline_chat_join_request.py @@ -0,0 +1,55 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan +# +# 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 Union + +from pyrogram import raw +from pyrogram.scaffold import Scaffold + + +class DeclineChatJoinRequest(Scaffold): + async def decline_chat_join_request( + self, + chat_id: Union[int, str], + user_id: int, + ) -> bool: + """Decline a chat join request. + + The bot must be an administrator in the chat for this to work and must have the *can_invite_users* administrator + right. + + Parameters: + chat_id (``int`` | ``str``): + Unique identifier for the target chat or username of the target channel/supergroup + (in the format @username). + + user_id (``int``): + Unique identifier of the target user. + + Returns: + ``bool``: True on success. + """ + await self.send( + raw.functions.messages.HideChatJoinRequest( + peer=await self.resolve_peer(chat_id), + user_id=await self.resolve_peer(user_id), + approved=False + ) + ) + + return True From 8f8c85e8f3be80252b6a012230f7e5de87041828 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 22 Dec 2021 14:13:44 +0100 Subject: [PATCH 0665/1185] Update ChatInviteLink --- .../types/user_and_chats/chat_invite_link.py | 30 +++++++++++++++---- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/pyrogram/types/user_and_chats/chat_invite_link.py b/pyrogram/types/user_and_chats/chat_invite_link.py index f4803b6a73..1f8a9256fc 100644 --- a/pyrogram/types/user_and_chats/chat_invite_link.py +++ b/pyrogram/types/user_and_chats/chat_invite_link.py @@ -44,6 +44,12 @@ class ChatInviteLink(Object): creator (:obj:`~pyrogram.types.User`, *optional*): Creator of the link. + name (``str``, *optional*): + Invite link name + + creates_join_request (``bool``, *optional*): + True, if users joining the chat via the link need to be approved by chat administrators. + expire_date (``int``, *optional*): Point in time (Unix timestamp) when the link will expire or has been expired. @@ -53,36 +59,45 @@ class ChatInviteLink(Object): member_count (``int``, *optional*): Number of users that joined via this link and are currently member of the chat. + + pending_join_request_count (``int``, *optional*): + Number of pending join requests created using this link """ def __init__( self, *, invite_link: str, - creator: "types.User", date: int, is_primary: bool = None, is_revoked: bool = None, + creator: "types.User" = None, + name: str = None, + creates_join_request: bool = None, start_date: int = None, expire_date: int = None, member_limit: int = None, - member_count: int = None + member_count: int = None, + pending_join_request_count: int = None ): super().__init__() self.invite_link = invite_link - self.creator = creator self.date = date self.is_primary = is_primary self.is_revoked = is_revoked + self.creator = creator + self.name = name + self.creates_join_request = creates_join_request self.start_date = start_date self.expire_date = expire_date self.member_limit = member_limit self.member_count = member_count + self.pending_join_request_count = pending_join_request_count @staticmethod def _parse( client: "pyrogram.Client", - invite: "raw.types.ChatInviteExported", + invite: "raw.base.ExportedChatInvite", users: Dict[int, "raw.types.User"] = None ) -> "ChatInviteLink": creator = ( @@ -93,11 +108,14 @@ def _parse( return ChatInviteLink( invite_link=invite.link, - creator=creator, date=invite.date, is_primary=invite.permanent, is_revoked=invite.revoked, + creator=creator, + name=invite.title, + creates_join_request=invite.request_needed, expire_date=invite.expire_date, member_limit=invite.usage_limit, - member_count=invite.usage + member_count=invite.usage, + pending_join_request_count=invite.requested ) From d103ae48feb21a1668103110aa88a106af013702 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 22 Dec 2021 14:34:12 +0100 Subject: [PATCH 0666/1185] Add support for ChatJoinRequest events --- pyrogram/dispatcher.py | 11 ++- pyrogram/handlers/__init__.py | 1 + .../handlers/chat_join_request_handler.py | 47 +++++++++++ pyrogram/methods/decorators/__init__.py | 4 +- .../decorators/on_chat_join_request.py | 61 ++++++++++++++ pyrogram/types/user_and_chats/__init__.py | 4 +- .../types/user_and_chats/chat_join_request.py | 82 +++++++++++++++++++ 7 files changed, 205 insertions(+), 5 deletions(-) create mode 100644 pyrogram/handlers/chat_join_request_handler.py create mode 100644 pyrogram/methods/decorators/on_chat_join_request.py create mode 100644 pyrogram/types/user_and_chats/chat_join_request.py diff --git a/pyrogram/dispatcher.py b/pyrogram/dispatcher.py index 0f46d64236..ccd8b9da82 100644 --- a/pyrogram/dispatcher.py +++ b/pyrogram/dispatcher.py @@ -26,7 +26,7 @@ from pyrogram.handlers import ( CallbackQueryHandler, MessageHandler, DeletedMessagesHandler, UserStatusHandler, RawUpdateHandler, InlineQueryHandler, PollHandler, - ChosenInlineResultHandler, ChatMemberUpdatedHandler + ChosenInlineResultHandler, ChatMemberUpdatedHandler, ChatJoinRequestHandler ) from pyrogram.raw.types import ( UpdateNewMessage, UpdateNewChannelMessage, UpdateNewScheduledMessage, @@ -34,7 +34,8 @@ UpdateDeleteMessages, UpdateDeleteChannelMessages, UpdateBotCallbackQuery, UpdateInlineBotCallbackQuery, UpdateUserStatus, UpdateBotInlineQuery, UpdateMessagePoll, - UpdateBotInlineSend, UpdateChatParticipant, UpdateChannelParticipant + UpdateBotInlineSend, UpdateChatParticipant, UpdateChannelParticipant, + UpdateBotChatInviteRequester ) log = logging.getLogger(__name__) @@ -106,6 +107,9 @@ async def chosen_inline_result_parser(update, users, chats): async def chat_member_updated_parser(update, users, chats): return pyrogram.types.ChatMemberUpdated._parse(self.client, update, users, chats), ChatMemberUpdatedHandler + async def chat_join_request_parser(update, users, chats): + return pyrogram.types.ChatJoinRequest._parse(self.client, update, users, chats), ChatJoinRequestHandler + self.update_parsers = { Dispatcher.MESSAGE_UPDATES: message_parser, Dispatcher.DELETE_MESSAGES_UPDATES: deleted_messages_parser, @@ -114,7 +118,8 @@ async def chat_member_updated_parser(update, users, chats): (UpdateBotInlineQuery,): inline_query_parser, (UpdateMessagePoll,): poll_parser, (UpdateBotInlineSend,): chosen_inline_result_parser, - Dispatcher.CHAT_MEMBER_UPDATES: chat_member_updated_parser + Dispatcher.CHAT_MEMBER_UPDATES: chat_member_updated_parser, + (UpdateBotChatInviteRequester,): chat_join_request_parser } self.update_parsers = {key: value for key_tuple, value in self.update_parsers.items() for key in key_tuple} diff --git a/pyrogram/handlers/__init__.py b/pyrogram/handlers/__init__.py index 71ecab72b0..1312c6e59d 100644 --- a/pyrogram/handlers/__init__.py +++ b/pyrogram/handlers/__init__.py @@ -17,6 +17,7 @@ # along with Pyrogram. If not, see . from .callback_query_handler import CallbackQueryHandler +from .chat_join_request_handler import ChatJoinRequestHandler from .chat_member_updated_handler import ChatMemberUpdatedHandler from .chosen_inline_result_handler import ChosenInlineResultHandler from .deleted_messages_handler import DeletedMessagesHandler diff --git a/pyrogram/handlers/chat_join_request_handler.py b/pyrogram/handlers/chat_join_request_handler.py new file mode 100644 index 0000000000..f26abefc95 --- /dev/null +++ b/pyrogram/handlers/chat_join_request_handler.py @@ -0,0 +1,47 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan +# +# 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 .handler import Handler + + +class ChatJoinRequestHandler(Handler): + """The ChatJoinRequest handler class. Used to handle join chat requests. + It is intended to be used with :meth:`~pyrogram.Client.add_handler`. + + For a nicer way to register this handler, have a look at the + :meth:`~pyrogram.Client.on_chat_join_request` decorator. + + Parameters: + callback (``callable``): + Pass a function that will be called when a new ChatJoinRequest event arrives. It takes + *(client, chat_join_request)* as positional arguments (look at the section below for a detailed + description). + + filters (:obj:`Filters`): + Pass one or more filters to allow only a subset of updates to be passed in your callback function. + + Other parameters: + client (:obj:`~pyrogram.Client`): + The Client itself, useful when you want to call other API methods inside the handler. + + chat_join_request (:obj:`~pyrogram.types.ChatJoinRequest`): + The received chat join request. + """ + + def __init__(self, callback: callable, filters=None): + super().__init__(callback, filters) diff --git a/pyrogram/methods/decorators/__init__.py b/pyrogram/methods/decorators/__init__.py index 01ddffcee6..384560fe3e 100644 --- a/pyrogram/methods/decorators/__init__.py +++ b/pyrogram/methods/decorators/__init__.py @@ -17,6 +17,7 @@ # along with Pyrogram. If not, see . from .on_callback_query import OnCallbackQuery +from .on_chat_join_request import OnChatJoinRequest from .on_chat_member_updated import OnChatMemberUpdated from .on_chosen_inline_result import OnChosenInlineResult from .on_deleted_messages import OnDeletedMessages @@ -38,6 +39,7 @@ class Decorators( OnInlineQuery, OnPoll, OnChosenInlineResult, - OnChatMemberUpdated + OnChatMemberUpdated, + OnChatJoinRequest ): pass diff --git a/pyrogram/methods/decorators/on_chat_join_request.py b/pyrogram/methods/decorators/on_chat_join_request.py new file mode 100644 index 0000000000..93d48c1059 --- /dev/null +++ b/pyrogram/methods/decorators/on_chat_join_request.py @@ -0,0 +1,61 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan +# +# 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 Callable + +import pyrogram +from pyrogram.filters import Filter +from pyrogram.scaffold import Scaffold + + +class OnChatJoinRequest(Scaffold): + def on_chat_join_request( + self=None, + filters=None, + group: int = 0 + ) -> callable: + """Decorator for handling chat join requests. + + This does the same thing as :meth:`~pyrogram.Client.add_handler` using the + :obj:`~pyrogram.handlers.ChatJoinRequestHandler`. + + Parameters: + filters (:obj:`~pyrogram.filters`, *optional*): + Pass one or more filters to allow only a subset of updates to be passed in your function. + + group (``int``, *optional*): + The group identifier, defaults to 0. + """ + + def decorator(func: Callable) -> Callable: + if isinstance(self, pyrogram.Client): + self.add_handler(pyrogram.handlers.ChatJoinRequestHandler(func, filters), group) + elif isinstance(self, Filter) or self is None: + if not hasattr(func, "handlers"): + func.handlers = [] + + func.handlers.append( + ( + pyrogram.handlers.ChatJoinRequestHandler(func, self), + group if filters is None else filters + ) + ) + + return func + + return decorator diff --git a/pyrogram/types/user_and_chats/__init__.py b/pyrogram/types/user_and_chats/__init__.py index 39e5d786ac..22455486b8 100644 --- a/pyrogram/types/user_and_chats/__init__.py +++ b/pyrogram/types/user_and_chats/__init__.py @@ -21,6 +21,7 @@ from .chat_event import ChatEvent from .chat_event_filter import ChatEventFilter from .chat_invite_link import ChatInviteLink +from .chat_join_request import ChatJoinRequest from .chat_member import ChatMember from .chat_member_updated import ChatMemberUpdated from .chat_permissions import ChatPermissions @@ -53,5 +54,6 @@ "VoiceChatEnded", "VoiceChatMembersInvited", "ChatMemberUpdated", - "VoiceChatScheduled" + "VoiceChatScheduled", + "ChatJoinRequest" ] diff --git a/pyrogram/types/user_and_chats/chat_join_request.py b/pyrogram/types/user_and_chats/chat_join_request.py new file mode 100644 index 0000000000..4ed7c1ed38 --- /dev/null +++ b/pyrogram/types/user_and_chats/chat_join_request.py @@ -0,0 +1,82 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan +# +# 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 + +import pyrogram +from pyrogram import raw, utils +from pyrogram import types +from ..object import Object +from ..update import Update + + +class ChatJoinRequest(Object, Update): + """Represents a join request sent to a chat. + + Parameters: + chat (:obj:`~pyrogram.types.Chat`): + Chat to which the request was sent. + + from_user (:obj:`~pyrogram.types.User`): + User that sent the join request. + + date (``int``): + Date the request was sent in Unix time + + bio (``str``, *optional*): + Bio of the user. + + invite_link (:obj:`~pyrogram.types.ChatInviteLink`, *optional*): + Chat invite link that was used by the user to send the join request. + """ + + def __init__( + self, + *, + client: "pyrogram.Client" = None, + chat: "types.Chat", + from_user: "types.User", + date: int, + bio: str = None, + invite_link: "types.ChatInviteLink" = None + ): + super().__init__(client) + + self.chat = chat + self.from_user = from_user + self.date = date + self.bio = bio + self.invite_link = invite_link + + @staticmethod + def _parse( + client: "pyrogram.Client", + update: "raw.types.UpdateBotChatInviteRequester", + users: Dict[int, "raw.types.User"], + chats: Dict[int, "raw.types.Chat"] + ) -> "ChatJoinRequest": + chat_id = utils.get_raw_peer_id(update.peer) + + return ChatJoinRequest( + chat=types.Chat._parse_chat(client, chats[chat_id]), + from_user=types.User._parse(client, users[update.user_id]), + date=update.date, + bio=update.about, + invite_link=types.ChatInviteLink._parse(client, update.invite, users), + client=client + ) From a909dc12e7d2d587533f73e7036718fe4e7e88c6 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 22 Dec 2021 14:36:02 +0100 Subject: [PATCH 0667/1185] Add support for user profile buttons --- .../inline_keyboard_button.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/pyrogram/types/bots_and_keyboards/inline_keyboard_button.py b/pyrogram/types/bots_and_keyboards/inline_keyboard_button.py index 84d70642bd..b3882d9342 100644 --- a/pyrogram/types/bots_and_keyboards/inline_keyboard_button.py +++ b/pyrogram/types/bots_and_keyboards/inline_keyboard_button.py @@ -43,6 +43,9 @@ class InlineKeyboardButton(Object): An HTTP URL used to automatically authorize the user. Can be used as a replacement for the `Telegram Login Widget `_. + user_id (``id``, *optional*): + User id, for links to the user profile. + switch_inline_query (``str``, *optional*): If set, pressing the button will prompt the user to select one of their chats, open that chat and insert the bot's username and the specified inline query in the input field. Can be empty, in which case just @@ -68,6 +71,7 @@ def __init__( callback_data: Union[str, bytes] = None, url: str = None, login_url: "types.LoginUrl" = None, + user_id: int = None, switch_inline_query: str = None, switch_inline_query_current_chat: str = None, callback_game: "types.CallbackGame" = None @@ -75,9 +79,10 @@ def __init__( super().__init__() self.text = str(text) + self.callback_data = callback_data self.url = url self.login_url = login_url - self.callback_data = callback_data + self.user_id = user_id self.switch_inline_query = switch_inline_query self.switch_inline_query_current_chat = switch_inline_query_current_chat self.callback_game = callback_game @@ -110,6 +115,12 @@ def read(b: "raw.base.KeyboardButton"): login_url=types.LoginUrl.read(b) ) + if isinstance(b, raw.types.KeyboardButtonUserProfile): + return InlineKeyboardButton( + text=b.text, + user_id=b.user_id + ) + if isinstance(b, raw.types.KeyboardButtonSwitchInline): if b.same_peer: return InlineKeyboardButton( @@ -150,6 +161,12 @@ async def write(self, client: "pyrogram.Client"): bot=await client.resolve_peer(self.login_url.bot_username) ) + if self.user_id is not None: + return raw.types.InputKeyboardButtonUserProfile( + text=self.text, + user_id=await client.resolve_peer(self.user_id) + ) + if self.switch_inline_query is not None: return raw.types.KeyboardButtonSwitchInline( text=self.text, From 2024b3c120d0e04a4cce3f53b8e81421d1721937 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 22 Dec 2021 14:39:52 +0100 Subject: [PATCH 0668/1185] Rename kick(ed) occurrences to ban(ned) --- compiler/docs/compiler.py | 4 ++-- pyrogram/methods/chats/__init__.py | 6 +++--- ...{kick_chat_member.py => ban_chat_member.py} | 12 ++++++------ pyrogram/methods/chats/get_chat_members.py | 8 ++++---- pyrogram/methods/chats/iter_chat_members.py | 8 ++++---- pyrogram/methods/chats/unban_chat_member.py | 2 +- pyrogram/types/user_and_chats/chat.py | 18 +++++++++--------- pyrogram/types/user_and_chats/chat_member.py | 8 ++++---- 8 files changed, 33 insertions(+), 33 deletions(-) rename pyrogram/methods/chats/{kick_chat_member.py => ban_chat_member.py} (92%) diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index 84fc4f2051..c390391385 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -187,7 +187,7 @@ def get_title_list(s: str) -> list: Chats join_chat leave_chat - kick_chat_member + ban_chat_member unban_chat_member restrict_chat_member promote_chat_member @@ -501,7 +501,7 @@ def get_title_list(s: str) -> list: Chat.set_title Chat.set_description Chat.set_photo - Chat.kick_member + Chat.ban_member Chat.unban_member Chat.restrict_member Chat.promote_member diff --git a/pyrogram/methods/chats/__init__.py b/pyrogram/methods/chats/__init__.py index 31ffe4fd2f..71125ce0a2 100644 --- a/pyrogram/methods/chats/__init__.py +++ b/pyrogram/methods/chats/__init__.py @@ -18,6 +18,7 @@ from .add_chat_members import AddChatMembers from .archive_chats import ArchiveChats +from .ban_chat_member import BanChatMember from .create_channel import CreateChannel from .create_group import CreateGroup from .create_supergroup import CreateSupergroup @@ -30,13 +31,13 @@ from .get_chat_member import GetChatMember from .get_chat_members import GetChatMembers from .get_chat_members_count import GetChatMembersCount +from .get_chat_online_count import GetChatOnlineCount from .get_dialogs import GetDialogs from .get_dialogs_count import GetDialogsCount from .get_nearby_chats import GetNearbyChats from .iter_chat_members import IterChatMembers from .iter_dialogs import IterDialogs from .join_chat import JoinChat -from .kick_chat_member import KickChatMember from .leave_chat import LeaveChat from .mark_chat_unread import MarkChatUnread from .pin_chat_message import PinChatMessage @@ -53,14 +54,13 @@ from .unpin_all_chat_messages import UnpinAllChatMessages from .unpin_chat_message import UnpinChatMessage from .update_chat_username import UpdateChatUsername -from .get_chat_online_count import GetChatOnlineCount class Chats( GetChat, LeaveChat, JoinChat, - KickChatMember, + BanChatMember, UnbanChatMember, RestrictChatMember, PromoteChatMember, diff --git a/pyrogram/methods/chats/kick_chat_member.py b/pyrogram/methods/chats/ban_chat_member.py similarity index 92% rename from pyrogram/methods/chats/kick_chat_member.py rename to pyrogram/methods/chats/ban_chat_member.py index 495eac9402..0d17fec699 100644 --- a/pyrogram/methods/chats/kick_chat_member.py +++ b/pyrogram/methods/chats/ban_chat_member.py @@ -23,14 +23,14 @@ from pyrogram.scaffold import Scaffold -class KickChatMember(Scaffold): - async def kick_chat_member( +class BanChatMember(Scaffold): + async def ban_chat_member( self, chat_id: Union[int, str], user_id: Union[int, str], until_date: int = 0 ) -> Union["types.Message", bool]: - """Kick a user from a group, a supergroup or a channel. + """Ban 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. @@ -63,10 +63,10 @@ async def kick_chat_member( from time import time # Ban chat member forever - app.kick_chat_member(chat_id, user_id) + app.ban_chat_member(chat_id, user_id) - # Kick chat member and automatically unban after 24h - app.kick_chat_member(chat_id, user_id, int(time.time() + 86400)) + # Ban chat member and automatically unban after 24h + app.ban_chat_member(chat_id, user_id, int(time.time() + 86400)) """ chat_peer = await self.resolve_peer(chat_id) user_peer = await self.resolve_peer(user_id) diff --git a/pyrogram/methods/chats/get_chat_members.py b/pyrogram/methods/chats/get_chat_members.py index 64abf1e5e2..d22cc44670 100644 --- a/pyrogram/methods/chats/get_chat_members.py +++ b/pyrogram/methods/chats/get_chat_members.py @@ -28,7 +28,7 @@ class Filters: ALL = "all" - KICKED = "kicked" + BANNED = "banned" RESTRICTED = "restricted" BOTS = "bots" RECENT = "recent" @@ -72,7 +72,7 @@ async def get_chat_members( Filter used to select the kind of members you want to retrieve. Only applicable for supergroups and channels. It can be any of the followings: *"all"* - all kind of members, - *"kicked"* - kicked (banned) members only, + *"banned"* - banned members only, *"restricted"* - restricted members only, *"bots"* - bots only, *"recent"* - recent members only, @@ -83,7 +83,7 @@ async def get_chat_members( .. [1] Server limit: on supergroups, you can get up to 10,000 members for a single query and up to 200 members on channels. - .. [2] A query string is applicable only for *"all"*, *"kicked"* and *"restricted"* filters only. + .. [2] A query string is applicable only for *"all"*, *"banned"* and *"restricted"* filters only. Returns: List of :obj:`~pyrogram.types.ChatMember`: On success, a list of chat members is returned. @@ -121,7 +121,7 @@ async def get_chat_members( if filter == Filters.ALL: filter = raw.types.ChannelParticipantsSearch(q=query) - elif filter == Filters.KICKED: + elif filter == Filters.BANNED: filter = raw.types.ChannelParticipantsKicked(q=query) elif filter == Filters.RESTRICTED: filter = raw.types.ChannelParticipantsBanned(q=query) diff --git a/pyrogram/methods/chats/iter_chat_members.py b/pyrogram/methods/chats/iter_chat_members.py index 86ef457c12..3db3935567 100644 --- a/pyrogram/methods/chats/iter_chat_members.py +++ b/pyrogram/methods/chats/iter_chat_members.py @@ -26,7 +26,7 @@ class Filters: ALL = "all" - KICKED = "kicked" + BANNED = "banned" RESTRICTED = "restricted" BOTS = "bots" RECENT = "recent" @@ -34,7 +34,7 @@ class Filters: QUERIES = [""] + [str(i) for i in range(10)] + list(ascii_lowercase) -QUERYABLE_FILTERS = (Filters.ALL, Filters.KICKED, Filters.RESTRICTED) +QUERYABLE_FILTERS = (Filters.ALL, Filters.BANNED, Filters.RESTRICTED) class IterChatMembers(Scaffold): @@ -67,7 +67,7 @@ async def iter_chat_members( Filter used to select the kind of members you want to retrieve. Only applicable for supergroups and channels. It can be any of the followings: *"all"* - all kind of members, - *"kicked"* - kicked (banned) members only, + *"banned"* - banned members only, *"restricted"* - restricted members only, *"bots"* - bots only, *"recent"* - recent members only, @@ -77,7 +77,7 @@ async def iter_chat_members( .. [1] Server limit: on supergroups, you can get up to 10,000 members for a single query and up to 200 members on channels. - .. [2] A query string is applicable only for *"all"*, *"kicked"* and *"restricted"* filters only. + .. [2] A query string is applicable only for *"all"*, *"banned"* and *"restricted"* filters only. Returns: ``Generator``: A generator yielding :obj:`~pyrogram.types.ChatMember` objects. diff --git a/pyrogram/methods/chats/unban_chat_member.py b/pyrogram/methods/chats/unban_chat_member.py index 44c19286a6..9f81f9c7ce 100644 --- a/pyrogram/methods/chats/unban_chat_member.py +++ b/pyrogram/methods/chats/unban_chat_member.py @@ -28,7 +28,7 @@ async def unban_chat_member( chat_id: Union[int, str], user_id: Union[int, str] ) -> bool: - """Unban a previously kicked user in a supergroup or channel. + """Unban a previously banned 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/types/user_and_chats/chat.py b/pyrogram/types/user_and_chats/chat.py index 4042c0739a..5ba68aacff 100644 --- a/pyrogram/types/user_and_chats/chat.py +++ b/pyrogram/types/user_and_chats/chat.py @@ -479,18 +479,18 @@ async def set_photo(self, photo: str) -> bool: photo=photo ) - async def kick_member( + async def ban_member( self, user_id: Union[int, str], until_date: int = 0 ) -> Union["types.Message", bool]: - """Bound method *kick_member* of :obj:`~pyrogram.types.Chat`. + """Bound method *ban_member* of :obj:`~pyrogram.types.Chat`. Use as a shortcut for: .. code-block:: python - client.kick_chat_member( + client.ban_chat_member( chat_id=chat_id, user_id=user_id ) @@ -498,7 +498,7 @@ async def kick_member( Example: .. code-block:: python - chat.kick_member(123456789) + chat.ban_member(123456789) Note: In regular groups (non-supergroups), this method will only work if the "All Members Are Admins" setting is @@ -523,7 +523,7 @@ async def kick_member( RPCError: In case of a Telegram RPC error. """ - return await self._client.kick_chat_member( + return await self._client.ban_chat_member( chat_id=self.id, user_id=user_id, until_date=until_date @@ -840,7 +840,7 @@ async def get_members( Filter used to select the kind of members you want to retrieve. Only applicable for supergroups and channels. It can be any of the followings: *"all"* - all kind of members, - *"kicked"* - kicked (banned) members only, + *"banned"* - banned members only, *"restricted"* - restricted members only, *"bots"* - bots only, *"recent"* - recent members only, @@ -851,7 +851,7 @@ async def get_members( .. [1] Server limit: on supergroups, you can get up to 10,000 members for a single query and up to 200 members on channels. - .. [2] A query string is applicable only for *"all"*, *"kicked"* and *"restricted"* filters only. + .. [2] A query string is applicable only for *"all"*, *"banned"* and *"restricted"* filters only. Example: .. code-block:: python @@ -903,7 +903,7 @@ def iter_members( Filter used to select the kind of members you want to retrieve. Only applicable for supergroups and channels. It can be any of the followings: *"all"* - all kind of members, - *"kicked"* - kicked (banned) members only, + *"banned"* - banned members only, *"restricted"* - restricted members only, *"bots"* - bots only, *"recent"* - recent members only, @@ -914,7 +914,7 @@ def iter_members( .. [1] Server limit: on supergroups, you can get up to 10,000 members for a single query and up to 200 members on channels. - .. [2] A query string is applicable only for *"all"*, *"kicked"* and *"restricted"* filters only. + .. [2] A query string is applicable only for *"all"*, *"banned"* and *"restricted"* filters only. Example: .. code-block:: python diff --git a/pyrogram/types/user_and_chats/chat_member.py b/pyrogram/types/user_and_chats/chat_member.py index d33bcfc5d0..a6f2f4ccb9 100644 --- a/pyrogram/types/user_and_chats/chat_member.py +++ b/pyrogram/types/user_and_chats/chat_member.py @@ -31,14 +31,14 @@ class ChatMember(Object): status (``str``): The member's status in the chat. - Can be "creator", "administrator", "member", "restricted", "left" or "kicked". + Can be "creator", "administrator", "member", "restricted", "left" or "banned". title (``str``, *optional*): A custom title that will be shown to all members instead of "Owner" or "Admin". Creator (owner) and administrators only. Can be None in case there's no custom title set. until_date (``int``, *optional*): - Restricted and kicked only. + Restricted and banned only. Date when restrictions will be lifted for this user; unix time. joined_date (``int``, *optional*): @@ -53,7 +53,7 @@ class ChatMember(Object): Administrators only. Information about the user who promoted this member as administrator. restricted_by (:obj:`~pyrogram.types.User`, *optional*): - Restricted and kicked only. Information about the user who restricted or kicked this member. + Restricted and banned only. Information about the user who restricted or banned this member. is_member (``bool``, *optional*): Restricted only. True, if the user is a member of the chat at the moment of the request. @@ -305,7 +305,7 @@ def _parse(client, member, users, chats) -> "ChatMember": return ChatMember( user=user, - status="kicked" if member.banned_rights.view_messages else "restricted", + status="banned" if member.banned_rights.view_messages else "restricted", until_date=denied_permissions.until_date, joined_date=member.date, is_member=not member.left, From a138b46668219399c3591d0c38c05d57636cf897 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 22 Dec 2021 14:49:28 +0100 Subject: [PATCH 0669/1185] Add Message/Chat.has_protected_content --- pyrogram/types/messages_and_media/message.py | 6 ++++++ pyrogram/types/user_and_chats/chat.py | 7 +++++++ 2 files changed, 13 insertions(+) diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index 2fa21c4eb7..9e1c7e1a24 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -128,6 +128,9 @@ class Message(Object, Update): Signature of the post author for messages in channels, or the custom title of an anonymous group administrator. + has_protected_content (``str``, *optional*): + True, if the message can't be forwarded. + text (``str``, *optional*): For text messages, the actual UTF-8 text of the message, 0-4096 characters. If the message contains entities (bold, italic, ...) you can access *text.markdown* or @@ -312,6 +315,7 @@ def __init__( edit_date: int = None, media_group_id: str = None, author_signature: str = None, + has_protected_content: bool = None, text: Str = None, entities: List["types.MessageEntity"] = None, caption_entities: List["types.MessageEntity"] = None, @@ -382,6 +386,7 @@ def __init__( self.edit_date = edit_date self.media_group_id = media_group_id self.author_signature = author_signature + self.has_protected_content = has_protected_content self.text = text self.entities = entities self.caption_entities = caption_entities @@ -743,6 +748,7 @@ async def _parse( else None ), author_signature=message.post_author, + has_protected_content=message.noforwards, forward_from=forward_from, forward_sender_name=forward_sender_name, forward_from_chat=forward_from_chat, diff --git a/pyrogram/types/user_and_chats/chat.py b/pyrogram/types/user_and_chats/chat.py index 5ba68aacff..3324cc9862 100644 --- a/pyrogram/types/user_and_chats/chat.py +++ b/pyrogram/types/user_and_chats/chat.py @@ -83,6 +83,9 @@ class Chat(Object): It is accurate only in case the owner has set the chat photo, otherwise the dc_id will be the one assigned to the administrator who set the current chat photo. + has_protected_content (``bool``, *optional*): + True, if messages from the chat can't be forwarded to other chats. + invite_link (``str``, *optional*): Chat invite link, for groups, supergroups and channels. Returned only in :meth:`~pyrogram.Client.get_chat`. @@ -139,6 +142,7 @@ def __init__( bio: str = None, description: str = None, dc_id: int = None, + has_protected_content: bool = None, invite_link: str = None, pinned_message=None, sticker_set_name: str = None, @@ -167,6 +171,7 @@ def __init__( self.bio = bio self.description = description self.dc_id = dc_id + self.has_protected_content = has_protected_content self.invite_link = invite_link self.pinned_message = pinned_message self.sticker_set_name = sticker_set_name @@ -211,6 +216,7 @@ def _parse_chat_chat(client, chat: raw.types.Chat) -> "Chat": permissions=types.ChatPermissions._parse(getattr(chat, "default_banned_rights", None)), members_count=getattr(chat, "participants_count", None), dc_id=getattr(getattr(chat, "photo", None), "dc_id", None), + has_protected_content=chat.noforwards, client=client ) @@ -234,6 +240,7 @@ def _parse_channel_chat(client, channel: raw.types.Channel) -> "Chat": permissions=types.ChatPermissions._parse(getattr(channel, "default_banned_rights", None)), members_count=getattr(channel, "participants_count", None), dc_id=getattr(getattr(channel, "photo", None), "dc_id", None), + has_protected_content=channel.noforwards, client=client ) From e8076d1b8a9b9be3d1e37ea83abefe6f31f7ad05 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 22 Dec 2021 15:00:03 +0100 Subject: [PATCH 0670/1185] Add method get_discussion_message --- compiler/docs/compiler.py | 1 + pyrogram/methods/messages/__init__.py | 4 +- .../messages/get_discussion_message.py | 62 +++++++++++++++++++ 3 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 pyrogram/methods/messages/get_discussion_message.py diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index c390391385..6e320bf9b9 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -182,6 +182,7 @@ def get_title_list(s: str) -> list: search_messages search_global download_media + get_discussion_message """, chats=""" Chats diff --git a/pyrogram/methods/messages/__init__.py b/pyrogram/methods/messages/__init__.py index 45cccb7e4e..8dee8402c3 100644 --- a/pyrogram/methods/messages/__init__.py +++ b/pyrogram/methods/messages/__init__.py @@ -29,6 +29,7 @@ from .edit_message_reply_markup import EditMessageReplyMarkup from .edit_message_text import EditMessageText from .forward_messages import ForwardMessages +from .get_discussion_message import GetDiscussionMessage from .get_history import GetHistory from .get_history_count import GetHistoryCount from .get_media_group import GetMediaGroup @@ -104,6 +105,7 @@ class Messages( CopyMessage, CopyMediaGroup, SearchMessagesCount, - SearchGlobalCount + SearchGlobalCount, + GetDiscussionMessage ): pass diff --git a/pyrogram/methods/messages/get_discussion_message.py b/pyrogram/methods/messages/get_discussion_message.py new file mode 100644 index 0000000000..cda7ae5212 --- /dev/null +++ b/pyrogram/methods/messages/get_discussion_message.py @@ -0,0 +1,62 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan +# +# 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 Union + +from pyrogram import raw +from pyrogram import types +from pyrogram.scaffold import Scaffold + + +class GetDiscussionMessage(Scaffold): + async def get_discussion_message( + self, + chat_id: Union[int, str], + message_id: int, + ) -> "types.Message": + """Get the discussion message from the linked discussion group of a channel post. + + Reply to the returned message to leave a comment on the linked channel post. + + Parameters: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + + message_id (``int``): + Message id. + + Example: + .. code-block:: python + + # Get the discussion message + m = app.get_discussion_message(channel_id, message_id) + + # Comment to the post by replying + m.reply("comment") + """ + r = await self.send( + raw.functions.messages.GetDiscussionMessage( + peer=await self.resolve_peer(chat_id), + msg_id=message_id + ) + ) + + users = {u.id: u for u in r.users} + chats = {c.id: c for c in r.chats} + + return await types.Message._parse(self, r.messages[0], users, chats) From 9a2bc25bc7f318378fa846048e85b983917887cc Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 23 Dec 2021 16:53:03 +0100 Subject: [PATCH 0671/1185] Add support for "send_as" chats - Add methods get_send_as_chats() and set_send_as_chat() - Add field Chat.send_as_chat --- compiler/docs/compiler.py | 2 + pyrogram/methods/chats/__init__.py | 6 +- pyrogram/methods/chats/get_send_as_chats.py | 63 +++++++++++++++++++++ pyrogram/methods/chats/set_send_as_chat.py | 55 ++++++++++++++++++ pyrogram/types/user_and_chats/chat.py | 52 ++++++++++------- 5 files changed, 158 insertions(+), 20 deletions(-) create mode 100644 pyrogram/methods/chats/get_send_as_chats.py create mode 100644 pyrogram/methods/chats/set_send_as_chat.py diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index 6e320bf9b9..c8823ceb03 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -224,6 +224,8 @@ def get_title_list(s: str) -> list: mark_chat_unread get_chat_event_log get_chat_online_count + get_send_as_chats + set_send_as_chat """, users=""" Users diff --git a/pyrogram/methods/chats/__init__.py b/pyrogram/methods/chats/__init__.py index 71125ce0a2..ce8fe14c5b 100644 --- a/pyrogram/methods/chats/__init__.py +++ b/pyrogram/methods/chats/__init__.py @@ -35,6 +35,7 @@ from .get_dialogs import GetDialogs from .get_dialogs_count import GetDialogsCount from .get_nearby_chats import GetNearbyChats +from .get_send_as_chats import GetSendAsChats from .iter_chat_members import IterChatMembers from .iter_dialogs import IterDialogs from .join_chat import JoinChat @@ -48,6 +49,7 @@ from .set_chat_permissions import SetChatPermissions from .set_chat_photo import SetChatPhoto from .set_chat_title import SetChatTitle +from .set_send_as_chat import SetSendAsChat from .set_slow_mode import SetSlowMode from .unarchive_chats import UnarchiveChats from .unban_chat_member import UnbanChatMember @@ -94,6 +96,8 @@ class Chats( UnpinAllChatMessages, MarkChatUnread, GetChatEventLog, - GetChatOnlineCount + GetChatOnlineCount, + GetSendAsChats, + SetSendAsChat ): pass diff --git a/pyrogram/methods/chats/get_send_as_chats.py b/pyrogram/methods/chats/get_send_as_chats.py new file mode 100644 index 0000000000..6eb6c4a684 --- /dev/null +++ b/pyrogram/methods/chats/get_send_as_chats.py @@ -0,0 +1,63 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan +# +# 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 List, Union + +from pyrogram import raw +from pyrogram import types +from pyrogram.scaffold import Scaffold + + +class GetSendAsChats(Scaffold): + async def get_send_as_chats( + self, + chat_id: Union[int, str] + ) -> List["types.Chat"]: + """Get the list of "send_as" chats available. + + Parameters: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + + Returns: + List[:obj:`~pyrogram.types.Chat`]: The list of chats. + + Example: + .. code-block:: python + + chats = app.get_send_as_chats(chat_id) + print(chats) + """ + r = await self.send( + raw.functions.channels.GetSendAs( + peer=await self.resolve_peer(chat_id) + ) + ) + + users = {u.id: u for u in r.users} + chats = {c.id: c for c in r.chats} + + send_as_chats = types.List() + + for p in r.peers: + if isinstance(p, raw.types.PeerUser): + send_as_chats.append(types.Chat._parse_chat(self, users[p.user_id])) + else: + send_as_chats.append(types.Chat._parse_chat(self, chats[p.channel_id])) + + return send_as_chats diff --git a/pyrogram/methods/chats/set_send_as_chat.py b/pyrogram/methods/chats/set_send_as_chat.py new file mode 100644 index 0000000000..ccf13952dc --- /dev/null +++ b/pyrogram/methods/chats/set_send_as_chat.py @@ -0,0 +1,55 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan +# +# 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 Union + +from pyrogram import raw +from pyrogram.scaffold import Scaffold + + +class SetSendAsChat(Scaffold): + async def set_send_as_chat( + self, + chat_id: Union[int, str], + send_as_chat_id: Union[int, str] + ) -> bool: + """Set the default "send_as" chat for a chat. + + Use :meth:`~pyrogram.Client.get_send_as_chats` to get all the "send_as" chats available for use. + + Parameters: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + + send_as_chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the send_as chat. + + Returns: + ``bool``: On success, true is returned + + Example: + .. code-block:: python + + app.set_send_as_chat(chat_id, send_as_chat_id) + """ + return await self.send( + raw.functions.messages.SaveDefaultSendAs( + peer=await self.resolve_peer(chat_id), + send_as=await self.resolve_peer(send_as_chat_id) + ) + ) diff --git a/pyrogram/types/user_and_chats/chat.py b/pyrogram/types/user_and_chats/chat.py index 3324cc9862..4cee964097 100644 --- a/pyrogram/types/user_and_chats/chat.py +++ b/pyrogram/types/user_and_chats/chat.py @@ -120,6 +120,10 @@ class Chat(Object): linked_chat (:obj:`~pyrogram.types.Chat`, *optional*): The linked discussion group (in case of channels) or the linked channel (in case of supergroups). Returned only in :meth:`~pyrogram.Client.get_chat`. + + send_as_chat (:obj:`~pyrogram.types.Chat`, *optional*): + The default "send_as" chat. + Returned only in :meth:`~pyrogram.Client.get_chat`. """ def __init__( @@ -151,7 +155,8 @@ def __init__( restrictions: List["types.Restriction"] = None, permissions: "types.ChatPermissions" = None, distance: int = None, - linked_chat: "types.Chat" = None + linked_chat: "types.Chat" = None, + send_as_chat: "types.Chat" = None ): super().__init__(client) @@ -181,6 +186,7 @@ def __init__( self.permissions = permissions self.distance = distance self.linked_chat = linked_chat + self.send_as_chat = send_as_chat @staticmethod def _parse_user_chat(client, user: raw.types.User) -> "Chat": @@ -275,43 +281,51 @@ def _parse_dialog(client, peer, users: dict, chats: dict): @staticmethod async def _parse_full(client, chat_full: Union[raw.types.messages.ChatFull, raw.types.users.UserFull]) -> "Chat": + users = {u.id: u for u in chat_full.users} + chats = {c.id: c for c in chat_full.chats} + if isinstance(chat_full, raw.types.users.UserFull): - parsed_chat = Chat._parse_user_chat(client, chat_full.users[0]) - parsed_chat.bio = chat_full.full_user.about + full_user = chat_full.full_user - if chat_full.full_user.pinned_msg_id: + parsed_chat = Chat._parse_user_chat(client, users[full_user.id]) + parsed_chat.bio = full_user.about + + if full_user.pinned_msg_id: parsed_chat.pinned_message = await client.get_messages( parsed_chat.id, - message_ids=chat_full.full_user.pinned_msg_id + message_ids=full_user.pinned_msg_id ) else: full_chat = chat_full.full_chat - chat = None - linked_chat = None - - for c in chat_full.chats: - if full_chat.id == c.id: - chat = c - - if isinstance(full_chat, raw.types.ChannelFull): - if full_chat.linked_chat_id == c.id: - linked_chat = c + chat_raw = chats[full_chat.id] if isinstance(full_chat, raw.types.ChatFull): - parsed_chat = Chat._parse_chat_chat(client, chat) + parsed_chat = Chat._parse_chat_chat(client, chat_raw) parsed_chat.description = full_chat.about or None if isinstance(full_chat.participants, raw.types.ChatParticipants): parsed_chat.members_count = len(full_chat.participants.participants) else: - parsed_chat = Chat._parse_channel_chat(client, chat) + parsed_chat = Chat._parse_channel_chat(client, chat_raw) parsed_chat.members_count = full_chat.participants_count parsed_chat.description = full_chat.about or None # TODO: Add StickerSet type parsed_chat.can_set_sticker_set = full_chat.can_set_stickers parsed_chat.sticker_set_name = getattr(full_chat.stickerset, "short_name", None) - if linked_chat: - parsed_chat.linked_chat = Chat._parse_channel_chat(client, linked_chat) + + linked_chat_raw = chats.get(full_chat.linked_chat_id, None) + + if linked_chat_raw: + parsed_chat.linked_chat = Chat._parse_channel_chat(client, linked_chat_raw) + + default_send_as = full_chat.default_send_as + + if isinstance(default_send_as, raw.types.PeerUser): + send_as_raw = users[default_send_as.user_id] + else: + send_as_raw = chats[default_send_as.channel_id] + + parsed_chat.send_as_chat = Chat._parse_chat(client, send_as_raw) if full_chat.pinned_msg_id: parsed_chat.pinned_message = await client.get_messages( From cc4a850134e393dace1149a86bfb101b111f3474 Mon Sep 17 00:00:00 2001 From: Udith Amasura Date: Thu, 23 Dec 2021 22:26:22 +0530 Subject: [PATCH 0672/1185] Make bot_username optional for LoginUrl (#817) * make bot username optional * Update login_url.py * Update login_url.py Co-authored-by: Dan <14043624+delivrance@users.noreply.github.com> --- pyrogram/types/bots_and_keyboards/inline_keyboard_button.py | 2 +- pyrogram/types/bots_and_keyboards/login_url.py | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/pyrogram/types/bots_and_keyboards/inline_keyboard_button.py b/pyrogram/types/bots_and_keyboards/inline_keyboard_button.py index 84d70642bd..0e4bb40ba9 100644 --- a/pyrogram/types/bots_and_keyboards/inline_keyboard_button.py +++ b/pyrogram/types/bots_and_keyboards/inline_keyboard_button.py @@ -147,7 +147,7 @@ async def write(self, client: "pyrogram.Client"): if self.login_url is not None: return self.login_url.write( text=self.text, - bot=await client.resolve_peer(self.login_url.bot_username) + bot=await client.resolve_peer(self.login_url.bot_username or "self") ) if self.switch_inline_query is not None: diff --git a/pyrogram/types/bots_and_keyboards/login_url.py b/pyrogram/types/bots_and_keyboards/login_url.py index 21c7a45dcc..52ef8dc8ee 100644 --- a/pyrogram/types/bots_and_keyboards/login_url.py +++ b/pyrogram/types/bots_and_keyboards/login_url.py @@ -31,7 +31,8 @@ class LoginUrl(Object): url (``str``): An HTTP URL to be opened with user authorization data added to the query string when the button is pressed. If the user refuses to provide authorization data, the original URL without information about the user will - be opened. The data added is the same as described in Receiving authorization data. + be opened. The data added is the same as described in + `Receiving authorization data `. **NOTE**: You **must** always check the hash of the received data to verify the authentication and the integrity of the data as described in @@ -42,7 +43,7 @@ class LoginUrl(Object): bot_username (``str``, *optional*): Username of a bot, which will be used for user authorization. - See `Setting up `_ a bot for more details. + See `Setting up a bot `_ for more details. If not specified, the current bot's username will be assumed. The url's domain must be the same as the domain linked with the bot. See `Linking your domain to the bot `_ From 42c690757dc27a396fca1745f33bc041ff5a45c3 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 30 Dec 2021 08:57:02 +0100 Subject: [PATCH 0673/1185] Update API schema to Layer 136 --- compiler/api/source/main_api.tl | 40 +++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl index bc5b1bf365..3cfee8a5e0 100644 --- a/compiler/api/source/main_api.tl +++ b/compiler/api/source/main_api.tl @@ -106,8 +106,8 @@ chatForbidden#6592a1a7 id:long title:string = Chat; channel#8261ac61 flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true fake:flags.25?true gigagroup:flags.26?true noforwards:flags.27?true id:long access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int restriction_reason:flags.9?Vector admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int = Chat; channelForbidden#17d493d5 flags:# broadcast:flags.5?true megagroup:flags.8?true id:long access_hash:long title:string until_date:flags.16?int = Chat; -chatFull#46a6ffb4 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:long about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer theme_emoticon:flags.16?string requests_pending:flags.17?int recent_requesters:flags.17?Vector = ChatFull; -channelFull#56662e2e flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector default_send_as:flags.29?Peer = ChatFull; +chatFull#d18ee226 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:long about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer theme_emoticon:flags.16?string requests_pending:flags.17?int recent_requesters:flags.17?Vector available_reactions:flags.18?Vector = ChatFull; +channelFull#e13c3d20 flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector default_send_as:flags.29?Peer available_reactions:flags.30?Vector = ChatFull; chatParticipant#c02d4007 user_id:long inviter_id:long date:int = ChatParticipant; chatParticipantCreator#e46bcee4 user_id:long = ChatParticipant; @@ -120,7 +120,7 @@ chatPhotoEmpty#37c1011c = ChatPhoto; chatPhoto#1c6e1c11 flags:# has_video:flags.0?true photo_id:long stripped_thumb:flags.1?bytes dc_id:int = ChatPhoto; messageEmpty#90a6ca84 flags:# id:int peer_id:flags.0?Peer = Message; -message#85d6cbe2 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true noforwards:flags.26?true id:int from_id:flags.8?Peer peer_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?long reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long restriction_reason:flags.22?Vector ttl_period:flags.25?int = Message; +message#38116ee0 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true from_scheduled:flags.18?true legacy:flags.19?true edit_hide:flags.21?true pinned:flags.24?true noforwards:flags.26?true id:int from_id:flags.8?Peer peer_id:Peer fwd_from:flags.2?MessageFwdHeader via_bot_id:flags.11?long reply_to:flags.3?MessageReplyHeader date:int message:string media:flags.9?MessageMedia reply_markup:flags.6?ReplyMarkup entities:flags.7?Vector views:flags.10?int forwards:flags.10?int replies:flags.23?MessageReplies edit_date:flags.15?int post_author:flags.16?string grouped_id:flags.17?long reactions:flags.20?MessageReactions restriction_reason:flags.22?Vector ttl_period:flags.25?int = Message; messageService#2b085862 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true legacy:flags.19?true id:int from_id:flags.8?Peer peer_id:Peer reply_to:flags.3?MessageReplyHeader date:int action:MessageAction ttl_period:flags.25?int = Message; messageMediaEmpty#3ded6320 = MessageMedia; @@ -359,6 +359,7 @@ updateGroupCallConnection#b783982 flags:# presentation:flags.0?true params:DataJ updateBotCommands#4d712f2e peer:Peer bot_id:long commands:Vector = Update; updatePendingJoinRequests#7063c3db peer:Peer requests_pending:int recent_requesters:Vector = Update; updateBotChatInviteRequester#11dfa986 peer:Peer date:int user_id:long about:string invite:ExportedChatInvite qts:int = Update; +updateMessageReactions#154798c3 peer:Peer msg_id:int reactions:MessageReactions = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -592,6 +593,7 @@ messageEntityUnderline#9c4e7e8b offset:int length:int = MessageEntity; messageEntityStrike#bf0693d4 offset:int length:int = MessageEntity; messageEntityBlockquote#20df5d0 offset:int length:int = MessageEntity; messageEntityBankCard#761e6af4 offset:int length:int = MessageEntity; +messageEntitySpoiler#32ca960f offset:int length:int = MessageEntity; inputChannelEmpty#ee8c1e86 = InputChannel; inputChannel#f35aec28 channel_id:long access_hash:long = InputChannel; @@ -897,6 +899,7 @@ channelAdminLogEventActionChangeHistoryTTL#6e941a38 prev_value:int new_value:int channelAdminLogEventActionParticipantJoinByRequest#afb6144a invite:ExportedChatInvite approved_by:long = ChannelAdminLogEventAction; channelAdminLogEventActionToggleNoForwards#cb2ac766 new_value:Bool = ChannelAdminLogEventAction; channelAdminLogEventActionSendMessage#278f2868 message:Message = ChannelAdminLogEventAction; +channelAdminLogEventActionChangeAvailableReactions#9cf7f76a prev_value:Vector new_value:Vector = ChannelAdminLogEventAction; channelAdminLogEvent#1fad68cd id:long date:int user_id:long action:ChannelAdminLogEventAction = ChannelAdminLogEvent; @@ -1260,7 +1263,7 @@ account.resetPasswordFailedWait#e3779861 retry_date:int = account.ResetPasswordR account.resetPasswordRequestedWait#e9effc7d until_date:int = account.ResetPasswordResult; account.resetPasswordOk#e926d63e = account.ResetPasswordResult; -sponsoredMessage#d151e19a flags:# random_id:bytes from_id:Peer channel_post:flags.2?int start_param:flags.0?string message:string entities:flags.1?Vector = SponsoredMessage; +sponsoredMessage#3a836df8 flags:# random_id:bytes from_id:flags.3?Peer chat_invite:flags.4?ChatInvite chat_invite_hash:flags.4?string channel_post:flags.2?int start_param:flags.0?string message:string entities:flags.1?Vector = SponsoredMessage; messages.sponsoredMessages#65a4c7d5 messages:Vector chats:Vector users:Vector = messages.SponsoredMessages; @@ -1280,6 +1283,19 @@ messages.peerSettings#6880b94d settings:PeerSettings chats:Vector users:Ve auth.loggedOut#c3a2835f flags:# future_auth_token:flags.0?bytes = auth.LoggedOut; +reactionCount#6fb250d1 flags:# chosen:flags.0?true reaction:string count:int = ReactionCount; + +messageReactions#87b6e36 flags:# min:flags.0?true can_see_list:flags.2?true results:Vector recent_reactons:flags.1?Vector = MessageReactions; + +messageUserReaction#932844fa user_id:long reaction:string = MessageUserReaction; + +messages.messageReactionsList#a366923c flags:# count:int reactions:Vector users:Vector next_offset:flags.0?string = messages.MessageReactionsList; + +availableReaction#21d7c4b flags:# inactive:flags.0?true reaction:string title:string static_icon:Document appear_animation:Document select_animation:Document activate_animation:Document effect_animation:Document = AvailableReaction; + +messages.availableReactionsNotModified#9f071957 = messages.AvailableReactions; +messages.availableReactions#768e3aad hash:int reactions:Vector = messages.AvailableReactions; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -1418,9 +1434,9 @@ messages.deleteHistory#b08f922a flags:# just_clear:flags.0?true revoke:flags.1?t messages.deleteMessages#e58e95d2 flags:# revoke:flags.0?true id:Vector = messages.AffectedMessages; messages.receivedMessages#5a954c0 max_id:int = Vector; messages.setTyping#58943ee2 flags:# peer:InputPeer top_msg_id:flags.0?int action:SendMessageAction = Bool; -messages.sendMessage#d9d75a4 flags:# no_webpage:flags.1?true silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates; -messages.sendMedia#e25ff8e0 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int media:InputMedia message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates; -messages.forwardMessages#cc30290b flags:# silent:flags.5?true background:flags.6?true with_my_score:flags.8?true drop_author:flags.11?true drop_media_captions:flags.12?true from_peer:InputPeer id:Vector random_id:Vector to_peer:InputPeer schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates; +messages.sendMessage#d9d75a4 flags:# no_webpage:flags.1?true silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true peer:InputPeer reply_to_msg_id:flags.0?int message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates; +messages.sendMedia#e25ff8e0 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true peer:InputPeer reply_to_msg_id:flags.0?int media:InputMedia message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates; +messages.forwardMessages#cc30290b flags:# silent:flags.5?true background:flags.6?true with_my_score:flags.8?true drop_author:flags.11?true drop_media_captions:flags.12?true noforwards:flags.14?true from_peer:InputPeer id:Vector random_id:Vector to_peer:InputPeer schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates; messages.reportSpam#cf1592db peer:InputPeer = Bool; messages.getPeerSettings#efd9a6a2 peer:InputPeer = messages.PeerSettings; messages.report#8953ab4e peer:InputPeer id:Vector reason:ReportReason message:string = Bool; @@ -1499,7 +1515,7 @@ messages.faveSticker#b9ffc55b id:InputDocument unfave:Bool = Bool; messages.getUnreadMentions#46578472 peer:InputPeer offset_id:int add_offset:int limit:int max_id:int min_id:int = messages.Messages; messages.readMentions#f0189d3 peer:InputPeer = messages.AffectedHistory; messages.getRecentLocations#702a40e0 peer:InputPeer limit:int hash:long = messages.Messages; -messages.sendMultiMedia#f803138f flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true peer:InputPeer reply_to_msg_id:flags.0?int multi_media:Vector schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates; +messages.sendMultiMedia#f803138f flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true peer:InputPeer reply_to_msg_id:flags.0?int multi_media:Vector schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates; messages.uploadEncryptedFile#5057c497 peer:InputEncryptedChat file:InputEncryptedFile = EncryptedFile; messages.searchStickerSets#35705b8a flags:# exclude_featured:flags.0?true q:string hash:long = messages.FoundStickerSets; messages.getSplitRanges#1cff7e08 = Vector; @@ -1558,6 +1574,12 @@ messages.hideChatJoinRequest#7fe7e815 flags:# approved:flags.0?true peer:InputPe messages.hideAllChatJoinRequests#e085f4ea flags:# approved:flags.0?true peer:InputPeer link:flags.1?string = Updates; messages.toggleNoForwards#b11eafa2 peer:InputPeer enabled:Bool = Updates; messages.saveDefaultSendAs#ccfddf96 peer:InputPeer send_as:InputPeer = Bool; +messages.sendReaction#25690ce4 flags:# peer:InputPeer msg_id:int reaction:flags.0?string = Updates; +messages.getMessagesReactions#8bba90e6 peer:InputPeer id:Vector = Updates; +messages.getMessageReactionsList#e0ee6b77 flags:# peer:InputPeer id:int reaction:flags.0?string offset:flags.1?string limit:int = messages.MessageReactionsList; +messages.setChatAvailableReactions#14050ea6 peer:InputPeer available_reactions:Vector = Updates; +messages.getAvailableReactions#18dea0ac hash:int = messages.AvailableReactions; +messages.setDefaultReaction#d960c4d4 reaction:string = Bool; updates.getState#edd4882a = updates.State; updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference; @@ -1706,4 +1728,4 @@ stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats; -// LAYER 135 \ No newline at end of file +// LAYER 136 \ No newline at end of file From f7b9137a6852e6f8c20903d2eabf0b08a62fb092 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 30 Dec 2021 09:14:49 +0100 Subject: [PATCH 0674/1185] Add support for "spoiler" MessageEntity --- docs/source/topics/text-formatting.rst | 7 +++++++ pyrogram/parser/html.py | 4 +++- pyrogram/parser/markdown.py | 8 +++++++- pyrogram/types/messages_and_media/message_entity.py | 3 +++ 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/docs/source/topics/text-formatting.rst b/docs/source/topics/text-formatting.rst index 5d9a9376c2..bada964b16 100644 --- a/docs/source/topics/text-formatting.rst +++ b/docs/source/topics/text-formatting.rst @@ -34,6 +34,7 @@ list of the basic styles currently supported by Pyrogram. - *italic* - :strike:`strike` - :underline:`underline` +- spoiler - `text URL `_ - `user text mention `_ - ``inline fixed-width code`` @@ -63,6 +64,8 @@ To strictly use this mode, pass "markdown" to the *parse_mode* parameter when us ~~strike~~ + ##spoiler## + [text URL](https://docs.pyrogram.org/) [text user mention](tg://user?id=23122162) @@ -86,6 +89,7 @@ To strictly use this mode, pass "markdown" to the *parse_mode* parameter when us "__italic__, " "--underline--, " "~~strike~~, " + "##spoiler##, " "[mention](tg://user?id=23122162), " "[URL](https://pyrogram.org), " "`code`, " @@ -113,6 +117,8 @@ The following tags are currently supported: strike, strike, strike + spoiler + text URL inline mention @@ -136,6 +142,7 @@ The following tags are currently supported: "italic, " "underline, " "strike, " + "spoiler, " "mention, " "URL, " "code\n\n" diff --git a/pyrogram/parser/html.py b/pyrogram/parser/html.py index f3f55195c2..805ca02153 100644 --- a/pyrogram/parser/html.py +++ b/pyrogram/parser/html.py @@ -61,6 +61,8 @@ def handle_starttag(self, tag, attrs): elif tag == "pre": entity = raw.types.MessageEntityPre extra["language"] = "" + elif tag == "spoiler": + entity = raw.types.MessageEntitySpoiler elif tag == "a": url = attrs.get("href", "") @@ -153,7 +155,7 @@ def unparse(text: str, entities: list): start = entity.offset end = start + entity.length - if entity_type in ("bold", "italic", "underline", "strikethrough"): + if entity_type in ("bold", "italic", "underline", "strikethrough", "spoiler"): start_tag = f"<{entity_type[0]}>" end_tag = f"" elif entity_type in ("code", "pre", "blockquote"): diff --git a/pyrogram/parser/markdown.py b/pyrogram/parser/markdown.py index f71503d832..3652c4c48f 100644 --- a/pyrogram/parser/markdown.py +++ b/pyrogram/parser/markdown.py @@ -28,6 +28,7 @@ ITALIC_DELIM = "__" UNDERLINE_DELIM = "--" STRIKE_DELIM = "~~" +SPOILER_DELIM = "##" CODE_DELIM = "`" PRE_DELIM = "```" @@ -41,7 +42,8 @@ STRIKE_DELIM, UNDERLINE_DELIM, ITALIC_DELIM, - BOLD_DELIM + BOLD_DELIM, + SPOILER_DELIM ] ]] ))) @@ -90,6 +92,8 @@ async def parse(self, text: str, strict: bool = False): tag = "code" elif delim == PRE_DELIM: tag = "pre" + elif delim == SPOILER_DELIM: + tag = "spoiler" else: continue @@ -127,6 +131,8 @@ def unparse(text: str, entities: list): start_tag = end_tag = CODE_DELIM elif entity_type in ("pre", "blockquote"): start_tag = end_tag = PRE_DELIM + elif entity_type == "spoiler": + start_tag = end_tag = SPOILER_DELIM elif entity_type == "text_link": url = entity.url start_tag = "[" diff --git a/pyrogram/types/messages_and_media/message_entity.py b/pyrogram/types/messages_and_media/message_entity.py index a0fc86a922..9a211fde74 100644 --- a/pyrogram/types/messages_and_media/message_entity.py +++ b/pyrogram/types/messages_and_media/message_entity.py @@ -42,6 +42,7 @@ class MessageEntityType(AutoName): ITALIC = auto() UNDERLINE = auto() STRIKETHROUGH = auto() + SPOILER = auto() CODE = auto() PRE = auto() TEXT_LINK = auto() @@ -62,6 +63,7 @@ class MessageEntityType(AutoName): raw.types.MessageEntityPre: MessageEntityType.PRE, raw.types.MessageEntityUnderline: MessageEntityType.UNDERLINE, raw.types.MessageEntityStrike: MessageEntityType.STRIKETHROUGH, + raw.types.MessageEntitySpoiler: MessageEntityType.SPOILER, raw.types.MessageEntityBlockquote: MessageEntityType.BLOCKQUOTE, raw.types.MessageEntityTextUrl: MessageEntityType.TEXT_LINK, raw.types.MessageEntityMentionName: MessageEntityType.TEXT_MENTION, @@ -90,6 +92,7 @@ class MessageEntity(Object): - "italic": *italic text*. - "underline": underlined text. - "strikethrough": strikethrough text. + - "spoiler": spoiler text. - "code": monowidth string. - "pre": monowidth block (see *language* below). - "text_link": for clickable text URLs. From 00c91120d82b5ae3b23961e60b5edb5f2eaadde4 Mon Sep 17 00:00:00 2001 From: SUBIN <64341611+subinps@users.noreply.github.com> Date: Thu, 30 Dec 2021 15:36:37 +0530 Subject: [PATCH 0675/1185] Handle the case when default_send_as is None (#842) * fix parsing send_as peer. * Update chat.py Co-authored-by: Dan <14043624+delivrance@users.noreply.github.com> --- pyrogram/types/user_and_chats/chat.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/pyrogram/types/user_and_chats/chat.py b/pyrogram/types/user_and_chats/chat.py index 4cee964097..898a4325ca 100644 --- a/pyrogram/types/user_and_chats/chat.py +++ b/pyrogram/types/user_and_chats/chat.py @@ -320,12 +320,13 @@ async def _parse_full(client, chat_full: Union[raw.types.messages.ChatFull, raw. default_send_as = full_chat.default_send_as - if isinstance(default_send_as, raw.types.PeerUser): - send_as_raw = users[default_send_as.user_id] - else: - send_as_raw = chats[default_send_as.channel_id] - - parsed_chat.send_as_chat = Chat._parse_chat(client, send_as_raw) + if default_send_as: + if isinstance(default_send_as, raw.types.PeerUser): + send_as_raw = users[default_send_as.user_id] + else: + send_as_raw = chats[default_send_as.channel_id] + + parsed_chat.send_as_chat = Chat._parse_chat(client, send_as_raw) if full_chat.pinned_msg_id: parsed_chat.pinned_message = await client.get_messages( From 46d3d8aaf60a67e4603cf11cdbc0afdaf4b0dd01 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 30 Dec 2021 13:39:58 +0100 Subject: [PATCH 0676/1185] Update REACTION_INVALID error message --- compiler/errors/source/400_BAD_REQUEST.tsv | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/errors/source/400_BAD_REQUEST.tsv b/compiler/errors/source/400_BAD_REQUEST.tsv index e1d29f3803..1b12472c13 100644 --- a/compiler/errors/source/400_BAD_REQUEST.tsv +++ b/compiler/errors/source/400_BAD_REQUEST.tsv @@ -249,7 +249,7 @@ RANDOM_ID_INVALID The provided random ID is invalid RANDOM_LENGTH_INVALID The random length is invalid RANGES_INVALID Invalid range provided REACTION_EMPTY The reaction provided is empty -REACTION_INVALID Invalid reaction provided (only emoji are allowed) +REACTION_INVALID Invalid reaction provided (only valid emoji are allowed) REFLECTOR_NOT_AVAILABLE The call reflector is not available REPLY_MARKUP_BUY_EMPTY Reply markup for buy button empty REPLY_MARKUP_GAME_EMPTY The provided reply markup for the game is empty From b0a9d28bda7e1408b8355cc31f80af8e22e05d4e Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 30 Dec 2021 13:41:23 +0100 Subject: [PATCH 0677/1185] Add field Chat.available_reactions --- pyrogram/types/user_and_chats/chat.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pyrogram/types/user_and_chats/chat.py b/pyrogram/types/user_and_chats/chat.py index 4cee964097..6622c18139 100644 --- a/pyrogram/types/user_and_chats/chat.py +++ b/pyrogram/types/user_and_chats/chat.py @@ -124,6 +124,10 @@ class Chat(Object): send_as_chat (:obj:`~pyrogram.types.Chat`, *optional*): The default "send_as" chat. Returned only in :meth:`~pyrogram.Client.get_chat`. + + available_reactions (List of ``str``, *optional*): + Available reactions in the chat. + Returned only in :meth:`~pyrogram.Client.get_chat`. """ def __init__( @@ -156,7 +160,8 @@ def __init__( permissions: "types.ChatPermissions" = None, distance: int = None, linked_chat: "types.Chat" = None, - send_as_chat: "types.Chat" = None + send_as_chat: "types.Chat" = None, + available_reactions: List[str] = None ): super().__init__(client) @@ -187,6 +192,7 @@ def __init__( self.distance = distance self.linked_chat = linked_chat self.send_as_chat = send_as_chat + self.available_reactions = available_reactions @staticmethod def _parse_user_chat(client, user: raw.types.User) -> "Chat": @@ -336,6 +342,8 @@ async def _parse_full(client, chat_full: Union[raw.types.messages.ChatFull, raw. if isinstance(full_chat.exported_invite, raw.types.ChatInviteExported): parsed_chat.invite_link = full_chat.exported_invite.link + parsed_chat.available_reactions = full_chat.available_reactions or None + return parsed_chat @staticmethod From 1fa637553d797b377929346251691d8c5e91e2e6 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 30 Dec 2021 13:43:30 +0100 Subject: [PATCH 0678/1185] Add method send_reaction --- pyrogram/methods/messages/__init__.py | 4 +- pyrogram/methods/messages/send_reaction.py | 65 ++++++++++++++++++++++ 2 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 pyrogram/methods/messages/send_reaction.py diff --git a/pyrogram/methods/messages/__init__.py b/pyrogram/methods/messages/__init__.py index 8dee8402c3..84cc143db9 100644 --- a/pyrogram/methods/messages/__init__.py +++ b/pyrogram/methods/messages/__init__.py @@ -53,6 +53,7 @@ from .send_message import SendMessage from .send_photo import SendPhoto from .send_poll import SendPoll +from .send_reaction import SendReaction from .send_sticker import SendSticker from .send_venue import SendVenue from .send_video import SendVideo @@ -106,6 +107,7 @@ class Messages( CopyMediaGroup, SearchMessagesCount, SearchGlobalCount, - GetDiscussionMessage + GetDiscussionMessage, + SendReaction ): pass diff --git a/pyrogram/methods/messages/send_reaction.py b/pyrogram/methods/messages/send_reaction.py new file mode 100644 index 0000000000..a65cb5bcee --- /dev/null +++ b/pyrogram/methods/messages/send_reaction.py @@ -0,0 +1,65 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan +# +# 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 Union + +from pyrogram import raw +from pyrogram.scaffold import Scaffold + + +class SendReaction(Scaffold): + async def send_reaction( + self, + chat_id: Union[int, str], + message_id: int, + emoji: str = "" + ) -> bool: + """Send a reaction to a message. + + Parameters: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + + message_id (``int``): + Identifier of the message. + + emoji (``str``, *optional*): + Reaction emoji. + Pass "" as emoji (default) to retract the reaction. + + Returns: + ``bool``: On success, True is returned. + + Example: + .. code-block:: python + + # Send a reaction + app.send_reaction(chat_id, message_id, "🔥") + + # Retract a reaction + app.send_reaction(chat_id, message_id) + """ + await self.send( + raw.functions.messages.SendReaction( + peer=await self.resolve_peer(chat_id), + msg_id=message_id, + reaction=emoji + ) + ) + + return True From 2799011c07f83fa239f5409377ccb6a282115f66 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 30 Dec 2021 13:45:43 +0100 Subject: [PATCH 0679/1185] Add type Reaction --- pyrogram/types/messages_and_media/__init__.py | 4 +- pyrogram/types/messages_and_media/reaction.py | 49 +++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 pyrogram/types/messages_and_media/reaction.py diff --git a/pyrogram/types/messages_and_media/__init__.py b/pyrogram/types/messages_and_media/__init__.py index 864019fe24..9f31dde066 100644 --- a/pyrogram/types/messages_and_media/__init__.py +++ b/pyrogram/types/messages_and_media/__init__.py @@ -36,8 +36,10 @@ from .video_note import VideoNote from .voice import Voice from .webpage import WebPage +from .reaction import Reaction __all__ = [ "Animation", "Audio", "Contact", "Document", "Game", "Location", "Message", "MessageEntity", "Photo", "Thumbnail", - "StrippedThumbnail", "Poll", "PollOption", "Sticker", "Venue", "Video", "VideoNote", "Voice", "WebPage", "Dice" + "StrippedThumbnail", "Poll", "PollOption", "Sticker", "Venue", "Video", "VideoNote", "Voice", "WebPage", "Dice", + "Reaction" ] diff --git a/pyrogram/types/messages_and_media/reaction.py b/pyrogram/types/messages_and_media/reaction.py new file mode 100644 index 0000000000..9d4baa54fa --- /dev/null +++ b/pyrogram/types/messages_and_media/reaction.py @@ -0,0 +1,49 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan +# +# 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 ..object import Object + + +class Reaction(Object): + """Contains information about a reaction. + + Parameters: + emoji (``str``): + Reaction emoji. + + count (``int``): + Reaction count. + + chosen (``bool``): + Whether this is the chosen reaction. + """ + + def __init__( + self, + *, + client: "pyrogram.Client" = None, + emoji: str, + count: int, + chosen: bool + ): + super().__init__(client) + + self.emoji = emoji + self.count = count + self.chosen = chosen From fb64e143b6e55a325df9c31b7e69d938c95e1c80 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 30 Dec 2021 13:46:44 +0100 Subject: [PATCH 0680/1185] Add field Message.reactions --- pyrogram/types/messages_and_media/message.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index 9e1c7e1a24..9306a894ce 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -284,6 +284,9 @@ class Message(Object, Update): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. + reactions (List of :obj:`~pyrogram.types.Reaction`): + List of the reactions to this message. + link (``str``, *property*): Generate a link to this message, only for groups and channels. """ @@ -361,7 +364,8 @@ def __init__( "types.ReplyKeyboardMarkup", "types.ReplyKeyboardRemove", "types.ForceReply" - ] = None + ] = None, + reactions: List["types.Reaction"] = None ): super().__init__(client) @@ -428,6 +432,7 @@ def __init__( self.voice_chat_started = voice_chat_started self.voice_chat_ended = voice_chat_ended self.voice_chat_members_invited = voice_chat_members_invited + self.reactions = reactions @staticmethod async def _parse( @@ -721,6 +726,9 @@ async def _parse( from_user = types.User._parse(client, users.get(user_id, None)) sender_chat = types.Chat._parse(client, message, users, chats, is_chat=False) if not from_user else None + reactions = [types.Reaction(emoji=r.reaction, count=r.count, chosen=r.chosen) + for r in message.reactions.results] if message.reactions else None + parsed_message = Message( message_id=message.id, date=message.date, @@ -780,6 +788,7 @@ async def _parse( via_bot=types.User._parse(client, users.get(message.via_bot_id, None)), outgoing=message.out, reply_markup=reply_markup, + reactions=reactions, client=client ) From f6625192d02b882068fa0e70eaef124479b37ef2 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 3 Jan 2022 11:12:24 +0100 Subject: [PATCH 0681/1185] Add parameter protect_content to send_* methods --- pyrogram/methods/bots/send_game.py | 5 +++++ pyrogram/methods/messages/copy_message.py | 5 +++++ pyrogram/methods/messages/forward_messages.py | 9 +++++++-- pyrogram/methods/messages/send_animation.py | 5 +++++ pyrogram/methods/messages/send_audio.py | 5 +++++ pyrogram/methods/messages/send_contact.py | 5 +++++ pyrogram/methods/messages/send_dice.py | 5 +++++ pyrogram/methods/messages/send_document.py | 5 +++++ pyrogram/methods/messages/send_location.py | 5 +++++ pyrogram/methods/messages/send_media_group.py | 7 ++++++- pyrogram/methods/messages/send_message.py | 7 ++++++- pyrogram/methods/messages/send_photo.py | 5 +++++ pyrogram/methods/messages/send_poll.py | 5 +++++ pyrogram/methods/messages/send_sticker.py | 5 +++++ pyrogram/methods/messages/send_venue.py | 5 +++++ pyrogram/methods/messages/send_video.py | 5 +++++ pyrogram/methods/messages/send_video_note.py | 5 +++++ pyrogram/methods/messages/send_voice.py | 5 +++++ pyrogram/types/messages_and_media/message.py | 6 ++++++ 19 files changed, 100 insertions(+), 4 deletions(-) diff --git a/pyrogram/methods/bots/send_game.py b/pyrogram/methods/bots/send_game.py index 8f6d10ab82..f6e6a3409c 100644 --- a/pyrogram/methods/bots/send_game.py +++ b/pyrogram/methods/bots/send_game.py @@ -30,6 +30,7 @@ async def send_game( game_short_name: str, disable_notification: bool = None, reply_to_message_id: int = None, + protect_content: bool = None, reply_markup: Union[ "types.InlineKeyboardMarkup", "types.ReplyKeyboardMarkup", @@ -55,6 +56,9 @@ async def send_game( reply_to_message_id (``int``, *optional*): If the message is a reply, ID of the original message. + protect_content (``bool``, *optional*): + Protects the contents of the sent message from forwarding and saving. + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*): An object for an inline keyboard. If empty, one ‘Play game_title’ button will be shown automatically. If not empty, the first button must launch the game. @@ -80,6 +84,7 @@ async def send_game( silent=disable_notification or None, reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), + noforwards=protect_content, reply_markup=await reply_markup.write(self) if reply_markup else None ) ) diff --git a/pyrogram/methods/messages/copy_message.py b/pyrogram/methods/messages/copy_message.py index 3250ac8906..5e8869496d 100644 --- a/pyrogram/methods/messages/copy_message.py +++ b/pyrogram/methods/messages/copy_message.py @@ -37,6 +37,7 @@ async def copy_message( disable_notification: bool = None, reply_to_message_id: int = None, schedule_date: int = None, + protect_content: bool = None, reply_markup: Union[ "types.InlineKeyboardMarkup", "types.ReplyKeyboardMarkup", @@ -88,6 +89,9 @@ async def copy_message( schedule_date (``int``, *optional*): Date when the message will be automatically sent. Unix time. + protect_content (``bool``, *optional*): + Protects the contents of the sent message from forwarding and saving. + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. @@ -112,5 +116,6 @@ async def copy_message( disable_notification=disable_notification, reply_to_message_id=reply_to_message_id, schedule_date=schedule_date, + protect_content=protect_content, reply_markup=reply_markup ) diff --git a/pyrogram/methods/messages/forward_messages.py b/pyrogram/methods/messages/forward_messages.py index b67e50ec0b..9218bf6ee2 100644 --- a/pyrogram/methods/messages/forward_messages.py +++ b/pyrogram/methods/messages/forward_messages.py @@ -30,7 +30,8 @@ async def forward_messages( from_chat_id: Union[int, str], message_ids: Union[int, Iterable[int]], disable_notification: bool = None, - schedule_date: int = None + schedule_date: int = None, + protect_content: bool = None ) -> Union["types.Message", List["types.Message"]]: """Forward messages of any kind. @@ -56,6 +57,9 @@ async def forward_messages( schedule_date (``int``, *optional*): Date when the message will be automatically sent. Unix time. + protect_content (``bool``, *optional*): + Protects the contents of the sent message from forwarding and saving. + Returns: :obj:`~pyrogram.types.Message` | List of :obj:`~pyrogram.types.Message`: In case *message_ids* was an integer, the single forwarded message is returned, otherwise, in case *message_ids* was an iterable, @@ -82,7 +86,8 @@ async def forward_messages( id=message_ids, silent=disable_notification or None, random_id=[self.rnd_id() for _ in message_ids], - schedule_date=schedule_date + schedule_date=schedule_date, + noforwards=protect_content ) ) diff --git a/pyrogram/methods/messages/send_animation.py b/pyrogram/methods/messages/send_animation.py index d365ae562c..d1ee9585ff 100644 --- a/pyrogram/methods/messages/send_animation.py +++ b/pyrogram/methods/messages/send_animation.py @@ -46,6 +46,7 @@ async def send_animation( disable_notification: bool = None, reply_to_message_id: int = None, schedule_date: int = None, + protect_content: bool = None, reply_markup: Union[ "types.InlineKeyboardMarkup", "types.ReplyKeyboardMarkup", @@ -116,6 +117,9 @@ async def send_animation( schedule_date (``int``, *optional*): Date when the message will be automatically sent. Unix time. + protect_content (``bool``, *optional*): + Protects the contents of the sent message from forwarding and saving. + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. @@ -222,6 +226,7 @@ def progress(current, total): reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), schedule_date=schedule_date, + noforwards=protect_content, reply_markup=await reply_markup.write(self) if reply_markup else None, **await utils.parse_text_entities(self, caption, parse_mode, caption_entities) ) diff --git a/pyrogram/methods/messages/send_audio.py b/pyrogram/methods/messages/send_audio.py index 4fa683a9ea..eeb45f6543 100644 --- a/pyrogram/methods/messages/send_audio.py +++ b/pyrogram/methods/messages/send_audio.py @@ -45,6 +45,7 @@ async def send_audio( disable_notification: bool = None, reply_to_message_id: int = None, schedule_date: int = None, + protect_content: bool = None, reply_markup: Union[ "types.InlineKeyboardMarkup", "types.ReplyKeyboardMarkup", @@ -113,6 +114,9 @@ async def send_audio( schedule_date (``int``, *optional*): Date when the message will be automatically sent. Unix time. + protect_content (``bool``, *optional*): + Protects the contents of the sent message from forwarding and saving. + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. @@ -217,6 +221,7 @@ def progress(current, total): reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), schedule_date=schedule_date, + noforwards=protect_content, reply_markup=await reply_markup.write(self) if reply_markup else None, **await utils.parse_text_entities(self, caption, parse_mode, caption_entities) ) diff --git a/pyrogram/methods/messages/send_contact.py b/pyrogram/methods/messages/send_contact.py index 6317a8296a..f744fab033 100644 --- a/pyrogram/methods/messages/send_contact.py +++ b/pyrogram/methods/messages/send_contact.py @@ -34,6 +34,7 @@ async def send_contact( disable_notification: bool = None, reply_to_message_id: int = None, schedule_date: int = None, + protect_content: bool = None, reply_markup: Union[ "types.InlineKeyboardMarkup", "types.ReplyKeyboardMarkup", @@ -71,6 +72,9 @@ async def send_contact( schedule_date (``int``, *optional*): Date when the message will be automatically sent. Unix time. + protect_content (``bool``, *optional*): + Protects the contents of the sent message from forwarding and saving. + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. @@ -97,6 +101,7 @@ async def send_contact( reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), schedule_date=schedule_date, + noforwards=protect_content, reply_markup=await reply_markup.write(self) if reply_markup else None ) ) diff --git a/pyrogram/methods/messages/send_dice.py b/pyrogram/methods/messages/send_dice.py index 4b7422b316..6c7f70cd4d 100644 --- a/pyrogram/methods/messages/send_dice.py +++ b/pyrogram/methods/messages/send_dice.py @@ -31,6 +31,7 @@ async def send_dice( disable_notification: bool = None, reply_to_message_id: int = None, schedule_date: int = None, + protect_content: bool = None, reply_markup: Union[ "types.InlineKeyboardMarkup", "types.ReplyKeyboardMarkup", @@ -63,6 +64,9 @@ async def send_dice( schedule_date (``int``, *optional*): Date when the message will be automatically sent. Unix time. + protect_content (``bool``, *optional*): + Protects the contents of the sent message from forwarding and saving. + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. @@ -91,6 +95,7 @@ async def send_dice( reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), schedule_date=schedule_date, + noforwards=protect_content, reply_markup=await reply_markup.write(self) if reply_markup else None, message="" ) diff --git a/pyrogram/methods/messages/send_document.py b/pyrogram/methods/messages/send_document.py index a7e342855f..3661b2552e 100644 --- a/pyrogram/methods/messages/send_document.py +++ b/pyrogram/methods/messages/send_document.py @@ -43,6 +43,7 @@ async def send_document( disable_notification: bool = None, reply_to_message_id: int = None, schedule_date: int = None, + protect_content: bool = None, reply_markup: Union[ "types.InlineKeyboardMarkup", "types.ReplyKeyboardMarkup", @@ -105,6 +106,9 @@ async def send_document( schedule_date (``int``, *optional*): Date when the message will be automatically sent. Unix time. + protect_content (``bool``, *optional*): + Protects the contents of the sent message from forwarding and saving. + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. @@ -194,6 +198,7 @@ def progress(current, total): reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), schedule_date=schedule_date, + noforwards=protect_content, reply_markup=await reply_markup.write(self) if reply_markup else None, **await utils.parse_text_entities(self, caption, parse_mode, caption_entities) ) diff --git a/pyrogram/methods/messages/send_location.py b/pyrogram/methods/messages/send_location.py index e70d9661d0..1bc3b0326c 100644 --- a/pyrogram/methods/messages/send_location.py +++ b/pyrogram/methods/messages/send_location.py @@ -32,6 +32,7 @@ async def send_location( disable_notification: bool = None, reply_to_message_id: int = None, schedule_date: int = None, + protect_content: bool = None, reply_markup: Union[ "types.InlineKeyboardMarkup", "types.ReplyKeyboardMarkup", @@ -63,6 +64,9 @@ async def send_location( schedule_date (``int``, *optional*): Date when the message will be automatically sent. Unix time. + protect_content (``bool``, *optional*): + Protects the contents of the sent message from forwarding and saving. + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. @@ -89,6 +93,7 @@ async def send_location( reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), schedule_date=schedule_date, + noforwards=protect_content, reply_markup=await reply_markup.write(self) if reply_markup else None ) ) diff --git a/pyrogram/methods/messages/send_media_group.py b/pyrogram/methods/messages/send_media_group.py index 4073ddecaa..dc8ca7f000 100644 --- a/pyrogram/methods/messages/send_media_group.py +++ b/pyrogram/methods/messages/send_media_group.py @@ -44,6 +44,7 @@ async def send_media_group( disable_notification: bool = None, reply_to_message_id: int = None, schedule_date: int = None, + protect_content: bool = None, ) -> List["types.Message"]: """Send a group of photos or videos as an album. @@ -66,6 +67,9 @@ async def send_media_group( schedule_date (``int``, *optional*): Date when the message will be automatically sent. Unix time. + protect_content (``bool``, *optional*): + Protects the contents of the sent message from forwarding and saving. + Returns: List of :obj:`~pyrogram.types.Message`: On success, a list of the sent messages is returned. @@ -377,7 +381,8 @@ async def send_media_group( multi_media=multi_media, silent=disable_notification or None, reply_to_msg_id=reply_to_message_id, - schedule_date=schedule_date + schedule_date=schedule_date, + noforwards=protect_content ), sleep_threshold=60 ) diff --git a/pyrogram/methods/messages/send_message.py b/pyrogram/methods/messages/send_message.py index 552a233bbe..54fcfe4850 100644 --- a/pyrogram/methods/messages/send_message.py +++ b/pyrogram/methods/messages/send_message.py @@ -34,6 +34,7 @@ async def send_message( disable_notification: bool = None, reply_to_message_id: int = None, schedule_date: int = None, + protect_content: bool = None, reply_markup: Union[ "types.InlineKeyboardMarkup", "types.ReplyKeyboardMarkup", @@ -75,6 +76,9 @@ async def send_message( schedule_date (``int``, *optional*): Date when the message will be automatically sent. Unix time. + protect_content (``bool``, *optional*): + Protects the contents of the sent message from forwarding and saving. + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. @@ -132,7 +136,8 @@ async def send_message( schedule_date=schedule_date, reply_markup=await reply_markup.write(self) if reply_markup else None, message=message, - entities=entities + entities=entities, + noforwards=protect_content ) ) diff --git a/pyrogram/methods/messages/send_photo.py b/pyrogram/methods/messages/send_photo.py index fcc3c04c6c..ff69558974 100644 --- a/pyrogram/methods/messages/send_photo.py +++ b/pyrogram/methods/messages/send_photo.py @@ -41,6 +41,7 @@ async def send_photo( disable_notification: bool = None, reply_to_message_id: int = None, schedule_date: int = None, + protect_content: bool = None, reply_markup: Union[ "types.InlineKeyboardMarkup", "types.ReplyKeyboardMarkup", @@ -93,6 +94,9 @@ async def send_photo( schedule_date (``int``, *optional*): Date when the message will be automatically sent. Unix time. + protect_content (``bool``, *optional*): + Protects the contents of the sent message from forwarding and saving. + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. @@ -172,6 +176,7 @@ async def send_photo( reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), schedule_date=schedule_date, + noforwards=protect_content, reply_markup=await reply_markup.write(self) if reply_markup else None, **await utils.parse_text_entities(self, caption, parse_mode, caption_entities) ) diff --git a/pyrogram/methods/messages/send_poll.py b/pyrogram/methods/messages/send_poll.py index dc0ccf178c..6fbb0aab95 100644 --- a/pyrogram/methods/messages/send_poll.py +++ b/pyrogram/methods/messages/send_poll.py @@ -36,6 +36,7 @@ async def send_poll( disable_notification: bool = None, reply_to_message_id: int = None, schedule_date: int = None, + protect_content: bool = None, reply_markup: Union[ "types.InlineKeyboardMarkup", "types.ReplyKeyboardMarkup", @@ -83,6 +84,9 @@ async def send_poll( schedule_date (``int``, *optional*): Date when the message will be automatically sent. Unix time. + protect_content (``bool``, *optional*): + Protects the contents of the sent message from forwarding and saving. + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. @@ -117,6 +121,7 @@ async def send_poll( reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), schedule_date=schedule_date, + noforwards=protect_content, reply_markup=await reply_markup.write(self) if reply_markup else None ) ) diff --git a/pyrogram/methods/messages/send_sticker.py b/pyrogram/methods/messages/send_sticker.py index c7ae122a57..144bba37a5 100644 --- a/pyrogram/methods/messages/send_sticker.py +++ b/pyrogram/methods/messages/send_sticker.py @@ -37,6 +37,7 @@ async def send_sticker( disable_notification: bool = None, reply_to_message_id: int = None, schedule_date: int = None, + protect_content: bool = None, reply_markup: Union[ "types.InlineKeyboardMarkup", "types.ReplyKeyboardMarkup", @@ -71,6 +72,9 @@ async def send_sticker( schedule_date (``int``, *optional*): Date when the message will be automatically sent. Unix time. + protect_content (``bool``, *optional*): + Protects the contents of the sent message from forwarding and saving. + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. @@ -150,6 +154,7 @@ async def send_sticker( reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), schedule_date=schedule_date, + noforwards=protect_content, reply_markup=await reply_markup.write(self) if reply_markup else None, message="" ) diff --git a/pyrogram/methods/messages/send_venue.py b/pyrogram/methods/messages/send_venue.py index 7dbae7a0e9..557aa673b4 100644 --- a/pyrogram/methods/messages/send_venue.py +++ b/pyrogram/methods/messages/send_venue.py @@ -36,6 +36,7 @@ async def send_venue( disable_notification: bool = None, reply_to_message_id: int = None, schedule_date: int = None, + protect_content: bool = None, reply_markup: Union[ "types.InlineKeyboardMarkup", "types.ReplyKeyboardMarkup", @@ -80,6 +81,9 @@ async def send_venue( schedule_date (``int``, *optional*): Date when the message will be automatically sent. Unix time. + protect_content (``bool``, *optional*): + Protects the contents of the sent message from forwarding and saving. + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. @@ -113,6 +117,7 @@ async def send_venue( reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), schedule_date=schedule_date, + noforwards=protect_content, reply_markup=await reply_markup.write(self) if reply_markup else None ) ) diff --git a/pyrogram/methods/messages/send_video.py b/pyrogram/methods/messages/send_video.py index d42846eda4..2356317518 100644 --- a/pyrogram/methods/messages/send_video.py +++ b/pyrogram/methods/messages/send_video.py @@ -47,6 +47,7 @@ async def send_video( disable_notification: bool = None, reply_to_message_id: int = None, schedule_date: int = None, + protect_content: bool = None, reply_markup: Union[ "types.InlineKeyboardMarkup", "types.ReplyKeyboardMarkup", @@ -122,6 +123,9 @@ async def send_video( schedule_date (``int``, *optional*): Date when the message will be automatically sent. Unix time. + protect_content (``bool``, *optional*): + Protects the contents of the sent message from forwarding and saving. + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. @@ -228,6 +232,7 @@ def progress(current, total): reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), schedule_date=schedule_date, + noforwards=protect_content, reply_markup=await reply_markup.write(self) if reply_markup else None, **await utils.parse_text_entities(self, caption, parse_mode, caption_entities) ) diff --git a/pyrogram/methods/messages/send_video_note.py b/pyrogram/methods/messages/send_video_note.py index 91ba93fe4c..231e149820 100644 --- a/pyrogram/methods/messages/send_video_note.py +++ b/pyrogram/methods/messages/send_video_note.py @@ -39,6 +39,7 @@ async def send_video_note( disable_notification: bool = None, reply_to_message_id: int = None, schedule_date: int = None, + protect_content: bool = None, reply_markup: Union[ "types.InlineKeyboardMarkup", "types.ReplyKeyboardMarkup", @@ -85,6 +86,9 @@ async def send_video_note( schedule_date (``int``, *optional*): Date when the message will be automatically sent. Unix time. + protect_content (``bool``, *optional*): + Protects the contents of the sent message from forwarding and saving. + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. @@ -174,6 +178,7 @@ async def send_video_note( reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), schedule_date=schedule_date, + noforwards=protect_content, reply_markup=await reply_markup.write(self) if reply_markup else None, message="" ) diff --git a/pyrogram/methods/messages/send_voice.py b/pyrogram/methods/messages/send_voice.py index c534767bc4..9828ccf898 100644 --- a/pyrogram/methods/messages/send_voice.py +++ b/pyrogram/methods/messages/send_voice.py @@ -41,6 +41,7 @@ async def send_voice( disable_notification: bool = None, reply_to_message_id: int = None, schedule_date: int = None, + protect_content: bool = None, reply_markup: Union[ "types.InlineKeyboardMarkup", "types.ReplyKeyboardMarkup", @@ -91,6 +92,9 @@ async def send_voice( schedule_date (``int``, *optional*): Date when the message will be automatically sent. Unix time. + protect_content (``bool``, *optional*): + Protects the contents of the sent message from forwarding and saving. + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. @@ -178,6 +182,7 @@ async def send_voice( reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), schedule_date=schedule_date, + noforwards=protect_content, reply_markup=await reply_markup.write(self) if reply_markup else None, **await utils.parse_text_entities(self, caption, parse_mode, caption_entities) ) diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index 9306a894ce..9690baf992 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -2924,6 +2924,7 @@ async def copy( disable_notification: bool = None, reply_to_message_id: int = None, schedule_date: int = None, + protect_content: bool = None, reply_markup: Union[ "types.InlineKeyboardMarkup", "types.ReplyKeyboardMarkup", @@ -2979,6 +2980,9 @@ async def copy( schedule_date (``int``, *optional*): Date when the message will be automatically sent. Unix time. + protect_content (``bool``, *optional*): + Protects the contents of the sent message from forwarding and saving. + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. @@ -3008,6 +3012,7 @@ async def copy( disable_notification=disable_notification, reply_to_message_id=reply_to_message_id, schedule_date=schedule_date, + protect_content=protect_content, reply_markup=self.reply_markup if reply_markup is object else reply_markup ) elif self.media: @@ -3017,6 +3022,7 @@ async def copy( disable_notification=disable_notification, reply_to_message_id=reply_to_message_id, schedule_date=schedule_date, + protect_content=protect_content, reply_markup=self.reply_markup if reply_markup is object else reply_markup ) From ebaf1a23fbe797d24e9ecc8d803bf4e174f9e967 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 3 Jan 2022 11:19:02 +0100 Subject: [PATCH 0682/1185] Change markdown spoiler delimiter --- docs/source/topics/text-formatting.rst | 4 ++-- pyrogram/parser/markdown.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/source/topics/text-formatting.rst b/docs/source/topics/text-formatting.rst index bada964b16..e3b730e585 100644 --- a/docs/source/topics/text-formatting.rst +++ b/docs/source/topics/text-formatting.rst @@ -64,7 +64,7 @@ To strictly use this mode, pass "markdown" to the *parse_mode* parameter when us ~~strike~~ - ##spoiler## + ||spoiler|| [text URL](https://docs.pyrogram.org/) @@ -89,7 +89,7 @@ To strictly use this mode, pass "markdown" to the *parse_mode* parameter when us "__italic__, " "--underline--, " "~~strike~~, " - "##spoiler##, " + "||spoiler||, " "[mention](tg://user?id=23122162), " "[URL](https://pyrogram.org), " "`code`, " diff --git a/pyrogram/parser/markdown.py b/pyrogram/parser/markdown.py index 3652c4c48f..5d14f8e8cb 100644 --- a/pyrogram/parser/markdown.py +++ b/pyrogram/parser/markdown.py @@ -28,7 +28,7 @@ ITALIC_DELIM = "__" UNDERLINE_DELIM = "--" STRIKE_DELIM = "~~" -SPOILER_DELIM = "##" +SPOILER_DELIM = "||" CODE_DELIM = "`" PRE_DELIM = "```" From b283bce2623d3646f3d785378267e5db271a85ec Mon Sep 17 00:00:00 2001 From: Fernando Werneck Date: Wed, 5 Jan 2022 08:37:25 -0300 Subject: [PATCH 0683/1185] Add No Forwards chat option (#839) * Add No Forwards chat option * Fix chat.py --- compiler/docs/compiler.py | 1 + pyrogram/methods/chats/__init__.py | 4 +- pyrogram/methods/chats/toggle_no_forwards.py | 49 ++++++++++++++++++++ pyrogram/types/user_and_chats/chat.py | 23 +++++++++ 4 files changed, 76 insertions(+), 1 deletion(-) create mode 100644 pyrogram/methods/chats/toggle_no_forwards.py diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index c8823ceb03..7becd223cf 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -515,6 +515,7 @@ def get_title_list(s: str) -> list: Chat.join Chat.leave Chat.mark_unread + Chat.no_forwards """, user=""" User diff --git a/pyrogram/methods/chats/__init__.py b/pyrogram/methods/chats/__init__.py index ce8fe14c5b..87634793b8 100644 --- a/pyrogram/methods/chats/__init__.py +++ b/pyrogram/methods/chats/__init__.py @@ -56,6 +56,7 @@ from .unpin_all_chat_messages import UnpinAllChatMessages from .unpin_chat_message import UnpinChatMessage from .update_chat_username import UpdateChatUsername +from .toggle_no_forwards import ToggleNoForwards class Chats( @@ -98,6 +99,7 @@ class Chats( GetChatEventLog, GetChatOnlineCount, GetSendAsChats, - SetSendAsChat + SetSendAsChat, + ToggleNoForwards ): pass diff --git a/pyrogram/methods/chats/toggle_no_forwards.py b/pyrogram/methods/chats/toggle_no_forwards.py new file mode 100644 index 0000000000..182c8e7790 --- /dev/null +++ b/pyrogram/methods/chats/toggle_no_forwards.py @@ -0,0 +1,49 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-2021 Dan +# +# 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 Union + +from pyrogram.raw import functions +from pyrogram.scaffold import Scaffold + + +class ToggleNoForwards(Scaffold): + async def toggle_no_forwards( + self, + chat_id: Union[int, str], + enabled: bool = False + ) -> bool: + """Toggle no forwards chat option + + Parameters: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + + enabled (``bool``, *optional*): + Pass True to enable no forwards + + Returns: + ``bool``: On success, True is returned. + """ + + return await self.send( + functions.messages.ToggleNoForwards( + peer=await self.resolve_peer(chat_id), + enabled=enabled + ) + ) diff --git a/pyrogram/types/user_and_chats/chat.py b/pyrogram/types/user_and_chats/chat.py index 898a4325ca..8d95e4a94c 100644 --- a/pyrogram/types/user_and_chats/chat.py +++ b/pyrogram/types/user_and_chats/chat.py @@ -1011,3 +1011,26 @@ async def mark_unread(self, ) -> bool: """ return await self._client.mark_chat_unread(self.id) + + async def no_forwards(self, enabled: bool = False) -> bool: + """Bound method *toggle_no_forwards* of :obj:`~pyrogram.types.Chat`. + + Use as a shortcut for: + + .. code-block:: python + + client.toggle_no_forwards(chat_id, enabled) + + Example: + .. code-block:: python + + chat.toggle_no_forwards(True) + + Returns: + ``bool``: On success, True is returned. + """ + + return await self._client.toggle_no_forwards( + self.id, + enabled=enabled + ) From ac3d2b8d7a6e48d9fd304ebc8d3676028f4b2a02 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 5 Jan 2022 12:45:09 +0100 Subject: [PATCH 0684/1185] Rename methods and add proper docs --- compiler/docs/compiler.py | 3 ++- pyrogram/methods/chats/__init__.py | 4 ++-- ...forwards.py => set_chat_protected_content.py} | 16 +++++++++------- pyrogram/types/user_and_chats/chat.py | 16 ++++++++++------ 4 files changed, 23 insertions(+), 16 deletions(-) rename pyrogram/methods/chats/{toggle_no_forwards.py => set_chat_protected_content.py} (81%) diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index 7becd223cf..6dd8adf4db 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -226,6 +226,7 @@ def get_title_list(s: str) -> list: get_chat_online_count get_send_as_chats set_send_as_chat + set_chat_protected_content """, users=""" Users @@ -515,7 +516,7 @@ def get_title_list(s: str) -> list: Chat.join Chat.leave Chat.mark_unread - Chat.no_forwards + Chat.set_protected_content """, user=""" User diff --git a/pyrogram/methods/chats/__init__.py b/pyrogram/methods/chats/__init__.py index 87634793b8..70c02dee15 100644 --- a/pyrogram/methods/chats/__init__.py +++ b/pyrogram/methods/chats/__init__.py @@ -48,6 +48,7 @@ from .set_chat_description import SetChatDescription from .set_chat_permissions import SetChatPermissions from .set_chat_photo import SetChatPhoto +from .set_chat_protected_content import SetChatProtectedContent from .set_chat_title import SetChatTitle from .set_send_as_chat import SetSendAsChat from .set_slow_mode import SetSlowMode @@ -56,7 +57,6 @@ from .unpin_all_chat_messages import UnpinAllChatMessages from .unpin_chat_message import UnpinChatMessage from .update_chat_username import UpdateChatUsername -from .toggle_no_forwards import ToggleNoForwards class Chats( @@ -100,6 +100,6 @@ class Chats( GetChatOnlineCount, GetSendAsChats, SetSendAsChat, - ToggleNoForwards + SetChatProtectedContent ): pass diff --git a/pyrogram/methods/chats/toggle_no_forwards.py b/pyrogram/methods/chats/set_chat_protected_content.py similarity index 81% rename from pyrogram/methods/chats/toggle_no_forwards.py rename to pyrogram/methods/chats/set_chat_protected_content.py index 182c8e7790..8057873da3 100644 --- a/pyrogram/methods/chats/toggle_no_forwards.py +++ b/pyrogram/methods/chats/set_chat_protected_content.py @@ -22,28 +22,30 @@ from pyrogram.scaffold import Scaffold -class ToggleNoForwards(Scaffold): - async def toggle_no_forwards( +class SetChatProtectedContent(Scaffold): + async def set_chat_protected_content( self, chat_id: Union[int, str], - enabled: bool = False + enabled: bool ) -> bool: - """Toggle no forwards chat option + """Set the chat protected content setting. Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. - enabled (``bool``, *optional*): - Pass True to enable no forwards + enabled (``bool``): + Pass True to enable the protected content setting, False to disable. Returns: ``bool``: On success, True is returned. """ - return await self.send( + await self.send( functions.messages.ToggleNoForwards( peer=await self.resolve_peer(chat_id), enabled=enabled ) ) + + return True diff --git a/pyrogram/types/user_and_chats/chat.py b/pyrogram/types/user_and_chats/chat.py index 8d95e4a94c..486fe66fd2 100644 --- a/pyrogram/types/user_and_chats/chat.py +++ b/pyrogram/types/user_and_chats/chat.py @@ -325,7 +325,7 @@ async def _parse_full(client, chat_full: Union[raw.types.messages.ChatFull, raw. send_as_raw = users[default_send_as.user_id] else: send_as_raw = chats[default_send_as.channel_id] - + parsed_chat.send_as_chat = Chat._parse_chat(client, send_as_raw) if full_chat.pinned_msg_id: @@ -1012,25 +1012,29 @@ async def mark_unread(self, ) -> bool: return await self._client.mark_chat_unread(self.id) - async def no_forwards(self, enabled: bool = False) -> bool: - """Bound method *toggle_no_forwards* of :obj:`~pyrogram.types.Chat`. + async def set_protected_content(self, enabled: bool) -> bool: + """Bound method *set_protected_content* of :obj:`~pyrogram.types.Chat`. Use as a shortcut for: .. code-block:: python - client.toggle_no_forwards(chat_id, enabled) + client.set_chat_protected_content(chat_id, enabled) + + Parameters: + enabled (``bool``): + Pass True to enable the protected content setting, False to disable. Example: .. code-block:: python - chat.toggle_no_forwards(True) + chat.set_protected_content(enabled) Returns: ``bool``: On success, True is returned. """ - return await self._client.toggle_no_forwards( + return await self._client.set_chat_protected_content( self.id, enabled=enabled ) From 8c8288412fc80552f7a001d77d9afac905d81fc7 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 7 Jan 2022 10:18:51 +0100 Subject: [PATCH 0685/1185] Various improvements --- .github/FUNDING.yml | 3 +- .github/ISSUE_TEMPLATE/bug_report.md | 2 +- .github/ISSUE_TEMPLATE/config.yml | 6 +- .github/ISSUE_TEMPLATE/feature_request.md | 2 +- README.md | 58 ++- compiler/docs/compiler.py | 8 + compiler/docs/template/bound-methods.rst | 7 +- compiler/docs/template/methods.rst | 7 +- compiler/docs/template/types.rst | 1 - compiler/errors/source/400_BAD_REQUEST.tsv | 3 +- compiler/errors/source/401_UNAUTHORIZED.tsv | 2 +- docs/README.md | 8 - docs/requirements.txt | 4 +- docs/robots.txt | 7 - docs/scripts/releases.py | 84 ---- docs/scripts/sitemap.py | 81 ---- docs/source/_images/favicon.ico | Bin 16958 -> 0 bytes docs/source/_images/pyrogram.png | Bin 48928 -> 0 bytes docs/source/api/client.rst | 1 - docs/source/api/decorators.rst | 4 - docs/source/api/handlers.rst | 1 - docs/source/conf.py | 28 +- docs/source/faq.rst | 407 ------------------ .../client-started-but-nothing-happens.rst | 11 + ...alling-stop-restart-add-remove-handler.rst | 12 + docs/source/faq/how-to-avoid-flood-waits.rst | 23 + docs/source/faq/how-to-use-webhooks.rst | 9 + docs/source/faq/index.rst | 47 ++ ...ing-the-account-to-another-data-center.rst | 10 + docs/source/faq/peer-id-invalid-error.rst | 14 + ...-raised-exception-oserror-timeouterror.rst | 10 + ...interfaceerror-error-binding-parameter.rst | 13 + ...e3-operationalerror-database-is-locked.rst | 17 + ...e-account-has-been-limited-deactivated.rst | 16 + .../unicodeencodeerror-codec-cant-encode.rst | 7 + ...h-urls-gives-error-webpage-curl-failed.rst | 7 + ...le-clients-at-once-on-the-same-account.rst | 7 + ...same-file-id-across-different-accounts.rst | 6 + ...-ip-addresses-of-telegram-data-centers.rst | 30 ++ .../why-is-the-api-key-needed-for-bots.rst | 12 + ...eacting-slowly-in-supergroups-channels.rst | 18 + ...-event-handler-triggered-twice-or-more.rst | 28 ++ docs/source/glossary.rst | 86 ---- docs/source/index.rst | 65 +-- docs/source/intro/install.rst | 18 +- docs/source/intro/quickstart.rst | 35 +- docs/source/intro/setup.rst | 24 +- docs/source/license.rst | 16 - docs/source/start/auth.rst | 21 +- docs/source/start/errors.rst | 13 +- docs/source/start/examples/bot_keyboards.rst | 4 +- docs/source/start/examples/echobot.rst | 2 +- docs/source/start/examples/hello_world.rst | 6 - docs/source/start/examples/inline_queries.rst | 2 - .../source/start/examples/use_inline_bots.rst | 4 +- docs/source/start/examples/welcomebot.rst | 2 +- docs/source/start/invoking.rst | 99 +++-- docs/source/start/updates.rst | 52 +-- docs/source/support.rst | 74 ++-- docs/source/topics/advanced-usage.rst | 69 ++- docs/source/topics/bots-interaction.rst | 50 --- ...ssion-settings.rst => client-settings.rst} | 26 +- docs/source/topics/config-file.rst | 4 +- docs/source/topics/create-filters.rst | 2 +- docs/source/topics/debugging.rst | 68 ++- docs/source/topics/more-on-updates.rst | 1 - docs/source/topics/mtproto-vs-botapi.rst | 33 +- docs/source/topics/scheduling.rst | 4 +- docs/source/topics/serializing.rst | 5 +- docs/source/topics/smart-plugins.rst | 40 +- docs/source/topics/storage-engines.rst | 38 +- docs/source/topics/test-servers.rst | 17 +- docs/source/topics/text-formatting.rst | 26 +- docs/source/topics/tgcrypto.rst | 19 +- docs/source/topics/use-filters.rst | 10 +- docs/source/topics/voice-calls.rst | 4 +- pyrogram/client.py | 20 +- .../connection/transport/tcp/tcp_abridged.py | 1 - pyrogram/connection/transport/tcp/tcp_full.py | 2 +- .../transport/tcp/tcp_intermediate.py | 2 +- .../transport/tcp/tcp_intermediate_o.py | 2 +- pyrogram/errors/rpc_error.py | 2 +- pyrogram/filters.py | 9 - pyrogram/methods/bots/__init__.py | 2 +- pyrogram/methods/chats/get_chat_member.py | 4 +- pyrogram/methods/chats/get_chat_members.py | 18 +- .../methods/chats/get_chat_members_count.py | 2 +- pyrogram/methods/chats/iter_chat_members.py | 72 ++-- .../methods/chats/set_administrator_title.py | 2 +- pyrogram/methods/chats/set_slow_mode.py | 4 +- pyrogram/methods/contacts/add_contact.py | 2 +- pyrogram/methods/contacts/import_contacts.py | 6 +- pyrogram/methods/messages/copy_media_group.py | 5 +- pyrogram/methods/messages/download_media.py | 2 +- pyrogram/methods/messages/forward_messages.py | 1 - pyrogram/methods/messages/get_history.py | 6 +- .../methods/messages/get_history_count.py | 2 +- pyrogram/methods/messages/get_media_group.py | 4 +- pyrogram/methods/messages/get_messages.py | 4 +- pyrogram/methods/messages/read_history.py | 4 +- pyrogram/methods/messages/search_global.py | 10 +- pyrogram/methods/messages/search_messages.py | 16 +- pyrogram/methods/messages/send_animation.py | 2 +- pyrogram/methods/messages/send_audio.py | 5 +- .../methods/messages/send_cached_media.py | 2 +- pyrogram/methods/messages/send_contact.py | 2 +- pyrogram/methods/messages/send_dice.py | 6 +- pyrogram/methods/messages/send_document.py | 2 +- pyrogram/methods/messages/send_media_group.py | 2 +- pyrogram/methods/messages/send_message.py | 5 +- pyrogram/methods/messages/send_sticker.py | 2 +- pyrogram/methods/messages/send_video.py | 2 +- pyrogram/methods/messages/send_voice.py | 2 +- pyrogram/methods/users/get_common_chats.py | 2 +- pyrogram/methods/users/get_profile_photos.py | 6 +- .../methods/users/get_profile_photos_count.py | 2 +- pyrogram/methods/users/get_users.py | 2 +- pyrogram/methods/users/iter_profile_photos.py | 2 +- pyrogram/methods/utilities/add_handler.py | 1 - .../utilities/export_session_string.py | 1 - pyrogram/methods/utilities/idle.py | 1 - pyrogram/methods/utilities/remove_handler.py | 1 - pyrogram/methods/utilities/restart.py | 1 - pyrogram/methods/utilities/run.py | 5 +- pyrogram/methods/utilities/start.py | 1 - pyrogram/methods/utilities/stop.py | 1 - .../methods/utilities/stop_transmission.py | 3 +- pyrogram/raw/core/primitives/vector.py | 2 +- pyrogram/session/session.py | 8 - pyrogram/types/bots_and_keyboards/__init__.py | 2 +- pyrogram/types/inline_mode/__init__.py | 2 +- pyrogram/types/messages_and_media/message.py | 2 +- .../messages_and_media/message_entity.py | 2 +- pyrogram/types/user_and_chats/chat.py | 4 +- setup.py | 5 +- 135 files changed, 836 insertions(+), 1416 deletions(-) delete mode 100644 docs/README.md delete mode 100644 docs/robots.txt delete mode 100644 docs/scripts/releases.py delete mode 100644 docs/scripts/sitemap.py delete mode 100644 docs/source/_images/favicon.ico delete mode 100644 docs/source/_images/pyrogram.png delete mode 100644 docs/source/faq.rst create mode 100644 docs/source/faq/client-started-but-nothing-happens.rst create mode 100644 docs/source/faq/code-hangs-when-calling-stop-restart-add-remove-handler.rst create mode 100644 docs/source/faq/how-to-avoid-flood-waits.rst create mode 100644 docs/source/faq/how-to-use-webhooks.rst create mode 100644 docs/source/faq/index.rst create mode 100644 docs/source/faq/migrating-the-account-to-another-data-center.rst create mode 100644 docs/source/faq/peer-id-invalid-error.rst create mode 100644 docs/source/faq/socket-send-raised-exception-oserror-timeouterror.rst create mode 100644 docs/source/faq/sqlite3-interfaceerror-error-binding-parameter.rst create mode 100644 docs/source/faq/sqlite3-operationalerror-database-is-locked.rst create mode 100644 docs/source/faq/the-account-has-been-limited-deactivated.rst create mode 100644 docs/source/faq/unicodeencodeerror-codec-cant-encode.rst create mode 100644 docs/source/faq/uploading-with-urls-gives-error-webpage-curl-failed.rst create mode 100644 docs/source/faq/using-multiple-clients-at-once-on-the-same-account.rst create mode 100644 docs/source/faq/using-the-same-file-id-across-different-accounts.rst create mode 100644 docs/source/faq/what-are-the-ip-addresses-of-telegram-data-centers.rst create mode 100644 docs/source/faq/why-is-the-api-key-needed-for-bots.rst create mode 100644 docs/source/faq/why-is-the-client-reacting-slowly-in-supergroups-channels.rst create mode 100644 docs/source/faq/why-is-the-event-handler-triggered-twice-or-more.rst delete mode 100644 docs/source/glossary.rst delete mode 100644 docs/source/license.rst delete mode 100644 docs/source/topics/bots-interaction.rst rename docs/source/topics/{session-settings.rst => client-settings.rst} (54%) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 13e0071eb0..19d0cd78d1 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,2 +1,3 @@ github: delivrance -custom: https://docs.pyrogram.org/support +liberapay: delivrance +open_collective: pyrogram diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 7efd4ca160..e3bc5909d1 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,6 +1,6 @@ --- name: Bug Report -about: Create a bug report affecting the library or the documentation +about: Create a bug report affecting the framework or the documentation --- diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 37e0cd33be..453151d8bd 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -2,7 +2,7 @@ blank_issues_enabled: false contact_links: - name: Ask Pyrogram related questions url: https://stackoverflow.com/questions/tagged/pyrogram - about: This place is for issues about Pyrogram. If you'd like to ask a question, please do so at StackOverflow. - - name: Join the Telegram community + about: This place is only for reporting issues about Pyrogram. You can ask questions at StackOverflow. + - name: Join the Telegram channel url: https://t.me/pyrogram - about: Join the official channel to stay tuned for news and updates. \ No newline at end of file + about: Join the official channel and stay tuned for news, updates and announcements. \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 854db44d9c..bf5e6a21f3 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -7,7 +7,7 @@ labels: "enhancement" ## Checklist -- [ ] I believe the idea is awesome and would benefit the library. +- [ ] I believe the idea is awesome and would benefit the framework. - [ ] I have searched in the issue tracker for similar requests, including closed ones. ## Description diff --git a/README.md b/README.md index f8c4432e5f..e51ee0eb0b 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

- Pyrogram + Pyrogram
Telegram MTProto API Framework for Python @@ -9,17 +9,19 @@ Documentation • - + Releases • - - Community + + News

## Pyrogram +> Elegant, modern and asynchronous Telegram MTProto API framework in Python for users and bots + ``` python from pyrogram import Client, filters @@ -28,34 +30,33 @@ app = Client("my_account") @app.on_message(filters.private) async def hello(client, message): - await message.reply_text(f"Hello {message.from_user.mention}") + await message.reply("Hello from Pyrogram!") app.run() ``` -**Pyrogram** is a modern, elegant and easy-to-use [Telegram](https://telegram.org/) client library framework written -from the ground up in Python and C. It enables you to easily create custom Telegram client applications for both user -and bot identities (bot API alternative) via the [MTProto API](https://docs.pyrogram.org/topics/mtproto-vs-botapi). +**Pyrogram** is a modern, elegant and asynchronous [MTProto API](https://docs.pyrogram.org/topics/mtproto-vs-botapi) +framework. It enables you to easily interact with the main Telegram API through a user account (custom client) or a bot +identity (bot API alternative) using Python. -### Features +### Support -- **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. -- **Asynchronous**: Allows both synchronous and asynchronous models to fit all usage needs. -- **Documented**: API methods, types and public interfaces are all [well documented](https://docs.pyrogram.org). -- **Type-hinted**: Types and methods are all type-hinted, enabling excellent editor support. -- **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. +If you'd like to support Pyrogram, you can consider: + +- [Become a GitHub sponsor](https://github.com/sponsors/delivrance). +- [Become a LiberaPay patron](https://liberapay.com/delivrance). +- [Become an OpenCollective backer](https://opencollective.com/pyrogram>). -### Requirements +### Key Features -- Python 3.6 or higher. -- A [Telegram API key](https://docs.pyrogram.org/intro/setup#api-keys). +- **Ready**: Install Pyrogram with pip and start building your applications right away. +- **Easy**: Makes the Telegram API simple and intuitive, while still allowing advanced usages. +- **Elegant**: Low-level details are abstracted and re-presented in a more convenient way. +- **Fast**: Boosted up by [TgCrypto](https://github.com/pyrogram/tgcrypto), a high-performance crypto library written in pure C. +- **Type-hinted**: Types and methods are all type-hinted, enabling excellent editor support. +- **Async**: Fully asynchronous (also usable synchronously if wanted, for convenience). +- **Powerful**: Full access to Telegram's API to execute any official client action and more. ### Installing @@ -65,11 +66,6 @@ pip3 install pyrogram ### Resources -- The docs contain lots of resources to help you get started with Pyrogram: https://docs.pyrogram.org. -- Seeking extra help? Come join and ask our community: https://t.me/pyrogram. -- For other kind of inquiries, you can send a [message](https://t.me/haskell) or an [e-mail](mailto:dan@pyrogram.org). - -### Copyright & License - -- Copyright (C) 2017-2021 Dan <> -- Licensed under the terms of the [GNU Lesser General Public License v3 or later (LGPLv3+)](COPYING.lesser) +- Check out the docs at https://docs.pyrogram.org to learn more about Pyrogram, get started right +away and discover more in-depth material for building your client applications. +- Join the official channel at https://t.me/pyrogram and stay tuned for news, updates and announcements. diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index 6dd8adf4db..65f6743660 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -158,6 +158,7 @@ def get_title_list(s: str) -> list: send_venue send_contact send_cached_media + send_reaction edit_message_text edit_message_caption edit_message_media @@ -180,7 +181,9 @@ def get_title_list(s: str) -> list: retract_vote send_dice search_messages + search_messages_count search_global + search_global_count download_media get_discussion_message """, @@ -284,6 +287,7 @@ def get_title_list(s: str) -> list: send_game set_game_score get_game_high_scores + set_bot_commands """, authorization=""" Authorization @@ -360,6 +364,7 @@ def get_title_list(s: str) -> list: ChatEvent ChatEventFilter ChatMemberUpdated + ChatJoinRequest Dialog Restriction """, @@ -384,6 +389,7 @@ def get_title_list(s: str) -> list: Poll PollOption Dice + Reaction VoiceChatScheduled VoiceChatStarted VoiceChatEnded @@ -420,6 +426,8 @@ def get_title_list(s: str) -> list: InlineQueryResultArticle InlineQueryResultPhoto InlineQueryResultAnimation + InlineQueryResultAudio + InlineQueryResultVideo ChosenInlineResult """, input_message_content=""" diff --git a/compiler/docs/template/bound-methods.rst b/compiler/docs/template/bound-methods.rst index 13a51b047e..ebaa2ab899 100644 --- a/compiler/docs/template/bound-methods.rst +++ b/compiler/docs/template/bound-methods.rst @@ -1,12 +1,11 @@ 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 Pyrogram types define what are called bound methods. Bound methods are functions attached to a type which are +accessed via an instance of that type. 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 @@ -15,7 +14,7 @@ some of the required arguments. @app.on_message() def hello(client, message) - message.reply_text("hi") + message.reply("hi") app.run() diff --git a/compiler/docs/template/methods.rst b/compiler/docs/template/methods.rst index dc2950dca7..794e657e4c 100644 --- a/compiler/docs/template/methods.rst +++ b/compiler/docs/template/methods.rst @@ -1,18 +1,17 @@ Available Methods ================= -This page is about Pyrogram methods. All the methods listed here are bound to a :class:`~pyrogram.Client` instance, -except for :meth:`~pyrogram.idle()`, which is a special function that can be found in the main package directly. +This page is about Pyrogram methods. All the methods listed here are bound to a :class:`~pyrogram.Client` instance. +Some other utility functions can instead be found in the main package directly. .. code-block:: python - :emphasize-lines: 6 from pyrogram import Client app = Client("my_account") with app: - app.send_message("haskell", "hi") + app.send_message("me", "hi") .. contents:: Contents :backlinks: none diff --git a/compiler/docs/template/types.rst b/compiler/docs/template/types.rst index 8b34000623..2651c3559a 100644 --- a/compiler/docs/template/types.rst +++ b/compiler/docs/template/types.rst @@ -6,7 +6,6 @@ Unless required as argument to a client method, most of the types don't need to are only returned by other methods. You also don't need to import them, unless you want to type-hint your variables. .. code-block:: python - :emphasize-lines: 1 from pyrogram.types import User, Message, ... diff --git a/compiler/errors/source/400_BAD_REQUEST.tsv b/compiler/errors/source/400_BAD_REQUEST.tsv index 1b12472c13..d6754027ee 100644 --- a/compiler/errors/source/400_BAD_REQUEST.tsv +++ b/compiler/errors/source/400_BAD_REQUEST.tsv @@ -62,6 +62,7 @@ CHAT_NOT_MODIFIED The chat settings (title, permissions, photo, etc..) were not CHAT_RESTRICTED The chat is restricted and cannot be used CHAT_SEND_INLINE_FORBIDDEN You cannot use inline bots to send messages in this chat CHAT_TITLE_EMPTY The chat title is empty +CHAT_TOO_BIG The chat is too big for this action CODE_EMPTY The provided code is empty CODE_HASH_INVALID The provided code hash invalid CODE_INVALID The provided code is invalid (i.e. from email) @@ -347,4 +348,4 @@ WEBDOCUMENT_URL_EMPTY The web document URL is empty WEBDOCUMENT_URL_INVALID The web document URL is invalid WEBPAGE_CURL_FAILED Telegram server could not fetch the provided URL WEBPAGE_MEDIA_EMPTY The URL doesn't contain any valid media -YOU_BLOCKED_USER You blocked this user +YOU_BLOCKED_USER You blocked this user \ No newline at end of file diff --git a/compiler/errors/source/401_UNAUTHORIZED.tsv b/compiler/errors/source/401_UNAUTHORIZED.tsv index 1bb07b89ce..32e0265f53 100644 --- a/compiler/errors/source/401_UNAUTHORIZED.tsv +++ b/compiler/errors/source/401_UNAUTHORIZED.tsv @@ -2,7 +2,7 @@ id message ACTIVE_USER_REQUIRED The method is only available to already activated users AUTH_KEY_INVALID The key is invalid AUTH_KEY_PERM_EMPTY The method is unavailable for temporary authorization key, not bound to permanent -AUTH_KEY_UNREGISTERED The key is not registered in the system +AUTH_KEY_UNREGISTERED The key is not registered in the system. Delete your session file and login again SESSION_EXPIRED The authorization has expired SESSION_PASSWORD_NEEDED The two-step verification is enabled and a password is required SESSION_REVOKED The authorization has been invalidated, because of the user terminating all sessions diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 680e2cf488..0000000000 --- a/docs/README.md +++ /dev/null @@ -1,8 +0,0 @@ -# Pyrogram Docs - -- Install requirements. -- Install `pandoc` and `latexmk`. -- HTML: `make html` -- PDF: `make latexpdf` - -TODO: Explain better \ No newline at end of file diff --git a/docs/requirements.txt b/docs/requirements.txt index 2cfd56798c..f83e673d74 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,6 +1,6 @@ sphinx -sphinx_rtd_theme +sphinx_rtd_theme==1.0.0 sphinx_copybutton pypandoc requests -sphinx-autobuild \ No newline at end of file +sphinx-autobuild diff --git a/docs/robots.txt b/docs/robots.txt deleted file mode 100644 index 3e416f245f..0000000000 --- a/docs/robots.txt +++ /dev/null @@ -1,7 +0,0 @@ -User-agent: * - -Allow: / - -Disallow: /old* - -Sitemap: https://docs.pyrogram.org/sitemap.xml \ No newline at end of file diff --git a/docs/scripts/releases.py b/docs/scripts/releases.py deleted file mode 100644 index 4a32dd298f..0000000000 --- a/docs/scripts/releases.py +++ /dev/null @@ -1,84 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan -# -# 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 shutil -from datetime import datetime -from pathlib import Path - -import pypandoc -import requests - -URL = "https://api.github.com/repos/pyrogram/pyrogram/releases" -DEST = Path("../source/releases") -INTRO = """ -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. - -**Contents** - -""".lstrip("\n") - -shutil.rmtree(DEST, ignore_errors=True) -DEST.mkdir(parents=True) - -releases = requests.get(URL).json() - -with open(DEST / "index.rst", "w") as index: - index.write(INTRO) - - tags = [] - - for release in releases: - tag = release["tag_name"] - title = release["name"] - name = title.split(" - ")[1] - - date = datetime.strptime( - release["published_at"], - "%Y-%m-%dT%H:%M:%SZ" - ).strftime("%b %d, %Y") - - body = pypandoc.convert_text( - release["body"].replace(r"\r\n", "\n"), - "rst", - format="markdown_github", - extra_args=["--wrap=none"] - ) - - tarball_url = release["tarball_url"] - zipball_url = release["zipball_url"] - - index.write("- :doc:`{} <{}>`\n".format(title, tag)) - tags.append(tag) - - with open(DEST / "{}.rst".format(tag), "w") as page: - page.write("Pyrogram " + tag + "\n" + "=" * (len(tag) + 9) + "\n\n") - page.write("\t\tReleased on " + str(date) + "\n\n") - page.write("- :download:`Source Code (zip) <{}>`\n".format(zipball_url)) - page.write("- :download:`Source Code (tar.gz) <{}>`\n\n".format(tarball_url)) - page.write(name + "\n" + "-" * len(name) + "\n\n") - page.write(body + "\n\n") - - index.write("\n.. toctree::\n :hidden:\n\n") - index.write("\n".join(" {}".format(tag) for tag in tags)) diff --git a/docs/scripts/sitemap.py b/docs/scripts/sitemap.py deleted file mode 100644 index 9c28bd1c5c..0000000000 --- a/docs/scripts/sitemap.py +++ /dev/null @@ -1,81 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-2021 Dan -# -# 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 datetime -import os - -canonical = "https://docs.pyrogram.org/" - -dirs = { - ".": ("weekly", 1.0), - "intro": ("weekly", 0.9), - "start": ("weekly", 0.9), - "api": ("weekly", 0.8), - "topics": ("weekly", 0.8), - "releases": ("weekly", 0.8), - "telegram": ("weekly", 0.6) -} - - -def now(): - return datetime.datetime.today().strftime("%Y-%m-%d") - - -with open("sitemap.xml", "w") as f: - f.write('\n') - f.write('\n') - - urls = [] - - - def search(path): - try: - for j in os.listdir(path): - search(f"{path}/{j}") - except NotADirectoryError: - if not path.endswith(".rst"): - return - - path = path.split("/")[2:] - - if path[0].endswith(".rst"): - folder = "." - else: - folder = path[0] - - path = f"{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(f" \n") - f.write(f" {i[0]}\n") - f.write(f" {i[1]}\n") - f.write(f" {i[2]}\n") - f.write(f" {i[3]}\n") - f.write(f" \n") - - f.write("") diff --git a/docs/source/_images/favicon.ico b/docs/source/_images/favicon.ico deleted file mode 100644 index 4165f9edce821093f62506253dc379390f17cb64..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16958 zcmd5@2YeLA^*;W@AvVp16PTt(FOp~mgRw>Lp_oo62G?MlVlZG+bSD84AUc>TnnY29 zP-3uYqBqkFrWygEqjJKrlQ{n*FW)zJdz@B#$LRn&r{B};%6t6a3UI_k7O4p8*>aAu;y{-$TN#7OH>g@z+H)OrEACf3- z-dih82doi+0D+pdY}8MdpxczhP&PHcO7?d7uQ-+=iw@kA`Frll>>YPx=2y2B z&HDN__y+yodry`gzAtOeWPyj&1D%ebu9Qm;l}>dYmMlR{rYT!$=f5=;qd5KK9f$?)+l2M1A59=_N zHeUTvCZ(K{*ZRaMyFgouer-knIQ2g@9%$GrHi?7WEwDQlA@=b8Dhiv8;Ua-BJ$sOTWHLDs|W^;qM=l zA@eUu{K-s7JfFJ_wwH7vN0uMGD}yF|EoJIX(sbb*c+l9vgIvBtew9Mgd3#+JHP&h4 zx(rX1r3Y{4!OcF=&aFO`DZdA9%9*%~@apmF^BO;b+onm~-Unq8c)8(nPA)#PJ@T64 zW%ROR;@>DnndaVNb||z8DKy`}&l2arpcL%13alC5GBDJ$@XR z9X<~5Z>H~EqtzVcldd(Gi`nj~6B$ys&mr`Qn1C4=13H|Ppf|5bQ1@#R)Z@k@>W*uc zb-5^k?T_PG%mL&@KKY<*D#pT?RaYg+U|wLq65w0+7`;jP&5+~I7zZ`U1HU?RnHc^w zdk>$s6W&p4mQhO%3+G!skC{)tkS*QDoxxnbO9EOSkf5%YCAile&|L}cea|!TnVz>K zsPlR7AwV9d4S9lhz_Y-PC-bcbw1=Hw3sNr```alOAIi{q z2b3+KJi&t@#8{wQMTOP%pZT|KR2I*S=d*!WyV1W&zVOgc_Nnl1!iSuZz&Fz+xKD-; zxOMu`=RWl3BJ>EhA|HAX0NtVg%(>TCM{}fc?>KF}knUH1+H(YIv}T0x?}C-)_XGUT}hUT$n?qynuVVIPR%0sh5rY9L|9W zr?ces5l>O>27IC(csoZ{?X*wEuf67WE?5uWa5Bbn5yWJGF%x54t(RTL0Lq(!ZWqCP zAC+`g_zroeC;EA_c4bQa51uW4&LM%nKjRe-I0oLEbrJZD`%Db9W7S&D)^uR9xpqb_ z6#({+DEpw-rg^L<@w+UsZpx65x1WLT=h6f47ybh0)yLUGt{Hp7A6f_5=;fv5ska}* z?p85HVBLl}-cMry>>gsxmQ{?gbsCo9c74OTl7N5A+6<{a;928m{r531LI)l*A80;t zJl35WUZZr@wkz<5RQ!xFBbWFgeO>E!zY7>6*7avR){%TZONOn4?f-)T{$42S3F_e> zUXxg!OSazTX$#ZgJ9}PFQvU6RpOodt9(e3KV+A!^&rxx9;OzmMr{V*UeLeWee=h~Q zzj*&mk98zp0R9y&_%*zJ?@7@684@@=QvyE)jn0z5PckLw!wd-;0A1F2@fbb?;<*al z?n!x20QrdL1KJ+)r~|~`cGwBTLOkPN_04_hJUm6klYyW6E)HM7(u%%*olXl>9NI{C zw6Y|~e^h*?RPAS%Yv9lf@sF}eg&8?gVWt5&%Kc+(5-=uHf(L>ZZuEh88x6g$_&X{A zF%-)K3GNG?Vm;w$43xuqsKX~GW#u=YtCfKA7`dHY=AWx_zPk0hVjh)FIJl% z%?7P@#;dh=^eOG%#0{BJV{E4Q#{%~p1I$KzVA(7?1$>q*L2u=rKPdMBvmXe3E4INj z^-`9MOU{xSy)Ssg&+*?4K9Ihz`aEsm=w(N-=cN3A!_fQZ)&5+lHG;OU^YB!mj_BWW z&96rtQx9fK$fA7U*7E~A^Pg&yz@g9w_)vk6dyCQcW!X`CJj=DcKYeeL-jlCMfCn2` zq4^#e82z1FA6To8>p080Gr~9)_T%nD?nTU@mfwO83yM`4C;cXViCCj&9U>3oPG(8{ zB{nIq$CaNvbz$~H2^b4I(f7W*JugFOTeZGwDgFBBEw??&Ke+8)8NT?6TmMI^i}2rBEW~Yi*YtOq&H=v*G5}^C}!Rzi>@?m_oytgDnQea#4 zZ4y7%H=f3SX!irc7(3Ub`uAP)ToX^-d_jU=pHBI6Jy;yF=ic2q;(u@YZp2bO&!Ma@ z31jo~ofyNjpI{8Q@T2VX9OhTm!Ev{7QP=v2f7+IdQZYPM zlRxmo28LKy7ODKX44z`&%W!?i=z1OcwFXpu{?oy)>CtE4slGk#e+*lES?J>$@gS&8 zntZh2GWK8G`@UM8#2>Tig4#F2e9yH2@pDgS4#$6=aa+`$qxQU~GzDW|$X1(_iG4ae zD2sWr_QD)l@=b0X*l^h16dmR5<2QfAZ{r`X9FN!r#LEs~z|HOn- z_OV~fas#*vo5Ln^{_nvU_Z$6JsQo0Z?Z>6;XUzU=DF@j-(OlrL1?5ps<+(W$kNq6B zt@cx3*J^al8xwjN`)TbzIq8+$U2%+Bd_ErcYP6m$%MRUf^4|45 zch}r|`e2JqN;yRsz_yrt~>PdS%I_v!C)uXG9aN8RXRPPGd?M`c-jxC@a=(Nrz8!#6xNV_YI2Om}YqMGev z+?V?-(VIQ5Q;hn#^7uXMy1M_vdXD={4WA0T$ddOnM1Y%cFyq3@SI&+{n3I5WUF2k_bh zzSRHPQS<@l(*`tuEB>*5guY(p>95l-d^6F`1KI>%#{q50{DT?NapXxSe)6FX_F2ZQ zxu*8kjr#DoGM+0cjI&t4`WN7@O5J~{=4d7+lnun3_|KQ?M9Er*{drt{`uo&_1iU+9 zd$zJ;zRU}>ffeW3WaeS4vlm`gdurxJJEhCm(`s*-=L+=gdXry&&W8DYE6y&c_(vk* zKgEgLFU)>s2>hrI9;9;}G<5EM6*u;#UZZE|A5GeuEmh~`sJX#gJs|dAU~jl4OJa}M z_poBN+?1(XZ>s&y$N9HLebLH@zf3*nzVH}g*Z+WjBmJV9ZRVBZ9 z^LpGn(CY9dW&iu8*u>w(*EiCI0IVZwF15+4>$2n$N$% z6}#)TIb=HRAkNQNq(QgEu(5g19_6PG#7~=84?ZGsQ^p$kW8FtxueR7G_2aXpaiRk| zc>zY`w!D&H*Y!r4+>uiM`~4aC5d-q3eoxQPUNGia8)ry~ z33S+1-tMk3K8}>c9Le*zix1p%yZ`N}ZS9^~D`HM)0&K+F5csI)9OF)Xcfnr|wlC_y z(;53bD>yK6yD+Y6$8UXA$=Y$2QIB8K38Yo{JpH7%qP~(f@EbKezN9J8)kKe(o=3;j33%`%SrmMM0-?@fK4o?#4&u}p`rhjqTSE%BMdrs!dM%;P=! zM7MbU{1wax+#B(APNYsZhHg{G^*mr1ZD7RyC)a!0d!CV|AIC8a3=xoJL61OnH?cqN zEW!J|P|rWOKhmhzGIj3LCmvu7yq@F+KV<*T_AKYR-;-=T-P`#6k1B@8Z!H}0tt4P6 zN-#M8;Kbz31?_T7rZaIp*jbg-LF4_=lJm0lyJMf!Iv92T$aQl02GKGh{*-E)bB!mx zB(~-7XX^t$?Hpwujb?|P_zp4Mm7(T{g#N#{S|>=67ruJ5>y;E8XB_fQZ08h$S{ z;){c7ez(W7EI*--v&~_S^IcCt?s=HtC_p4ddqS=dcq_XFz5-9?#U| z(R1htb%=8e$M-<%b}5f@{oJ=@8xF!c)!NV;5}|Ap0lCV@XMp*5(e*4bz6Yy_ex-o0 zuWb1zV)w{o_~UbcXEDD2TZUNVvLkowgt5Hz;4Q>^Z%FLcOEM3_4dFsN zukBj)Yh6B}WuBWiiV5y3<4$@bVHbF{ijBBhlthI0* zgtRm;<~gHUDlFQ|7XLRM^Q$|!`qy2B0jxX+Kn4GKi#{7 ze?p#P;(g=B!zv%0w_q&`FNXJSF18>?8wRlVdm9Fu4v$E=!zjboDg1Z${LLIxVH)M}!O0`CrAWs_{yAxC*rQdm%eDpjvjiOh9n7?XLtM7l zdT;!#C|EYqH~jGA{EP;2v3hywDYe#PvC{t6)YSjo>R}ek>hi{|cK8TvXH~WlDKm8x zh4w#gve;#{dxS<}P8FCthKr9be|+$Fo-%9LzwnZ7_+{oIeQ74%^{ew-`O(RnvR;?N zVlurq3aM=mI^6qEBV^H>Jkm@ZFq316GtD+!)!4{%*9koDBaeLO7DVP$GymuW&;gs% z&Nb6dfWJA#hP3?&@3=TKY;8S6?@!NFL@C$*MeBT=|0DY6wX@d1esy>GzJ|#5h{Ip4 zhIb4NGUN0h#;Al+teW7Kz_{9~ccb-->Xmr$9Z@6h5m7G57)N3@(~C6c?QgZRnz1k6 zaApi=I{-)Ad-f$%`s2O+^k*~~xC!{fJ9pGCGLfrM+aBvhOI-xGga1Utiw(Xot#&lu ze}#Y`b+T|$u)l;G$e3G3uij+waZ+cvwmVk?Ag<*SBIH%^Z+|HA9G$~&KKO&`wA8x! z`IlDQgx03$Q9?<6z=-v7^F-S#hOni~&fDo)RKMS0?ek9$;F`@wW!Bf1qkR{X4zW4f+fgiTXV?vxbL_nr&dW1`ZS^s+Z_AHQT21MS1Ksb-N&5L6)%WDqcOR{!bgAmo*V{a<540{hXZNxh z$!S_H@49`@^<_Fzob?Nw&Wg!TxXZBf8U}hnZm}4U&(En}5sZ$~oaPuO=kpZr?%bSZ z_V5tcc!t8Pzg^QyPoFQZueTK0tYF8!cb6B^Zr3)D&(Y8;pkKn$HO{4_Ad-lFD|~0n6UId1G!@BdcJatc0#w);<9TtfAd|?^jvO9o`pQ% zp81jY)FbcLw9Tg20NfjcuI{{3_)u0bV|=O!$e^#{K5Ln*k!5|_ZRDOjPLyBmP&oUp z!M6NFeP(7_eP*y|DH5i+A=*Iu)}w!DzjIU2rv=g_1d8+Mf36f&or_7;<%#5aY{Yk0 zGv**ZBp8Z?8*;qz?36FFdmqB5WHJR)oe~_jMLCff-O3(^jDJIY6~#O#%Hni%nyh{x zAgtn0y~pVF>X7^Pdn2(rwZOyu2ET%=F4D9dLZfvjQQ3P&l3k>C^gcFg8&<}Y+dtW4 zj)?kr8mk*I7WC4USSk+GRfDubH;?hxt#b*;5_7s&Co|Anz}u(<4{p|F?1NnOCipP!SbFKM@^4}Xa^_C6C&*=k$A`PSuf>dqG7@7WW? zA5xU)1btA&>Pz+_gl|= z^g>7a!2INBK9FAE7NdP2>_z!4Z@@r`?lTL+Z^f(A7E~E~Z_B5}9o;SqXnH-QdN3|XZxFJf{&ULtT0S*DI zVZQ6ni4;cGK6@IV%l>8$y5>E|gtB97oZ*`v@^=DKO_YOg2|Ca)_!1~gwPLH_K{=SL z==QG%D#?1g3O3#ZIv{X`V^Y8z`!cVk9b)5GmhJL$a#EnoGdW_6fsA;L*MxBT)AXLgT$g+WITU6u6%(IMRB-YnB_ zS+@S9BbwqZE~Z0w($}ybMrQ>1+w#2G>hURmLu)F+xCh6&f6DS_7=`J|sN^W_t*|JW zH&XMj0GYEt++wyMVBq@pkkjO`T+)3(qV(xM6IM43Zj^nnIHSB5y#TipC44D8J@Fq2 z`K|-YsFv7WbGnEz!DI72q0#@!$VUpFg?#%AsFj@H+)&PUS|wO2`@B|1)@7XO7!&X( zEA$S{=HCFcVZ-Mvd8q%pll2GFa*N6ezj#3QDJF#dNhsllReF~KMpiWVT*;lwev04+ z(CUc>wql8~TFc^vDI^WiN#;=b8+T9bQF9@Ur;@>=VLvk$)DB_|6err)AR}N;blL<$$;Is zg;O0W)0co@zKF-wqnrt$&n?Om&-!a?gMKvU327*vJ?UW{@9~z)N^$V2PI?$gFrlcm zK_?)7Xd2;o%jaX+@i)ppokTBh{Ltpm7?3UX#=H_&kYH+lHSIi0B!7Feh2K-UcQ4(o ztYPs zi2~mdIT*G7+@c#b<$P9c=LTeK_ZWE?n;=CK@G_2xVb0Sc zSNLm@C1$+(_&>R((Xa{P16q>>cJxXK!9N#mD}n@Vi9xXym52 zX~A9zw*TE51D7>Vb((wmLq^?#MFMgnpD(lrTY*(sk%R*tV9-GRpHRBfl;n@CA}_7p zmRzX)WFRu$S=D1|ejL&Hz;<=zRu6lq#9^?N&UfS0=PmW*J~9Vn16y*0pZ@B6A_O&w zt~`+cI{bO~(d?%J(Ugvf7hay9QeqX4?$FkdqU9Cu)6f7e|4{YUhJ$xXh$21MbEcvL zdlcfc*4-4`0>Do+?kdZ|vpnHN8{4EsBRn^Nh4xWMio&=%B=<1!4+*u$NIhdz`6=mk z{Ib|?3<844x4Ukp->2j)YZ8uU1=&|crj_sg7sKDUA}s+(C^C5uR#tAZB|!HB$WZ zqZw_o1?GgVNT!ZLLzc*nmG>l?lBvO$w?PR}zkHGKkNbWe4-BH5)w5!MD!!AU;1bcj(=4bLCD@#y;j??f{d0qomNG9yCND!` zIVGm8l-HX?YEAHBppN3!g=&m;+8woGgM-hHEAzf#bZ3`9LR`jg!xU%Kob29%?vTg& zAIq72u?5)bP?WyQyt9C?0^AYsKxit_KK+i3m9|Y(On>|$`{`fK|0u!+I=GcoV-J4V zd{GbFI0`ZqA(WB(>%@0o)Ln*nt()Z;V*s}u2m6!sLknRl%2*Cd#p9u7b^ASG>vYqn zzt(sujY59d$d9mwq&%JZmbUxg=Ii6VlwvBbr{?Oc*|;={XY`jWB-ZyrXLIML)*{w~ z895$@x)w_ih@B*V0`pEs-FixN05E8NwlcrfcMmGu5K?Y?hvdH-;If{M?4OfihVdtn zzbd>&t3%B7NEe3^UlNDW@Ka4hZoMh&gs)jLV^voMXGRWq6qk-`Lm!W$qFkucHweU5 z)LMjIo;AJuy&REBk3(Q*y_&R^{J@7>SSDI~n7CC^B3tCg@7=s6CY&qSFP&F!CB zDG(JnWj1@Q9DbD8d)zoEdE`DUc{Dqcj@pA+kQG7AoVyVT4q4ZUo0oKaca z;#GXjGf&Lw<*v4hc$Hl$jXo_|8&6i(UY#Dd)-LW%psCbxD6QbUtpJ>YXn)=dz8{Ut zS5Wh|LAZ7(KjFQm`gF!1qi>!!V$5rhSq{_7JA95>DlyUXH(&ZMac8#@SUkHgFm~(u z)m}*w@NEF@uOd2M=g3OAerK#h?{|TY*s^m}hRUVFK5f z-~PV8_qih#t82$O={Fkd|J9?=dVq5MEnEcmw}(3newIm+M|Az>%LAp+6O*!y0mMsq zaN0IqB|55OY(+miTFUjpkAUX-9rJg);pH*%*=-_uSe&r+cyp;@ah>k+sc|J?Vn-@? zqS3_u`n?u?b?SzmS2dn0-Y_}TJ&Zsih_xSq`fcXIR5V){Di4v3(e9ciGPfw zY+KNN{J7qpd0FOv;}0JZib<>d-1FMhS(OSGZYO~(9q`CyUvg~S`^U^DrHbPgY`}>m zoSH{-q$Rt&aC>;o-Hb{t_#eOlJsyBFbu{bpb(7X}pRBxk4Pj|tn^5JO*W+vSBw#<= zt@12(m6nZrq(;Xd55_cfwEwxn4TxgEFcJ#u`%rR!T+){vUB`f1!xU=?|AU6e1`Lm| z=r#5wfPKqUF;!ZJ75?}aShL1xuU=)A#eV1=qH9j58B z@0AymEdzaYJ!~$1WwE{>Qy214<*)`94m56#D<)eW@=w0D2ZUlB5iZ7S=dTUfQGOl_ zs9gxh!)walnRv>%&^{^l5Rb?!8#s39HP|1T==?E3eusLv;&S_;Dz=_955wq@t9TM3;;bhCTW{0H>;68TIzK9apFH?hEw8UA0z;~i+#BZe2dtz-7fk5m$MWc zB-hkroFrg4?0ZhfK=!t%GOdH(_}MJ^yNK(t9JV;J-Bxhtv4Ofucu@bp4AvD& zkU3xI%j^L0BdJP=Jm=PHJ)R6o&3%}ayubO_y?7gDvUAPWPa`DrCu>hub@;9a z*sSr)4{y!Y=8W*5_Quv0?Xnf*ukG{cwS2&EPacn1YpbLEN15ybk0>AEO91YOv%l2! zA805Z)WEUt1IpUqd$wI!{%aFzX^r6jQOn_-K7ffhQH{K(8NX}_O8ZS&bI|@1j|wkd z(q@*f!0I|SQn2*+s092E@!b_-nUkJorpt4Dq1YE;tGRnUk`s$JLFw9AHdS-qdY%}l zQMe>4O5e!`t4wu0euIqyFG!hF*ETd$6TF(aF49`y^`I8t5yXF9eaHKnepvLa#BFZ) zz9(t)pCRPvKeAW3S3{-4KO&B2HDaXPPgJS?1q2%b9*<+vUAD|yT6@;jY`K93X93ghk`C|o?HW~4c$xrk6 zM4T_T3M7!xdhY3Wu#?Th^!S?W+X!B8)%}5a!J0np(4Yb^(r?{~@t>c3L*mCGF=!FB zzNGulv>w0tUtHq0*mt03$i06q{TCkxepcFcBtex; zuBBFolm!1W$?;P{G5WBJEq-72Nl%8j59GqGbTa?r9&d9EpY)N0^cT#JccAv!PartN z5$V6M`>hCA`}iE!PaH*3eaS>JV_}j%+$jB;;hOXlJzlnmq%WbfOBMu{cW2Ss`LMIt zA^v}ZJ9z6weAxVm0=WK)10TWd&D;q-|8GiscD$RXVJ4+eTBY$MEb}X<)OV5a|5zo$ zv-)y~8YZxn-jh_PM*9CN~7Ly z2b>f$Eq#P^9&X4W*i*CAMJuaSJF7qu8CdM#LviUUQ1Z6abKIT>z<88?r7=X^X6yBp z9Iw!L6O{Zi8x~|i#%ZsHItQ^#=7Sbv#R|1adc zJ8zZV?Yh7wx-t9sv2WxGE;tGqkUI=J3XP317D}|(W~NJ}=>_=-x|GuanL_``M~{~; z7WKwA3*+r;HuCIcCSJ$-IiUEAx2i?L+tquAa%rpc-_k7CYBhL%6%;t!@~U}uolzKw z@sz5YrJThnRciA|XVoj|VMo0nkX{ZbLRG&}*wnus6AChTQXrTsZCt+Hw!=+5E%_$9F@F2%DBN`hZnB! zIofAQyJ&Tb>B08T*}4qulxkj8F>CVOEXDH9PgU|((}f|m5Z0^x|alm2LSP+BIYku2RdIF*O+VbZB55XOTXv)e#PI7B?7`0iHNyV z%^pXCJr!oF#fXKEYJA+q-VqrM*aeep?l~z&vCHDkrSO}C6yv;N6l~1bEAA&_hR<Fggcu#nHDOrO{>0O}K_$YEf4Q)FXobhO?o% zgHA)S@phA|7Q;($wo|;;Kn;l@9=nUrO;}A ztM;W%jbMgoA;3gkB=|O@k|apGMU*UhLd6h(Q8Q>ArKp`&12;!SMNM3n-Aj~st-tfx zvK6f-zIYY3W+BHk;IUlSZSAXQYdbC~`&GgcIep`E*DJ`%;ShF8w&;-qbsDeLmw;s} zY~q1nUj{2^=`nq$wY0krcD^qamR6TIm5R`W1$D+H15N|1^@JuS3rg_EVK;8{O3L1I zgDqDPXIoiD__J<6UrlGddb52@SW_5ioZsQNlgXo=?E$-^J?U)ZGV%r+kK~- zve7z}wNP>0!k zrpaI>GwDV?8qq1GjyQpFPR)@do1#egTCd~1V5XSfbvk4deikm}@t0TG)cjZ^h{4O! zEAwR^*xApxGQ-qe7N zrunEfO7w!^PW1@oC z`LsTL=M2@lmGX_0qLuZ@fkmcKSDQ{&P^5C*0AQCJ>HHwLym&xl1lXblHS?tK1Ozjm zzg39JeZMsPMi-!EAQ-d!M=fPB-y5J`?Z~~d6Ej#*SHBQ+aGDA`c!JZ zU#(qh5D(>t1HuG(GtJ zw5V|VV;@Q6_ZmHQqX<0Vu3Y zrI~XSLk#qhXU5EjbUraWI-c)ieDTv;AMojmc^Px^}eg9VHGvH-D5g&uxZouEnVYiWrd2#)D&mQ&fmfxr9u^VW7k|m; zgERHd_Bi2Uw>STQ&j>#N=d#n2doIn|Eam7euXHaXhaG`#i5H;%>RExxD!8};2kLeE zCiN>;%vqI2iQ;~dCf`*B|8Qc#b)VFL{-jyWs3C4EIH!Pd`w;cf$@8T=URus6|) zuy+fWkXy0zCIK0Rq>m;l%h$9P*`opZ2(#kJC;9Sguq?DneEYTi$@(k_;7KYR*Sms-g@j40Fqr-Y9e=92me3^p|Va zb2m0&b`|yXE)}pX2C}PpzH&_|Q5PinN=uE^xqRq4a0G}Z+Z8n$sr#N@!XHzhitAx3 zW*-J;vnCy~cf8!xp~k|atu?#x)O3*bolE#$UGqfesC@|x*j!Z=3l0X5W{AHgK{mUrCSt5*YSHIAvQsb$EF25-2zSYjT7KevwDHTf{Ze z6o#I=>prUk9{6rDkaT{U=pNHlvw-DE)FhR)JAqO)=2orP_R#?_J15*4S_*}h^MN-- zOuJ__4RlRjvt3PgwNEE{wYf|$!sX?EU~ISOFv0;Zgob{o(DbV71AC}+W9CpJ?T0_y zGqLh6=hIu;X<}qO9=!~;)N{s@qCjV6#4X*>LvJJPz-2)8v2F+CKu-whNbD^P%)Simvsx=>rrN^nAq?xO2 z#&=~Qn?G^&J0O@roB8*rtR`}54=cM{K9od>%0=> z#-(gPRSdq^D@bb95FZnmM5YZtE$(9>3{N)T97pRl=DWZy_B~kO-uvWBk49QPW1^=t z=LV9{SQuVyzL+h2^}N?#^khw(@+;1B3Zz#Sk3m40GK-x5pnfI>y6xis{OlnK81mS6 zos;R7N6h6fIdbVDYe%XlyI~~79*Xnf!ishUmSg!L`+uj@(tm#OSQR)w5BBFe>%Lsf zgfmXPF{4ffrxpk{OL@LlkeSM*j%<(QMeay%+$ef}T|g*}ZfOk!u5Q~+we3}a$}h_t zp&N=_ZDqNPQi|DP&pyf6<%IX;Fzb}j+i=rkzR_h9q5$i zq|8*6``DXMw22P!#KSWCs;9?oJV$~1F{XxIpqZLUe-pRy<%$kLIjVJsR=`~P#v;}* zU(3{;w!(@gmFeEq7WtEJT34#R|U@ z({cF0eUv~%@@Wc@`meh2og>f60lO0ft<$8_gZYn#^(ryqDbrK|xCX2ma7c9L6^8js zC#ak7W&TyfztVg_5)k=IHhcPqz@#(`JyaX2aPyuBrzds`!LE%ChncaW&V~Y&yV6(+ zW{k3{nz!3jnC9Cyq&S>@Q*Ai3;*)hcR4Qe|ikiq?$mBIb49E$ngw_F5<*YQYzBhsS z%xXyjYFdQ_=geDKl3+%*M$pWZCn#m?0PM`lrdHiYB@{M5h@ znwK4RLoGTSEut{wCpBi)y7mj4p@cmF=IqE${pRa>fyC_5MDN;&jl-fdd-Bg ze0ENRKc-=+W&0$Nv6I$PA@q5sFgN4OZa3XQI>C%0JrvvCHXFY{Q)P zq!=#5h4boF**{G-$l z(=He7we&{sQM3pbMUAZ9^7PaAjg3Z@3YhMVaMtv*n z*o~hn8Ju*stT~=1$n8 z0e18y4Gaj1#Oqy^Fgb6ZngnoTf8XBmNLvREKYdBfX7Y7L(+o2{GJG+l8)u&++rTNSsWCN-<#O=_*?1KybV$ zH^Ozt0BeTZB0d`X*jj(B9UrLs?YTU~-sriF#EY~3I$}tn-NfK%qw3cQAB>Kp*cKqo zsu8{$H4a1^n!KRDI|Pq76W8C))_Y;HiTW)tf&7KH9eL$s92Cw_Iwadgt+;>HwS7|& z{#=IK_n<~n)n(P)jkLCO?>~Jp*UU@-h?T$p)~B5_OdpiJReHI8ygCg1Y86`82)|g4 zY0K{4if;pCXz69TXB^iLY&t}b- zTD6$U9x|6Sc@J_aBQfkUu*Wo>qIV}rox&?oTW`DBUD>PdPD}EXw2u3h!CmbitrMv+ zf!#J2?_YbX6we1tzR|Cho1$X{HQx;<%{jSRc-ZuAXXZq}|IR&8zPl}G!Z|OiOCI(u z^d{_5(9^Rk@ApsOk?(HZ?1!D8=)$O&HqXB5it&2)Id74o#7#(dYdX)Sjx1zLtk3Vc zM)g(FxKZ+n6g<|E;gYe;V{9d+?-yy$P4bzyii`fT^OZkj#m=v=rnJ?6K9WtvclSVR zDE&y)qbJ+cd%m8ubdxrGbs?pFstjE(w1v!JZV`W`jN=`;?w;)Bo0Edo?KW-~xep2I zxIQ6F;>tJI~j}`Ia_p?IQCp#6dae6u<@bIm+Lp2XHlnKcbI0LQzE@y!)H002uwC%X0u4t z8Ez+Gn^OMpnYIcYqqyfflkBdhFJ$vago5hKj>kw=dAi=&mdCvJ7Gg0b|4(C-I$bmh z-ca3bT4V0sSV6h5^B3g{I2?_<(kMgYeGVCN2#(sucB{i#yUa>V*T^~`pGwd*`{W9i z&bO2(n)|dWa;cjL)+=Vt$tnYsM!V?R9bJk&PF!rX3w1!d)l4j~ zl=t1)E5wJZ>SAY-#3K3Kb>8P^zQ_vrpCOmb6aTuPwi7%zzf1PQm1 zUR(7>T(Yc@RB-`YrjUd!-MHz30-v&{S8#4UM}%VIbvVB9)@6aR8l~F`EuMcO?|#`> zROya7ucz5DW5En#L9{B=F0j^%k0A!S1_Y6JnHpbfEj{J%=&pA z#?(td7muoH;xaIhMX~)OGF5UPrrs>Q_YH$h3fnp)66R_@d{7rNi}{7SOb{z-#{)9` zAK(B!46?M3UUI2a#Tnaw4Mjb%a!LJwI}cls8g5E`s`6icPuoIr(?OM~x7O(&X8NMw zr9p8LFd;G;dtPtz^eOF&E5?>0c}T%U#T$Bs(>3=CQtfidp_E#XE zjQ{EeR4wP$Dx^evNGGaMmsO=0ZG|@V_n0LQ$m*G$7TbIW#v_l}Rd*Yaenl&-tAmB# zA9Cu?RLPLHFOHTn`$@5L&fw2juE1jXFVbX?V_1)_bdJ)7Kej5YXP{0L@v4NuN&*4> zk!}XbZk!3xfd3g*TiWBD;16aWqo+i;fsG378@${Qb|;ig3B>cKB$_ z>dOu?0c@|$X-agW+3(cq-rEXATS22lCn5*EkRnOe9<$~u>+z!ahBK>+Qh^MMMfmAc z+hJ{gg#_N`5ls%%0`@_?@X3lSV zbp$3aE&j5?&L5$^tyROPZ+t5nUw3|s9ZDqrSe!A%FTvH?zz->@M!$xNcLUJpT|uYA1+;J0IP4U=Daxf)#8eP`4)s|j zSNeslo)DNEWFx7WNu-mhc?w(boX?*hkq-OX>q-qgrZBi5mhP_d?ds+%?p z&FwtV57Pj-GS&BVb$4uOB$+Vd7CRQ5vbQmSSA@P7(ALop`)j(5zjlKEaq0ua~aip z#ozhjIlk!WFJJB^($$@HL^(^ReVC5o+&6uUJhyv4D@Q;4uv%G2v7>J7boFI~^E;0| z=T>*M&ibLCJHb_aG^wCSZ&R&9Ju~OVng^j*C-$S#S?c%#!5=$T`;^?T!w1r8H(N}#kJ>`jHlO@wI2DUAY;>LzGE;5we3&bfTUt`4 z$3`Qni?D*SIISug8%U}Z;4wd4>^lYw%O#r@frV>V!&^L(>VH-HLZ%$LsOsmXRL*Ip zWUh>_Q=f9K*0TG?;*-Q}z_+=a!Ygk1AL1$0kA?kfJUzs@3rdxo zCfh;xdu7mL>5ejhI)UsZ)XJs;z-geb)!)lPy-r>jmeetnA+9gvq+F@m97$* zt;EDX0$^c7eX3`%iHtHT=cXKIbnuNLYgE_`0_vS!0vx zvI{WKqNAdEnAIkd`GVo*W`=tm@73vqfKpka4o|rlu)NaI!3yFu-19I|ce1cM1q6Mh zYkL=$A35d-Q~?Z8_gf8B-RHCkSne*HQ0`W$N_1sTcdH;7NSeU;4U(nNvR5nEC>9JQB~xYdC_{91a2P(r2k4SHuE>J5i*Oa7>EQTIsKTclBAeo#W2oy_q>gCFn1 zE4uqijQK8`w`rJAgNH6P$d0g(&NycvFVG0!lvJl&Qx2#_7`Ap3HVq9t+kg2x80YC| z4oyt8ElBM@OXmiqdDIt2cBfq)Hp95(zt;GK1kvhBwrvr#k@JA`dKRi0KvkO4^>_S+85bJE|| zMO>^3PPC^2PXX7jNrXWn^tq6w7L*7nD@wP5sg{s27?-;+$1MCZ~0TDLu>( z83YieVw}2xa_nW6x!5yMkRtwP0iw|(w4^BJ_!!fp$KNbLm$PH#nmN$Sy1cj z@4scEjL^0jyvt0Rob+5FjU%5gUew*8Ai)|sRU%9lU1xV%tp?Rou}`R*=&yS7S}iK7 zKC7izS5I#sA*J0K99dwM0DQOYgy)ygc4v;AGXBlWQQG^8X^AI`d&)o;py5)#5I3xR zYstAe-Z%w$pA3Rqcm3SWs?R$@xM_R?QQ(vi0LS{M}WwtJc#4Kws>bKyBN z4-;P~VIqdXaoe|pC&2i2l0LvB`-kjB4!*g&?&ZIWl`T#Jax@>;tIGnJ9ILwSy`FVoMK|`i0-&I%eN3tX&add7Q$Y%2X40`UZQ`PYXL6a89%7Qe6`wFg5AS-9#`n zdy96lr8Wv&$5%`G2r9tkJ4BSc?TD`%Fdo4D%6gdK6w>qFkIXa5M77QieZ*&ISuJtL z$*)+BtSu|C+P-MoL#E~avnM4IM#C4zt$2zayxVkp(IqPt_DRGhIZqySp(l?jht6Oo zE|s8~?(QVyM;usH)?`Q;hdLUXR-YGk{REjW za-2slMPN;ICAAO&@y*$)BhgIQ;b`_E+eO2w%X3$kUUy@tPc3A&$T2UUH+|Gc8-eI< zNi&V`OuaU&=3d}CC#k~U z+#ga{sV5zPfIHLy7cf@pKz(LOyUHw31*~sIP@A%f&dPc?793q5duR8fc_L7%gB?TJ zCzYgJm|S6uedcdGBlh%9{ePkxW|ty;&ChL>ZF;tSo0H?+p-Un~not)~uuvjcELkXv z2Xr&ac~p4Rsldbt+U~vkUgT@*(UwBXSK`2n%fUJ{=jqP*51oQBg%u^B#4W zP*TKNw02mf@uD1H)~j1RLOXn8WLb8R8L0|lW(dW^R0C3qyboJe)_!`tzG%^bvORGy(*x%ASj>i_6Mm8GSAi(tf7#*BlHVdCjG|^d3sh5o zuIkvB${f~(i^k#=jt{7PQBk9`@_TUr#{JN zp-kljKok($vJ+W7obf+DW?NT1xD)7tKe7|I-fAa0)7Y;IFCEZs=Vk?93 zDfZ1Px)X=cwT~~-zowcNV(%HJ2emN z0K)E2{{yLVlqsyigitRm*Z`c*zr;dTDe?cCgUI&=jo>Si{t6k-w+$v8idyy73MUnk zVkqmLyET_^yGRiJg1}M#bZcc@O*MRq^tO|WOS-h@W|Nr`&X5sIa5_>YbW>5Wcb!#~ zsysU3z=9I;mX8Q;4Br|o{0nY+>7@zNGT#abP@j|kmTMD1E%G-b-^%nGkv&pQUk7|j z)#sgem#kN@-~7D)NKlgckvW@AMP>DYsJP7~Crif;)d;!A656-!RC2a-YHe+MXehg2 zfk=7ek-|4gc88gJBi}0Cw)|4YRrZ7*elII4YolqZt`cIwaMHmVI=0rRs~lE2Yr7-$ zf7+-bv3Cof6u;Q|Xo#ThAH-1)OIuR`J09Y_~WcXDYZS;y z2>RHY_gSSvjN+}POtqQOoaV%8k# z$BJPg+Ia}CREy`8rXGgF+&t^?_mI$Lf9}xn@o^>sdov3M#oU7ZhGJsDRo3>4&c9Xg zr`YKEG>6{7@Rrx&rqqaWTOD0?yj5Q*JV^9j=AY-4G(?=?eYEh+2XxMWmS}Oy$EBv@ zLueNA@s90`1|y|pB4432VQ{&MZjeO0MX2OSvs?VTbLlS%U80*nkFCQXta#HDk35|u z1lGjwQ?VnP?dV+Qx1Tm%{w^qxa`PFTwn^sEXNR&LP&^nG(n*q_ik`}J#EX`6pzG~! z_uG!zvz{fsp7`Bee)WCVIWhMA=$fF;Nvo&~#Y6*lcI4J2SeRiX+8>)Oh{QiHrc4!1 zNJUX`#8z?V3_4hhG=DyjOMn}HdIjs7q%qvtSo$!H zz@zN-f>Lnx%559;)Zf=yFW4{xP;UX9r>(nLt(M zg?Z}wnuz-et$q@M>TUtPb?<8QCrV$sqBOB?R5sV!#YWcyJymL;Tl;2edIz|Nph_{B znl@oU>s6E_7M!;4#p1FrOG?s&J1NdJQ-=ulY^phrc+fr!spJoXnUa-la|^U}j$who z)YN;@Ne3U>Xhdysk8<_TgMF#8(RyaP_I+?x|yfMi|pGlqrmSZ=aax8$#kZqCY5TC7Sna3oRD*8CkRIy>loyN);3bvFAc?0zmNPuMXt~>{lBdopbHJHu?b#x^KN0yH( z+v>d&4yuHO@cKASFoBtRe5XSnzQ?S_MziWYle1~6f^u!yjdI)_ z^#b+k$L(58r%H$G)QU%X5VG-O74fYpa&+_qDpJ$RM@8a<7tqyl&72 z%029EuwGeyw+-?CXu8JeIJ;;a+jbf?jcvP8(_muTwrw`Hn`Gj~PLswqC$??e`R-kJ zt(jl5=FC1@@7~XT&Y4#5R(;GkH>u7A^S{b@4?96?T!`4%i%pPKp~#r?N-NPN2j&jv z`j)AWG$o)sNW`AntC-`j`<{+I_!;~(@0#${Hl6b}MObr-Vn4O0-yW+uXEFKs$p=G> zjG`u?r&wE3JEl`RMbq4l-)%5k{Oj$>+AOL>N1@_5V696X2pk&beUyLceB-itVJBw% zbY#;l*eFlo9SJfvR#hpY_>!H->FgTA(P3RrKGkn$wW|5e?bpS{$MP>?7Svh(SAi4e zahP0FA(wvp@>;8;ySj*A7hkZCbqN{hTfO zf31p5r8sE*8J73kR31GW&%S)=DRi=$n4ZXM0H!sHC^k>Ub2xV=E+zf8$^UitC&Cyj zz2s*rxGq7LBig-vT_R209?q%mAwbf&G>i3;o|Wq40z9Zuw%-X8ev)IWq+6~5a}2`tlp;KM7< zu%Krdzxn77+MpME3=3pSjk7!7bcdujwQH&HPYq~pmo!xc*o_GnzkLq)maw(!sCea# z)sK1`+5w}1C!$|4TBS3WMpl|`^`_o`b)UeT-xmtf9>5p>vDVKtxs#WDlr@%U7DkJ! zS|E>b?A!|Lb^v2$@+w++V>5=;WF$Kb_nt|v7qt%IK;Z2IeCFB3-R4cN=7ESeScPgM zZ(3n&9iFjC{s2Co8Y)@vvo6(CKgBg8ValqV?O=?RkJWpIj5$7e=_1 zy_~g&p4YNWR<9TXrLsgU*FWlA#N2)v(yN!WE$3+TP=hG~gHO#t1jPmEaP7y9ufWa* zb=heG(!-z__xhX9(Ro$*^u@c=pgGOz8&SQyZzIUr5N}1`K%mWsEw9SLca3cmoAnsr zHx)QOxu+pKB*xwL>XwH9Bionuv9~<38sKAx>FK&Ml@V2#T5I2|yz-`wO=ah9$+cf& zz$j0le*a?^=ZZfugGsku=1>#29?d7ku~fo8D!OBjjaKRK@p|W+esFYQe#Jf%Uxqk# z3A(BG!MO>`{Oo~yX#R!s4NhII3T4#J>u`CRyVl@nNfjxUxylTC4g}JTdAD11e{EW7JdjD>*P;y9CLTjae)VB9S%Q-<|C1UOIxt_Y0a{!8TEjAcB-=|vaiAT zRrc%`_YkInZWi-WpNJc126K({??`i|@Oui$XnS`2vp4Ph497AzgW9e#3_ZkUC}Uk? z!@5+*bh63FdpU!I$8%x6$_Guw@k0Jy(<&i zOeNNGgIN|VUB@?8oXdZa36hZrcGpPttCgb!x##*uKV(%BWTIVMt=K;~sH>Y2OlTgYY)EL=IT9 zx%0={nn3tbO1x5pYjFYXcu;B3GPa~5)LnGc^rpio(rs=^)_VfF{@Xk%gOb|z`LMsN znWw&2p?->&;&f)sLE8z4GpxHlP>ZhZYr)+CDQ8pC$2qcqe!%&CFc9eJAp0MIZO$@c zi*E88A?u9;)RIYUtK6?s>*SIlL6w|(c7FEDg%tOe?^*d}{UXV(bI>-s>b3M81TIN( zV<$Y%{GJ0B#aZqaH;F#pVQq?1Xz%a|O7244er` z@aQ$bPWG+KjCO+YcvF#8W}+fb%My@gX??(zQAof@l*}%iCuULg2-nV#HZQl~J#k;+ zi(dc+tSjHT*zcdRUV`j;?aZ=EKJ1v4Ezv)%G`OC5uNwJWOre4}sQr|v58whZ*!t1qu0snGNV~P5whI)yNVrnRm`?rG1gRpFm z`hnYT0cs&itHKiV`~{OJwil^#>pTl{;s_w{kvr22LX+RzakYvoCcNR3akjNtdqo6& zERu%9{JO|p_H**Vhey6$Y8LvJHT4uz$gf8cUQIAFqx3jc2 zXJk>iu^LKV;a?n99OTMHx6s;JdBD;al=yF;%B>R?Opjl7=Q8rzQZ{7&lCw?Tgat|! z$o$?X~gioB3iVKc1%o;s-AF&d+lzHAF465youxWfmi5p+rr}x1)|{zxl@aNX`poa* zNJwq~-h4a?F6i+4h~tSP$g8Bf0)Ekp{#?PyRdy2V#WWxtvU64Iy(WD`y>~NmohMZ8 zg;j=;RKpR&fsrwDf=pGTsgNrJHb^{FCvN~NPxvm$7rsNSw0GpaGWrGND+!Ap`CoJ$ zZunfZ_4wc&d2@^Xb)vix+=A^L3xfHJ(Dl#=kx^s=vjVR`rV3PQ8;~e`c($!af$D*( zanXC$TLPNNP@~9)nCU>BI z_gR@FC4lb;_-?Vrg-bgBz>892O+>~AKi%-ydT!viR$)Y3n0 z0nLvK)hD&NH;vh#$GJ@fJq{Ua(ovgs^11=qzI1b->QNMr)Q_xJMR-X*H<3_&nQ=$a zzLJU}Y19=We+cFHgZC(N!1uvrNPLnM$wvL)8{QwEnCG+mns%!#(0o^8aHJ}XS64nM zMv@1V1GgC^L<>U+`14cn8@kl@;rrgY5n{9oMN4tU)JIXqlz(saf(lR&{)zo6bjsz# z3Nju1!~GlxV{CzOksp)&m#?^#289=G-qLLOT_xb}99ZtHb0{px;_2Sx4ltbN zc`}Y|;VRi!Sc7}TkkU(=4u z{RJ!O*LSlt2!lS9=NIZLIn0fpqZY(+H6Cs?(<&6%Oa~3*l5Ut+XofK&>P%y(jT{HA z26XwijIJtDJgT%~3Oh!C$x8LgbllsO)Z*r@u?xLXjV<@qKAJ+X-O9=r0wh@?LDuv)?8WWf=%xr#SqXd z{u-{Zb0hB`+C2#bSN~}W^iTn)*<6iR-&h_KW^#i#Z%v)zL~;}0UtcGL84shc&yVjX z&RT3D?6qyhg&9+J${Gg9zByYom`c&SgAy0mBd)|(LgR_G0iyS9U?1sTU}(t0LKqOQ zX#pEr1~4_?OZol>Si?2_4sp%r@7?q(AN#96BHxi!~C_j2clqUG+b#3YmG>DTI zvWTNg7+UywYPYWu)-AJziNdJWSNcEm9 z`?5{?l{h zx@tW%hkuitnTvitQyc)qJt8pTIx_>w2YP-kRX}QSB&=qK4zMzAOU_I0q02U3Mz!iG zP=txQ$=jMfAE1qy-*Fu$XV)UcA+)RaXxRW{ay3oMR+!T5f){57;US)VKxv8 zJgG?L^mA$IW`rpxk3j*@K*+33>@E_?`a}k(o-3=GRj5DaX)FKj z&(V4wZVz2dgShVbP#l2?Ib`4~Fgqt4d^k2&XQTVQ;}Y2y<+*vP$36bxzH_X)O|7zN zc(Q42so`JEQ&O>%sm*W%>qrE5#J*dr(YuM9BM|kIyoU%cBPx@`wWe>1=vAs&x%P?h zc~=!R!n6g&ncJ|)^RLEG3x7f;$=XXQ1LmFi7JYDXvVG^5^eoG7d2#}gzOa`u&$bX< zRVh=?Z8DF9_72H}R57&SJ_li%kMH9@RUfw}LZ(c5UBD?vv9u3K_2mFA-&Mzj6)!H0 z6@2$1WoM-BDrbPWE{*$dJ$WO3{lfPC!W@Ub^M`NfP7aTrdP(o!x6JBI4i`}Fnmg(V zPNZ9h6aVo?^K?2C>Am$2-CImXr|Q`uKePURnm%~IJo-((?Xb{Lc2y)v&oNTgtVsCv z)EF3Gw+AnuyFwJ(O=*izr@t_fWo;`qWDLLU61WtG%w6bfw8DNniB@^bCk0|rt}P(I z(KX%klxBw0-*Msy>FoihC??^tdCn*^)6UXKTij*QyP49H8qS%Y)#_^yZ-wnXqhd8_fqI0I)IWTkZ>|8^c(~y?>vzgk5kgyUr|X675BBMhmK~e*BBOBL?;BIB zou~hd3ESsN8AC)H@zEE|d`Kx*qMl>wwLsM^=?PaOM+;O?6zR>VLKDVw-2{ObkQOXl zbgfa_@+R8#T7>VMa05N2+-U7eDr*Sqz>!_=uHuJEHSp(GZYkj&ZW{-BcXwVw8~oN+ zekGn~h)+k5&f81sEzO&|4k$m==z?j*@#MH-+lmlPqzwB#fQ?8Y&Z>{6za-++nfCYt zLx+;`N~fV6RQMt}p5tAC)_gRG%5(N~;WL$Y-E7kyLLG0X(PdmDT)!<13{ivtP<99E zrEmBi%EordnG_EeR5IDI(ViLk%z9bY8#C;3&5oZo ztP@x=w5wSL%xBuyfHV3`L2a@4^DS(_FU}sLOFmJs&J!np1*UV-4J6`LebW>_lOME1 zV9)V=13o)Q3Q~88YfEga1Grv!lvg8u@x_=U>3f0VE(xZVXjl2nerUlH(R~Wkro?kI za;{$383kb=VFgaIm9^UzWug)>Z*qLNdzN-~V zU)JBn&HPZ#AB~(J&K26n()2ps1vI+N4%TW>|B2O~W8?GRp{$Q~=a#*Jj7h4b0w@|2 zl%OA1N!bKB1!g~L6YP)CpIOZ_xc7pR0Ih28Oq9fsJuaHv#?FA%<^=;a#Xd=|=eQF+ zK$9Y-2I5)-v$pis7Z$iYn}UjLxo2IaFScpUQ$jyZL-Su9Glg*UV`X6UC{xMceWwGN zKWr-!*YFeHO|l9&o_R^!KrMuq43%V|D#9AFCG7b{dABaq=@~s0C#9w0_9hT}hwuUf z)Csi(`_AfHm|+R2xvNyf8dbb!=y)*$Wu{s%!X!#&_V`aUj=rLg*0`+zfQT}GxHU-N z#>;))?N({0;?6I49Yy!d!G^l^eRI-+EjbAcn`myMo9g_TmWs;{&hu<%~wqq)omko zPDzB+Fm2o1BPPDeTBAOZ7{VSo@W>o`if6^J)5Hpe0|qoDf1ilWawYi>(B~lcT!lI7 zZp3~LK|c6eri6d@y8xlJvJzQJB;3mxJgn*|F77W@8Z+yKN_aI8wP}H|{LHAivHevL z(|+MopoYG{={MDh-Rc9=^NbAea}piqE60~(Jx9eT8SPtxo=>;m(kt&QPnjZ1$`Qbk zdp=`V+Z1I*EJ5uJ+YkC^Aw%YSJgeV-D1MPP3`;PvlrD`z_{%$1AdvUwLn+dtLom*> z(A6}WR(^YqQZ8zy8hbPPbzKkgOP((Nxvsm{xd*y_(```g(_*1rODB2D^z-J+-pgkU z*;W@L`&YrEtb}Zq=alGYj|yWI!YmEQ(O^E~a4YcwnIEm}Rh=y4;x&w%l-b=Juq}j= zu=Si)RLg|x93)#?d{u((u9%2Wq6c|A?aOlU_hlJiR7d0&T+_Kf zl7`yz;n1NR?vw4vwHG7t8~Qw)_r9i-+9u$Ii{` z@uX9kaxpP}e+NH};iA>fM=fT&#ZUiC>1850TlYV_nYTmDj0u<41rKSY=K{|YMCX<#|=zDow&yMn?%`1h?3l#31@^dKoR?3Wm%c+3E=KjL=0mL+p z+XivW1FIZMvDV69r{9ZLM4xvV_w)PceUUIfQu?v+pT=sCbm59DuwgL(Qsp3 zqh?|Vobx4HZSs|ttO*2 zO`z2iGS{y#|H!5}A^Jl#ChlgD3xkTjUvv~tYan;|k-8@Qa>Ky8{Ul(a=T%I*fw$K4 z1~>Em?%+-yaMp8lDs21f?S`PR5-89-;4bo_(cu&xbUKwIHL$>4!1J%4RgdfR5J0-V z00D4JOYcs#!Ole=bzTB-CuaIc3ox2Qc z@aiYRW7fUl;=#+N?4#w&C?SSnW;tl!fm_6K0YpVv>9Bv-5A_cxHV7Gejj2@Uyz6dj z{y8m4?}Gk;<+|zG^zc#B7ahJ?3b;R{ypamN-XgLl@N05PXK`t?@EEJtN48!gh11|< z)3hyWo&-l#Yb2Eh@k>ekPl*o*wH!mulAJ}!~X{^ z1m(kv`qyeVemm#m!?@Ih+UQZUhk1_);;NfF5=li`2@h>ay;->j_nhxP?6$nI zbCtd(LpX3#S8k>wjX|IK)Si4GetTMvGnMH1dIMK5uq!MQe{urxqe3>u?I&q5j1&_@ zdu_pyS?PbF9#ZP-ZU;`S=w;kj)n6F%7AiH$PZxzHglr$_cDqnIR~ED#NB9b~Raq}* zxG}z9J^^H3ETa9(Tj@_1%q|126J}ZsW}hmm;?rQ zXq3Ay#t^8r@EsLQsCuZ?1O96lI{!rd7014q4BXEkMqDRqx9h*XX9;+^Uwq7YjIhgZ z-%}LmexAe(+j-v}L~q!=nEJ8yu$}%sw&;=f>@#6OZaDRD>zH7V{Gc&}qDn%%eD|-y zorhFl)tJ2rNVJ;EZSfZ>kOPIgO+oM71iHtA<3nVh8k9lZDbiJn_}yoH6eN{!doxd> zam|xrhT&;Lmv7_w&Dx@jP@qZtmU7Eg;>P>iFu!f2;9KDoC__Xf)he>E{aYKs);-*d zuS|WNV5CL5Y;X-et_i!N$;nIAD_kH?`b}H-_~6J>riJ} zDp*4>3R?Nq2VnQ{hMPxAD#}g= z;|Sl^`VW_^FDjQ?iuVhUs%5b2c~Mf}hX%b>(%&o9*ZjDuLejMz7EsS8i8uNEu{I(U z$s$=&)%SsMI)5(ucX;(g*AHFp7kUG{RwB!>Win3AqjrR5>YK^Zjt*(=wX5epaLl`? zDANi59O|B=#}qK^kW<~zjUUVkVl)_AGN4{bSIEv;?fBJ<5IHJ2GU=Ec8S%nxf2%it zcJ6W88L}ioXe{?7xwr_^s?JLj+c*-t$xXT*T?HyYhJ@ga|&v(al{@pkBHp+EdYE03=@b48@ znGe>=Rymlepi)h9gWNUKR<~XpyJDQ>I-$Tu#f*p*i2qkrfL*+cvTn(I0xfzEwa_2ejwez+Bcu$f^R)P9}$vG|N)xm7T zcxsNY%h^A!D1PJJNe@1c_NTq$|L#e5AkKGN*hbmwP?ATW|CWiL30Xxt!DEa@J9iX} z9WmGsnJ1U5bnYj?$EMha9&8^|#G{2g*%+6a8GGeD??>Es4AU@c=J1ohi1!@}bdz{m z)IN#(0?WPY0L+~3XXFCeO((Cxf>_k@9SL+7B)zmaLMpS$ZKIXfcDK-AT-O*ZwaU-k z$^{8c9Nn;?$Fc|+Ih8?sTNe#`IR=hS1OzrPpl*ACYnef*Tl|IFyMWzsEvERV2cnHY zVwzeQ^URq^-^Q_Gxx@kAMc81+=hnNF6dqV7u%TeS>>F*~a#b^Nd}9znXunLkY5VDu znDfUHv$qn3n=nfw0Pr3&l|G?-7=h!7xPh(4e02z+v~L??cVDYxCCHOW-hP=~>T6Gq zmq(SSAWxYpORJK3im?c_2{B2Xv6oXs^aY*Yjm=86?6_a9=R@ zF1*^u-1-XpFgEw5j*^2I9jjWR=(HM$CzXZkbaep&oW9Rw6`1`cEl+k{rM(x{hGI@U zstu|%Vffe7?9h|@M75h*pHUK&L$wnbI+yXiWR@-Qk;wXDsQgtYK`FaK^aDQQs91B# zrY{rHGbzQ9j`i;tpLTya*7swLyR}df#rj1=&r{iFh0yQ<>_&7`LdQHMJWaK@082dwTC;E>NeGV5&Xtp7<8hmQ*N}`Wzss7sEC3+kFb# z%ve~pZM})s3>Z+(iowNaTi%!AoN{8esj`4kf9K)9{)e=U3;LEkFX3L@k zVXH@jh%u_6%qsBl$^+L}e!`O05#scR=dm3K!Ed3Zo&O^zoanueGJP4St9~auMk#cLWhH6rqa##AOZ>vAkKltYF-UG<@_ojMn{sni$L86PK%nPKrakn>oT%!l7z_P@%Wo{=jnEz#(%#CQ zYnql+In-J#@AwG64-WBkBfd=Tz5sbn!(YBcKV5x>_mc1H&u0K8Y6ny#4h8v@XP6&% zM304vbM2P_=bSubQKknlp$tI*u;!tz1Y8SSI8i^9ajArJ2O7n-&UGNPWm$wHshSY&(h`DYORJ-%Dx3E-)~jsW%QdpqC3rE2E!t*jhXQ z?@S+ue?18&>ciFlMAObyd+PkXZ&neu&|b;xE_uLm&+%dIy;UF!sbMGt5|zmP9XgP? zhhV0h>Q@MgF`h1)*&}*1^8M{*fR2YMn80xj2E|ZAe{4~OW;<2@xI`^X{0?P)@lJa; zR|{co5cyQO6YPT++YhV4q@bCy{?@m-4>+Cekwa%Gzbtq$4O3mK9f0pBVnUVb!5$e8 z>wOinHx~ic(&7G*Zg5CPYJ=P=6OQqWMqsf$`)vxjfqQ!-^W28+%>FJ%_L3g)bn!mn zDL*faEwEHI8ZKu~xf&LrEt?83Yd=BzxSnq4U6nm!8#lz1$2hs_`3a_f|?KMf65NRv&{ zGe8YP-q@A%xuj_bf;cgJo8PH9XeWi=?|L8jl12sLx8X%&hVb(O=5UMJ4QffA+cwrPRkzn2T(4k+-)485s&_cF$Sufvj)1Yo)w0_pCX`$ z&+Pe+I-?seRGad!s0J#^^28`pFC6FF5QiZhj2a8i{)*QD`xatuq}XH28m%+oerzb~ z%Tt7E1=qB#{3kDgYnZc{EtcyKooqdbAreA z7$e*IR?T!ξZHTiogJDl32mR9){duBKZ9NM0%1NBvQY1PR=A&Hr|4xP|A<8Pm4uf@_=C-n5j{D?2xQyFldf~LkyYN&ol z^nvHMuE>tfGVkk%-uX)Gf70i-`#Hs0cx<8Q@sJ@-{jB|QOcU~gGZWjgzO0?AzX-ft z6*RQ7^n%mu5ID)(REXRONKF*|KNp}2)-vwO5&A%k7Q^oHr5hokdeb^#=C38L2B8S% z`G#GoJton&zKxH1oo|I!qoy4Q(hf#E(1*~6RL+W&sa`lwxT6lZ>hqEeHn)6AulPz# zxMDw`yAe7G9Fh|luK1SRYLB&>#G&h=0%EVc-h8g{5@UiyI1L$ZF{k>7E}0KpUW41h z7}0`KwjcVkR0mnm>y;CQW{t`deAMREgT09sLnfgDCE0}tlRhCfkQE52`qA*jG+I=O z6{b;_>$ON8KI|HsqEzdSnHl>+H-IYDaeCD0z{x2-Q{6-A^c@@xPS!#(&*Y z0^6*%cQY_G`l1|l5N)3#-r0g-jx~2~` zpKCK{*oCze7f2Oo$hcx5vSNGQPm@F7np&vFbb$TDRk^nAk$Ti!(j)K;();HNhBZ>n zH()seXCm_G@Q;SZ+r z(XS|L|4Ay6H+S%n@VYq-it#NtHW*FNHBg17gIfOzYX@IgsZ}8ie%*;-r)1Md@kJbt zRc3=Z!<0_7^vRG`-z@*x^tOIvm{Ks;#Gs**q!TLbLVre1Fg5@iHi~^}2X$P0+$4=m zYL4>GcU#aSbj2rE4#^cn6wDAQk;`z`aCR{P^fs$VL|*@`yibPN4%uVeY!a;d?;8St znVmSYTi*imy8hYtiX4zaYBk`mTd3!h_(LjHltpJt)K9>BYEPro30q3JyZ5Z|@m}9xa0^KoT3e;*U_I@MO z*YuTc%M<+tpxU}{allGP#)mnFiLs6Pc^g%bj0xy+b2RQ93FqglOj44FH5>hzkL++v$CILFjkrggoW==MbJ z{!n17Gdh+LWsn64p4+#XC&<&84pCPf5Q21P-h8pA$uIZ{kp z)qzA;9GQ}7=K;p9Mqpty322V2(KkMqUUmz@qsJ>I)@}M{69O|> z7dZ+)NbceWe3`Gd9e{!;wl{MdBtm0fpzO-1>(=P#s^w_sQ0?4-S0{Mm# zx>T{A>_SqK_R!tZLR{Z5>cYI8s_}!@l*cVZ^chVxmT+tSv7UnPtL}Tu73b4S7H;t( z!e&UmPQLvM?C>4XX4Ur_@7u|k(FlViZuxHZSkn_Pa1Ar2Mh0o(nYs=p0MO$)zi(%; zbO90{w+y7L-3ljBRemg<{M>b7_w5C-_4x{M9)7FXL}Y$)H+Ro zf&REf0N5Sle@sFKXMJ_FJxbi}ABpbWab*3ZTs^aGG;Tqlch|mb>nS#Snb%WCiiqH7 z4zKKWD}N0DkBOTc(Io?@hb^wAsE%mZqT=Aa6ZRu2f1ILR5AI&RmQGfoS|QTD4d-*x z{c9iNlvdTXiXL&B=kNadVEo~s8O%({TTu*+`$(C%7UU%CYQ*E%1=Lri-*b=OgJ$00 zH-G%S0BvscpMUo~!Adazh?Pt*zu&1+FLrUob555(2gu(p4S&=e7~mvOC5cA*~kCZ?=&Nf?>PR* zX<3K{QARG$*w{5dTLsA;mnv!Ukr1&N8ro|90Bk`s`?6#0k^H3J!+8S3d7k&I{tx?P zjR6keX{WJ=v*k9t7>__~@Gox~$>inYqvYZzzhoGYQ*h|^uU7J5BFmxfX=#WkwHL-9 za-+PQu?pwBB8}LzoXS(?mSCDG#bVn!Cixz1u;-wgpM%nin?Q0Kls}J{8K40^Xu@H? zt>#j-2M%IL){fx)DvJ!j6Q1HGzLbtYVRE0VKaw)-`<5b&1ympu-RY~DB^N~PP<$rg zMn0Y#A3a5x=qft&cA@_SD(R!1UJRVtLIsX1dooS$o@>{`=q+O^nH>bJ2k`)=(Bbi) zz5)+ZkXDA|o(Ex~D|Y8uPZz8ZsTyE%?+Y3bCU+d!%P#*$*)b;shmT@7S!3M711wFRitrk2)m0-K zSlkRurP8(13(|n1zvec*5v0}K9h$@U-dFEp0u9ne;5n+6lMV^mU|cmJz8=@`lPq!7 z5ffCuB+9qctouBHqxlTZ(%Vqvn#N$>Jv*i2neQUL)iU=|;5@vfB&-#=2hq_k2jPEU zh`q%n^GWdxk3M;PSu=TP>lEqOp8!RxYBI}7<@yVY?VRGFOqrbRYbllVNbF4CDWywV z0WuvP^&I7}MNk7O_bC*)BcNwpOY=A?DNrkT(tA2HwdFq@F76~coikK}g({5FTVAkm znG!mE;Bg8D6-NYN9hZDbfI7rQZac!iRendU>mMTPq|8$we_$t5GKbJ#+2S4pddGkU z@Tf$I`}!~*)=Y;j6;owoo>AxYac?C!(@%vyx@nHYkubdwuk`yT^iSI_mfDdvGUS0p zn!%HtRq(k|U$)SF7~W>0Hz*O77f*Mz58EsX%~t1wQ~L_5(-B0x-Z7qrjD-chMu*@a zw~=k(3%hUQU}`yseu~5m`arK&pVi;M_H| z*|KPefT?8u8^IU4A#`XH=l%N*uGFO=J7X~xtAxrwTGOhP(4eCt!)VaXK~ z`f!+oN42&QkMq;EM&u2j5q3&ewdd0vH_20k1k^$9Hua2j&a?H*t7>2 zT=$2roS`6!qR(| zp`I;ghMWQ&mf|6k-diw7PKa0|UeQf=l!Y9||HK6U7v)P8MuSt$9E$GpR&%%BsD+(W zt7R{X(h?tKiTKw_y71MC!WU8KZj)7Lhj6M%I-`v50QW5lzUuNOAMsEx`Lw{{!$9tk z33avSBC!}3uxz*P=QqVlU+-(d&dmF!x89PbGxWX^z1f$ZJy9?#WY~3x7IAPq_hDLk zWvhV0L%&_k>=@Sv3x?k(H1jp84>xY*DR{r&3#}=*=OK?<84A3rf%A9_RfwN(69eZ) z!<>x**Zib)1)m=3(~p%p-z>?!^~7~ecW5E^R~?APw)(js4MO_|;glaP2T{s>-+tyb zzQX$*$IGIdXP=HydS)w|Prh&%-zsZoNDY9^*lc}j^k}BaVb@?GY)o@)tCsZbUym&$ zvyrumc62 z`N>-KTK&aHs-jJJLOqP|D8}k>K*S`gL&3ujEWB}fCfHV_Tk4_eQh*i}YMa~ts;g1J za3f@L&!qal*xxXB{lRyf&=||GMpwrQ3)_4@Uk;AHeoAhq**v zM_R}K*t;DzfW`0|6r=m-94L{SdSnYoT9JG5Ybh{_?*`ptpxU}I=~_}ZG1Qz+Ni9KL znHj#?)*-fed@m-A|1UW+Epfc8T3m=oGBm^%pcU!mu%RCYAKRo}8Kjj^BZ}!dfg3j+ zEp&%bE&c~#-8|>|g~i@vw(*AmEZ*-r3(*x3ORp#@3b1S@x6B`&J*odVV#So{x0|q= zAwDT7Yv~Rebk1zapnxKOhgdZ*p9Lsr(aIo^iJsnv8pjRJsbx;GPH4pr1K(UCO}U0d ziIvK4EJM2YW&HoPQD&iy_}Vv~*r-LbV1-6Rb`r8G(u-(Us>OP-*no=XsKxNVO&t-W z6*LfZT%|IlFE(#3gEbcP0sPao6y8jBFKSEf(UaO|6kFQ=Su2#Gbmqrc4#iVo3Jw9g zN)6_Qvpb`$Xm?2e1O3ju!0e$@B46%+;>s~^1>x5`B5T2Imsf{)+Vptz_C&mrSv1Ga zpx^%5^qf@?WBLIXP|jN47rXBZh}YT^Bj8iGGZG3ZOH2vCir>8N~MRWdV>0My_dq*%>Hw_n-$}x~XF{mdXtte28Ge-uw zYxMbkPf1ma#0V&_htlTzvTFv{%p7)csStl1uTUj7UH=Di*V6JV+u9z!Z`Eedp5l0t++hJ82TFY(W@^MIncqx? zrq~R`uIU;PoDqlldg)N#E>K_HZJQ}hF*{0tf6zY3p+7Ez<6R4qb&(bRzS7s^T1rR^ zdpuFsB*WYj zuLz4NfyDr^JyXNqCh8#D|Bw4nHuTzMwS=tR=Sx%G>GL6o>91*uJL_dvbnK&CO)jHN)XuVRuwZH< z8~qi?O>HotXDBZnPrsm}>g=S2Mkis#I-B)_Rvihx7`F8Dh`$F{<}3LVewcU=n2FO2 zrh|87Om&N=dKIw12*V4Co7|w-73$~SGM2mq8&mikW_8jWNJrQB>Idogo%P)YN@J0E z@YJw3 zSwe!jRKKtVm_YY_p7HyH}E_TuXu*OCD2(k?ZJQ05$UJ9ox zx23}yBmc;EYS9j!EP^a=9UZ(N;v!z*XN(nax&Swi@WoOEhs*cx<@B(4F$oeNo1)kN zz}ui8zkmRk>e+(rAADg`D$cB+J>6Ky&-_*i_#{b8VQ&zv(N7%jEUrvM9p4R_TRq#_ zF>5`z$+}t#Ut@*tvL9rKGhsOzmg z4ZH4e5<2c0xyNJ;{sGB$E{4y++Y&zF%ym>>7H{e9Rf6_; zqVlgNI)Cb)4(WDj(CjwXPA>|2cB}iusBQnPWtG7i?0wrk_7r~3SO|MMxRlciU5g^) zvjEHJG@HRDeL_9S8Nvj|_Qr=d^FVUzV-VX!Yn{W0O}#hJgH|uyqw0nu82_YVfOvH4qVxPi1cb-~m;Oae&6Lr>3@)zHcoujN;92E8ZZ zD-#%Xn+*PQtac?H$o#*yIfl~+Xn%d_nN`o9m0^ZS_*kqr&@kfE>Sw@n%*m143)8nf zdq&3apt9k%k7$}h>EPuOx&gvNp+_aKe44fWk9pAY(^AO59vE+l<=Zv+=d)&T+tjEr z^zS>P>XShMQ2}$8b|O0Ck>-vK?aqh-@&=~s8cx^DoENS-q}Wxk5bE9<@~@Ai8tvMD z0z7?XaSML^{c8EExqJW9-di@*)dOpwxJxPS#f!UJu|m<}?pB=Q?ykk1P0`|3+&0DC z-5oaW4tKrhp7ZVggqzP<$;!-RGV)~d3E@C?J<79ssu613oeNpuwl8T;?lX$=Kw!mhn)_RDtKyU-F!XD9j~DC1NF`FZ^wtgAV1 z_m!dmUUS|;xY}-UhYYQh$!`>!z`Z~SmQVjr6V~en?EHzow#gdSlq-HE@+7G>`#hXq z{JtGjSC8;Re;@wlnNxkMtnLrbzg-!@pfRYP(r4Qt@oGRC`O}lR$kaIg1%I zzPf%-N3I7K zhfdES47?s|&j2zM^@aV5^Am;iZW5}%^w;zMd>(iYkfsIotqW960H~<1;*7W|V;|Fm zGV!K0!Gq%z$p_azpCd@hakkpMIFgL&v~<{moZ31u+u_RCXD9LljMOBJsF$jh=F05G z$w|FAA2UY0lfu%Zegpr(*K*+!`40D^fzJ2BlwRmm&|$6D#9Y8YAaA~&+UP-}_K_6& z6JwsJB3n>t5s20@A-_m`dG3v-<8JyTDy@@O)jw|6v3S98Gd7J>?a=u|< zSKkBnRRfd+;$AF;QrPj&7@Uwl++V;fA-2MKU|e__RnPebhJ!q($M5}yuJHhHVn6;L z7)r_r%xk+at&g|C@|h+TG`D@hEH}XB55fM6(J;>cCMfu%*Lc_bXU~u+_dol#wyi(! zww`2fJf8h*7ux}dQ%pisy>8QUSQ!3vmZndvYcQFeTE~2{R$oGXVWD0-Rk`W>5WVuZ zR)9TMmRH(1M^+f;Y6ALX*+=Y*5M_abK7Ijq!1LlLg~cnW1vTz1&^bX^RfRUtU zo95KQdV+tQPC41-I*21vpf8*jRk?{e4|0r=!OHw@ZGX@c-sN4)NEjP_n2bRxU_>## zxmUC9Iq$Izd)}Qa`RSqQNSTY{%pXC-6$7(imOF}Ix)cjJ=+b{M<;uq+itKim=qJa! z6Rw&;`(#oM$mKC~3K>TuG~BZN*$kpEjR=3eQ$aH!(K+Q9>B?G%9yWz7RP&t5zD!Ff-j9>UH^+s zJNALx#ZYi-d~UZ%^XOpvb~OTBf-)rob`>^z_j-|*HgO1yW2RgPf4v@3S=`t~e!Zem zNjS9i2ZAbwAG>WRfmC=uP}qm+5Ur3|hY5M?pLf+fYUqRNr`8J{d$zGXcdcd2WB-vU z;CsK@7j0jk=t0Du47u=E2w(+pVH%9x5ml&7?dTrVn``YI$rGn&H2(PcMX{UY=oUX%oy@1za0}LE;cW~Pdsh*u;)oNY87097 z$haC(rVvi~MbUB20IBUZsX^!_sB8529kamtmnmO9k;6_4>RHc2FCY7NE-b6`G<(Vv z5ke0r8Tc=a%nS$y*y>F`N;#JyrHhXo+_aNXGh_%Z+j0P|mEsrAVDWK@1op zDLmQAKH7g1V`)-}p#^LoXg`8Y7LD&!=uNo5kcf^xg?Qx<*DnqM-X0`_`s^h~(U)q) zzvp0fhFnN?G1|XAX^e>V&BP>?cu~LXPTax*59Oop4%{19ul0 zU{Uly=A0b>{Kl`8uzFE{*@(du0IL@x7$3gY?O?g%7Rl=is*TGcs`eidt{9J?k0z;!GC*uH}(RN zF{X=AtVl?Jc(Vt906q7K+c%|H+~k#YeM~S2BE-8PF8xNa3xjY;L%y2*xpKyO&NIJM zy9-f|aMPX#mi(iKCuPbb6h!1ZQ%w8SMUa{f$*vUmz7$(3_G?L*U+G`#P}YA8cy%ej zuXAqtQfLLK2Jk{Td`J+}$!w%i$#4ZL<_Wo6=7_CH*du%>q-4g=_UPBPeOfUMb0xxz8MaL;pH%%{tpYV|NaN!3*1H0>LO7p&XU~S2%Pv>F`UF6k^z1yd814i z{Y#|>|AII{TMfsD+YA6gMjc0hw?*_vh!==2rFe?l?_NF~!tKK4jr5XT8v6`jQNX3{ z>s)mc^E8ZS-Pc>by>`~sRkeKup?k&xJBV5M7KS1<`+9Si-)bE*)#oD2PUEAJHkGXF z4W>)|52L|gDR4ymo9{|sIP4-fe3gEOm-e4MMpvqhcsWQrK(>ZH^Sl?{?z?<%l;}Wj zEZ}ixvwiaIp)K{<9FUO0cB%ZTSf`Y*QWpNM9(;2<09<`&A93j0=zX9PLv^Hd6mg`# z{r52q=0KS)JV-;VrJ%CQR!B%z@)hYPd^)j-dK1m3*L1X18cGlegQBw2sAGONCz#7( zJj2<-%(nGQ+H&sSAKjM2#Jq2+l_k%4J?$#xn%CLuXO63Uh*(UN0l>Lu-X*uh;hg)4 z$7&ed3r}ZgM{~)wEVWdPuKej7P|Jn@9$!hP^egJ|d?f%ec!BfK2DfTJoyGs-xf#Oa zr?<9g=m>T7#qAVv`Hdm~=RyBZ4xS#63J`-=*C$W)HGPKNw_H4~same$IC+?TWG9}( zJ}|0qC!CKZxb$`0fr4Kxt%XY%L1@F;bb?L!7vBYbK_$Z}5_|b`m^(G%A691YJ9a`7 zfLwEcb}xstzE&!}&zs+d*FS8_qnU?+m%v#8ns6^?zB)n~o?4SifiK5wPs<2CPE+}# zHiClnvHYJT`%Oi9U9*3?y$q+hMg4a3xy&OLHD~L$pj6i3nuOCI*jsQB@!AU&Jv%;m zJFo=V)YMNDxqr_P?*3glROezGYTY4wJno0zu$t|@d==8tk0@@&Ocdg;$mJ} zX!1NV1S2ed(jxb08S-wg%g)a92@MHW-Sp4KXfE4YM zi!(oeFXtRCSR@DSh3>)=Yf0GZySt*S`BSOo_Y7I#LP4g!-l`8jQee z9#JQKW8j*=0lfPe^+)Z}U&4BEN}>#v(m8pY>LCO1w;~FV6#wS}M{0PHq?Hg=rB+!T z9n7rBPM}EEc~x!wM53T^uj+ME&p4(eV()ye}h6#R>_;vjz{k1X?c4Mbjo?S1=*rK-vb; zvs?B3r?}8~P_GrBZSerO12151IH(tyht8rZ_@M6vQvGt?RLbR1jj!=jS20+enlgrv z@Wz%##ZgfrwF6~B&a;j)OBCDAKKG*nCgA*OL(9>>piEuXr&>RU*ub(J&T6bIzAZ-7 zjmwnV|CkIO&g03(P7iQ=EG#Xf9q@Fe-_$Njl#(t*w~Z;r+k=&X(-#V1;Kd$o zk2D7Rm+@i3mr4M~E#?EcAa{Vfy3heJdE_6%C(A~vX|nzy_un#X3@th&L{`TM=ZxKywGzL4f3q)4~?cssiMRs-P9JX!Pb*{W$j zkJQGj`d|tyKj`+?N^NfOHcvaMcNXEfOjakh_8PBz#ZWbr$7ESKI6qQCwAl{EKx5Rh z`fS)To;&jWr^rp0dS;)h(@>=}`UCFW$)`jv3TeRI$XNaNdq@nhzWF!obJ>s6u~!%V zl8LvtIi#G`MeiNGNc5@moLB!RUpMoqGyW5y@{~t z65lDREUAMQKLqJa$O*dFNx@}!k)1jK!mt~d*Y__MSGL$@?Ij0JIUX^OeB^TIf44MQ znpF$xti&ZIncoH#UMmMC{HnRQF()8p-TSY%dX~b$sU#ecW#vGbO6|6iukPB+->eCy zSIc%3v!Bo9M4V>wQofR&Zcm^4a;=H~+?BXaWodwkCsHGa^71_qqbpOsCcBu4Hv5^J zW}``=3H_>RRIt`A-F((l!cg@ha7YSV9EFYQ1PN$!&tDJ>L#>B~HNcLA4eXYjq5iDX z8&jQ-vc$4Y4vc5;`biI3;RxEer-GNtLP`?u$clVWn9LDz?L468eWg6aW7bQK>D@I`22iN9+ses&TzX^qAY`WI#4doE^Xm|sm6gz08>`)Xt z<|R_Bmsw}(U8l)f`?J7t*)x>mH9>aC@rT@S_eX{mwZB$qN1Zg0aYY~dL%%caGJr8F zAShh(Q16GWPocnVY{D&Gi^oX^#Pi5*?S~}yd-`loy1uod-Y5)dwu%%-0kFG%Jvr@ z%q3jpF`TpDI{zMuYAddRQ5Qj~AeOkEEgB{Tdy+LyQQy93n@Zgm=?z$+9tVBj!!rVx z)od!DUzkEDDly$Fhhq<^=x9H}bgJ)WA6(vBJ_<>rN|IEY3yu%8L`%{vOxDK8i9F<_ zR5~B{8UNj&*Gw6E)}KGAtgGK>P&btR9qEnvN%$V^4~_6^7!n|dkE7f7zU5}2%F@Kc z0Yv^aI-e;wv?dcGmk1*gr)Qr3gxjb0kM)VWX~NO`w;Z7wM<%9~Of?6Xw-NizE~{N^3(vN0 z3em}-t9E_K@P00lZ(~F^`)|mSQM=1uO$M@E|7CwasErcFs$E;e17ZR6xy8n`zfA#0^g8 zS@@!solR3)$^pS*=Vz!0I|zT+aE)$j&NnB=KiErs2L(?IOs*_k4g@K*Mp?-lT27HT z1Mooo+uT1%;V`T5ye7+HNG66R$Mu)_G;L9NZ5? z7)6xMkFFB>8cY;O3-#xEpWSDByG&@7*xD=2%qE)XmdI6`>{cRrd8FvFd(Vr`=e(19 zSrM|WHoGjh+ua)l9Qzc#1*>EC{id(|2cxw6P}kLvYLbJcn{sf>T9wJ~Ec18GOenK) z{uSk7@q=FoZ;){Rx88aDD+Rtt+|A`I`m+!lloQ^>OD*!604DNnqu8Vy!lXdtJe@8NmY4V+!rO$zFG2!w6WSPtshO#CR+65a#aemyh^x8aNGPY->9qP zOiEsvO)aj}9MS41bJ-3#rb-iiz?Y(=iYQvx?*OsD+E<}V!;~fDUwT+vq;uYLRYu|2 zRA-&1&C%=(Gx|ytZP8+ncvsj=z~qtyO`LwMc&)&WuVe|!`l-=3t@VtXr-d2(^tPvb>6v_; zy?c|KLs@g0bFu#6VSW2l!na}bvlVNQNIYW(pLyRP{Ix4XuR)G(Ms14_!FC79E+EqD(CYhSk*oa1o=KQJ_0{#^tVo6w(GK|N({$$GIB3zxG&@Fv0tvK>Xb z%F06}c+g{yF-Dy~LG_ztx9$I1@3V)iy-~ArwJ*<+wm;7`#!TPnVrG$q`*b2;^IOn- zX=$&{Sc#Utsg`*xmyBi)WnYY?`jCXjbUCA*^+BvDKS4RDsK=`~niH&9B28JAZX)AI zZGz{)Af4v@IU~Xq&y*zA9L94!9QlHH)L5btotMT$c)s-j>Ye=}N_8|Jyrn4ASWMtB z{;hYpqG14b0(XeH!>Cf22KW{hZu*TQuQt73vR_5wji>wl*e0rvJEa#U3-g8x~%9-lONj=CaU`@T5!3NK<07aITOjbZ(&Yv%KvO)al#w^StOz*%7ed^9k>YEX= zdh9*T*QQ$Gpio~hQdGXNZT)B+DBLG9Y&qI&-3`>o`_6{2E>PRNPVDN(wCGUs$1SQq ziT{VmE8ZFNHo<&CS8U$>-O=yGfTYO1S1lF<7C5h{8yZ-WMGW9bG7gFO!>lWig37B-v+vRCO1>veV0YLal|&{n$9J z6+wMB*S3=*SF7`pQQKohZLgV(5Ci|^L?_OTXT-M(I`gBGroZ>v%ihd@2(7f~HM$jg z`YL4t8*6BdKCzASd7xwJtgfChiTY$AaW4PkAF_rPFXP>ymW_wn=?Yke7&?qF4_=TO zI&OHeBp3V($4J`ax9eftz870wk3y0ppMH1nce@cAP3D2ffwz!`=)n^?%|i#2Hm2+? z9Tt1p-muxo{h^epPBOzMP{8%&{+s%99Wq}PaEp3*7xKf4{M}zHZha-(B*?yO0_RRX ziI@NK$45Q{j{>OZE+W2hLRHtsq=?hS5);%)V9pP-L+IA{Y*6sB^jI zz-zJogvW9cO2c&}19jyq=H4YE#HTD-%N(35^G(H8_CwS+nL5+d?v|ar=r2pH^YWDT zs=Ap9r&f(_%_k5itD_<6GcVOlTwa{?#OY4pv9Iyc_kPk9oYf2(l~qO9|S=-Fh4DTFQ}n}xsEGjk+u)4ml=fSnkYK%|+>6UO|X3FBEv z(*rqn+dEC*J4l)DwN5^6nzOp+feYsA1|y~Y>y3NDfZ93rJ=ai9b)Mz5Eri&fHRpoA ze>~NAxWF&=46RWpK9|Ec!;L^I3O#Z8KlBK``-`NW-oZ3Mqbmw|X{uTv|2!iqWH${M z7Fv)4gN*^{<0rH6SB>3T6g!rPB|QD@O=6A)Vx8@*_yN3tiTJiM;jb%Fen%v)yp}pB zlB-9)$7$(nxz!-*t=R1$2d{RsbM&Ga>$=7X@aU?xBDJjs`5VRBw;I z%ZA(7z~Ae63l-YNIg&TZHdb(*puW4D*uXQ*+PfcgSaxKP+h*e)1i)HYH4#PD3%a+M zu|pVl)W4rKqinrhdrOR-P^wDQrsXY>E%Kq6+kL-*bmXY%ZXB|vHlFvIV$74Fyo~qs zoxs||>Yn!&@bX0-iacxEc&3a7Vzn3r8bTo&tKjor9>xjvSum_Gf-VO$Gc&v2MwiHJ zqc_k(5f2`l(rL_6$Gy7kYv2XHvb4%;SeY-m7O9rJi|>4;x7e!}k4GGV;br{j(BeGx zkkD2+aa^((_b(k~PNS9$1Qlk!6|8U5Qu2o?JOf(I^N$RL;n`j5Rl6DGvbboPNe1RB zyhnq>DdJf}YM8M1vb}rQV&eRL<$y;8-Q1cRY%0QLXJGFa5m#8znt2|9%fpFG4QBU^ z5>UGQcxv0;{$KuJ@HpWz3kw*UXav)ELQ0VZ0`!s(#r%s|R5D=*tuu4O{&m^X7x&M@)e7t-&)* zm&lmHmInftixBbCo+uVE{Kxg3vl`?AMa-CtJZ$Eo&+r3EKxH5dLDQ|+u1WEIXm>KM z-z0}e_iyXp{A^BT=CroT(WTKYDw%v=P>rH}eb=EkOOL8VHc1*zIuKeEaR45>ZBL=r zganfRdRG|Pm_-z_H?GBw_oK*&uwQO0)lO-|92*-0_~u1NCX8u=|LfbV>uxi_8u%4& z<*>J4!)JxQ@|=I0^V*3=qn4i-+taQ~j@;2=>eld53xpkSfKCGQlKaw0-U#ikdjiP5 zS0;9Rm91jC|HlgY+Eo~L%gR2>R4@q0+=E=Ur!12q)A84Tcho7Z@cCFw-o@S#a$*o1 zObzTAN^Cs_v)fFeKHKVU648CJ&euaFYB@U~?XjHCK4Bzol&ZE+k@vw($qJju z_L>eN&OOxN;yBadTDPn%GW7G*73=LKP%YJA46>YZ{DEiq_3Rt1NndV&=wPt90JGW# zpLnG=dGMNi&_UMxvJOY&sO8YaXtFR^DF8NyU7e10XUe=R2U9h^HUv99*Rs~tEVZ-h zle3puqb5<~ym(N`^+xZSPI9Pa*YaC^c7jN&xkB&^=BKDbZQ)}~) zsp-Ds7M2y}9nLKkAT>Q~Q%Z2nN-*Ky4wg-?5#;x`|N?S4bHc7ad zEcVjQwyxFM#u^=S32GB1|A4|9*H0Y2A%vo5cwri2;)|^9_*$Siw;kMh$Vfb2uE{|k z6#}k_I)n5@k4Nj(yX53wHo8Nw3_Y!MZJ=>mBpWmq#jg*DH=XJ@VJqhTO+*X&VX@}i zG&HdzT=O?vxTneYE&~RGn7;*B7uCmjGn6mu85h8R!gm^R!meHCc)0Y5@TjF>>cn3z z;?a?Hbce#5-4Va=WE9%0eDVQ6%f_2*ac z4*Gs?zTG)vS{tAf|7P)ouZ&LQl0zNWJC^2vRl%WJf&&e1k=XI;kX}+-)V{-$^es4jS{Qd(}#673)y3oZu$0W8jBJ0o7TV}4RQdC~Q-6WgXq2*Lo z@_r|q(?><2%wjO^ZP;J}BbmojUv%7?_h-h`G|OIYz$G!g8DOXMzZJa7Dq z+kDxRBti)@{9Rbav6+1~(26>VBnw)e@Xr7w5Lyd4Lz{06%wjYHzHTLXh;HCHbN6o3 zs3uE3^>=D1^_sVwBwUDt;k?#=>ZCP7&%l;hfFZssI48!GCpvsC?kG5C^ls{`c3Tg6 zx7KMHWnRAP^vXbGf=vyD5VLZp^P5@xV}*eXQHL{Z6iw85`<@g1xF3UcZTx3q*t5MT zva&Y&*XR4lDRq8r>(gL2m>XRBnrwo*)N0j3>xHOy7%&4L@t`Sh#T z<}O7JZxJ_4pIbB^SJ)0bFSHvSTh{p-7+>0%^IK^8PoWeY=TCp3Z3r!62)VzrzWHu| zVrj5z=S>dF(WK694cCksg`W?~1vQ!8t6XQK{JT^_g0;IgG-fBdd{%$URe{`Iickgv z$Vs`@9r9jASnZC#b5U!G-(qzI9t@6OX)KteQSa2IMe&EZ-X=(yS?E_1Jm?_zRz{op zG`4k>kLfqr>!(7hrx4+4i~9)#qesI|UA=u3phN&eR;j$f-=+TF^(CP{ludaIyDGxA zK;~%oCkI+jA6V9XeKSSlH4|oHrgn@QUYdN_LCv{CWztucX|CR8hI%y zRk8RBJmkL}3pN6d?_8IQi)mkt67I>KF;j?NuL+|&H=0bJX|y`dzd>Ib}v{eN9PgZ1_x+xYBLC%eNWH^!lQ=Z-UR=XDTJ^UCtvUx=F;lMiTFR zhUlcrMmtmp!ljE!(;1uBUP*QF~cm8&%Z+O3Wt%|ZrQe?^DlKLia`FDCqm5f-# zZu+Qx{3%zFVtvF8F^=_?nD4!)@--@KSLHJaA-tJ$>7zroq-{7QGbPNf{do(o704P3 zYe;b8d)wAy+WLmNI{A~{P~p(TpQk?OnBQjGyL1dTmf0fb(}>C0D-V0Zx> z@vG|}ess~WUr~kre2$KInfpWHew8UsM@i{+vB(y9j|xxD_72xwXmoyy))mA>9%^WP>SuFQ#$&DR3-Tk2jA%FaU}3djmYs}@ zkXe5WgTyZQA9UujBeDuvX?b*}wnfpQWvRYD{DCD_%JT2?zojRlT>pSFaUa-{ags~@ zAj3fhWXL3UnfVna2_MjFt=y5bp4bT5wV-&@CVUo|DgKp>b4Q2 z$y2m1fQ@AB)wb()L5{RM9huabWxtk{Qz!-nh1K*;QcTVBZFHtu!%4AM0s^5j4Imym z+uu?S)-L`53_BJ<(v@&@K@X6>EkGO!aqX54Rqt)MNy+*=g85l9OEHa}A@NqT`%6um z(-!o*6ZlrnL5)kY-CV=B7is|}sotj)ByjmBRRf@xzCE@J-fNuIAq`UILwit3Wt~BFE6r!7yy7i$_U2zH_!S)? z(@8IOkAb*$HJXH5e=I^qz;98XPqFmj4^c~J9mUIKpS#erjg&;MH?W)(Flm73NSfC? z0T2B)C8a-A8nOVY3*#;&Wuk-Z zaZjR}o{VxGyR15Vbb5Y?zj?L8Mx+1leQAWp`tA_o-ic1b^**svR^D*~!l~GQVpRgS z)`s-nrE3WCd13e06mWftm(P=gPIxs5qG|NeI3-Pbdpq3eF?`+nDka%l9uyA!o*s;9 zSAP++%}i;4Tjs#!y47z5sAmT?wfmg3dUx6dLlm@;tx7l3SjC^XSPAVj6ja};s(-p0yx=xeDajq`Q3|ZaE+WE$JZi%atA8{Tv9}Cy;r_= z->3txRr}s2YYHxKy>iX9mK!K1U^4L8o8+7H|DD0tYjSDHr`Xna%}~{KfV0@!bh4^( zX9T9e{q1Plz`jqgDZlwWZ#$=&tA#O^5M!4^Y$3rdB@Y`b;YBs*3|7ZujcQ80$^R6) zvYNT8#eFBt*;a>hYfspK8CFb|ZsDc5%y_)%pW(A|7NAu;>0l4`4|PUntlTriStuXX zG|A}^;}8^KV@>At~AZ1(>OHe^E|vGhvb2vTL%% zZ|BuF%Rp4G{s<2I0Gljd^*9#s#E(Dvyq`nd{K|_G6)`XuN@dC>1QRnlMdMx3fXR48 znaBtbZj!(mDx+|92%H>$!Cv63wHwKgF-2@m1JD3ocG6ceR#T|?=L{z=X~XT3sQ%>K z>PyE>ZmO)5`QcUkAA4pH|MdzshOU#tSZ&&8Q$`h};Q6P1r_HXw7XJ1V&Ur0#1S#rr ziwUa>ZBR8?j}>4fF&o|OPz@-ehA;h++0B38=Eqo|Z6LtHx`d%Qyz5CW=vu;DZG7=Q z5MHY-J}T~H&Qh#7`U}GuLf{{eNUus>ovwV{&_6=%lS53d`CSs_i+(dZ(!Rl_Du3sV zufoq9LMqB&RuYUPj&2>c5^X;Cbi@b5qaEqA4@|ILPL7N-P9&`hb6%2?y{O4~#Bk_o z>nrWsWfP!TQO|8c{jp<*lZ{%}pnR2=AX7S9hVqOTSI|##yY?3`c*(*qA^SXVZ2LSM zBOOjwNevn{8&YPIvtxE_52I#mAUSi3VP;A}x+=gemvu_Y%SzZ`tJ0MFh=s-aJ;?so zXxEh`p2v^A`*F|q>)Wz&FJ8OW!P7G)b+f4WY9B4lrP(9* zoX`be9kT2sdfj81$Rx5}`&BWkXtSmEN!c6eN_@jnvS9+f5A6qe#ILb_?Os#=7!Q1R zq<>0D7S@FPL#OF?Lc9nS((A-Lfh~i_di(?V7u&6QgxnzA++ePg_rZvF8zQS@967d zkPX?}T47I@H7;(#KNWm?62uo2K6)jo8n8btHv26(Wu55Lwa;s?#4T3Rp3UgQrSQ=# z86bja%s;)2*(V324+6V^4WEr6&BOzDV!zi zs`q`w-djph(K66wsL~L2A(+uIQ+AEEd6Dwv4+*ccxd+G6F%1QQIoj?0(EObxnzI#v z=rJbejr^YE-#A9FUwRo;aJPHyK|OHgA5WVmwL`TNsND1jxO}YavfizW;;%m^YvXfr z2e#J@0|RBKRsSkHobR31rihVCZnU$vD&>5&4pzcz@_EO!Ec-q-*i_q734`}3ApYRO zZyFqJtpP{_?pKOI&|H(jkL4_0vW2F$y}Gwb2a+GE0`iosm|`{5q$HwSdq(DcbKQ|U zAOpvYyPx>UtizU+$UPb#>x=^ zLT>e!_NQBX(me0c)mu=s)4`Q1b^VWNKM?fE###>DP=%0%9wALiTIrCJmUhdYR;sn! zGZZPhXP=@y(xRDSVNwDOx3yi1C9>WeOPO_7Sx5}5>~P-4ay zc1>u*9$n6!cbc5D7K>BJTp2YMAr5dfTLu(d>BVR|uJw(V?VWvEUuZG25IwNAowi}- zOpH9lYhVCG7KP>z<;xY_yI#Zd+@5sF^NgY5>FQ6J)`ChkWl#UNC@kT?TGRK5gZnjp zj*os`ccSb0<`$GvX7lYxA|ocS?$Fo58tr3R`}~nV%B^K35#;oJcZoKygz$UdVPvM#(h9bVps(S_HAe8s(_$GP=N+~`Wbm=hDW|9Yi$F+DXL#9;c97#30 z@rc3AJKx)c38=2yFUtAz%c?QmMoU)H_Q#prj3PHG@yji-exQj})a;y{E6`DI!Fv(j zf4V0hP9Hk@eKV*m_q0uP36Sk(HJg#GqsDoiEFvB6wahP2KpLa4%c=KV_G|2m`qAL9 z=xEmo^jI=By~+#~mh2NhN;6^l&XkFWDf0jgwz8cF3uQ0^jy;J*%o1G8&6l6{h>9>_ zu!4bn7aSMf6~5iydY{&h$rr+?pg~*DV9^5)`74A}0XnQxwdntJy>d{M4E6cb*F1M6 zX}g6m1?j@pR?#CjHx|W)vs_!%*Uo(Fn!r3_0&a_}>cZXlx8bb`Opx+5qW?F?x$J0r zxpLeHw8-uwYSU43@YWY3B(I8yV`d*A!gvT25f-5f=kfkWq||lH?47`E7nlWT&5i=r zMlev^e-kJ-6g@%of&gPQ0+xsFgz;g*-0SR(ly>-T1N>L@fB!{e-!l2|^5%Iubc=X* zY9G1T8a-p@4OxAAOK7YvVI#7v!7qBH$b{LvRm=k_%w_HDUhF3T+AGOjNk3DVJ0<$@yU(Vx^<%5j-+S-C#q3*vwm= z_iQ4d^K5|QR($?_Lj6tnlH0YkYCZT_=YFw@h1lSe?+Nwu|hgM>oKUNv>=D>HHbeJxZ5Up^GLNKvdTz ztxu$du?llXLHD*U*D5J_*H(?n3m1FY7tjJIk?1ePjv)gQ2QZd4=lsCNVW2q-L66th zoqDo%Nv^gwYaaO)XSR5ueU_U#^m>|8M6)wMp1oWD{`}2`#_*9Wpv`vbRIkAkF242{~Q*-C}X(LYTVG! z_T+ABC=A(z)hz+Jgx-Xjrb*|*Er>`AVNBvLgRDu*mYS5MRMDs1L zQ@@-yp5EARi22(v+B>iwt&fIa`CHLxQ$Kuds8)g*spy1?1g7%?a=pP(nWf77ozh2O|e`n6qI{G`QZ%h?^Jx9Vt?l2_HyG78?DCEdQSo0?SA= zT7|!>+;OB&K+{MFkyPv0C|NJHTmzABx=Nk!hzOjIq#r5%8%+glF;+HUi1&haf%IJ% zPGS=3sz6zy7Ze zHP8n;&^w?Y`hn+pa1^gyBt@1cbD=`54{$2GRfB+40No=(AXH+o_)rVb>Oj)>6buVy zwkImFa#edC1w8$!@BiCvaCUa~y8*u!QJ77@1Z+1<2QDffcn$CQi=p&!BkE{R6`*_l|&^QGO=iH2nrnwg2HI1-06SmOWcIObHYSo_4>MZza$WR!RHh zJUs2ejYYRnK82c+QX=kuxgwzkM4iw~i2{_M$zkze3!n}0SS*K_^nI}&3{fWtPn?n< zN80rku?&D86UIez_#Y`GLo&8JA1>j+ydwnru+D)W*hv3SBsDWa!vwthij2bx1kxH2 zUJ#|G)ws1ppW5j(=t1LSVJ$`f@03@a)(1Sv4>5#R#v$y*L>}1yR0Y2VSxfYy0rx@F zX6cu_WE(E9<^SK7z=RY^MJ2HnmPh$NjQ@|T|9|`cpuqFZJ2Yn+bcKa6A2SrtzDX%c JR*4%0{XfJV4qE^K diff --git a/docs/source/api/client.rst b/docs/source/api/client.rst index 4190af5b1d..3db28693dc 100644 --- a/docs/source/api/client.rst +++ b/docs/source/api/client.rst @@ -8,7 +8,6 @@ found starting from this page. This page is about the Client class, which exposes high-level methods for an easy access to the API. .. code-block:: python - :emphasize-lines: 1-3 from pyrogram import Client diff --git a/docs/source/api/decorators.rst b/docs/source/api/decorators.rst index c859063a70..fa0e7ad337 100644 --- a/docs/source/api/decorators.rst +++ b/docs/source/api/decorators.rst @@ -1,16 +1,12 @@ Decorators ========== -While still being methods bound to the :class:`~pyrogram.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 :doc:`Handlers `; they do so by instantiating the correct handler and calling :meth:`~pyrogram.Client.add_handler` automatically. All you need to do is adding the decorators on top of your functions. .. code-block:: python - :emphasize-lines: 6 from pyrogram import Client diff --git a/docs/source/api/handlers.rst b/docs/source/api/handlers.rst index 2d83a05d8a..9694142932 100644 --- a/docs/source/api/handlers.rst +++ b/docs/source/api/handlers.rst @@ -5,7 +5,6 @@ Handlers are used to instruct Pyrogram about which kind of updates you'd like to For a much more convenient way of registering callback functions have a look at :doc:`Decorators ` instead. .. code-block:: python - :emphasize-lines: 2, 11 from pyrogram import Client from pyrogram.handlers import MessageHandler diff --git a/docs/source/conf.py b/docs/source/conf.py index a9fcef52cb..ae84c5b806 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -29,35 +29,37 @@ FriendlyStyle.background_color = "#f3f2f1" project = "Pyrogram" -copyright = f"2017-{datetime.now().year}, Dan" +copyright = f"2017-present, Dan" author = "Dan" +version = ".".join(__version__.split(".")[:-1]) + extensions = [ "sphinx.ext.autodoc", "sphinx.ext.napoleon", "sphinx.ext.autosummary", - "sphinx_copybutton", - "sphinx_tabs.tabs" + "sphinx_copybutton" ] master_doc = "index" source_suffix = ".rst" autodoc_member_order = "bysource" -version = __version__ -release = version - -templates_path = ["_templates"] +templates_path = ["_resources/templates"] +html_copy_source = False napoleon_use_rtype = False +napoleon_use_param = False pygments_style = "friendly" copybutton_prompt_text = "$ " +suppress_warnings = ["image.not_readable"] + html_title = "Pyrogram Documentation" html_theme = "sphinx_rtd_theme" -html_static_path = ["_static"] +html_static_path = ["_resources/static"] html_show_sourcelink = True html_show_copyright = False html_theme_options = { @@ -65,17 +67,15 @@ "collapse_navigation": True, "sticky_navigation": False, "logo_only": True, - "display_version": True, + "display_version": False, "style_external_links": True } -napoleon_use_param = False - -html_logo = "_images/pyrogram.png" -html_favicon = "_images/favicon.ico" +html_logo = "_resources/static/img/pyrogram.png" +html_favicon = "_resources/static/img/favicon.ico" latex_engine = "xelatex" -latex_logo = "_images/pyrogram.png" +latex_logo = "_resources/static/img/pyrogram.png" latex_elements = { "pointsize": "12pt", diff --git a/docs/source/faq.rst b/docs/source/faq.rst deleted file mode 100644 index 4bfc7135c7..0000000000 --- a/docs/source/faq.rst +++ /dev/null @@ -1,407 +0,0 @@ -Pyrogram FAQ -============ - -.. role:: strike - :class: strike - -This FAQ page provides answers to common questions about Pyrogram and, to some extent, Telegram in general. - -.. tip:: - - If you think something interesting could be added here, feel free to propose it by opening a `Feature Request`_. - -.. contents:: Contents - :backlinks: none - :depth: 1 - :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 for both user and bot identities (bot API alternative) via the -:doc:`MTProto API ` with the Python programming language. - -.. _Telegram: https://telegram.org - -Where does the name come from? ------------------------------- - -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 Pyrogram? --------------------- - -Pyrogram was first released on December 12, 2017. The actual work on the framework began roughly three months prior to 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. -- **Asynchronous**: Allows both synchronous and asynchronous models to fit all usage needs. -- **Documented**: API methods, types and public interfaces are all well documented. -- **Type-hinted**: Types and methods are all type-hinted, enabling excellent editor support. -- **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 :doc:`Smart Plugin ` system allows to write components with minimal - boilerplate code. -- **Comprehensive**: Execute any :doc:`advanced action ` an official client is able to do, and - even more. - -.. _TgCrypto: https://github.com/pyrogram/tgcrypto - -Why is Pyrogram defined as both Client Library and Framework? -------------------------------------------------------------- - -Simply because it falls in both categories, depending on how you use it. - -Pyrogram as a client library makes it easy and intuitive accessing the Telegram API by offering idiomatic Python code -that is generated or hand-written. Low-level details and client-server communication protocols are handled under the -hood. Pyrogram acts as a client library when *you call* its methods and use its types in a batch application that -executes a set of instructions. - -Pyrogram as a framework makes it easy to handle live events by allowing you to register event handlers that will be -executed as soon as they arrive from the server side. Pyrogram acts as a framework when it's Pyrogram itself that -*calls your code*, that is, your registered event handlers. Such applications are usually started and left online -indefinitely, until you decide to stop them. - -What can MTProto do more than the Bot API? ------------------------------------------- - -For a detailed answer, please refer to the :doc:`MTProto vs. Bot API ` page. - -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 an intermediate server -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/WvwBoZo.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. - -Can I use Webhooks? -------------------- - -Lots of people ask this question because they are used to the bot API, but things are different in Pyrogram! - -There is no webhook in Pyrogram, simply because there is no HTTP involved, by default. However, a similar technique is -being used to make receiving updates efficient. - -Pyrogram uses persistent connections via TCP sockets to interact with the server and instead of actively asking for -updates every time (polling), Pyrogram will simply sit down and wait for the server to send updates by itself -the very moment they are available (server push). - -Can I use the same file_id across different accounts? ------------------------------------------------------ - -No, Telegram doesn't allow this. - -File ids are personal and bound to a specific account; an attempt in using a foreign file id will result in errors such -as ``[400 MEDIA_EMPTY]``. - -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_id values in Pyrogram? ------------------------------------------------ - -Yes! All file ids you take or might have taken from the Bot API are 100% compatible and re-usable in Pyrogram. -The opposite is also valid, you can take any file id generated by Pyrogram and re-use in the Bot API. - -Can I use multiple clients at once on the same account? -------------------------------------------------------- - -Yes, you can. Both user and bot accounts are able to run multiple sessions in parallel (up to 10 per account). However, -you must pay attention and not use the *same* exact session in more than one client at the same time. In other words: - -- Avoid copying your session file: even if you rename the file, the copied sessions will still point to a specific one - stored in the server. - -- Make sure that only one instance of your script runs, using your session file. - -If you -- even accidentally -- fail to do so, all the previous session copies will immediately stop receiving updates -and eventually the server will start throwing the error ``[406 AUTH_KEY_DUPLICATED]``, inviting you to login again. - -Why is that so? Because the server has recognized two identical sessions are running in two different locations, and -concludes it could possibly be due to a cloned/stolen device. Having the session terminated in such occasions will -protect the user's privacy. - -So, the only correct way to run multiple clients on the same account is authorizing your account (either user or bot) -from the beginning every time, and use one separate session for each parallel client you are going to use. - -I started a client and nothing happens! ---------------------------------------- - -If you are connecting from Russia, China or Iran :doc:`you need a proxy `, because Telegram could be -partially or totally blocked in those countries. More information about this block can be found at -`Wikipedia `_. - -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) - -Another way to confirm you aren't able to connect to Telegram is by pinging the IP addresses below and see whether ping -fails or not. - -What are the IP addresses of Telegram Data Centers? ---------------------------------------------------- - -The Telegram cloud is currently composed by a decentralized, multi-DC infrastructure (currently 5 DCs, each of which can -work independently) spread in different locations worldwide. However, some of the less busy DCs have been lately -dismissed and their IP addresses are now kept as aliases to the nearest one. - -.. csv-table:: Production Environment - :header: ID, Location, IPv4, IPv6 - :widths: auto - :align: center - - DC1, "MIA, Miami FL, USA", ``149.154.175.53``, ``2001:b28:f23d:f001::a`` - DC2, "AMS, Amsterdam, NL", ``149.154.167.51``, ``2001:67c:4e8:f002::a`` - DC3*, "MIA, Miami FL, USA", ``149.154.175.100``, ``2001:b28:f23d:f003::a`` - DC4, "AMS, Amsterdam, NL", ``149.154.167.91``, ``2001:67c:4e8:f004::a`` - DC5, "SIN, Singapore, SG", ``91.108.56.130``, ``2001:b28:f23f:f005::a`` - -.. csv-table:: Test Environment - :header: ID, Location, IPv4, IPv6 - :widths: auto - :align: center - - DC1, "MIA, Miami FL, USA", ``149.154.175.10``, ``2001:b28:f23d:f001::e`` - DC2, "AMS, Amsterdam, NL", ``149.154.167.40``, ``2001:67c:4e8:f002::e`` - DC3*, "MIA, Miami FL, USA", ``149.154.175.117``, ``2001:b28:f23d:f003::e`` - -.. centered:: More info about the Test Environment can be found :doc:`here `. - -***** Alias DC - -Thanks to `@FrayxRulez `_ for telling about alias DCs. - -I want to migrate my account from DCX to DCY. ---------------------------------------------- - -This question is often asked by people who find their account(s) always being connected to DC1 - USA (for example), but -are connecting from a place far away (e.g DC4 - Europe), thus resulting in slower interactions when using the API -because of the great physical distance between the user and its associated DC. - -When registering an account for the first time, is up to Telegram to decide which DC the new user is going to be created -in, based on the phone number origin. - -Even though Telegram `documentations `_ state the server might -decide to automatically migrate a user in case of prolonged usages from a distant, unusual location and albeit this -mechanism is also `confirmed `_ to exist by Telegram itself, -it's currently not possible to have your account migrated, in any way, simply because the feature was once planned but -not yet implemented. - -Thanks to `@gabriel `_ for confirming the feature was not implemented yet. - -Why is my client reacting slowly in supergroups? ------------------------------------------------- - -This issue affects only some supergroups or only some members within the same supergroup. Mostly, it affects supergroups -whose creator's account (and thus the supergroup itself) lives inside a **different DC**, far away from yours, but could -also depend on where a member is connecting from. - -Because of how Telegram works internally, every single message you receive from and send to other members must pass -through the creator's DC, and in the worst case where you, the creator and another member all belong to three different -DCs, the other member messages have to go through from its DC to the creator's DC and finally to your DC. This process -will inevitably take its time. - - To confirm this theory and see it by yourself, you can test in a supergroup where you are sure all parties live - inside the same DC. In this case the responses will be faster. - -Another reason that makes responses come slowly is that messages are **dispatched by priority**. Depending on the kind -of member, some users receive messages faster than others and for big and busy supergroups the delay might become -noticeable, especially if you are among the lower end of the priority list: - -1. Creator. -2. Administrators. -3. Bots. -4. Mentioned users. -5. Recent online users. -6. Everyone else. - -Thanks to `@Manuel15 `_ for the priority list. - -I keep getting PEER_ID_INVALID error! -------------------------------------- - -The error in question is ``[400 PEER_ID_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 argument you passed is in form of a string; you have to convert it into an integer with ``int(chat_id)``. -- The chat id refers to a user or chat your current session hasn't met yet. - -About the last point: in order for you to meet a user and thus communicate with them, you should ask yourself how to -contact people using official apps. The answer is the same for Pyrogram too and involves normal usages such as searching -for usernames, meeting them in a common group, having their phone contacts saved, getting a message mentioning them -(either a forward or a mention in the message text) or obtaining the dialogs list. - -Code hangs when I stop, restart, add/remove_handler ---------------------------------------------------- - -You tried to ``.stop()``, ``.restart()``, ``.add_handler()`` or ``.remove_handler()`` *inside* a running handler, but -that can't be done because the way Pyrogram deals with handlers would make it hang. - -When calling one of the methods above inside an event handler, Pyrogram needs to wait for all running handlers to finish -in order to safely continue. In other words, since your handler is blocking the execution by waiting for the called -method to finish and since Pyrogram needs to wait for your handler to finish, you are left with a deadlock. - -The solution to this problem is to pass ``block=False`` to such methods so that they return immediately and the actual -code called asynchronously. - -UnicodeEncodeError: '' codec can't encode … ------------------------------------------------------ - -Where ```` might be *ascii*, *cp932*, *charmap* or anything else other than **utf-8**. This error usually -shows up when you try to print something and has very little to do with Pyrogram itself as it is strictly related to -your own terminal. To fix it, either find a way to change the encoding settings of your terminal to UTF-8 or switch to a -better terminal altogether. - -Uploading with URLs gives error WEBPAGE_CURL_FAILED ---------------------------------------------------- - -When uploading media files using an URL, the server automatically tries to download the media and uploads it to the -Telegram cloud. This error usually happens in case the provided URL is not publicly accessible by Telegram itself or the -media exceeds 20 MB in size. In such cases, your only option is to download the media yourself and upload from your -local machine. - -sqlite3.OperationalError: database is locked --------------------------------------------- - -This error occurs when more than one process is using the same session file, that is, when you run two or more clients -at the same time using the same session name. - -It could also occur when a background script is still running and you forgot about it. In this case, you either restart -your system or find and kill the process that is locking the database. On Unix based systems, you can do the following: - -#. ``cd`` into your session file directory. -#. ``fuser my_account.session`` to find the process id. -#. ``kill 1234`` to gracefully stop the process. -#. If the last command doesn't help, use ``kill -9 1234`` instead. - -If you want to run multiple clients on the same account, you must authorize your account (either user or bot) -from the beginning every time, and use different session names for each parallel client you are going to use. - -sqlite3.OperationalError: unable to open database file ------------------------------------------------------- - -Stackoverflow to the rescue: https://stackoverflow.com/questions/4636970 - -sqlite3.InterfaceError: Error binding parameter 0 -------------------------------------------------- - -This error occurs when you pass a chat id value of the wrong type when trying to call a method. Most likely, you -accidentally passed the whole user or chat object instead of the id or username. - -.. code-block:: python - - # Wrong. You passed the whole Chat instance - app.send_message(chat, "text") - - # Correct - app.send_message(chat.id, "text") - -My verification code expires immediately! ------------------------------------------ - -That is because you likely shared it across any of your Telegram chats. Yes, that's right: the server keeps scanning the -messages you send and if an active verification code is found it will immediately expire, automatically. - -The reason behind this is to protect unaware users from giving their account access to any potential scammer, but if you -legitimately want to share your account(s) verification codes, consider scrambling them, e.g. ``12345`` → ``1-2-3-4-5``. - -How can avoid Flood Waits? --------------------------- - -Long story short: make less requests, and remember that the API is designed to be used by official apps, by real people; -anything above normal usage could be limited. - -This question is being asked quite a lot of times, but the bottom line is that nobody knows the exact limits and it's -unlikely that such information will be ever disclosed, because otherwise people could easily circumvent them and defeat -their whole purpose. - -Do also note that Telegram wants to be a safe and reliable place and that limits exist to protect itself from abuses. -Having said that, here's some insights about limits: - -- They are tuned by Telegram based on real people usage and can change anytime. -- Some limits are be applied to single sessions, some others apply to the whole account. -- Limits vary based on methods and the arguments passed to methods. For example: log-ins are expensive and thus have - stricter limits; replying to a user command could cause a flood wait in case the user starts flooding, but - such limit will only be applied to that particular chat (i.e.: other users are not affected). -- You can catch Flood Wait exceptions in your code and wait the required seconds before continuing, this way: - - .. code-block:: python - - import time - from pyrogram.errors import FloodWait - - try: - ... # Your code - except FloodWait as e: - time.sleep(e.x) # Wait "x" seconds before continuing - - - More info about error handling can be found `here `_. - -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 review reports. - -.. centered:: 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 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. - -And thanks to `@koteeq `_, here's a good explanation of how, probably, the system works: - -.. raw:: html - - -

- -However, you might be right, and your account was deactivated/limited without any good 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`_. - -Are there any secret easter eggs? ---------------------------------- - -Yes. If you found one, `let me know`_! - -.. _let me know: https://t.me/pyrogram - -.. _@smstelegram: https://twitter.com/smstelegram -.. _this form: https://telegram.org/support - -.. _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/faq/client-started-but-nothing-happens.rst b/docs/source/faq/client-started-but-nothing-happens.rst new file mode 100644 index 0000000000..ab85f51832 --- /dev/null +++ b/docs/source/faq/client-started-but-nothing-happens.rst @@ -0,0 +1,11 @@ +Client started, but nothing happens +=================================== + +A possible cause might be network issues, either yours or Telegram's. To check this, add the following code at +the top of your script and run it again. You should see some error mentioning a socket timeout or an unreachable +network: + +.. code-block:: python + + import logging + logging.basicConfig(level=logging.INFO) \ No newline at end of file diff --git a/docs/source/faq/code-hangs-when-calling-stop-restart-add-remove-handler.rst b/docs/source/faq/code-hangs-when-calling-stop-restart-add-remove-handler.rst new file mode 100644 index 0000000000..37d47a6444 --- /dev/null +++ b/docs/source/faq/code-hangs-when-calling-stop-restart-add-remove-handler.rst @@ -0,0 +1,12 @@ +Code hangs when calling stop, restart, add/remove_handler +========================================================= + +You tried to ``.stop()``, ``.restart()``, ``.add_handler()`` or ``.remove_handler()`` inside a running handler, but +that can't be done because the way Pyrogram deals with handlers would make it hang. + +When calling one of the methods above inside an event handler, Pyrogram needs to wait for all running handlers to finish +in order to continue. Since your handler is blocking the execution by waiting for the called method to finish +and since Pyrogram needs to wait for your handler to finish, you are left with a deadlock. + +The solution to this problem is to pass ``block=False`` to such methods so that they return immediately and the actual +code called asynchronously. \ No newline at end of file diff --git a/docs/source/faq/how-to-avoid-flood-waits.rst b/docs/source/faq/how-to-avoid-flood-waits.rst new file mode 100644 index 0000000000..0736e576f2 --- /dev/null +++ b/docs/source/faq/how-to-avoid-flood-waits.rst @@ -0,0 +1,23 @@ +How to avoid Flood Waits? +========================= + +Slow things down and make less requests. Moreover, exact limits are unknown and can change anytime based on normal +usages. + +When a flood wait happens the server will tell you how much time to wait before continuing. +The following shows how to catch the exception in your code and wait the required seconds. + +.. code-block:: python + + import time + from pyrogram.errors import FloodWait + + ... + try: + ... # Your code + except FloodWait as e: + await asyncio.sleep(e.x) # Wait "x" seconds before continuing + ... + + +More info about error handling can be found :doc:`here <../start/errors>`. \ No newline at end of file diff --git a/docs/source/faq/how-to-use-webhooks.rst b/docs/source/faq/how-to-use-webhooks.rst new file mode 100644 index 0000000000..b0dd4008cd --- /dev/null +++ b/docs/source/faq/how-to-use-webhooks.rst @@ -0,0 +1,9 @@ +How to use webhooks? +==================== + +There is no webhook in Pyrogram, simply because there is no HTTP involved. However, a similar technique is +being used to make receiving updates efficient. + +Pyrogram uses persistent connections via TCP sockets to interact with the server and instead of actively asking for +updates every time (polling), Pyrogram will sit down and wait for the server to send updates by itself the very moment +they are available (server push). diff --git a/docs/source/faq/index.rst b/docs/source/faq/index.rst new file mode 100644 index 0000000000..0b16534943 --- /dev/null +++ b/docs/source/faq/index.rst @@ -0,0 +1,47 @@ +Frequently Asked Questions +========================== + +This FAQ page provides answers to common questions about Pyrogram and, to some extent, Telegram in general. + +**Contents** + +- :doc:`why-is-the-api-key-needed-for-bots` +- :doc:`how-to-use-webhooks` +- :doc:`using-the-same-file-id-across-different-accounts` +- :doc:`using-multiple-clients-at-once-on-the-same-account` +- :doc:`client-started-but-nothing-happens` +- :doc:`what-are-the-ip-addresses-of-telegram-data-centers` +- :doc:`migrating-the-account-to-another-data-center` +- :doc:`why-is-the-client-reacting-slowly-in-supergroups-channels` +- :doc:`peer-id-invalid-error` +- :doc:`code-hangs-when-calling-stop-restart-add-remove-handler` +- :doc:`unicodeencodeerror-codec-cant-encode` +- :doc:`uploading-with-urls-gives-error-webpage-curl-failed` +- :doc:`why-is-the-event-handler-triggered-twice-or-more` +- :doc:`sqlite3-operationalerror-database-is-locked` +- :doc:`sqlite3-interfaceerror-error-binding-parameter` +- :doc:`socket-send-raised-exception-oserror-timeouterror` +- :doc:`how-to-avoid-flood-waits` +- :doc:`the-account-has-been-limited-deactivated` + +.. toctree:: + :hidden: + + why-is-the-api-key-needed-for-bots + how-to-use-webhooks + using-the-same-file-id-across-different-accounts + using-multiple-clients-at-once-on-the-same-account + client-started-but-nothing-happens + what-are-the-ip-addresses-of-telegram-data-centers + migrating-the-account-to-another-data-center + why-is-the-client-reacting-slowly-in-supergroups-channels + peer-id-invalid-error + code-hangs-when-calling-stop-restart-add-remove-handler + unicodeencodeerror-codec-cant-encode + uploading-with-urls-gives-error-webpage-curl-failed + why-is-the-event-handler-triggered-twice-or-more + sqlite3-operationalerror-database-is-locked + sqlite3-interfaceerror-error-binding-parameter + socket-send-raised-exception-oserror-timeouterror + how-to-avoid-flood-waits + the-account-has-been-limited-deactivated \ No newline at end of file diff --git a/docs/source/faq/migrating-the-account-to-another-data-center.rst b/docs/source/faq/migrating-the-account-to-another-data-center.rst new file mode 100644 index 0000000000..7ae76a1e36 --- /dev/null +++ b/docs/source/faq/migrating-the-account-to-another-data-center.rst @@ -0,0 +1,10 @@ +Migrating the account to another data center +============================================ + +This question is asked by people who find their account always being connected to one DC (data center), but are +connecting from a place far away, thus resulting in slower interactions when using the API because of the greater +physical distance between the user and the associated DC. + +When registering an account for the first time, is up to Telegram to decide which DC the new user is going to be +created in. It's also up to the server to decide whether to automatically migrate a user in case of prolonged usages +from a distant location. \ No newline at end of file diff --git a/docs/source/faq/peer-id-invalid-error.rst b/docs/source/faq/peer-id-invalid-error.rst new file mode 100644 index 0000000000..197ea8374d --- /dev/null +++ b/docs/source/faq/peer-id-invalid-error.rst @@ -0,0 +1,14 @@ +PEER_ID_INVALID error +===================== + +This error could mean several things: + +- The chat id you tried to use is simply wrong, check it again. +- The chat id refers to a group or channel you are not a member of. +- The chat id argument you passed is in form of a string; you have to convert it into an integer with ``int(chat_id)``. +- The chat id refers to a user or chat your current session hasn't met yet. + +About the last point: in order for you to meet a user and thus communicate with them, you should ask yourself how to +contact people using official apps. The answer is the same for Pyrogram too and involves normal usages such as searching +for usernames, meeting them in a common group, having their phone contacts saved, getting a message mentioning them +or obtaining the dialogs list. \ No newline at end of file diff --git a/docs/source/faq/socket-send-raised-exception-oserror-timeouterror.rst b/docs/source/faq/socket-send-raised-exception-oserror-timeouterror.rst new file mode 100644 index 0000000000..e6a934d2e9 --- /dev/null +++ b/docs/source/faq/socket-send-raised-exception-oserror-timeouterror.rst @@ -0,0 +1,10 @@ +socket.send() raised exception, OSError(), TimeoutError() +========================================================= + +If you get this error chances are you are blocking the event loop for too long. +In general, it means you are executing thread-blocking code that prevents the event loop from +running properly. For example: + +- You are using ``time.sleep()`` instead of ``asyncio.sleep()``. +- You are running processing loops that take too much time to complete. +- You are reading/writing files to disk that take too much time to complete. \ No newline at end of file diff --git a/docs/source/faq/sqlite3-interfaceerror-error-binding-parameter.rst b/docs/source/faq/sqlite3-interfaceerror-error-binding-parameter.rst new file mode 100644 index 0000000000..5d148186b6 --- /dev/null +++ b/docs/source/faq/sqlite3-interfaceerror-error-binding-parameter.rst @@ -0,0 +1,13 @@ +sqlite3.InterfaceError: Error binding parameter +=============================================== + +This error occurs when you pass a chat id value of the wrong type when trying to call a method. Most likely, you +accidentally passed the whole user or chat object instead of the id or username. + +.. code-block:: python + + # Wrong. You passed the whole Chat instance + app.send_message(chat, "text") + + # Correct + app.send_message(chat.id, "text") \ No newline at end of file diff --git a/docs/source/faq/sqlite3-operationalerror-database-is-locked.rst b/docs/source/faq/sqlite3-operationalerror-database-is-locked.rst new file mode 100644 index 0000000000..b5cb2d8293 --- /dev/null +++ b/docs/source/faq/sqlite3-operationalerror-database-is-locked.rst @@ -0,0 +1,17 @@ +sqlite3.OperationalError: database is locked +============================================ + +This error occurs when more than one process is using the same session file, that is, when you run two or more clients +at the same time using the same session name or in case another program has accessed the file. + +For example, it could occur when a background script is still running and you forgot about it. In this case, you either +restart your system or find and kill the process that is locking the database. On Unix based systems, you can try the +following: + +#. ``cd`` into your session file directory. +#. ``fuser my_account.session`` to find the process id. +#. ``kill 1234`` to gracefully stop the process. +#. If the last command doesn't help, use ``kill -9 1234`` instead. + +If you want to run multiple clients on the same account, you must authorize your account (either user or bot) +from the beginning every time, and use different session names for each parallel client you are going to use. \ No newline at end of file diff --git a/docs/source/faq/the-account-has-been-limited-deactivated.rst b/docs/source/faq/the-account-has-been-limited-deactivated.rst new file mode 100644 index 0000000000..79d589eaf8 --- /dev/null +++ b/docs/source/faq/the-account-has-been-limited-deactivated.rst @@ -0,0 +1,16 @@ +The account has been limited/deactivated +======================================== + +Pyrogram is a framework that interfaces with Telegram; it is at your commands, meaning it only does what you tell it to +do, the rest is up to you and Telegram (see `Telegram's ToS`_). + +If you found your account being limited/deactivated, it could be due spam/flood/abuse of the API or the usage of certain +virtual/VoIP numbers. + +If you think your account was limited/deactivated by mistake, you can write to recover@telegram.org, contact +`@SpamBot`_ or use `this form`_. + +.. _@SpamBot: https://t.me/spambot +.. _this form: https://telegram.org/support +.. _Telegram's ToS: https://telegram.org/tos + diff --git a/docs/source/faq/unicodeencodeerror-codec-cant-encode.rst b/docs/source/faq/unicodeencodeerror-codec-cant-encode.rst new file mode 100644 index 0000000000..a4511ce5d1 --- /dev/null +++ b/docs/source/faq/unicodeencodeerror-codec-cant-encode.rst @@ -0,0 +1,7 @@ +UnicodeEncodeError: '...' codec can't encode ... +================================================ + +Where ```` might be *ascii*, *cp932*, *charmap* or anything else other than *utf-8*. This error usually +shows up when you try to print something and has very little to do with Pyrogram itself as it is strictly related to +your own terminal. To fix it, either find a way to change the encoding settings of your terminal to UTF-8 or switch to +another terminal altogether. diff --git a/docs/source/faq/uploading-with-urls-gives-error-webpage-curl-failed.rst b/docs/source/faq/uploading-with-urls-gives-error-webpage-curl-failed.rst new file mode 100644 index 0000000000..2b7c5a7e9f --- /dev/null +++ b/docs/source/faq/uploading-with-urls-gives-error-webpage-curl-failed.rst @@ -0,0 +1,7 @@ +Uploading with URLs gives error WEBPAGE_CURL_FAILED +=================================================== + +When uploading media files using an URL, the server automatically tries to download the media and uploads it to the +Telegram cloud. This error usually happens in case the provided URL is not publicly accessible by Telegram itself or the +media file is too large. In such cases, your only option is to download the media yourself and upload it from your +local machine. \ No newline at end of file diff --git a/docs/source/faq/using-multiple-clients-at-once-on-the-same-account.rst b/docs/source/faq/using-multiple-clients-at-once-on-the-same-account.rst new file mode 100644 index 0000000000..ab73b29c1d --- /dev/null +++ b/docs/source/faq/using-multiple-clients-at-once-on-the-same-account.rst @@ -0,0 +1,7 @@ +Using multiple clients at once on the same account +================================================== + +Both user and bot accounts are able to run multiple sessions in parallel. However, you must not use the same session +in more than one client at the same time. The correct way to run multiple clients on the same account is by authorizing +your account (either user or bot) from the beginning each time, and use one separate session for each parallel client. + diff --git a/docs/source/faq/using-the-same-file-id-across-different-accounts.rst b/docs/source/faq/using-the-same-file-id-across-different-accounts.rst new file mode 100644 index 0000000000..00305ef12d --- /dev/null +++ b/docs/source/faq/using-the-same-file-id-across-different-accounts.rst @@ -0,0 +1,6 @@ +Using the same file_id across different accounts +================================================ + +Telegram file_id strings are bound to the account which generated them. An attempt in using a foreign file id will +result in errors such as ``[400 MEDIA_EMPTY]``. The only exception are stickers' file ids; you can use them across +different accounts without any problem. \ No newline at end of file diff --git a/docs/source/faq/what-are-the-ip-addresses-of-telegram-data-centers.rst b/docs/source/faq/what-are-the-ip-addresses-of-telegram-data-centers.rst new file mode 100644 index 0000000000..c951230ba6 --- /dev/null +++ b/docs/source/faq/what-are-the-ip-addresses-of-telegram-data-centers.rst @@ -0,0 +1,30 @@ +What are the IP addresses of Telegram Data Centers? +=================================================== + +Telegram is currently composed by a decentralized, multi-DC infrastructure (currently 5 DCs, each of which can +work independently) spread across different locations worldwide. However, some of the less busy DCs have been lately +dismissed and their IP addresses are now kept as aliases to the nearest one. + +.. csv-table:: Production Environment + :header: ID, Location, IPv4, IPv6 + :widths: auto + :align: center + + DC1, "MIA, Miami FL, USA", ``149.154.175.53``, ``2001:b28:f23d:f001::a`` + DC2, "AMS, Amsterdam, NL", ``149.154.167.51``, ``2001:67c:4e8:f002::a`` + DC3*, "MIA, Miami FL, USA", ``149.154.175.100``, ``2001:b28:f23d:f003::a`` + DC4, "AMS, Amsterdam, NL", ``149.154.167.91``, ``2001:67c:4e8:f004::a`` + DC5, "SIN, Singapore, SG", ``91.108.56.130``, ``2001:b28:f23f:f005::a`` + +.. csv-table:: Test Environment + :header: ID, Location, IPv4, IPv6 + :widths: auto + :align: center + + DC1, "MIA, Miami FL, USA", ``149.154.175.10``, ``2001:b28:f23d:f001::e`` + DC2, "AMS, Amsterdam, NL", ``149.154.167.40``, ``2001:67c:4e8:f002::e`` + DC3*, "MIA, Miami FL, USA", ``149.154.175.117``, ``2001:b28:f23d:f003::e`` + +.. centered:: More info about the Test Environment can be found :doc:`here <../topics/test-servers>`. + +***** Alias DC \ No newline at end of file diff --git a/docs/source/faq/why-is-the-api-key-needed-for-bots.rst b/docs/source/faq/why-is-the-api-key-needed-for-bots.rst new file mode 100644 index 0000000000..2e062d405d --- /dev/null +++ b/docs/source/faq/why-is-the-api-key-needed-for-bots.rst @@ -0,0 +1,12 @@ +Why is the API key needed for bots? +=================================== + +Requests against the official bot API endpoints are made via JSON/HTTP and are handled by an intermediate server +application that implements the MTProto protocol and uses its own API key to communicate with the MTProto servers. + +.. figure:: //_static/img/mtproto-vs-bot-api.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. \ No newline at end of file diff --git a/docs/source/faq/why-is-the-client-reacting-slowly-in-supergroups-channels.rst b/docs/source/faq/why-is-the-client-reacting-slowly-in-supergroups-channels.rst new file mode 100644 index 0000000000..4d19616469 --- /dev/null +++ b/docs/source/faq/why-is-the-client-reacting-slowly-in-supergroups-channels.rst @@ -0,0 +1,18 @@ +Why is the client reacting slowly in supergroups/channels? +========================================================== + +Because of how Telegram works internally, every message you receive and send must pass through the creator's DC, and in +the worst case where you, the creator and another member all belong to three different DCs, the other member messages +have to go through from their DC to the creator's DC and finally to your DC. This is applied to each message and member +of a supergroup/channel and the process will inevitably take its time. + +Another reason that makes responses come slowly is that messages are dispatched by priority. Depending on the kind +of member, some users receive messages faster than others and for big and busy supergroups the delay might become +noticeable, especially if you are among the lower end of the priority list: + +1. Creator. +2. Administrators. +3. Bots. +4. Mentioned users. +5. Recent online users. +6. Everyone else. \ No newline at end of file diff --git a/docs/source/faq/why-is-the-event-handler-triggered-twice-or-more.rst b/docs/source/faq/why-is-the-event-handler-triggered-twice-or-more.rst new file mode 100644 index 0000000000..ada12f1de3 --- /dev/null +++ b/docs/source/faq/why-is-the-event-handler-triggered-twice-or-more.rst @@ -0,0 +1,28 @@ +Why is the event handler called twice or more? +============================================== + +The event handler is being called twice or more because one or more message edit events have arrived. +By default, Pyrogram listens to both new and edit message events inside ``on_message`` handlers. To prevent edit events +from calling the handler, use the "not edited" filter ``~filters.edited``. + +For example: + +.. code-block:: python + + ... + + @app.on_message(... & ~filters.edited) + async def handler(client, message): + ... + +Or, avoid handling any edited message altogether this way: + +.. code-block:: python + + ... + + @app.on_message(filters.edited) + async def edited(client, message): + pass + + ... # other handlers \ No newline at end of file diff --git a/docs/source/glossary.rst b/docs/source/glossary.rst deleted file mode 100644 index fa6086ce6c..0000000000 --- a/docs/source/glossary.rst +++ /dev/null @@ -1,86 +0,0 @@ -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. - -.. tip:: - - If you think something interesting could be added here, feel free to propose it by opening a `Feature Request`_. - -.. contents:: Contents - :backlinks: none - :depth: 1 - :local: - ------ - -Terms ------ - -.. glossary:: - :sorted: - - 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. - :doc:`More on API keys `. - - DC - Also known as *data center*, is a place where lots of computer systems are housed and used together in order to - achieve high quality and availability for services. - - RPC - Acronym for Remote Procedure Call, that is, a function which gets executed at some remote place (i.e. Telegram - server) and not in your local machine. - - 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. :doc:`More on RPCError `. - - MTProto - The name of the custom-made, open and encrypted protocol by Telegram, implemented in Pyrogram. - :doc:`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 TL-schema. - :doc:`More on MTProto API `. - - Bot API - The Telegram Bot API that is able to only connect normal bots only to Telegram using HTTP as application layer - protocol and allows to execute a sub-set of the main Telegram API. - :doc:`More on Bot 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. - Not to be confused with *bot*, that is, a normal Telegram bot created by `@BotFather `_. - - Session - Also known as *login session*, is a strictly personal piece of data 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. - :doc:`More on Handlers `. - - 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. - :doc:`More on Decorators `. - -.. _Feature Request: https://github.com/pyrogram/pyrogram/issues/new?labels=enhancement&template=feature_request.md diff --git a/docs/source/index.rst b/docs/source/index.rst index 3834853434..8838c3db17 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -5,7 +5,8 @@ Welcome to Pyrogram @@ -14,15 +15,15 @@ Welcome to Pyrogram
- Source Code + Development • - + Releases • - - Community + + News

@@ -35,16 +36,34 @@ Welcome to Pyrogram @app.on_message(filters.private) async def hello(client, message): - await message.reply_text(f"Hello {message.from_user.mention}") + await message.reply("Hello from Pyrogram!") app.run() -**Pyrogram** is a modern, elegant and easy-to-use Telegram_ 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 -:doc:`MTProto API `. +**Pyrogram** is a modern, elegant and asynchronous :doc:`MTProto API ` framework. +It enables you to easily interact with the main Telegram API through a user account (custom client) or a bot identity +(bot API alternative) using Python. -.. _Telegram: https://telegram.org +Support +------- + +If you'd like to support Pyrogram, you can consider: + +- `Become a GitHub sponsor `_. +- `Become a LiberaPay patron `_. +- `Become an OpenCollective backer `_. + +Key Features +------------ + +- **Ready**: Install Pyrogram with pip and start building your applications right away. +- **Easy**: Makes the Telegram API simple and intuitive, while still allowing advanced usages. +- **Elegant**: Low-level details are abstracted and re-presented in a more convenient way. +- **Fast**: Boosted up by :doc:`TgCrypto `, a high-performance crypto library written in pure C. +- **Type-hinted**: Types and methods are all type-hinted, enabling excellent editor support. +- **Async**: Fully asynchronous (also usable synchronously if wanted, for convenience). +- **Powerful**: Full access to Telegram's API to execute any official client action and more. How the Documentation is Organized ---------------------------------- @@ -53,18 +72,11 @@ Contents are organized into sections composed of self-contained topics which can following them in order using the :guilabel:`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. -.. admonition :: Cloud Credits - :class: tip - - If you need a cloud server to host your applications, we recommend using **Hetzner Cloud**. Sign up with - `this link `_ to get €20 in cloud credits and help support Pyrogram as - well. - First Steps ^^^^^^^^^^^ .. hlist:: - :columns: 2 + :columns: 1 - :doc:`Quick Start `: Overview to get you started quickly. - :doc:`Calling Methods `: How to call Pyrogram's methods. @@ -75,7 +87,7 @@ API Reference ^^^^^^^^^^^^^ .. hlist:: - :columns: 2 + :columns: 1 - :doc:`Pyrogram Client `: Reference details about the Client class. - :doc:`Available Methods `: List of available high-level methods. @@ -86,16 +98,12 @@ Meta ^^^^ .. hlist:: - :columns: 2 + :columns: 1 - - :doc:`Pyrogram FAQ `: Answers to common Pyrogram questions. - - :doc:`Pyrogram Glossary `: List of words with brief explanations. + - :doc:`Pyrogram FAQ `: Answers to common Pyrogram questions. - :doc:`Support Pyrogram `: Ways to show your appreciation. - - :doc:`About the License `: Information about the Project license. - :doc:`Release Notes `: Release notes for Pyrogram releases. -Last updated on |today| - .. toctree:: :hidden: :caption: Introduction @@ -136,14 +144,13 @@ Last updated on |today| topics/more-on-updates topics/config-file topics/smart-plugins - topics/session-settings + topics/client-settings topics/tgcrypto topics/storage-engines topics/text-formatting topics/serializing topics/proxy topics/scheduling - topics/bots-interaction topics/mtproto-vs-botapi topics/debugging topics/test-servers @@ -154,10 +161,8 @@ Last updated on |today| :hidden: :caption: Meta - faq - glossary + faq/index support - license releases/index .. toctree:: diff --git a/docs/source/intro/install.rst b/docs/source/intro/install.rst index 813961dcac..67a6b9432d 100644 --- a/docs/source/intro/install.rst +++ b/docs/source/intro/install.rst @@ -1,16 +1,9 @@ Install Guide ============= -Being a modern Python library, **Pyrogram** requires Python 3.6+ to be installed in your system. +Being a modern Python framework, Pyrogram requires an up to date version of Python to be installed in your system. We recommend using the latest versions of both Python 3 and pip. -- Get **Python 3** from https://www.python.org/downloads/ (or with your package manager). -- Get **pip** by following the instructions at https://pip.pypa.io/en/latest/installing/. - -.. important:: - - Pyrogram supports **Python 3** only, starting from version 3.6. **PyPy** is supported too. - .. contents:: Contents :backlinks: none :depth: 1 @@ -36,12 +29,7 @@ Install Pyrogram Bleeding Edge ------------- -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'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 ``master`` branch using this command (note "master.zip" in -the link): +You can install the development version from the git ``master`` branch using this command: .. code-block:: text @@ -57,6 +45,6 @@ If no error shows up you are good to go. >>> import pyrogram >>> pyrogram.__version__ - '|version|' + 'x.y.z' .. _`Github repo`: http://github.com/pyrogram/pyrogram diff --git a/docs/source/intro/quickstart.rst b/docs/source/intro/quickstart.rst index eeb9848264..0981d14794 100644 --- a/docs/source/intro/quickstart.rst +++ b/docs/source/intro/quickstart.rst @@ -1,49 +1,54 @@ Quick Start =========== -The next few steps serve as a quick start for all new :term:`Pyrogrammers ` that want to see Pyrogram in -action as fast as possible. Let's go! +The next few steps serve as a quick start to see Pyrogram in action as fast as possible. Get Pyrogram Real Fast ---------------------- +.. admonition :: Cloud Credits + :class: tip + + If you need a cloud server to host your applications, try Hetzner Cloud. You can sign up with + `this link `_ to get €20 in cloud credits. + 1. Install Pyrogram with ``pip3 install -U pyrogram``. 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 the text editor of your choice and paste the following: .. code-block:: python + import asyncio from pyrogram import Client api_id = 12345 api_hash = "0123456789abcdef0123456789abcdef" - with Client("my_account", api_id, api_hash) as app: - app.send_message("me", "Greetings from **Pyrogram**!") + async def main(): + async with Client("my_account", api_id, api_hash) as app: + await app.send_message("me", "Greetings from **Pyrogram**!") + + asyncio.run(main()) 4. Replace *api_id* and *api_hash* values with your own. -5. Save the file as ``pyro.py``. +5. Save the file as ``hello.py``. -6. Run the script with ``python3 pyro.py`` +6. Run the script with ``python3 hello.py`` 7. Follow the instructions on your terminal to login. 8. Watch Pyrogram send a message to yourself. -9. Join our `community`_. - -10. Say, "hi!". - 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 above. +That was just a quick overview. 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 to continue? You can take a shortcut to :doc:`Calling Methods <../start/invoking>` and come back later to -learn some more details. +If you are feeling eager to continue you can take a shortcut to :doc:`Calling Methods <../start/invoking>` and come back +later to learn some more details. .. _community: https://t.me/Pyrogram diff --git a/docs/source/intro/setup.rst b/docs/source/intro/setup.rst index e4c6e64065..8bffcd86b4 100644 --- a/docs/source/intro/setup.rst +++ b/docs/source/intro/setup.rst @@ -2,7 +2,7 @@ Project Setup ============= We have just :doc:`installed Pyrogram `. In this page we'll discuss what you need to do in order to set up a -project with the library. Let's see how it's done. +project with the framework. .. contents:: Contents :backlinks: none @@ -17,18 +17,13 @@ API Keys The very first step requires you to obtain a valid Telegram API key (API id/hash pair): #. Visit https://my.telegram.org/apps and log in with your Telegram Account. -#. Fill out the form to register a new Telegram application. -#. Done! The API key consists of two parts: **api_id** and **api_hash**. - -.. important:: - - The API key is personal and must be kept secret. +#. Fill out the form with your details and register a new Telegram application. +#. Done. The API key consists of two parts: **api_id** and **api_hash**. Keep it secret. .. note:: - The API key is unique for each user, but defines a token for a Telegram *application* you are going to build. This - means that you are able to authorize multiple users (and bots too) to access the Telegram database through the - MTProto API by a single API key. + The API key defines a token for a Telegram *application* you are going to build. + This means that you are able to authorize multiple users or bots with a single API key. Configuration ------------- @@ -36,9 +31,9 @@ Configuration Having the API key from the previous step in handy, we can now begin to configure a Pyrogram project. There are two ways to do so, and you can choose what fits better for you: -- First option (recommended): create a new ``config.ini`` file next to your main script, copy-paste the following and - replace the **api_id** and **api_hash** values with your own. This is the preferred method because allows you to - keep your credentials out of your code without having to deal with how to load them: +- First option: create a new ``config.ini`` file next to your main script, copy-paste the following and + replace the *api_id* and *api_hash* values with your own. This method allows you to keep your credentials out of + your code without having to deal with how to load them. .. code-block:: ini @@ -47,8 +42,7 @@ There are two ways to do so, and you can choose what fits better for you: api_hash = 0123456789abcdef0123456789abcdef - Alternatively, you can pass your API key to Pyrogram by simply using the *api_id* and *api_hash* parameters of the - Client class. This way you can have full control on how to store and load your credentials (e.g., you can load the - credentials from the environment variables and directly pass the values into Pyrogram): + Client class. This way you can have full control on how to store and load your credentials: .. code-block:: python diff --git a/docs/source/license.rst b/docs/source/license.rst deleted file mode 100644 index 5f1d25ee0d..0000000000 --- a/docs/source/license.rst +++ /dev/null @@ -1,16 +0,0 @@ -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 code (either open source, under the same or a different -license, or even proprietary) for any purpose whatsoever without being required to release the source code of your -applications. Derivative works, including modifications to the library itself can only be redistributed under the same -LGPLv3+ license. - -.. _GNU Lesser General Public License v3 or later (LGPLv3+): https://github.com/pyrogram/pyrogram/blob/develop/COPYING.lesser diff --git a/docs/source/start/auth.rst b/docs/source/start/auth.rst index 23ff9fe276..0e61e59d11 100644 --- a/docs/source/start/auth.rst +++ b/docs/source/start/auth.rst @@ -26,23 +26,20 @@ the :meth:`~pyrogram.Client.run` method: app = Client("my_account") app.run() -This starts an interactive shell asking you to input your **phone number** (including your `Country Code`_) and the -**phone code** you will receive in your devices that are already authorized or via SMS: +This starts an interactive shell asking you to input your **phone number**, including your `Country Code`_ (the plus +``+`` and minus ``-`` symbols can be omitted) and the **phone code** you will receive in your devices that are already +authorized or via SMS: .. code-block:: text - Enter phone number: +39********** - Is "+39**********" correct? (y/n): y - Enter phone code: 32768 - Logged in successfully as Dan + Enter phone number: +1-123-456-7890 + Is "+1-123-456-7890" correct? (y/n): y + Enter phone code: 12345 + Logged in successfully After successfully authorizing yourself, a new file called ``my_account.session`` will be created allowing Pyrogram to -execute API calls with your identity. This file will be loaded again when you restart your app, and as long as you -keep the session alive, Pyrogram won't ask you again to enter your phone number. - -.. important:: - - Your ``*.session`` file is personal and must be kept secret. +execute API calls with your identity. This file is personal and will be loaded again when you restart your app, and as +long as you keep the session alive, Pyrogram won't ask you again to enter your phone number. .. note:: diff --git a/docs/source/start/errors.rst b/docs/source/start/errors.rst index 8e592f18c7..5f59deef6c 100644 --- a/docs/source/start/errors.rst +++ b/docs/source/start/errors.rst @@ -1,8 +1,8 @@ Error Handling ============== -Errors are inevitable when working with the API, and they can be correctly handled with ``try...except`` blocks in order -to control the behaviour of your application. Pyrogram errors all live inside the ``errors`` package: +Errors can be correctly handled with ``try...except`` blocks in order to control the behaviour of your application. +Pyrogram errors all live inside the ``errors`` package: .. code-block:: python @@ -25,10 +25,10 @@ This error is raised every time a method call against Telegram's API was unsucce from pyrogram.errors import RPCError -.. warning:: +.. note:: - 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. + Avoid catching this error everywhere, 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. Error Categories ---------------- @@ -84,9 +84,6 @@ whole category of errors and be sure to also handle these unknown errors. In case a whole class of errors is unknown (that is, an error code that is unknown), Pyrogram will raise a special ``520 UnknownError`` exception. -In both cases, Pyrogram will log them in the ``unknown_errors.txt`` file. Users are invited to report -these unknown errors in the `discussion group `_. - Errors with Values ------------------ diff --git a/docs/source/start/examples/bot_keyboards.rst b/docs/source/start/examples/bot_keyboards.rst index 6e288c4314..a3a549f4df 100644 --- a/docs/source/start/examples/bot_keyboards.rst +++ b/docs/source/start/examples/bot_keyboards.rst @@ -19,7 +19,7 @@ like send_audio(), send_document(), send_location(), etc... with app: app.send_message( - "haskell", # Edit this + "me", # Edit this "This is a ReplyKeyboardMarkup example", reply_markup=ReplyKeyboardMarkup( [ @@ -33,7 +33,7 @@ like send_audio(), send_document(), send_location(), etc... ) app.send_message( - "haskell", # Edit this + "me", # Edit this "This is a InlineKeyboardMarkup example", reply_markup=InlineKeyboardMarkup( [ diff --git a/docs/source/start/examples/echobot.rst b/docs/source/start/examples/echobot.rst index 61dc9929da..2ff578e97d 100644 --- a/docs/source/start/examples/echobot.rst +++ b/docs/source/start/examples/echobot.rst @@ -15,7 +15,7 @@ It uses the ``@on_message`` decorator to register a ``MessageHandler`` and appli @app.on_message(filters.text & filters.private) def echo(client, message): - message.reply_text(message.text) + message.reply(message.text) app.run() # Automatically start() and idle() \ No newline at end of file diff --git a/docs/source/start/examples/hello_world.rst b/docs/source/start/examples/hello_world.rst index e68847798a..997659e237 100644 --- a/docs/source/start/examples/hello_world.rst +++ b/docs/source/start/examples/hello_world.rst @@ -13,9 +13,3 @@ This example demonstrates a basic API usage with app: # Send a message, Markdown is enabled by default app.send_message("me", "Hi there! I'm using **Pyrogram**") - - # Send a location - app.send_location("me", 51.500729, -0.124583) - - # Send a sticker - app.send_sticker("me", "CAADBAADzg4AAvLQYAEz_x2EOgdRwBYE") \ No newline at end of file diff --git a/docs/source/start/examples/inline_queries.rst b/docs/source/start/examples/inline_queries.rst index 023b9c6ed1..09d226ef6b 100644 --- a/docs/source/start/examples/inline_queries.rst +++ b/docs/source/start/examples/inline_queries.rst @@ -26,7 +26,6 @@ It uses the @on_inline_query decorator to register an InlineQueryHandler. ), url="https://docs.pyrogram.org/intro/install", description="How to install Pyrogram", - thumb_url="https://i.imgur.com/JyxrStE.png", reply_markup=InlineKeyboardMarkup( [ [InlineKeyboardButton( @@ -43,7 +42,6 @@ It uses the @on_inline_query decorator to register an InlineQueryHandler. ), url="https://docs.pyrogram.org/start/invoking", description="How to use Pyrogram", - thumb_url="https://i.imgur.com/JyxrStE.png", reply_markup=InlineKeyboardMarkup( [ [InlineKeyboardButton( diff --git a/docs/source/start/examples/use_inline_bots.rst b/docs/source/start/examples/use_inline_bots.rst index 284432d8a0..63e4698506 100644 --- a/docs/source/start/examples/use_inline_bots.rst +++ b/docs/source/start/examples/use_inline_bots.rst @@ -11,8 +11,8 @@ This example shows how to query an inline bot (as user). app = Client("my_account") with app: - # Get bot results for "Fuzz Universe" from the inline bot @vid - bot_results = app.get_inline_bot_results("vid", "Fuzz Universe") + # Get bot results for "hello" from the inline bot @vid + bot_results = app.get_inline_bot_results("vid", "hello") # Send the first result (bot_results.results[0]) to your own chat (Saved Messages) app.send_inline_bot_result("me", bot_results.query_id, bot_results.results[0].id) \ No newline at end of file diff --git a/docs/source/start/examples/welcomebot.rst b/docs/source/start/examples/welcomebot.rst index cdd7f022d7..a742059007 100644 --- a/docs/source/start/examples/welcomebot.rst +++ b/docs/source/start/examples/welcomebot.rst @@ -8,7 +8,7 @@ to make it only work for specific messages in a specific chat. from pyrogram import Client, emoji, filters - TARGET = "PyrogramChat" # Target chat. Can also be a list of multiple chat ids/usernames + TARGET = -100123456789 # 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.org/)'s group chat {}!" # Welcome message diff --git a/docs/source/start/invoking.rst b/docs/source/start/invoking.rst index b8eddb9e62..de1e321ed1 100644 --- a/docs/source/start/invoking.rst +++ b/docs/source/start/invoking.rst @@ -2,7 +2,7 @@ Calling Methods =============== At this point, we have successfully :doc:`installed Pyrogram <../intro/install>` and :doc:`authorized ` our -account; we are now aiming towards the core of the library. It's time to start playing with the API! +account; we are now aiming towards the core of the framework. .. contents:: Contents :backlinks: none @@ -22,44 +22,53 @@ Making API method calls with Pyrogram is very simple. Here's a basic example we app = Client("my_account") - with app: - app.send_message("me", "Hi!") + async def main(): + async with app: + await app.send_message("me", "Hi!") -Basic step-by-step -^^^^^^^^^^^^^^^^^^ + app.run(main()) + +Step-by-step +^^^^^^^^^^^^ -#. Let's begin by importing the Client class: +#. Let's begin by importing the Client class. .. code-block:: python from pyrogram import Client -#. Now instantiate a new Client object, "my_account" is a session name of your choice: +#. Now instantiate a new Client object, "my_account" is a session name of your choice. .. code-block:: python app = Client("my_account") -#. The ``with`` context manager is a shortcut for starting, executing and stopping the Client: +#. Async methods can't be executed at the top level, because they must be inside an async context. + Here we define an async function and put our code inside. Also notice the ``await`` keyword in front of the method + call; this is required for all asynchronous methods. .. code-block:: python - with app: + async def main(): + async with app: + await app.send_message("me", "Hi!") -#. Now, you can call any method you like: +#. Finally, we tell Python to schedule our ``main()`` async function by using Pyrogram's :meth:`~pyrogram.Client.run` + method. .. code-block:: python - app.send_message("me", "Hi!") + app.run(main()) Context Manager --------------- -The ``with`` statement starts a context manager used as a shortcut to automatically call :meth:`~pyrogram.Client.start` -and :meth:`~pyrogram.Client.stop`, which are methods required for Pyrogram to work properly. The context manager does -also gracefully stop the client, even in case of unhandled exceptions in your code. +The ``async with`` statement starts a context manager, which is used as a shortcut for starting, executing and stopping +the Client, asynchronously. It does so by automatically calling :meth:`~pyrogram.Client.start` and +:meth:`~pyrogram.Client.stop` in a more convenient way which also gracefully stops the client, even in case of +unhandled exceptions in your code. -This is how Pyrogram looks without the context manager: +Below there's the same example as above, but without the use of the context manager: .. code-block:: python @@ -67,53 +76,49 @@ This is how Pyrogram looks without the context manager: app = Client("my_account") - app.start() - app.send_message("me", "Hi!") - app.stop() + async def main(): + await app.start() + await app.send_message("me", "Hi!") + await app.stop() -Asynchronous Calls ------------------- + app.run(main()) -In case you want Pyrogram to run asynchronously (e.g.: if you are using third party libraries that require you to call -them with ``await``), use the asynchronous context manager: +Using asyncio.run() +------------------- + +Alternatively to the :meth:`~pyrogram.Client.run` method, you can use Python's ``asyncio.run()`` to execute the main +function, with one little caveat: the Client instance (and possibly other asyncio resources you are going to use) must +be instantiated inside the main function. .. code-block:: python + import asyncio from pyrogram import Client - app = Client("my_account") - async def main(): + app = Client("my_account") + async with app: await app.send_message("me", "Hi!") - app.run(main()) - -Asynchronous step-by-step -^^^^^^^^^^^^^^^^^^^^^^^^^ - -#. Import the Client class and create an instance: - - .. code-block:: python + asyncio.run(main()) - from pyrogram import Client - - app = Client("my_account") +Synchronous Calls +------------------ -#. Async methods can't normally be executed at the top level, because they must be inside an async-defined function; - here we define one and put our code inside; the context manager is also being used differently in asyncio and - method calls require the await keyword: +Pyrogram is an asynchronous framework, but it also provides a convenience way for calling methods without the need +of async/await keywords and the extra boilerplate. In case you want Pyrogram to run synchronously, simply use the +synchronous context manager: - .. code-block:: python +.. code-block:: python - async def main(): - async with app: - await app.send_message("me", "Hi!") + from pyrogram import Client -#. Finally, we tell Python to schedule our ``main()`` async function, which in turn will execute Pyrogram's methods. - Using :meth:`~pyrogram.Client.run` this way is a friendly alternative for the much more verbose - ``asyncio.get_event_loop().run_until_complete(main())``: + app = Client("my_account") - .. code-block:: python + with app: + app.send_message("me", "Hi!") - app.run(main()) +As you can see, the non-async example becomes less cluttered. Use Pyrogram in this non-asynchronous way only when you +want to write something without the boilerplate or in case you want to combine Pyrogram with other libraries that are +not async. \ No newline at end of file diff --git a/docs/source/start/updates.rst b/docs/source/start/updates.rst index 27dd0316b0..dee5a11589 100644 --- a/docs/source/start/updates.rst +++ b/docs/source/start/updates.rst @@ -1,8 +1,8 @@ Handling Updates ================ -Calling :doc:`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. +Calling :doc:`API methods ` sequentially is one way to use Pyrogram, but how to react when, for example, a +new message arrives? This page deals with updates and how to handle such events in Pyrogram. .. contents:: Contents :backlinks: none @@ -14,10 +14,9 @@ This page deals with updates and how to handle such events in Pyrogram. Let's ha 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 has changed. These updates are handled by registering one or more callback functions in your app -using :doc:`Handlers <../api/handlers>`. +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 has changed. These updates are +handled by registering one or more callback functions in your app using :doc:`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. @@ -40,50 +39,51 @@ The most elegant way to register a message handler is by using the :meth:`~pyrog app = Client("my_account") - @app.on_message() - def my_handler(client, message): - message.forward("me") - + async def my_handler(client, message): + await message.forward("me") app.run() The defined function ``my_handler``, which accepts the two arguments *(client, message)*, will be the function that gets executed every time a new message arrives. -Asynchronous handlers +In the last line we see again the :meth:`~pyrogram.Client.run` method, this time used without any argument. +Its purpose here is simply to automatically :meth:`~pyrogram.Client.start`, keep the Client online so that it can listen +for updates and :meth:`~pyrogram.Client.stop` it once you hit ``CTRL+C``. + +Synchronous handlers ^^^^^^^^^^^^^^^^^^^^^ -You can also have asynchronous handlers; you only need to define the callback function using ``async def`` and call API -methods by placing ``await`` in front of them: +You can also have synchronous handlers; you only need to define the callback function without using ``async def`` and +call API methods by not placing ``await`` in front of them: .. code-block:: python @app.on_message() - async def my_handler(client, message): - await message.forward("me") + def my_handler(client, message): + message.forward("me") .. note:: - You can mix ``def`` and ``async def`` handlers as much as you need, Pyrogram will still work concurrently and - efficiently regardless of what you choose. + You can mix ``def`` and ``async def`` handlers as much as you like, Pyrogram will still work concurrently and + efficiently regardless of what you choose. However, it is recommended to use Pyrogram in its native, asynchronous + form at all times, unless you want to write something without the boilerplate or in case you want to combine + Pyrogram with other libraries that are not async. Using add_handler() ^^^^^^^^^^^^^^^^^^^ The :meth:`~pyrogram.Client.add_handler` method takes any handler instance that wraps around your defined callback -function and registers it in your Client. It is useful in case you want to programmatically add handlers (or in case, -for some reason, you don't like to use decorators). +function and registers it in your Client. It is useful in case you want to programmatically add handlers. .. code-block:: python from pyrogram import Client from pyrogram.handlers import MessageHandler - - def my_function(client, message): - message.forward("me") - + async def my_function(client, message): + await message.forward("me") app = Client("my_account") @@ -92,12 +92,12 @@ for some reason, you don't like to use decorators). app.run() -The same about asynchronous handlers applies for :meth:`~pyrogram.Client.add_handler`: +The same about synchronous handlers applies for :meth:`~pyrogram.Client.add_handler`: .. code-block:: python - async def my_function(client, message): - await message.forward("me") + def my_function(client, message): + message.forward("me") .. note:: diff --git a/docs/source/support.rst b/docs/source/support.rst index 029eeec043..32dfdb6b71 100644 --- a/docs/source/support.rst +++ b/docs/source/support.rst @@ -1,66 +1,62 @@ Support Pyrogram ================ -As a developer, you probably understand that "open source" doesn't mean "free work". If you wish to tip me for Pyrogram --- or any of my `other works`_ -- you can do so by the ways shown below. Your appreciation means a lot and helps -staying motivated! - ---- `Dan`_ +.. raw:: html ------ + -Star ----- +
+ Fork -Pyrogram is free and open source software, and thus powered by your love and support! If you like the project and have -found it to be useful, give Pyrogram a `Star on GitHub`_. + Star +
-.. raw:: html +
- Star -

+Pyrogram is a free and open source project. +If you enjoy Pyrogram and would like to show your appreciation, consider donating or becoming +a sponsor of the project. You can support Pyrogram via the ways shown below: ----- -Sponsor -------- +GitHub Sponsor +-------------- -You can become a GitHub sponsor: +`Become a GitHub sponsor `_. .. raw:: html - + + Sponsor ----- -Donate ------- +LiberaPay Patron +---------------- -You can donate via PayPal using the button below: +`Become a LiberaPay patron `_. .. raw:: html -
- - - -
+ ----- -Cloud Credits -------------- +OpenCollective Backer +--------------------- + +`Become an OpenCollective backer `_ -If you need a cloud server to host your applications, try **Hetzner Cloud**. You can sign up with -`this link `_ to get €20 in cloud credits and help support Pyrogram and -my `other projects`_. +.. raw:: html -.. _Star on GitHub: https://github.com/pyrogram/pyrogram -.. _other projects: https://github.com/delivrance -.. _other works: https://github.com/delivrance -.. _Dan: https://t.me/haskell + \ No newline at end of file diff --git a/docs/source/topics/advanced-usage.rst b/docs/source/topics/advanced-usage.rst index 9df028ad31..0b0f068323 100644 --- a/docs/source/topics/advanced-usage.rst +++ b/docs/source/topics/advanced-usage.rst @@ -1,9 +1,8 @@ Advanced Usage ============== -Pyrogram's API, which consists of well documented convenience :doc:`methods <../api/methods/index>` and facade -:doc:`types <../api/types/index>`, exists to provide a much easier interface to the undocumented and often confusing -Telegram API. +Pyrogram's API -- which consists of well documented :doc:`methods <../api/methods/index>` and +:doc:`types <../api/types/index>` -- exists to provide an easier interface to the more complex Telegram API. In this section, you'll be shown the alternative way of communicating with Telegram using Pyrogram: the main "raw" Telegram API with its functions and types. @@ -21,25 +20,18 @@ Telegram Raw API If you can't find a high-level method for your needs or if you want complete, low-level access to the whole Telegram API, you have to use the raw :mod:`~pyrogram.raw.functions` and :mod:`~pyrogram.raw.types`. -As already hinted, raw functions and types can be really confusing, mainly because people don't realize soon enough they -accept *only* the right types and that all required parameters must be filled in. This section will therefore explain -some pitfalls to take into consideration when working with the raw API. +As already hinted, raw functions and types can be less convenient. This section will therefore explain some pitfalls to +take into consideration when working with the raw API. -.. hint:: +.. tip:: - Every available high-level methods in Pyrogram is built on top of these raw functions. - - Nothing stops you from using the raw functions only, but they are rather complex and - :doc:`plenty of them <../api/methods/index>` are already re-implemented by providing a much simpler and cleaner - interface which is very similar to the Bot API (yet much more powerful). - - If you think a raw function should be wrapped and added as a high-level method, feel free to ask in our Community_! + Every available high-level method in Pyrogram is built on top of these raw functions. Invoking Functions -^^^^^^^^^^^^^^^^^^ +------------------ Unlike the :doc:`methods <../api/methods/index>` found in Pyrogram's API, which can be called in the usual simple way, -functions to be invoked from the raw Telegram API have a different way of usage and are more complex. +functions to be invoked from the raw Telegram API have a different way of usage. First of all, both :doc:`raw functions <../telegram/functions/index>` and :doc:`raw types <../telegram/types/index>` live in their respective packages (and sub-packages): ``pyrogram.raw.functions``, ``pyrogram.raw.types``. They all exist @@ -61,12 +53,12 @@ Here's some examples: with Client("my_account") as app: app.send( functions.account.UpdateProfile( - first_name="Dan", last_name="Tès", - about="Bio written from Pyrogram" + first_name="First Name", last_name="Last Name", + about="New bio text" ) ) -- Disable links to your account when someone forwards your messages: +- Set online/offline status: .. code-block:: python @@ -74,14 +66,13 @@ Here's some examples: from pyrogram.raw import functions, types with Client("my_account") as app: - app.send( - functions.account.SetPrivacy( - key=types.PrivacyKeyForwards(), - rules=[types.InputPrivacyValueDisallowAll()] - ) - ) + # Set online status + app.send(functions.account.UpdateStatus(offline=False)) + + # Set offline status + app.send(functions.account.UpdateStatus(offline=True)) -- Invite users to your channel/supergroup: +- Get chat info: .. code-block:: python @@ -89,21 +80,18 @@ Here's some examples: from pyrogram.raw import functions, types with Client("my_account") as app: - app.send( - functions.channels.InviteToChannel( - channel=app.resolve_peer(123456789), # ID or Username - users=[ # The users you want to invite - app.resolve_peer(23456789), # By ID - app.resolve_peer("username"), # By username - app.resolve_peer("+393281234567"), # By phone number - ] + r = app.send( + functions.channels.GetFullChannel( + channel=app.resolve_peer("username") ) ) + print(r) + Chat IDs -^^^^^^^^ +-------- -The way Telegram works makes it impossible to directly send a message to a user or a chat by using their IDs only. +The way Telegram works makes it not possible to directly send a message to a user or a chat by using their IDs only. Instead, a pair of ``id`` and ``access_hash`` wrapped in a so called ``InputPeer`` is always needed. Pyrogram allows sending messages with IDs only thanks to cached access hashes. @@ -112,18 +100,17 @@ Whenever an InputPeer is needed you must pass one of these: - :class:`~pyrogram.raw.types.InputPeerUser` - Users - :class:`~pyrogram.raw.types.InputPeerChat` - Basic Chats -- :class:`~pyrogram.raw.types.InputPeerChannel` - Either Channels or Supergroups +- :class:`~pyrogram.raw.types.InputPeerChannel` - Channels & Supergroups -But you don't necessarily have to manually instantiate each object because, luckily for you, Pyrogram already provides +But you don't necessarily have to manually instantiate each object because Pyrogram already provides :meth:`~pyrogram.Client.resolve_peer` as a convenience utility method that returns the correct InputPeer by accepting a peer ID only. Another thing to take into consideration about chat IDs is the way they are represented: they are all integers and all positive within their respective raw types. -Things are different when working with Pyrogram's API because having them in the same space can theoretically lead to -collisions, and that's why Pyrogram (as well as the official Bot API) uses a slightly different representation for each -kind of ID. +Things are different when working with Pyrogram's API because having them in the same space could lead to +collisions, and that's why Pyrogram uses a slightly different representation for each kind of ID. For example, given the ID *123456789*, here's how Pyrogram can tell entities apart: diff --git a/docs/source/topics/bots-interaction.rst b/docs/source/topics/bots-interaction.rst deleted file mode 100644 index 1fad206988..0000000000 --- a/docs/source/topics/bots-interaction.rst +++ /dev/null @@ -1,50 +0,0 @@ -Bots Interaction -================ - -Users can interact with other bots via plain text messages as well as inline queries. - -.. contents:: Contents - :backlinks: none - :depth: 1 - :local: - ------ - -Inline Bots ------------ - -- If a bot accepts inline queries, you can call it by using - :meth:`~pyrogram.Client.get_inline_bot_results` to get the list of its inline results for a query: - - .. code-block:: python - - # Get bot results for "Fuzz Universe" from the inline bot @vid - bot_results = app.get_inline_bot_results("vid", "Fuzz Universe") - - .. figure:: https://i.imgur.com/IAqLs54.png - :width: 90% - :align: center - :figwidth: 60% - - ``get_inline_bot_results()`` is the equivalent action of writing ``@vid Fuzz Universe`` and getting the - results list. - -- After you retrieved the bot results, you can use - :meth:`~pyrogram.Client.send_inline_bot_result` to send a chosen result to any chat: - - .. code-block:: python - - # Send the first result to your own chat - app.send_inline_bot_result( - "me", - bot_results.query_id, - bot_results.results[0].id - ) - - .. figure:: https://i.imgur.com/wwxr7B7.png - :width: 90% - :align: center - :figwidth: 60% - - ``send_inline_bot_result()`` is the equivalent action of choosing a result from the list and sending it - to a chat. diff --git a/docs/source/topics/session-settings.rst b/docs/source/topics/client-settings.rst similarity index 54% rename from docs/source/topics/session-settings.rst rename to docs/source/topics/client-settings.rst index 847427cf25..1b93d99d7b 100644 --- a/docs/source/topics/session-settings.rst +++ b/docs/source/topics/client-settings.rst @@ -1,24 +1,12 @@ -Session Settings -================ +Client 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. +You can control the way your client appears in the Active Sessions menu of an official client by changing some client +settings. By default you will see something like the following: -Briefly explaining, sessions are simply new logins in your account. They can be reviewed in the settings of an official -app (or by invoking :class:`~pyrogram.api.functions.account.GetAuthorizations` 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/YaqtMLO.png - :width: 600 - :align: center - - **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.13.0** -- ``device_model``: **CPython 3.7.2** -- ``system_version``: **Linux 4.15.0-23-generic** +- Device Model: ``CPython x.y.z`` +- Application: ``Pyrogram x.y.z`` +- System Version: ``Linux x.y.z`` .. contents:: Contents :backlinks: none diff --git a/docs/source/topics/config-file.rst b/docs/source/topics/config-file.rst index 38bb9e3375..1657625acd 100644 --- a/docs/source/topics/config-file.rst +++ b/docs/source/topics/config-file.rst @@ -15,7 +15,7 @@ Introduction ------------ The idea behind using a configuration file is to help keeping your code free of private settings information such as -the API Key and Proxy, without having you to even deal with how to load such settings. The configuration file, usually +the API Key and Proxy, without having you to deal with how to load such settings. The configuration file, usually referred as ``config.ini`` file, is automatically loaded from the root of your working directory; all you need to do is fill in the necessary parts. @@ -23,7 +23,7 @@ fill in the necessary parts. The configuration file is optional, but recommended. If, for any reason, you prefer not to use it, there's always an alternative way to configure Pyrogram via Client's parameters. Doing so, you can have full control on how to store - and load your settings (e.g.: from environment variables). + and load your settings. Settings specified via Client's parameter have higher priority and will override any setting stored in the configuration file. diff --git a/docs/source/topics/create-filters.rst b/docs/source/topics/create-filters.rst index 191eeb05b8..ae7bd5ecf7 100644 --- a/docs/source/topics/create-filters.rst +++ b/docs/source/topics/create-filters.rst @@ -87,7 +87,7 @@ Finally, the filter usage remains the same: Filters with Arguments ---------------------- -A much cooler filter would be one that accepts "pyrogram" or any other string as argument at usage time. +A more flexible filter would be one that accepts "pyrogram" or any other string as argument at usage time. A dynamic filter like this will make use of named arguments for the :meth:`~pyrogram.filters.create` method and the first argument of the callback function, which is a reference to the filter object itself holding the extra data passed via named arguments. diff --git a/docs/source/topics/debugging.rst b/docs/source/topics/debugging.rst index ac0b396fd0..6e6e6d5ed8 100644 --- a/docs/source/topics/debugging.rst +++ b/docs/source/topics/debugging.rst @@ -2,7 +2,7 @@ Debugging ========= When working with the API, chances are you'll stumble upon bugs, get stuck and start wondering how to continue. Nothing -to actually worry about -- that's normal -- and luckily for you, Pyrogram provides some commodities to help you in this. +to actually worry about since Pyrogram provides some commodities to help you in this. .. contents:: Contents :backlinks: none @@ -27,8 +27,8 @@ Consider the following code: .. code-block:: python - dan = app.get_users("haskell") - print(dan) # User + me = app.get_users("me") + print(me) # User This will show a JSON representation of the object returned by :meth:`~pyrogram.Client.get_users`, which is a :class:`~pyrogram.types.User` instance, in this case. The output on your terminal will be something similar to this: @@ -36,9 +36,9 @@ This will show a JSON representation of the object returned by :meth:`~pyrogram. .. code-block:: json { - "_": "pyrogram.User", - "id": 23122162, - "is_self": false, + "_": "User", + "id": 123456789, + "is_self": true, "is_contact": false, "is_mutual_contact": false, "is_deleted": false, @@ -46,19 +46,13 @@ This will show a JSON representation of the object returned by :meth:`~pyrogram. "is_verified": false, "is_restricted": false, "is_support": false, - "is_scam": false, - "first_name": "Dan", - "status": { - "_": "pyrogram.UserStatus", - "user_id": 23122162, - "recently": true - }, - "username": "haskell", - "language_code": "en", + "first_name": "Pyrogram", "photo": { - "_": "pyrogram.ChatPhoto", - "small_file_id": "AQADBAAD8tBgAQAEJjCxGgAEo5IBAAIC", - "big_file_id": "AQADBAAD8tBgAQAEJjCxGgAEpZIBAAEBAg" + "_": "ChatPhoto", + "small_file_id": "AbCdE...EdCbA", + "small_photo_unique_id": "VwXyZ...ZyXwV", + "big_file_id": "AbCdE...EdCbA", + "big_photo_unique_id": "VwXyZ...ZyXwV" } } @@ -69,37 +63,23 @@ Accessing Attributes -------------------- Even though you see a JSON output, it doesn't mean we are dealing with dictionaries; in fact, all Pyrogram types are -full-fledged Python objects and the correct way to access any attribute of them is by using the dot notation ``.``: +fully-fledged Python objects and the correct way to access any attribute of them is by using the dot notation ``.``: .. code-block:: python - dan_photo = dan.photo - print(dan_photo) # ChatPhoto + photo = me.photo + print(photo) # ChatPhoto .. code-block:: json { - "_": "pyrogram.ChatPhoto", - "small_file_id": "AQADBAAD8tBgAQAEJjCxGgAEo5IBAAIC", - "big_file_id": "AQADBAAD8tBgAQAEJjCxGgAEpZIBAAEBAg" + "_": "ChatPhoto", + "small_file_id": "AbCdE...EdCbA", + "small_photo_unique_id": "VwXyZ...ZyXwV", + "big_file_id": "AbCdE...EdCbA", + "big_photo_unique_id": "VwXyZ...ZyXwV" } -However, the bracket notation ``[]`` is also supported, but its usage is discouraged: - -.. warning:: - - Bracket notation in Python is not commonly used for getting/setting object attributes. While it works for Pyrogram - objects, it might not work for anything else and you should not rely on this. - -.. code-block:: python - - dan_photo_big = dan["photo"]["big_file_id"] - print(dan_photo_big) # str - -.. code-block:: text - - AQADBAAD8tBgAQAEJjCxGgAEpZIBAAEBAg - Checking an Object's Type ------------------------- @@ -111,8 +91,8 @@ error. The correct way to get the object type is by using the built-in function .. code-block:: python - dan_status = dan.status - print(type(dan_status)) + status = me.status + print(type(status)) .. code-block:: text @@ -125,8 +105,8 @@ And to check if an object is an instance of a given class, you use the built-in from pyrogram.types import UserStatus - dan_status = dan.status - print(isinstance(dan_status, UserStatus)) + status = me.status + print(isinstance(status, UserStatus)) .. code-block:: text diff --git a/docs/source/topics/more-on-updates.rst b/docs/source/topics/more-on-updates.rst index a636008ace..81a46f3be4 100644 --- a/docs/source/topics/more-on-updates.rst +++ b/docs/source/topics/more-on-updates.rst @@ -24,7 +24,6 @@ group. Dispatching groups hold one or more handlers and are processed sequential For example, take these two handlers: .. code-block:: python - :emphasize-lines: 1, 6 @app.on_message(filters.text | filters.sticker) def text_or_sticker(client, message): diff --git a/docs/source/topics/mtproto-vs-botapi.rst b/docs/source/topics/mtproto-vs-botapi.rst index c73d669219..9681c1eb65 100644 --- a/docs/source/topics/mtproto-vs-botapi.rst +++ b/docs/source/topics/mtproto-vs-botapi.rst @@ -1,10 +1,10 @@ MTProto vs. Bot API =================== -Pyrogram is a framework that acts as a fully-fledged Telegram client based on MTProto, and this very feature makes it -already superior to, what is usually called, the official Bot API, in many respects. This page will therefore show you -why Pyrogram might be a better choice for your project by comparing the two APIs, but first, let's make it clear what -actually is the MTProto and the Bot API. +Pyrogram is a framework written from the ground up that acts as a fully-fledged Telegram client based on the MTProto +API. This means that Pyrogram is able to execute any official client and bot API action and more. This page will +therefore show you why Pyrogram might be a better choice for your project by comparing the two APIs, but first, let's +make it clear what actually is the MTProto and the Bot API. .. contents:: Contents :backlinks: none @@ -19,10 +19,11 @@ 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 and the actual Telegram servers. -The MTProto **API** on the other hand, is what people, for convenience, call the main Telegram API as a whole. This API -is able to authorize both users and bots and is built on top of the MTProto encryption protocol by means of -`binary data serialized`_ in a specific way, as described by the `TL language`_, and delivered using UDP, TCP or even -HTTP as transport-layer protocol. +The MTProto API on the other hand, is what people for convenience call the main Telegram API in order to distinguish it +from the Bot API. The main Telegram API is able to authorize both users and bots and is built on top of the MTProto +encryption protocol by means of `binary data serialized`_ in a specific way, as described by the `TL language`_, and +delivered using UDP, TCP or even HTTP as transport-layer protocol. Clients that make use of Telegram's main API, such as +Pyrogram, implement all these details. .. _MTProto: https://core.telegram.org/mtproto .. _binary data serialized: https://core.telegram.org/mtproto/serialize @@ -31,12 +32,12 @@ HTTP as transport-layer protocol. What is the Bot API? -------------------- -The `Bot API`_ is an HTTP(S) interface for building normal bots using a sub-set of the main MTProto API. 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. +The `Bot API`_ is an HTTP(S) interface for building normal bots using a sub-set of the main Telegram API. 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/WvwBoZo.png +.. figure:: //_static/img/mtproto-vs-bot-api.png :align: center .. _Bot API: https://core.telegram.org/bots/api @@ -44,8 +45,8 @@ using MTProto. Advantages of the MTProto API ----------------------------- -Here is a list of all the advantages in using MTProto-based libraries -- such as Pyrogram -- instead of the official -HTTP Bot API. Using Pyrogram you can: +Here is a non-exhaustive list of all the advantages in using MTProto-based libraries -- such as Pyrogram -- instead of +the official HTTP Bot API. Using Pyrogram you can: .. hlist:: :columns: 1 @@ -69,7 +70,7 @@ HTTP Bot API. Using Pyrogram you can: .. hlist:: :columns: 1 - - :guilabel:`+` **Run multiple sessions at once, up to 10 per account (either bot or user)** + - :guilabel:`+` **Run multiple sessions at once (for both user and bot identities)** - :guilabel:`--` The Bot API intermediate server will terminate any other session in case you try to use the same bot again in a parallel connection. diff --git a/docs/source/topics/scheduling.rst b/docs/source/topics/scheduling.rst index f96e7b07f2..a67a92548a 100644 --- a/docs/source/topics/scheduling.rst +++ b/docs/source/topics/scheduling.rst @@ -14,8 +14,8 @@ non-asynchronous contexts. For more detailed information, you can visit and lear ----- -Using ``apscheduler`` ---------------------- +Using apscheduler +----------------- - Install with ``pip3 install apscheduler`` - Documentation: https://apscheduler.readthedocs.io diff --git a/docs/source/topics/serializing.rst b/docs/source/topics/serializing.rst index 7e01357330..e12e3f0511 100644 --- a/docs/source/topics/serializing.rst +++ b/docs/source/topics/serializing.rst @@ -24,8 +24,7 @@ If you want a nicely formatted, human readable JSON representation of any object ... with app: - r = app.get_chat("haskell") - + r = app.get_chat("me") print(str(r)) .. tip:: @@ -48,7 +47,7 @@ as the process requires the package to be in scope. ... with app: - r = app.get_chat("haskell") + r = app.get_chat("me") print(repr(r)) print(eval(repr(r)) == r) # True diff --git a/docs/source/topics/smart-plugins.rst b/docs/source/topics/smart-plugins.rst index fece636622..a94e3212fe 100644 --- a/docs/source/topics/smart-plugins.rst +++ b/docs/source/topics/smart-plugins.rst @@ -1,9 +1,9 @@ Smart Plugins ============= -Pyrogram embeds a **smart**, lightweight yet powerful plugin system that is meant to further simplify the organization -of large projects and to provide a way for creating pluggable (modular) components that can be **easily shared** across -different Pyrogram applications with **minimal boilerplate code**. +Pyrogram embeds a smart, lightweight yet powerful plugin system that is meant to further simplify the organization +of large projects and to provide a way for creating pluggable (modular) components that can be easily shared across +different Pyrogram applications with minimal boilerplate code. .. tip:: @@ -74,8 +74,8 @@ after importing your modules, like this: 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:`~pyrogram.Client.add_handler` and manually instantiate each -:class:`~pyrogram.handlers.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. +:class:`~pyrogram.handlers.MessageHandler` object because you can't use decorators for your functions. +So, what if you could? Smart Plugins solve this issue by taking care of handlers registration automatically. Using Smart Plugins ------------------- @@ -91,7 +91,6 @@ Setting up your Pyrogram project to accommodate Smart Plugins is pretty straight This is the same example application as shown above, written using the Smart Plugin system. .. code-block:: text - :emphasize-lines: 2, 3 myproject/ plugins/ @@ -102,7 +101,6 @@ Setting up your Pyrogram project to accommodate Smart Plugins is pretty straight - ``plugins/handlers.py`` .. code-block:: python - :emphasize-lines: 4, 9 from pyrogram import Client, filters @@ -164,7 +162,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 :doc:`More on Updates `. + filters included a second time will not work, by design. Learn more at :doc:`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`` @@ -300,32 +298,30 @@ In the previous section we've explained how to specify which plugins to load and starts. Here we'll show, instead, how to unload and load again a previously registered plugin at runtime. Each function decorated with the usual ``on_message`` decorator (or any other decorator that deals with Telegram -updates) will be modified in such a way that a special ``handler`` attribute pointing to a tuple of +updates) will be modified in such a way that a special ``handlers`` attribute pointing to a list of tuples of *(handler: Handler, group: int)* is attached to the function object itself. - ``plugins/handlers.py`` .. code-block:: python - :emphasize-lines: 5, 6 @Client.on_message(filters.text & filters.private) def echo(client, message): message.reply(message.text) print(echo) - print(echo.handler) + print(echo.handlers) - Printing ``echo`` will show something like ````. -- Printing ``echo.handler`` will reveal the handler, that is, a tuple containing the actual handler and the group it - was registered on ``(, 0)``. +- Printing ``echo.handlers`` will reveal the handlers, that is, a list of tuples containing the actual handlers and + the groups they were registered on ``[(, 0)]``. Unloading ^^^^^^^^^ In order to unload a plugin, all you need to do is obtain a reference to it by importing the relevant module and call -:meth:`~pyrogram.Client.remove_handler` Client's method with your function's *handler* special attribute preceded by the -star ``*`` operator as argument. Example: +:meth:`~pyrogram.Client.remove_handler` Client's method with your function's *handler* instance: - ``main.py`` @@ -333,16 +329,19 @@ star ``*`` operator as argument. Example: from plugins.handlers import echo - ... + handlers = echo.handlers - app.remove_handler(*echo.handler) + for h in handlers: + app.remove_handler(*h) The star ``*`` operator is used to unpack the tuple into positional arguments so that *remove_handler* will receive exactly what is needed. The same could have been achieved with: .. code-block:: python - handler, group = echo.handler + handlers = echo.handlers + handler, group = handlers[0] + app.remove_handler(handler, group) Loading @@ -359,4 +358,7 @@ using :meth:`~pyrogram.Client.add_handler` instead. Example: ... - app.add_handler(*echo.handler) \ No newline at end of file + handlers = echo.handlers + + for h in handlers: + app.add_handler(*h) \ No newline at end of file diff --git a/docs/source/topics/storage-engines.rst b/docs/source/topics/storage-engines.rst index 1a86cad8b5..8d693647d6 100644 --- a/docs/source/topics/storage-engines.rst +++ b/docs/source/topics/storage-engines.rst @@ -18,28 +18,18 @@ Persisting Sessions In order to make a client reconnect successfully between restarts, that is, without having to start a new authorization process from scratch each time, Pyrogram needs to store the generated session data somewhere. -Other useful data being stored is peers' cache. In short, peers are all those entities you can chat with, such as users -or bots, basic groups, but also channels and supergroups. Because of how Telegram works, a unique pair of **id** and -**access_hash** is needed to contact a peer. This, plus other useful info such as the peer type, is what is stored -inside a session storage. - -So, if you ever wondered how is Pyrogram able to contact peers just by asking for their ids, it's because of this very -reason: the peer *id* is looked up in the internal database and the available *access_hash* is retrieved, which is then -used to correctly invoke API methods. - Different Storage Engines ------------------------- -Let's now talk about how Pyrogram actually stores all the relevant data. Pyrogram offers two different types of storage -engines: a **File Storage** and a **Memory Storage**. These engines are well integrated in the library and require a -minimal effort to set up. Here's how they work: +Pyrogram offers two different types of storage engines: a **File Storage** and a **Memory Storage**. +These engines are well integrated in the framework and require a minimal effort to set up. Here's how they work: File Storage ^^^^^^^^^^^^ -This is the most common storage engine. It is implemented by using **SQLite**, which will store the session and peers -details. The database will be saved to disk as a single portable file and is designed to efficiently store and retrieve -peers whenever they are needed. +This is the most common storage engine. It is implemented by using **SQLite**, which will store the session details. +The database will be saved to disk as a single portable file and is designed to efficiently store and retrieve +data whenever they are needed. To use this type of engine, simply pass any name of your choice to the ``session_name`` parameter of the :obj:`~pyrogram.Client` constructor, as usual: @@ -68,8 +58,8 @@ session name "**:memory:**" to the ``session_name`` parameter of the :obj:`~pyro with Client(":memory:") as app: print(app.get_me()) -This storage engine is still backed by SQLite, but the database exists purely in memory. This means that, once you stop a -client, the entire database is discarded and the session details used for logging in again will be lost forever. +This storage engine is still backed by SQLite, but the database exists purely in memory. This means that, once you stop +a client, the entire database is discarded and the session details used for logging in again will be lost forever. Session Strings --------------- @@ -84,8 +74,8 @@ In case you want to use an in-memory storage, but also want to keep access to th with Client(":memory:") as app: print(app.export_session_string()) -...and save the resulting (quite long) string somewhere. You can use this string as session name the next time you want -to login using the same session; the storage used will still be completely in-memory: +...and save the resulting string. You can use this string as session name the next time you want to login +using the same session; the storage used will still be in-memory: .. code-block:: python @@ -96,11 +86,5 @@ to login using the same session; the storage used will still be completely in-me with Client(session_string) as app: print(app.get_me()) -Session strings are useful when you want to run authorized Pyrogram clients on platforms like -`Heroku `_, where their ephemeral filesystems makes it much harder for a file-based storage -engine to properly work as intended. - -But, why is the session string so long? Can't it be shorter? No, it can't. The session string already packs the bare -minimum data Pyrogram needs to successfully reconnect to an authorized session, and the 2048-bits auth key is the major -contributor to the overall length. Needless to say that this string, as well as any other session storage, represent -strictly personal data. Keep them safe. +Session strings are useful when you want to run authorized Pyrogram clients on platforms where their ephemeral +filesystems makes it harder for a file-based storage engine to properly work as intended. diff --git a/docs/source/topics/test-servers.rst b/docs/source/topics/test-servers.rst index 3ed996eea9..cba5e709f0 100644 --- a/docs/source/topics/test-servers.rst +++ b/docs/source/topics/test-servers.rst @@ -15,8 +15,7 @@ Telegram's test servers without hassle. All you need to do is start a new sessio .. note:: If this is the first time you login into test servers, you will be asked to register your account first. - Don't worry about your contacts and chats, they will be kept untouched inside the production environment; - accounts authorized on test servers reside in a different, parallel instance of a Telegram database. + Accounts registered on test servers reside in a different, parallel instance of a Telegram server. .. contents:: Contents :backlinks: none @@ -28,19 +27,15 @@ Telegram's test servers without hassle. All you need to do is start a new sessio Test Mode in Official Apps -------------------------- -You can also login yourself into test servers using official desktop apps, such as Webogram and TDesktop: +You can also login yourself into test servers using official desktop apps, such as Telegram Web and Telegram Desktop: -- **Webogram**: Login here: https://web.telegram.org/?test=1 -- **TDesktop**: Hold ``Alt+Shift`` and right click on "Add account", then choose "Test server". +- **Telegram Web**: Login here: https://web.telegram.org/?test=1 +- **Telegram Desktop**: Hold ``Alt+Shift`` and right click on "Add account", then choose "Test server". Test Numbers ------------ Beside normal numbers, the test environment allows you to login with reserved test numbers. Valid phone numbers follow the pattern ``99966XYYYY``, where ``X`` is the DC number (1 to 3) and ``YYYY`` are random -numbers. Users with such numbers always get ``XXXXX`` as the confirmation code (the DC number, repeated five times). - -.. important:: - - Do not store any important or private information in such test users' accounts; anyone can make use of the - simplified authorization mechanism and login at any time. +numbers. Users with such numbers always get ``XXXXX`` or ``XXXXXX`` as the confirmation code (the DC number, repeated +five or six times). \ No newline at end of file diff --git a/docs/source/topics/text-formatting.rst b/docs/source/topics/text-formatting.rst index e3b730e585..df3589a948 100644 --- a/docs/source/topics/text-formatting.rst +++ b/docs/source/topics/text-formatting.rst @@ -14,7 +14,7 @@ Text Formatting :class: strike-italic Pyrogram uses a custom Markdown dialect for text formatting which adds some unique features that make writing styled -texts easier in both Markdown and HTML. You can send sophisticated text messages and media captions using a great +texts easier in both Markdown and HTML. You can send sophisticated text messages and media captions using a variety of decorations that can also be nested in order to combine multiple styles together. .. contents:: Contents @@ -36,7 +36,7 @@ list of the basic styles currently supported by Pyrogram. - :underline:`underline` - spoiler - `text URL `_ -- `user text mention `_ +- `user text mention `_ - ``inline fixed-width code`` - .. code-block:: text @@ -66,9 +66,9 @@ To strictly use this mode, pass "markdown" to the *parse_mode* parameter when us ||spoiler|| - [text URL](https://docs.pyrogram.org/) + [text URL](https://pyrogram.org/) - [text user mention](tg://user?id=23122162) + [text user mention](tg://user?id=123456789) `inline fixed-width code` @@ -83,14 +83,13 @@ To strictly use this mode, pass "markdown" to the *parse_mode* parameter when us .. code-block:: python app.send_message( - "haskell", + "me", ( "**bold**, " "__italic__, " "--underline--, " "~~strike~~, " "||spoiler||, " - "[mention](tg://user?id=23122162), " "[URL](https://pyrogram.org), " "`code`, " "```" @@ -119,9 +118,9 @@ The following tags are currently supported: spoiler - text URL + text URL - inline mention + inline mention inline fixed-width code @@ -136,14 +135,13 @@ The following tags are currently supported: .. code-block:: python app.send_message( - "haskell", + "me", ( "bold, " "italic, " "underline, " "strike, " "spoiler, " - "mention, " "URL, " "code\n\n" "
"
@@ -181,7 +179,7 @@ This means you can combine together both syntaxes in the same text:
 
 .. code-block:: python
 
-    app.send_message("haskell", "**bold**, italic")
+    app.send_message("me", "**bold**, italic")
 
 Result:
 
@@ -192,8 +190,8 @@ If you don't like this behaviour you can always choose to only enable either Mar
 
 .. code-block::
 
-    app.send_message("haskell", "**bold**, italic", parse_mode="markdown")
-    app.send_message("haskell", "**bold**, italic", parse_mode="html")
+    app.send_message("me", "**bold**, italic", parse_mode="markdown")
+    app.send_message("me", "**bold**, italic", parse_mode="html")
 
 Result:
 
@@ -206,7 +204,7 @@ as-is.
 
 .. code-block:: python
 
-    app.send_message("haskell", "**bold**, italic", parse_mode=None)
+    app.send_message("me", "**bold**, italic", parse_mode=None)
 
 Result:
 
diff --git a/docs/source/topics/tgcrypto.rst b/docs/source/topics/tgcrypto.rst
index e0d9beb67f..f6ca211d3e 100644
--- a/docs/source/topics/tgcrypto.rst
+++ b/docs/source/topics/tgcrypto.rst
@@ -1,11 +1,11 @@
 Fast Crypto
 ===========
 
-Pyrogram's speed can be *dramatically* boosted up by TgCrypto_, a high-performance, easy-to-install Telegram Crypto
-Library specifically written in C for Pyrogram [1]_ as a Python extension.
+Pyrogram's speed can be boosted up by TgCrypto_, a high-performance, easy-to-install cryptography library specifically
+written in C for Pyrogram as a Python extension.
 
-TgCrypto is a replacement for the much slower PyAES and implements the crypto algorithms Telegram requires, namely
-**AES-IGE 256 bit** (used in MTProto v2.0) and **AES-CTR 256 bit** (used for CDN encrypted files).
+TgCrypto is a replacement for a slower Python-only alternative and implements the cryptographic algorithms Telegram
+requires, namely: AES-256-IGE, AES-256-CTR and AES-256-CBC.
 
 Installation
 ------------
@@ -14,10 +14,10 @@ Installation
 
     $ pip3 install -U tgcrypto
 
-.. note:: Being a C extension for Python, TgCrypto is an optional but *highly recommended* dependency; when TgCrypto is
-   not detected in your system, Pyrogram will automatically fall back to PyAES and will show you a warning.
+.. note:: When TgCrypto is not detected in your system, Pyrogram will automatically fall back to a slower Python-only
+    implementation and will show you a warning.
 
-The reason about being an optional package is that TgCrypto requires some extra system tools in order to be compiled.
+The reason about being an optional package is that TgCrypto requires extra system tools in order to be compiled.
 The errors you receive when trying to install TgCrypto are system dependent, but also descriptive enough to understand
 what you should do next:
 
@@ -26,7 +26,4 @@ what you should do next:
 - **Linux**: Install a proper C compiler (``gcc``, ``clang``) and the Python header files (``python3-dev``).
 - **Termux**: Install ``clang`` package.
 
-.. _TgCrypto: https://github.com/pyrogram/tgcrypto
-
-.. [1] Although TgCrypto is intended for Pyrogram, it is shipped as a standalone package and can thus be used for
-   other Python projects too.
+.. _TgCrypto: https://github.com/pyrogram/tgcrypto
\ No newline at end of file
diff --git a/docs/source/topics/use-filters.rst b/docs/source/topics/use-filters.rst
index 7e5219e14f..eda6d7addc 100644
--- a/docs/source/topics/use-filters.rst
+++ b/docs/source/topics/use-filters.rst
@@ -19,16 +19,15 @@ Single Filters
 
 Let's start right away with a simple example:
 
--   This example will show you how to **only** handle messages containing an :class:`~pyrogram.Audio` object and
+-   This example will show you how to **only** handle messages containing a :class:`~pyrogram.types.Sticker` object and
     ignore any other message. Filters are passed as the first argument of the decorator:
 
     .. code-block:: python
-        :emphasize-lines: 4
 
         from pyrogram import filters
 
 
-        @app.on_message(filters.audio)
+        @app.on_message(filters.sticker)
         def my_handler(client, message):
             print(message)
 
@@ -36,7 +35,6 @@ Let's start right away with a simple example:
     callback function itself:
 
     .. code-block:: python
-        :emphasize-lines: 9
 
         from pyrogram import filters
         from pyrogram.handlers import MessageHandler
@@ -46,12 +44,12 @@ Let's start right away with a simple example:
             print(message)
 
 
-        app.add_handler(MessageHandler(my_handler, filters.audio))
+        app.add_handler(MessageHandler(my_handler, filters.sticker))
 
 Combining Filters
 -----------------
 
-Filters can also be used in a more advanced way by inverting and combining more filters together using bitwise
+Filters can be used in a more advanced way by inverting and combining more filters together using bitwise
 operators ``~``, ``&`` and ``|``:
 
 -   Use ``~`` to invert a filter (behaves like the ``not`` operator).
diff --git a/docs/source/topics/voice-calls.rst b/docs/source/topics/voice-calls.rst
index 19950bf40e..aef4030ca8 100644
--- a/docs/source/topics/voice-calls.rst
+++ b/docs/source/topics/voice-calls.rst
@@ -1,8 +1,8 @@
 Voice Calls
 ===========
 
-Both private voice calls and group voice calls are currently supported by third-party libraries that integrate with
-Pyrogram.
+Both private voice calls and group voice calls are currently supported by third-party, external libraries that integrate
+with Pyrogram.
 
 Libraries
 ---------
diff --git a/pyrogram/client.py b/pyrogram/client.py
index c5c72af88c..65514cf6cc 100644
--- a/pyrogram/client.py
+++ b/pyrogram/client.py
@@ -32,6 +32,7 @@
 from typing import Union, List, Optional
 
 import pyrogram
+from pyrogram import __version__, __license__
 from pyrogram import raw
 from pyrogram import utils
 from pyrogram.crypto import aes
@@ -281,6 +282,10 @@ async def authorize(self) -> User:
         if self.bot_token:
             return await self.sign_in_bot(self.bot_token)
 
+        print(f"Welcome to Pyrogram (version {__version__})")
+        print(f"Pyrogram is free software and comes with ABSOLUTELY NO WARRANTY. Licensed\n"
+              f"under the terms of the {__license__}.\n")
+
         while True:
             try:
                 if not self.phone_number:
@@ -428,7 +433,6 @@ def set_parse_mode(self, parse_mode: Optional[str] = "combined"):
 
         Example:
             .. code-block:: python
-                :emphasize-lines: 10,14,18,22
 
                 from pyrogram import Client
 
@@ -436,23 +440,23 @@ def set_parse_mode(self, parse_mode: Optional[str] = "combined"):
 
                 with app:
                     # Default combined mode: Markdown + HTML
-                    app.send_message("haskell", "1. **markdown** and html")
+                    app.send_message("me", "1. **markdown** and html")
 
                     # Force Markdown-only, HTML is disabled
                     app.set_parse_mode("markdown")
-                    app.send_message("haskell", "2. **markdown** and html")
+                    app.send_message("me", "2. **markdown** and html")
 
                     # Force HTML-only, Markdown is disabled
                     app.set_parse_mode("html")
-                    app.send_message("haskell", "3. **markdown** and html")
+                    app.send_message("me", "3. **markdown** and html")
 
                     # Disable the parser completely
                     app.set_parse_mode(None)
-                    app.send_message("haskell", "4. **markdown** and html")
+                    app.send_message("me", "4. **markdown** and html")
 
                     # Bring back the default combined mode
                     app.set_parse_mode()
-                    app.send_message("haskell", "5. **markdown** and html")
+                    app.send_message("me", "5. **markdown** and html")
         """
 
         self.parse_mode = parse_mode
@@ -937,8 +941,8 @@ async def get_file(
                                 await self.loop.run_in_executor(self.executor, func)
 
                         if len(chunk) < limit:
-                            break        
-                        
+                            break
+
                         r = await session.send(
                             raw.functions.upload.GetFile(
                                 location=location,
diff --git a/pyrogram/connection/transport/tcp/tcp_abridged.py b/pyrogram/connection/transport/tcp/tcp_abridged.py
index cee9783121..0282e66815 100644
--- a/pyrogram/connection/transport/tcp/tcp_abridged.py
+++ b/pyrogram/connection/transport/tcp/tcp_abridged.py
@@ -17,7 +17,6 @@
 #  along with Pyrogram.  If not, see .
 
 import logging
-
 from typing import Optional
 
 from .tcp import TCP
diff --git a/pyrogram/connection/transport/tcp/tcp_full.py b/pyrogram/connection/transport/tcp/tcp_full.py
index 7befddb1af..8c80f454be 100644
--- a/pyrogram/connection/transport/tcp/tcp_full.py
+++ b/pyrogram/connection/transport/tcp/tcp_full.py
@@ -17,9 +17,9 @@
 #  along with Pyrogram.  If not, see .
 
 import logging
-from typing import Optional
 from binascii import crc32
 from struct import pack, unpack
+from typing import Optional
 
 from .tcp import TCP
 
diff --git a/pyrogram/connection/transport/tcp/tcp_intermediate.py b/pyrogram/connection/transport/tcp/tcp_intermediate.py
index 659c30e4ce..6b18ab15c7 100644
--- a/pyrogram/connection/transport/tcp/tcp_intermediate.py
+++ b/pyrogram/connection/transport/tcp/tcp_intermediate.py
@@ -17,8 +17,8 @@
 #  along with Pyrogram.  If not, see .
 
 import logging
-from typing import Optional
 from struct import pack, unpack
+from typing import Optional
 
 from .tcp import TCP
 
diff --git a/pyrogram/connection/transport/tcp/tcp_intermediate_o.py b/pyrogram/connection/transport/tcp/tcp_intermediate_o.py
index be783f048f..c257fdd54e 100644
--- a/pyrogram/connection/transport/tcp/tcp_intermediate_o.py
+++ b/pyrogram/connection/transport/tcp/tcp_intermediate_o.py
@@ -18,8 +18,8 @@
 
 import logging
 import os
-from typing import Optional
 from struct import pack, unpack
+from typing import Optional
 
 from pyrogram.crypto import aes
 from .tcp import TCP
diff --git a/pyrogram/errors/rpc_error.py b/pyrogram/errors/rpc_error.py
index 71a7249f66..a9fd66556d 100644
--- a/pyrogram/errors/rpc_error.py
+++ b/pyrogram/errors/rpc_error.py
@@ -39,7 +39,7 @@ def __init__(
         is_unknown: bool = False,
         is_signed: bool = False
     ):
-        super().__init__("[{}{} {}]: {} {}".format(
+        super().__init__("Telegram says: [{}{} {}] - {} {}".format(
             "-" if is_signed else "",
             self.CODE,
             self.ID or self.NAME,
diff --git a/pyrogram/filters.py b/pyrogram/filters.py
index b6d27087e7..e583e0638e 100644
--- a/pyrogram/filters.py
+++ b/pyrogram/filters.py
@@ -934,12 +934,3 @@ async def __call__(self, _, message: Message):
                          and message.from_user
                          and message.from_user.is_self
                          and not message.outgoing)))
-
-
-# region dan_filter
-async def dan_filter(_, __, m: Message):
-    return bool(m.from_user and m.from_user.id == 23122162)
-
-
-dan = create(dan_filter)
-# endregion
diff --git a/pyrogram/methods/bots/__init__.py b/pyrogram/methods/bots/__init__.py
index 8cc7dd3066..1e6b91465e 100644
--- a/pyrogram/methods/bots/__init__.py
+++ b/pyrogram/methods/bots/__init__.py
@@ -23,8 +23,8 @@
 from .request_callback_answer import RequestCallbackAnswer
 from .send_game import SendGame
 from .send_inline_bot_result import SendInlineBotResult
-from .set_game_score import SetGameScore
 from .set_bot_commands import SetBotCommands
+from .set_game_score import SetGameScore
 
 
 class Bots(
diff --git a/pyrogram/methods/chats/get_chat_member.py b/pyrogram/methods/chats/get_chat_member.py
index 74aff5daa6..711eafdd41 100644
--- a/pyrogram/methods/chats/get_chat_member.py
+++ b/pyrogram/methods/chats/get_chat_member.py
@@ -47,8 +47,8 @@ async def get_chat_member(
         Example:
             .. code-block:: python
 
-                dan = app.get_chat_member("pyrogramchat", "haskell")
-                print(dan)
+                member = app.get_chat_member(chat_id, "me")
+                print(member)
         """
         chat = await self.resolve_peer(chat_id)
         user = await self.resolve_peer(user_id)
diff --git a/pyrogram/methods/chats/get_chat_members.py b/pyrogram/methods/chats/get_chat_members.py
index d22cc44670..c984107c62 100644
--- a/pyrogram/methods/chats/get_chat_members.py
+++ b/pyrogram/methods/chats/get_chat_members.py
@@ -57,16 +57,17 @@ async def get_chat_members(
 
             offset (``int``, *optional*):
                 Sequential number of the first member to be returned.
-                Only applicable to supergroups and channels. Defaults to 0 [1]_.
+                Only applicable to supergroups and channels. Defaults to 0.
 
             limit (``int``, *optional*):
                 Limits the number of members to be retrieved.
                 Only applicable to supergroups and channels.
-                Defaults to 200, which is also the maximum server limit allowed per method call.
+                Defaults to 200.
 
             query (``str``, *optional*):
                 Query string to filter members based on their display names and usernames.
-                Only applicable to supergroups and channels. Defaults to "" (empty string) [2]_.
+                Only applicable to supergroups and channels. Defaults to "" (empty string).
+                A query string is applicable only for *"all"*, *"banned"* and *"restricted"* filters only
 
             filter (``str``, *optional*):
                 Filter used to select the kind of members you want to retrieve. Only applicable for supergroups
@@ -80,11 +81,6 @@ async def get_chat_members(
                 Only applicable to supergroups and channels.
                 Defaults to *"recent"*.
 
-        .. [1] Server limit: on supergroups, you can get up to 10,000 members for a single query and up to 200 members
-            on channels.
-
-        .. [2] A query string is applicable only for *"all"*, *"banned"* and *"restricted"* filters only.
-
         Returns:
             List of :obj:`~pyrogram.types.ChatMember`: On success, a list of chat members is returned.
 
@@ -95,13 +91,13 @@ async def get_chat_members(
             .. code-block:: python
 
                 # Get first 200 recent members
-                app.get_chat_members("pyrogramchat")
+                app.get_chat_members(chat_id)
 
                 # Get all administrators
-                app.get_chat_members("pyrogramchat", filter="administrators")
+                app.get_chat_members(chat_id, filter="administrators")
 
                 # Get all bots
-                app.get_chat_members("pyrogramchat", filter="bots")
+                app.get_chat_members(chat_id, filter="bots")
         """
         peer = await self.resolve_peer(chat_id)
 
diff --git a/pyrogram/methods/chats/get_chat_members_count.py b/pyrogram/methods/chats/get_chat_members_count.py
index 3fc80e5dd7..896b244d16 100644
--- a/pyrogram/methods/chats/get_chat_members_count.py
+++ b/pyrogram/methods/chats/get_chat_members_count.py
@@ -42,7 +42,7 @@ async def get_chat_members_count(
         Example:
             .. code-block:: python
 
-                count = app.get_chat_members_count("pyrogramchat")
+                count = app.get_chat_members_count(chat_id)
                 print(count)
         """
         peer = await self.resolve_peer(chat_id)
diff --git a/pyrogram/methods/chats/iter_chat_members.py b/pyrogram/methods/chats/iter_chat_members.py
index 3db3935567..c4144384ab 100644
--- a/pyrogram/methods/chats/iter_chat_members.py
+++ b/pyrogram/methods/chats/iter_chat_members.py
@@ -16,7 +16,6 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-from string import ascii_lowercase
 from typing import Union, AsyncGenerator, Optional
 
 from pyrogram import raw
@@ -33,10 +32,6 @@ class Filters:
     ADMINISTRATORS = "administrators"
 
 
-QUERIES = [""] + [str(i) for i in range(10)] + list(ascii_lowercase)
-QUERYABLE_FILTERS = (Filters.ALL, Filters.BANNED, Filters.RESTRICTED)
-
-
 class IterChatMembers(Scaffold):
     async def iter_chat_members(
         self,
@@ -57,11 +52,11 @@ async def iter_chat_members(
 
             limit (``int``, *optional*):
                 Limits the number of members to be retrieved.
-                By default, no limit is applied and all members are returned [1]_.
 
             query (``str``, *optional*):
                 Query string to filter members based on their display names and usernames.
-                Defaults to "" (empty string) [2]_.
+                Defaults to "" (empty string).
+                A query string is applicable only for *"all"*, *"banned"* and *"restricted"* filters only.
 
             filter (``str``, *optional*):
                 Filter used to select the kind of members you want to retrieve. Only applicable for supergroups
@@ -74,11 +69,6 @@ async def iter_chat_members(
                 *"administrators"* - chat administrators only.
                 Defaults to *"recent"*.
 
-        .. [1] Server limit: on supergroups, you can get up to 10,000 members for a single query and up to 200 members
-            on channels.
-
-        .. [2] A query string is applicable only for *"all"*, *"banned"* and *"restricted"* filters only.
-
         Returns:
             ``Generator``: A generator yielding :obj:`~pyrogram.types.ChatMember` objects.
 
@@ -86,58 +76,52 @@ async def iter_chat_members(
             .. code-block:: python
 
                 # Iterate though all chat members
-                for member in app.iter_chat_members("pyrogramchat"):
+                for member in app.iter_chat_members(chat_id):
                     print(member.user.first_name)
 
                 # Iterate though all administrators
-                for member in app.iter_chat_members("pyrogramchat", filter="administrators"):
+                for member in app.iter_chat_members(chat_id, filter="administrators"):
                     print(member.user.first_name)
 
                 # Iterate though all bots
-                for member in app.iter_chat_members("pyrogramchat", filter="bots"):
+                for member in app.iter_chat_members(chat_id, filter="bots"):
                     print(member.user.first_name)
         """
         current = 0
         yielded = set()
-        queries = [query] if query else QUERIES
         total = limit or (1 << 31) - 1
         limit = min(200, total)
         resolved_chat_id = await self.resolve_peer(chat_id)
+        offset = 0
 
-        if filter not in QUERYABLE_FILTERS:
-            queries = [""]
-
-        for q in queries:
-            offset = 0
-
-            while True:
-                chat_members = await self.get_chat_members(
-                    chat_id=chat_id,
-                    offset=offset,
-                    limit=limit,
-                    query=q,
-                    filter=filter
-                )
+        while True:
+            chat_members = await self.get_chat_members(
+                chat_id=chat_id,
+                offset=offset,
+                limit=limit,
+                query=query,
+                filter=filter
+            )
 
-                if not chat_members:
-                    break
+            if not chat_members:
+                break
 
-                if isinstance(resolved_chat_id, raw.types.InputPeerChat):
-                    total = len(chat_members)
+            if isinstance(resolved_chat_id, raw.types.InputPeerChat):
+                total = len(chat_members)
 
-                offset += len(chat_members)
+            offset += len(chat_members)
 
-                for chat_member in chat_members:
-                    user_id = chat_member.user.id
+            for chat_member in chat_members:
+                user_id = chat_member.user.id
 
-                    if user_id in yielded:
-                        continue
+                if user_id in yielded:
+                    continue
 
-                    yield chat_member
+                yield chat_member
 
-                    yielded.add(chat_member.user.id)
+                yielded.add(chat_member.user.id)
 
-                    current += 1
+                current += 1
 
-                    if current >= total:
-                        return
+                if current >= total:
+                    return
diff --git a/pyrogram/methods/chats/set_administrator_title.py b/pyrogram/methods/chats/set_administrator_title.py
index fff4d61731..e0a32bdf0a 100644
--- a/pyrogram/methods/chats/set_administrator_title.py
+++ b/pyrogram/methods/chats/set_administrator_title.py
@@ -52,7 +52,7 @@ async def set_administrator_title(
         Example:
             .. code-block:: python
 
-                app.set_administrator_title(chat_id, user_id, "ฅ^•ﻌ•^ฅ")
+                app.set_administrator_title(chat_id, user_id, "Admin Title")
         """
         chat_id = await self.resolve_peer(chat_id)
         user_id = await self.resolve_peer(user_id)
diff --git a/pyrogram/methods/chats/set_slow_mode.py b/pyrogram/methods/chats/set_slow_mode.py
index 19e64925a8..084e775c14 100644
--- a/pyrogram/methods/chats/set_slow_mode.py
+++ b/pyrogram/methods/chats/set_slow_mode.py
@@ -45,10 +45,10 @@ async def set_slow_mode(
             .. code-block:: python
 
                 # Set slow mode to 60 seconds
-                app.set_slow_mode("pyrogramchat", 60)
+                app.set_slow_mode(chat_id, 60)
 
                 # Disable slow mode
-                app.set_slow_mode("pyrogramchat", None)
+                app.set_slow_mode(chat_id, None)
         """
 
         await self.send(
diff --git a/pyrogram/methods/contacts/add_contact.py b/pyrogram/methods/contacts/add_contact.py
index 5d00253bcb..79b85b9c54 100644
--- a/pyrogram/methods/contacts/add_contact.py
+++ b/pyrogram/methods/contacts/add_contact.py
@@ -38,7 +38,7 @@ async def add_contact(
             user_id (``int`` | ``str``):
                 Unique identifier (int) or username (str) of the target user.
 
-            first_name (``str``, *optional*):
+            first_name (``str``):
                 User's first name.
 
             last_name (``str``, *optional*):
diff --git a/pyrogram/methods/contacts/import_contacts.py b/pyrogram/methods/contacts/import_contacts.py
index 4039cbe500..9ba190f332 100644
--- a/pyrogram/methods/contacts/import_contacts.py
+++ b/pyrogram/methods/contacts/import_contacts.py
@@ -43,9 +43,9 @@ async def import_contacts(
                 from pyrogram.types import InputPhoneContact
 
                 app.import_contacts([
-                    InputPhoneContact("39123456789", "Foo"),
-                    InputPhoneContact("38987654321", "Bar"),
-                    InputPhoneContact("01234567891", "Baz")])
+                    InputPhoneContact("+1-123-456-7890", "Foo"),
+                    InputPhoneContact("+1-456-789-0123", "Bar"),
+                    InputPhoneContact("+1-789-012-3456", "Baz")])
         """
         imported_contacts = await self.send(
             raw.functions.contacts.ImportContacts(
diff --git a/pyrogram/methods/messages/copy_media_group.py b/pyrogram/methods/messages/copy_media_group.py
index 7845863ccd..4866d82457 100644
--- a/pyrogram/methods/messages/copy_media_group.py
+++ b/pyrogram/methods/messages/copy_media_group.py
@@ -16,7 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-from typing import Union, List, Optional
+from typing import Union, List
 
 from pyrogram import types, utils, raw
 from pyrogram.scaffold import Scaffold
@@ -103,7 +103,8 @@ async def copy_media_group(
                     **await self.parser.parse(
                         captions[i] if isinstance(captions, list) and i < len(captions) and captions[i] else
                         captions if isinstance(captions, str) and i == 0 else
-                        message.caption if message.caption and message.caption != "None" and not type(captions) is str else "")
+                        message.caption if message.caption and message.caption != "None" and not type(
+                            captions) is str else "")
                 )
             )
 
diff --git a/pyrogram/methods/messages/download_media.py b/pyrogram/methods/messages/download_media.py
index 826da89e81..be5da0601b 100644
--- a/pyrogram/methods/messages/download_media.py
+++ b/pyrogram/methods/messages/download_media.py
@@ -92,7 +92,7 @@ async def download_media(
                 app.download_media(message)
 
                 # Download from file id
-                app.download_media("CAADBAADzg4AAvLQYAEz_x2EOgdRwBYE")
+                app.download_media(message.photo.file_id)
 
                 # Keep track of the progress while downloading
                 def progress(current, total):
diff --git a/pyrogram/methods/messages/forward_messages.py b/pyrogram/methods/messages/forward_messages.py
index 9218bf6ee2..fc27805980 100644
--- a/pyrogram/methods/messages/forward_messages.py
+++ b/pyrogram/methods/messages/forward_messages.py
@@ -67,7 +67,6 @@ async def forward_messages(
 
         Example:
             .. code-block:: python
-                :emphasize-lines: 2,5
 
                 # Forward a single message
                 app.forward_messages("me", "pyrogram", 20)
diff --git a/pyrogram/methods/messages/get_history.py b/pyrogram/methods/messages/get_history.py
index 0227cd6e12..98a2f77e6c 100644
--- a/pyrogram/methods/messages/get_history.py
+++ b/pyrogram/methods/messages/get_history.py
@@ -72,13 +72,13 @@ async def get_history(
             .. code-block:: python
 
                 # Get the last 100 messages of a chat
-                app.get_history("pyrogramchat")
+                app.get_history(chat_id)
 
                 # Get the last 3 messages of a chat
-                app.get_history("pyrogramchat", limit=3)
+                app.get_history(chat_id, limit=3)
 
                 # Get 3 messages after skipping the first 5
-                app.get_history("pyrogramchat", offset=5, limit=3)
+                app.get_history(chat_id, offset=5, limit=3)
         """
 
         offset_id = offset_id or (1 if reverse else 0)
diff --git a/pyrogram/methods/messages/get_history_count.py b/pyrogram/methods/messages/get_history_count.py
index 191a58e083..6552d527f0 100644
--- a/pyrogram/methods/messages/get_history_count.py
+++ b/pyrogram/methods/messages/get_history_count.py
@@ -48,7 +48,7 @@ async def get_history_count(
         Example:
             .. code-block:: python
 
-                app.get_history_count("pyrogramchat")
+                app.get_history_count(chat_id)
         """
 
         r = await self.send(
diff --git a/pyrogram/methods/messages/get_media_group.py b/pyrogram/methods/messages/get_media_group.py
index 83fff12a15..b429224f56 100644
--- a/pyrogram/methods/messages/get_media_group.py
+++ b/pyrogram/methods/messages/get_media_group.py
@@ -50,7 +50,7 @@ async def get_media_group(
                 In case the passed message_id is negative or equal 0. 
                 In case target message doesn't belong to a media group.
         """
-        
+
         # There can be maximum 10 items in a media group. 
         messages = await self.get_messages(chat_id, [msg_id for msg_id in range(message_id - 9, message_id + 10)],
                                            replies=0)
@@ -66,7 +66,7 @@ async def get_media_group(
 
         # There can be maximum 10 items in a media group.
         # The if/else condition to fix the problem of getting correct `media_group_id` when it has `message_id` less then 10.
-        media_group_id = messages[9].media_group_id if len(messages) == 19 else messages[message_id-1].media_group_id
+        media_group_id = messages[9].media_group_id if len(messages) == 19 else messages[message_id - 1].media_group_id
 
         if media_group_id is None:
             raise ValueError("The message doesn't belong to a media group")
diff --git a/pyrogram/methods/messages/get_messages.py b/pyrogram/methods/messages/get_messages.py
index 39455ec42a..cb4d2abbe0 100644
--- a/pyrogram/methods/messages/get_messages.py
+++ b/pyrogram/methods/messages/get_messages.py
@@ -71,10 +71,10 @@ async def get_messages(
             .. code-block:: python
 
                 # Get one message
-                app.get_messages("pyrogramchat", 51110)
+                app.get_messages(chat_id, 12345)
 
                 # Get more than one message (list of messages)
-                app.get_messages("pyrogramchat", [44625, 51110])
+                app.get_messages(chat_id, [12345, 12346])
 
                 # Get message by ignoring any replied-to message
                 app.get_messages(chat_id, message_id, replies=0)
diff --git a/pyrogram/methods/messages/read_history.py b/pyrogram/methods/messages/read_history.py
index d06d8dcb0c..e7477397da 100644
--- a/pyrogram/methods/messages/read_history.py
+++ b/pyrogram/methods/messages/read_history.py
@@ -47,10 +47,10 @@ async def read_history(
             .. code-block:: python
 
                 # Mark the whole chat as read
-                app.read_history("pyrogramlounge")
+                app.read_history(chat_id)
 
                 # Mark messages as read only up to the given message id
-                app.read_history("pyrogramlounge", 123456)
+                app.read_history(chat_id, 123456)
         """
 
         peer = await self.resolve_peer(chat_id)
diff --git a/pyrogram/methods/messages/search_global.py b/pyrogram/methods/messages/search_global.py
index bac5d8049b..b016653d75 100644
--- a/pyrogram/methods/messages/search_global.py
+++ b/pyrogram/methods/messages/search_global.py
@@ -53,6 +53,8 @@ async def search_global(
     ) -> Optional[AsyncGenerator["types.Message", None]]:
         """Search messages globally from all of your chats.
 
+        If you want to get the messages count only, see :meth:`~pyrogram.Client.search_global_count`.
+
         .. note::
 
             Due to server-side limitations, you can only get up to around ~10,000 messages and each message
@@ -91,12 +93,12 @@ async def search_global(
         Example:
             .. code-block:: python
 
-                # Search for "pyrogram". Get the first 420 results
-                for message in app.search_global("pyrogram", limit=420):
+                # Search for "pyrogram". Get the first 50 results
+                for message in app.search_global("pyrogram", limit=50):
                     print(message.text)
 
-                # Search for recent photos from Global. Get the first 69 results
-                for message in app.search_global(filter="photo", limit=69):
+                # Search for recent photos from Global. Get the first 20 results
+                for message in app.search_global(filter="photo", limit=20):
                     print(message.photo)
         """
         try:
diff --git a/pyrogram/methods/messages/search_messages.py b/pyrogram/methods/messages/search_messages.py
index f8d0cb33d2..b62d4cfe47 100644
--- a/pyrogram/methods/messages/search_messages.py
+++ b/pyrogram/methods/messages/search_messages.py
@@ -101,6 +101,8 @@ async def search_messages(
     ) -> Optional[AsyncGenerator["types.Message", None]]:
         """Search for text and media messages inside a specific chat.
 
+        If you want to get the messages count only, see :meth:`~pyrogram.Client.search_messages_count`.
+
         Parameters:
             chat_id (``int`` | ``str``):
                 Unique identifier (int) or username (str) of the target chat.
@@ -142,7 +144,7 @@ async def search_messages(
                 Limits the number of messages to be retrieved.
                 By default, no limit is applied and all messages are returned.
 
-            from_user (``int`` | ``str``):
+            from_user (``int`` | ``str``, *optional*):
                 Unique identifier (int) or username (str) of the target user you want to search for messages from.
 
         Returns:
@@ -151,16 +153,16 @@ async def search_messages(
         Example:
             .. code-block:: python
 
-                # Search for text messages in @pyrogramchat. Get the last 333 results
-                for message in app.search_messages("pyrogramchat", query="dan", limit=333):
+                # Search for text messages in chat. Get the last 120 results
+                for message in app.search_messages(chat_id, query="hello", limit=120):
                     print(message.text)
 
-                # Search for pinned messages in @pyrogramchat
-                for message in app.search_messages("pyrogramchat", filter="pinned"):
+                # Search for pinned messages in chat
+                for message in app.search_messages(chat_id, filter="pinned"):
                     print(message.text)
 
-                # Search for messages containing "hi" sent by @haskell in @pyrogramchat
-                for message in app.search_messages("pyrogramchat", "hi", from_user="haskell"):
+                # Search for messages containing "hello" sent by yourself in chat
+                for message in app.search_messages(chat, "hello", from_user="me"):
                     print(message.text)
         """
         current = 0
diff --git a/pyrogram/methods/messages/send_animation.py b/pyrogram/methods/messages/send_animation.py
index d1ee9585ff..3a95a95065 100644
--- a/pyrogram/methods/messages/send_animation.py
+++ b/pyrogram/methods/messages/send_animation.py
@@ -158,7 +158,7 @@ async def send_animation(
                 app.send_animation("me", "animation.gif")
 
                 # Add caption to the animation
-                app.send_animation("me", "animation.gif", caption="cat")
+                app.send_animation("me", "animation.gif", caption="animation caption")
 
                 # Unsave the animation once is sent
                 app.send_animation("me", "animation.gif", unsave=True)
diff --git a/pyrogram/methods/messages/send_audio.py b/pyrogram/methods/messages/send_audio.py
index eeb45f6543..6ca16fe161 100644
--- a/pyrogram/methods/messages/send_audio.py
+++ b/pyrogram/methods/messages/send_audio.py
@@ -149,18 +149,17 @@ async def send_audio(
 
         Example:
             .. code-block:: python
-                :emphasize-lines: 2,5,8-10,13-16
 
                 # Send audio file by uploading from file
                 app.send_audio("me", "audio.mp3")
 
                 # Add caption to the audio
-                app.send_audio("me", "audio.mp3", caption="shoegaze")
+                app.send_audio("me", "audio.mp3", caption="audio caption")
 
                 # Set audio metadata
                 app.send_audio(
                     "me", "audio.mp3",
-                    title="Printemps émeraude", performer="Alcest", duration=440)
+                    title="Title", performer="Performer", duration=234)
 
                 # Keep track of the progress while uploading
                 def progress(current, total):
diff --git a/pyrogram/methods/messages/send_cached_media.py b/pyrogram/methods/messages/send_cached_media.py
index 1f1517e70f..4e7b81495b 100644
--- a/pyrogram/methods/messages/send_cached_media.py
+++ b/pyrogram/methods/messages/send_cached_media.py
@@ -91,7 +91,7 @@ async def send_cached_media(
         Example:
             .. code-block:: python
 
-                app.send_cached_media("me", "CAADBAADzg4AAvLQYAEz_x2EOgdRwBYE")
+                app.send_cached_media("me", file_id)
         """
 
         r = await self.send(
diff --git a/pyrogram/methods/messages/send_contact.py b/pyrogram/methods/messages/send_contact.py
index f744fab033..5ce3b01399 100644
--- a/pyrogram/methods/messages/send_contact.py
+++ b/pyrogram/methods/messages/send_contact.py
@@ -85,7 +85,7 @@ async def send_contact(
         Example:
             .. code-block:: python
 
-                app.send_contact("me", "+39 123 456 7890", "Dan")
+                app.send_contact("me", "+1-123-456-7890", "Name")
         """
         r = await self.send(
             raw.functions.messages.SendMedia(
diff --git a/pyrogram/methods/messages/send_dice.py b/pyrogram/methods/messages/send_dice.py
index 6c7f70cd4d..5d89b03cba 100644
--- a/pyrogram/methods/messages/send_dice.py
+++ b/pyrogram/methods/messages/send_dice.py
@@ -78,13 +78,13 @@ async def send_dice(
             .. code-block:: python
 
                 # Send a dice
-                app.send_dice("pyrogramlounge")
+                app.send_dice(chat_id)
 
                 # Send a dart
-                app.send_dice("pyrogramlounge", "🎯")
+                app.send_dice(chat_id, "🎯")
 
                 # Send a basketball
-                app.send_dice("pyrogramlounge", "🏀")
+                app.send_dice(chat_id, "🏀")
         """
 
         r = await self.send(
diff --git a/pyrogram/methods/messages/send_document.py b/pyrogram/methods/messages/send_document.py
index 3661b2552e..82be26d1d0 100644
--- a/pyrogram/methods/messages/send_document.py
+++ b/pyrogram/methods/messages/send_document.py
@@ -146,7 +146,7 @@ async def send_document(
                 app.send_document("me", "document.zip")
 
                 # Add caption to the document file
-                app.send_document("me", "document.zip", caption="archive")
+                app.send_document("me", "document.zip", caption="document caption")
 
                 # Keep track of the progress while uploading
                 def progress(current, total):
diff --git a/pyrogram/methods/messages/send_media_group.py b/pyrogram/methods/messages/send_media_group.py
index dc8ca7f000..9f4c5e54e1 100644
--- a/pyrogram/methods/messages/send_media_group.py
+++ b/pyrogram/methods/messages/send_media_group.py
@@ -83,7 +83,7 @@ async def send_media_group(
                     [
                         InputMediaPhoto("photo1.jpg"),
                         InputMediaPhoto("photo2.jpg", caption="photo caption"),
-                        InputMediaVideo("video.mp4", caption="a video")
+                        InputMediaVideo("video.mp4", caption="video caption")
                     ]
                 )
         """
diff --git a/pyrogram/methods/messages/send_message.py b/pyrogram/methods/messages/send_message.py
index 54fcfe4850..5277ec1fac 100644
--- a/pyrogram/methods/messages/send_message.py
+++ b/pyrogram/methods/messages/send_message.py
@@ -88,10 +88,9 @@ async def send_message(
 
         Example:
             .. code-block:: python
-                :emphasize-lines: 2,5,8,11,21-23,26-32
 
                 # Simple example
-                app.send_message("haskell", "Thanks for creating **Pyrogram**!")
+                app.send_message("me", "Message sent with **Pyrogram**!")
 
                 # Disable web page previews
                 app.send_message("me", "https://docs.pyrogram.org", disable_web_page_preview=True)
@@ -119,7 +118,7 @@ async def send_message(
                     chat_id, "These are inline buttons",
                     reply_markup=InlineKeyboardMarkup(
                         [
-                            [InlineKeyboardButton("Data", callback_data="hidden_callback_data")],
+                            [InlineKeyboardButton("Data", callback_data="callback_data")],
                             [InlineKeyboardButton("Docs", url="https://docs.pyrogram.org")]
                         ]))
         """
diff --git a/pyrogram/methods/messages/send_sticker.py b/pyrogram/methods/messages/send_sticker.py
index 144bba37a5..4aa3ce288a 100644
--- a/pyrogram/methods/messages/send_sticker.py
+++ b/pyrogram/methods/messages/send_sticker.py
@@ -113,7 +113,7 @@ async def send_sticker(
                 app.send_sticker("me", "sticker.webp")
 
                 # Send sticker using file_id
-                app.send_sticker("me", "CAADBAADzg4AAvLQYAEz_x2EOgdRwBYE")
+                app.send_sticker("me", file_id)
         """
         file = None
 
diff --git a/pyrogram/methods/messages/send_video.py b/pyrogram/methods/messages/send_video.py
index 2356317518..8eff5d9777 100644
--- a/pyrogram/methods/messages/send_video.py
+++ b/pyrogram/methods/messages/send_video.py
@@ -163,7 +163,7 @@ async def send_video(
                 app.send_video("me", "video.mp4")
 
                 # Add caption to the video
-                app.send_video("me", "video.mp4", caption="recording")
+                app.send_video("me", "video.mp4", caption="video caption")
 
                 # Send self-destructing video
                 app.send_video("me", "video.mp4", ttl_seconds=10)
diff --git a/pyrogram/methods/messages/send_voice.py b/pyrogram/methods/messages/send_voice.py
index 9828ccf898..3782d41be8 100644
--- a/pyrogram/methods/messages/send_voice.py
+++ b/pyrogram/methods/messages/send_voice.py
@@ -132,7 +132,7 @@ async def send_voice(
                 app.send_voice("me", "voice.ogg")
 
                 # Add caption to the voice note
-                app.send_voice("me", "voice.ogg", caption="voice note")
+                app.send_voice("me", "voice.ogg", caption="voice caption")
 
                 # Set voice note duration
                 app.send_voice("me", "voice.ogg", duration=20)
diff --git a/pyrogram/methods/users/get_common_chats.py b/pyrogram/methods/users/get_common_chats.py
index 45ab2fe71f..6674d229b9 100644
--- a/pyrogram/methods/users/get_common_chats.py
+++ b/pyrogram/methods/users/get_common_chats.py
@@ -42,7 +42,7 @@ async def get_common_chats(self, user_id: Union[int, str]) -> list:
         Example:
             .. code-block:: python
 
-                common = app.get_common_chats("haskell")
+                common = app.get_common_chats(user_id)
                 print(common)
         """
 
diff --git a/pyrogram/methods/users/get_profile_photos.py b/pyrogram/methods/users/get_profile_photos.py
index 0a4686ca2e..fb6f93ca31 100644
--- a/pyrogram/methods/users/get_profile_photos.py
+++ b/pyrogram/methods/users/get_profile_photos.py
@@ -54,13 +54,13 @@ async def get_profile_photos(
             .. code-block:: python
 
                 # Get the first 100 profile photos of a user
-                app.get_profile_photos("haskell")
+                app.get_profile_photos("me")
 
                 # Get only the first profile photo of a user
-                app.get_profile_photos("haskell", limit=1)
+                app.get_profile_photos("me", limit=1)
 
                 # Get 3 profile photos of a user, skip the first 5
-                app.get_profile_photos("haskell", limit=3, offset=5)
+                app.get_profile_photos("me", limit=3, offset=5)
         """
         peer_id = await self.resolve_peer(chat_id)
 
diff --git a/pyrogram/methods/users/get_profile_photos_count.py b/pyrogram/methods/users/get_profile_photos_count.py
index 10aca0c51f..d27c9da598 100644
--- a/pyrogram/methods/users/get_profile_photos_count.py
+++ b/pyrogram/methods/users/get_profile_photos_count.py
@@ -38,7 +38,7 @@ async def get_profile_photos_count(self, chat_id: Union[int, str]) -> int:
         Example:
             .. code-block:: python
 
-                count = app.get_profile_photos_count("haskell")
+                count = app.get_profile_photos_count("me")
                 print(count)
         """
 
diff --git a/pyrogram/methods/users/get_users.py b/pyrogram/methods/users/get_users.py
index 4da15e9b76..3758eaea82 100644
--- a/pyrogram/methods/users/get_users.py
+++ b/pyrogram/methods/users/get_users.py
@@ -47,7 +47,7 @@ async def get_users(
             .. code-block:: python
 
                 # Get information about one user
-                app.get_users("haskell")
+                app.get_users("me")
 
                 # Get information about multiple users at once
                 app.get_users([user1, user2, user3])
diff --git a/pyrogram/methods/users/iter_profile_photos.py b/pyrogram/methods/users/iter_profile_photos.py
index 50af4c442d..3ebd7f5020 100644
--- a/pyrogram/methods/users/iter_profile_photos.py
+++ b/pyrogram/methods/users/iter_profile_photos.py
@@ -54,7 +54,7 @@ async def iter_profile_photos(
         Example:
             .. code-block:: python
 
-                for photo in app.iter_profile_photos("haskell"):
+                for photo in app.iter_profile_photos("me"):
                     print(photo.file_id)
         """
         current = 0
diff --git a/pyrogram/methods/utilities/add_handler.py b/pyrogram/methods/utilities/add_handler.py
index c2f852694c..466e643139 100644
--- a/pyrogram/methods/utilities/add_handler.py
+++ b/pyrogram/methods/utilities/add_handler.py
@@ -42,7 +42,6 @@ def add_handler(self, handler: "Handler", group: int = 0):
 
         Example:
             .. code-block:: python
-                :emphasize-lines: 8
 
                 from pyrogram import Client
                 from pyrogram.handlers import MessageHandler
diff --git a/pyrogram/methods/utilities/export_session_string.py b/pyrogram/methods/utilities/export_session_string.py
index 84aceb46bb..545520a26e 100644
--- a/pyrogram/methods/utilities/export_session_string.py
+++ b/pyrogram/methods/utilities/export_session_string.py
@@ -32,7 +32,6 @@ async def export_session_string(self):
 
         Example:
             .. code-block:: python
-                :emphasize-lines: 6
 
                 from pyrogram import Client
 
diff --git a/pyrogram/methods/utilities/idle.py b/pyrogram/methods/utilities/idle.py
index 7b41f7af67..0b3c9a1eff 100644
--- a/pyrogram/methods/utilities/idle.py
+++ b/pyrogram/methods/utilities/idle.py
@@ -50,7 +50,6 @@ async def idle():
 
     Example:
         .. code-block:: python
-            :emphasize-lines: 13
 
             from pyrogram import Client, idle
 
diff --git a/pyrogram/methods/utilities/remove_handler.py b/pyrogram/methods/utilities/remove_handler.py
index 97c7f18358..8447f8a9db 100644
--- a/pyrogram/methods/utilities/remove_handler.py
+++ b/pyrogram/methods/utilities/remove_handler.py
@@ -37,7 +37,6 @@ def remove_handler(self, handler: "Handler", group: int = 0):
 
         Example:
             .. code-block:: python
-                :emphasize-lines: 11
 
                 from pyrogram import Client
                 from pyrogram.handlers import MessageHandler
diff --git a/pyrogram/methods/utilities/restart.py b/pyrogram/methods/utilities/restart.py
index afd22bb478..9c9e510935 100644
--- a/pyrogram/methods/utilities/restart.py
+++ b/pyrogram/methods/utilities/restart.py
@@ -40,7 +40,6 @@ async def restart(self, block: bool = True):
 
         Example:
             .. code-block:: python
-                :emphasize-lines: 8
 
                 from pyrogram import Client
 
diff --git a/pyrogram/methods/utilities/run.py b/pyrogram/methods/utilities/run.py
index 6a55b28cde..53f66f44ba 100644
--- a/pyrogram/methods/utilities/run.py
+++ b/pyrogram/methods/utilities/run.py
@@ -28,16 +28,13 @@ def run(self, coroutine=None):
         """Start the client, idle the main script and finally stop the client.
 
         This is a convenience method that calls :meth:`~pyrogram.Client.start`, :meth:`~pyrogram.idle` and
-        :meth:`~pyrogram.Client.stop` in sequence. 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, since :meth:`~pyrogram.idle` will block after
-        starting the own client.
+        :meth:`~pyrogram.Client.stop` in sequence. It makes running a single client less verbose.
 
         Raises:
             ConnectionError: In case you try to run an already started client.
 
         Example:
             .. code-block:: python
-                :emphasize-lines: 7
 
                 from pyrogram import Client
 
diff --git a/pyrogram/methods/utilities/start.py b/pyrogram/methods/utilities/start.py
index 0f08d223c5..62e8acfc5d 100644
--- a/pyrogram/methods/utilities/start.py
+++ b/pyrogram/methods/utilities/start.py
@@ -39,7 +39,6 @@ async def start(self):
 
         Example:
             .. code-block:: python
-                :emphasize-lines: 4
 
                 from pyrogram import Client
 
diff --git a/pyrogram/methods/utilities/stop.py b/pyrogram/methods/utilities/stop.py
index 38f0d4c474..4f338d3a2b 100644
--- a/pyrogram/methods/utilities/stop.py
+++ b/pyrogram/methods/utilities/stop.py
@@ -39,7 +39,6 @@ async def stop(self, block: bool = True):
 
         Example:
             .. code-block:: python
-                :emphasize-lines: 8
 
                 from pyrogram import Client
 
diff --git a/pyrogram/methods/utilities/stop_transmission.py b/pyrogram/methods/utilities/stop_transmission.py
index a0d04b4c52..6f76f11f47 100644
--- a/pyrogram/methods/utilities/stop_transmission.py
+++ b/pyrogram/methods/utilities/stop_transmission.py
@@ -29,7 +29,6 @@ def stop_transmission(self):
 
         Example:
             .. code-block:: python
-                :emphasize-lines: 9
 
                 from pyrogram import Client
 
@@ -42,6 +41,6 @@ def progress(current, total, client):
                         client.stop_transmission()
 
                 with app:
-                    app.send_document("me", "files.zip", progress=progress, progress_args=(app,))
+                    app.send_document("me", "file.zip", progress=progress, progress_args=(app,))
         """
         raise pyrogram.StopTransmission
diff --git a/pyrogram/raw/core/primitives/vector.py b/pyrogram/raw/core/primitives/vector.py
index 48b2eed91c..a150fba76d 100644
--- a/pyrogram/raw/core/primitives/vector.py
+++ b/pyrogram/raw/core/primitives/vector.py
@@ -33,7 +33,7 @@ class Vector(bytes, TLObject):
     def read_bare(b: BytesIO, size: int) -> Union[int, Any]:
         if size == 4:
             return Int.read(b)
-        
+
         if size == 8:
             return Long.read(b)
 
diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py
index 1cf8c1b1fb..8df167a774 100644
--- a/pyrogram/session/session.py
+++ b/pyrogram/session/session.py
@@ -25,7 +25,6 @@
 from io import BytesIO
 
 import pyrogram
-from pyrogram import __copyright__, __license__, __version__
 from pyrogram import raw
 from pyrogram.connection import Connection
 from pyrogram.crypto import mtproto
@@ -54,8 +53,6 @@ class Session:
     ACKS_THRESHOLD = 8
     PING_INTERVAL = 5
 
-    notice_displayed = False
-
     def __init__(
         self,
         client: "pyrogram.Client",
@@ -65,11 +62,6 @@ def __init__(
         is_media: bool = False,
         is_cdn: bool = False
     ):
-        if not Session.notice_displayed:
-            print(f"Pyrogram v{__version__}, {__copyright__}")
-            print(f"Licensed under the terms of the {__license__}", end="\n\n")
-            Session.notice_displayed = True
-
         self.client = client
         self.dc_id = dc_id
         self.auth_key = auth_key
diff --git a/pyrogram/types/bots_and_keyboards/__init__.py b/pyrogram/types/bots_and_keyboards/__init__.py
index a1f3a662d8..fa6aa050f7 100644
--- a/pyrogram/types/bots_and_keyboards/__init__.py
+++ b/pyrogram/types/bots_and_keyboards/__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 .bot_command import BotCommand
 from .callback_game import CallbackGame
 from .callback_query import CallbackQuery
 from .force_reply import ForceReply
@@ -26,7 +27,6 @@
 from .login_url import LoginUrl
 from .reply_keyboard_markup import ReplyKeyboardMarkup
 from .reply_keyboard_remove import ReplyKeyboardRemove
-from .bot_command import BotCommand
 
 __all__ = [
     "CallbackGame",
diff --git a/pyrogram/types/inline_mode/__init__.py b/pyrogram/types/inline_mode/__init__.py
index 9d464eb48e..d22937b929 100644
--- a/pyrogram/types/inline_mode/__init__.py
+++ b/pyrogram/types/inline_mode/__init__.py
@@ -21,9 +21,9 @@
 from .inline_query_result import InlineQueryResult
 from .inline_query_result_animation import InlineQueryResultAnimation
 from .inline_query_result_article import InlineQueryResultArticle
+from .inline_query_result_audio import InlineQueryResultAudio
 from .inline_query_result_photo import InlineQueryResultPhoto
 from .inline_query_result_video import InlineQueryResultVideo
-from .inline_query_result_audio import InlineQueryResultAudio
 
 __all__ = [
     "InlineQuery", "InlineQueryResult", "InlineQueryResultArticle", "InlineQueryResultPhoto",
diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py
index 9690baf992..1d70d773e6 100644
--- a/pyrogram/types/messages_and_media/message.py
+++ b/pyrogram/types/messages_and_media/message.py
@@ -1370,7 +1370,7 @@ async def reply_contact(
         Example:
             .. code-block:: python
 
-                message.reply_contact(phone_number, "Dan")
+                message.reply_contact("+1-123-456-7890", "Name")
 
         Parameters:
             phone_number (``str``):
diff --git a/pyrogram/types/messages_and_media/message_entity.py b/pyrogram/types/messages_and_media/message_entity.py
index 9a211fde74..864bf54778 100644
--- a/pyrogram/types/messages_and_media/message_entity.py
+++ b/pyrogram/types/messages_and_media/message_entity.py
@@ -87,7 +87,7 @@ class MessageEntity(Object):
             - "bot_command": ``/start@pyrogrambot``.
             - "url": ``https://pyrogram.org`` (see *url* below).
             - "email": ``do-not-reply@pyrogram.org``.
-            - "phone_number": ``+69-420-1337``.
+            - "phone_number": ``+1-123-456-7890``.
             - "bold": **bold text**.
             - "italic": *italic text*.
             - "underline": underlined text.
diff --git a/pyrogram/types/user_and_chats/chat.py b/pyrogram/types/user_and_chats/chat.py
index a925c7dffd..6ccd946524 100644
--- a/pyrogram/types/user_and_chats/chat.py
+++ b/pyrogram/types/user_and_chats/chat.py
@@ -228,7 +228,7 @@ def _parse_chat_chat(client, chat: raw.types.Chat) -> "Chat":
             permissions=types.ChatPermissions._parse(getattr(chat, "default_banned_rights", None)),
             members_count=getattr(chat, "participants_count", None),
             dc_id=getattr(getattr(chat, "photo", None), "dc_id", None),
-            has_protected_content=chat.noforwards,
+            has_protected_content=getattr(chat, "noforwards", None),
             client=client
         )
 
@@ -252,7 +252,7 @@ def _parse_channel_chat(client, channel: raw.types.Channel) -> "Chat":
             permissions=types.ChatPermissions._parse(getattr(channel, "default_banned_rights", None)),
             members_count=getattr(channel, "participants_count", None),
             dc_id=getattr(getattr(channel, "photo", None), "dc_id", None),
-            has_protected_content=channel.noforwards,
+            has_protected_content=getattr(channel, "noforwards", None),
             client=client
         )
 
diff --git a/setup.py b/setup.py
index c2e2ce96b9..5cbd5eb208 100644
--- a/setup.py
+++ b/setup.py
@@ -133,7 +133,7 @@ def run(self):
 setup(
     name="Pyrogram",
     version=version,
-    description="Telegram MTProto API Client Library and Framework for Python",
+    description="Elegant, modern and asynchronous Telegram MTProto API framework in Python for users and bots",
     long_description=readme,
     long_description_content_type="text/markdown",
     url="https://github.com/pyrogram",
@@ -153,6 +153,7 @@ def run(self):
         "Programming Language :: Python :: 3.7",
         "Programming Language :: Python :: 3.8",
         "Programming Language :: Python :: 3.9",
+        "Programming Language :: Python :: 3.10",
         "Programming Language :: Python :: Implementation",
         "Programming Language :: Python :: Implementation :: CPython",
         "Programming Language :: Python :: Implementation :: PyPy",
@@ -166,7 +167,7 @@ def run(self):
     keywords="telegram chat messenger mtproto api client library python",
     project_urls={
         "Tracker": "https://github.com/pyrogram/pyrogram/issues",
-        "Community": "https://t.me/Pyrogram",
+        "Community": "https://t.me/pyrogram",
         "Source": "https://github.com/pyrogram/pyrogram",
         "Documentation": "https://docs.pyrogram.org",
     },

From 626a1bd93831eaa6deef61966164de1680660f3a Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Fri, 7 Jan 2022 10:23:45 +0100
Subject: [PATCH 0686/1185] Update copyright year

---
 NOTICE                                                        | 2 +-
 compiler/__init__.py                                          | 2 +-
 compiler/api/__init__.py                                      | 2 +-
 compiler/api/compiler.py                                      | 2 +-
 compiler/docs/__init__.py                                     | 2 +-
 compiler/docs/compiler.py                                     | 2 +-
 compiler/errors/__init__.py                                   | 2 +-
 compiler/errors/compiler.py                                   | 2 +-
 compiler/errors/sort.py                                       | 2 +-
 docs/source/conf.py                                           | 2 +-
 pyrogram/__init__.py                                          | 4 ++--
 pyrogram/client.py                                            | 2 +-
 pyrogram/connection/__init__.py                               | 2 +-
 pyrogram/connection/connection.py                             | 2 +-
 pyrogram/connection/transport/__init__.py                     | 2 +-
 pyrogram/connection/transport/tcp/__init__.py                 | 2 +-
 pyrogram/connection/transport/tcp/tcp.py                      | 2 +-
 pyrogram/connection/transport/tcp/tcp_abridged.py             | 2 +-
 pyrogram/connection/transport/tcp/tcp_abridged_o.py           | 2 +-
 pyrogram/connection/transport/tcp/tcp_full.py                 | 2 +-
 pyrogram/connection/transport/tcp/tcp_intermediate.py         | 2 +-
 pyrogram/connection/transport/tcp/tcp_intermediate_o.py       | 2 +-
 pyrogram/crypto/__init__.py                                   | 2 +-
 pyrogram/crypto/aes.py                                        | 2 +-
 pyrogram/crypto/prime.py                                      | 2 +-
 pyrogram/crypto/rsa.py                                        | 2 +-
 pyrogram/dispatcher.py                                        | 2 +-
 pyrogram/emoji.py                                             | 2 +-
 pyrogram/errors/__init__.py                                   | 2 +-
 pyrogram/errors/rpc_error.py                                  | 2 +-
 pyrogram/file_id.py                                           | 2 +-
 pyrogram/filters.py                                           | 2 +-
 pyrogram/handlers/__init__.py                                 | 2 +-
 pyrogram/handlers/callback_query_handler.py                   | 2 +-
 pyrogram/handlers/chat_join_request_handler.py                | 2 +-
 pyrogram/handlers/chat_member_updated_handler.py              | 2 +-
 pyrogram/handlers/chosen_inline_result_handler.py             | 2 +-
 pyrogram/handlers/deleted_messages_handler.py                 | 2 +-
 pyrogram/handlers/disconnect_handler.py                       | 2 +-
 pyrogram/handlers/handler.py                                  | 2 +-
 pyrogram/handlers/inline_query_handler.py                     | 2 +-
 pyrogram/handlers/message_handler.py                          | 2 +-
 pyrogram/handlers/poll_handler.py                             | 2 +-
 pyrogram/handlers/raw_update_handler.py                       | 2 +-
 pyrogram/handlers/user_status_handler.py                      | 2 +-
 pyrogram/methods/__init__.py                                  | 2 +-
 pyrogram/methods/advanced/__init__.py                         | 2 +-
 pyrogram/methods/advanced/resolve_peer.py                     | 2 +-
 pyrogram/methods/advanced/save_file.py                        | 2 +-
 pyrogram/methods/advanced/send.py                             | 2 +-
 pyrogram/methods/auth/__init__.py                             | 2 +-
 pyrogram/methods/auth/accept_terms_of_service.py              | 2 +-
 pyrogram/methods/auth/check_password.py                       | 2 +-
 pyrogram/methods/auth/connect.py                              | 2 +-
 pyrogram/methods/auth/disconnect.py                           | 2 +-
 pyrogram/methods/auth/get_password_hint.py                    | 2 +-
 pyrogram/methods/auth/initialize.py                           | 2 +-
 pyrogram/methods/auth/log_out.py                              | 2 +-
 pyrogram/methods/auth/recover_password.py                     | 2 +-
 pyrogram/methods/auth/resend_code.py                          | 2 +-
 pyrogram/methods/auth/send_code.py                            | 2 +-
 pyrogram/methods/auth/send_recovery_code.py                   | 2 +-
 pyrogram/methods/auth/sign_in.py                              | 2 +-
 pyrogram/methods/auth/sign_in_bot.py                          | 2 +-
 pyrogram/methods/auth/sign_up.py                              | 2 +-
 pyrogram/methods/auth/terminate.py                            | 2 +-
 pyrogram/methods/bots/__init__.py                             | 2 +-
 pyrogram/methods/bots/answer_callback_query.py                | 2 +-
 pyrogram/methods/bots/answer_inline_query.py                  | 2 +-
 pyrogram/methods/bots/get_game_high_scores.py                 | 2 +-
 pyrogram/methods/bots/get_inline_bot_results.py               | 2 +-
 pyrogram/methods/bots/request_callback_answer.py              | 2 +-
 pyrogram/methods/bots/send_game.py                            | 2 +-
 pyrogram/methods/bots/send_inline_bot_result.py               | 2 +-
 pyrogram/methods/bots/set_bot_commands.py                     | 2 +-
 pyrogram/methods/bots/set_game_score.py                       | 2 +-
 pyrogram/methods/chats/__init__.py                            | 2 +-
 pyrogram/methods/chats/add_chat_members.py                    | 2 +-
 pyrogram/methods/chats/archive_chats.py                       | 2 +-
 pyrogram/methods/chats/ban_chat_member.py                     | 2 +-
 pyrogram/methods/chats/create_channel.py                      | 2 +-
 pyrogram/methods/chats/create_group.py                        | 2 +-
 pyrogram/methods/chats/create_supergroup.py                   | 2 +-
 pyrogram/methods/chats/delete_channel.py                      | 2 +-
 pyrogram/methods/chats/delete_chat_photo.py                   | 2 +-
 pyrogram/methods/chats/delete_supergroup.py                   | 2 +-
 pyrogram/methods/chats/delete_user_history.py                 | 2 +-
 pyrogram/methods/chats/get_chat.py                            | 2 +-
 pyrogram/methods/chats/get_chat_event_log.py                  | 2 +-
 pyrogram/methods/chats/get_chat_member.py                     | 2 +-
 pyrogram/methods/chats/get_chat_members.py                    | 2 +-
 pyrogram/methods/chats/get_chat_members_count.py              | 2 +-
 pyrogram/methods/chats/get_chat_online_count.py               | 2 +-
 pyrogram/methods/chats/get_dialogs.py                         | 2 +-
 pyrogram/methods/chats/get_dialogs_count.py                   | 2 +-
 pyrogram/methods/chats/get_nearby_chats.py                    | 2 +-
 pyrogram/methods/chats/get_send_as_chats.py                   | 2 +-
 pyrogram/methods/chats/iter_chat_members.py                   | 2 +-
 pyrogram/methods/chats/iter_dialogs.py                        | 2 +-
 pyrogram/methods/chats/join_chat.py                           | 2 +-
 pyrogram/methods/chats/leave_chat.py                          | 2 +-
 pyrogram/methods/chats/mark_chat_unread.py                    | 2 +-
 pyrogram/methods/chats/pin_chat_message.py                    | 2 +-
 pyrogram/methods/chats/promote_chat_member.py                 | 2 +-
 pyrogram/methods/chats/restrict_chat_member.py                | 2 +-
 pyrogram/methods/chats/set_administrator_title.py             | 2 +-
 pyrogram/methods/chats/set_chat_description.py                | 2 +-
 pyrogram/methods/chats/set_chat_permissions.py                | 2 +-
 pyrogram/methods/chats/set_chat_photo.py                      | 2 +-
 pyrogram/methods/chats/set_chat_protected_content.py          | 2 +-
 pyrogram/methods/chats/set_chat_title.py                      | 2 +-
 pyrogram/methods/chats/set_send_as_chat.py                    | 2 +-
 pyrogram/methods/chats/set_slow_mode.py                       | 2 +-
 pyrogram/methods/chats/unarchive_chats.py                     | 2 +-
 pyrogram/methods/chats/unban_chat_member.py                   | 2 +-
 pyrogram/methods/chats/unpin_all_chat_messages.py             | 2 +-
 pyrogram/methods/chats/unpin_chat_message.py                  | 2 +-
 pyrogram/methods/chats/update_chat_username.py                | 2 +-
 pyrogram/methods/contacts/__init__.py                         | 2 +-
 pyrogram/methods/contacts/add_contact.py                      | 2 +-
 pyrogram/methods/contacts/delete_contacts.py                  | 2 +-
 pyrogram/methods/contacts/get_contacts.py                     | 2 +-
 pyrogram/methods/contacts/get_contacts_count.py               | 2 +-
 pyrogram/methods/contacts/import_contacts.py                  | 2 +-
 pyrogram/methods/decorators/__init__.py                       | 2 +-
 pyrogram/methods/decorators/on_callback_query.py              | 2 +-
 pyrogram/methods/decorators/on_chat_join_request.py           | 2 +-
 pyrogram/methods/decorators/on_chat_member_updated.py         | 2 +-
 pyrogram/methods/decorators/on_chosen_inline_result.py        | 2 +-
 pyrogram/methods/decorators/on_deleted_messages.py            | 2 +-
 pyrogram/methods/decorators/on_disconnect.py                  | 2 +-
 pyrogram/methods/decorators/on_inline_query.py                | 2 +-
 pyrogram/methods/decorators/on_message.py                     | 2 +-
 pyrogram/methods/decorators/on_poll.py                        | 2 +-
 pyrogram/methods/decorators/on_raw_update.py                  | 2 +-
 pyrogram/methods/decorators/on_user_status.py                 | 2 +-
 pyrogram/methods/invite_links/__init__.py                     | 2 +-
 pyrogram/methods/invite_links/approve_chat_join_request.py    | 2 +-
 pyrogram/methods/invite_links/create_chat_invite_link.py      | 2 +-
 pyrogram/methods/invite_links/decline_chat_join_request.py    | 2 +-
 .../methods/invite_links/delete_chat_admin_invite_links.py    | 2 +-
 pyrogram/methods/invite_links/delete_chat_invite_link.py      | 2 +-
 pyrogram/methods/invite_links/edit_chat_invite_link.py        | 2 +-
 pyrogram/methods/invite_links/export_chat_invite_link.py      | 2 +-
 pyrogram/methods/invite_links/get_chat_admin_invite_links.py  | 2 +-
 .../methods/invite_links/get_chat_admin_invite_links_count.py | 2 +-
 .../methods/invite_links/get_chat_admins_with_invite_links.py | 2 +-
 pyrogram/methods/invite_links/get_chat_invite_link.py         | 2 +-
 pyrogram/methods/invite_links/get_chat_invite_link_members.py | 2 +-
 .../invite_links/get_chat_invite_link_members_count.py        | 2 +-
 pyrogram/methods/invite_links/revoke_chat_invite_link.py      | 2 +-
 pyrogram/methods/messages/__init__.py                         | 2 +-
 pyrogram/methods/messages/copy_media_group.py                 | 2 +-
 pyrogram/methods/messages/copy_message.py                     | 2 +-
 pyrogram/methods/messages/delete_messages.py                  | 2 +-
 pyrogram/methods/messages/download_media.py                   | 2 +-
 pyrogram/methods/messages/edit_inline_caption.py              | 2 +-
 pyrogram/methods/messages/edit_inline_media.py                | 2 +-
 pyrogram/methods/messages/edit_inline_reply_markup.py         | 2 +-
 pyrogram/methods/messages/edit_inline_text.py                 | 2 +-
 pyrogram/methods/messages/edit_message_caption.py             | 2 +-
 pyrogram/methods/messages/edit_message_media.py               | 2 +-
 pyrogram/methods/messages/edit_message_reply_markup.py        | 2 +-
 pyrogram/methods/messages/edit_message_text.py                | 2 +-
 pyrogram/methods/messages/forward_messages.py                 | 2 +-
 pyrogram/methods/messages/get_discussion_message.py           | 2 +-
 pyrogram/methods/messages/get_history.py                      | 2 +-
 pyrogram/methods/messages/get_history_count.py                | 2 +-
 pyrogram/methods/messages/get_media_group.py                  | 2 +-
 pyrogram/methods/messages/get_messages.py                     | 2 +-
 pyrogram/methods/messages/inline_session.py                   | 2 +-
 pyrogram/methods/messages/iter_history.py                     | 2 +-
 pyrogram/methods/messages/read_history.py                     | 2 +-
 pyrogram/methods/messages/retract_vote.py                     | 2 +-
 pyrogram/methods/messages/search_global.py                    | 2 +-
 pyrogram/methods/messages/search_global_count.py              | 2 +-
 pyrogram/methods/messages/search_messages.py                  | 2 +-
 pyrogram/methods/messages/search_messages_count.py            | 2 +-
 pyrogram/methods/messages/send_animation.py                   | 2 +-
 pyrogram/methods/messages/send_audio.py                       | 2 +-
 pyrogram/methods/messages/send_cached_media.py                | 2 +-
 pyrogram/methods/messages/send_chat_action.py                 | 2 +-
 pyrogram/methods/messages/send_contact.py                     | 2 +-
 pyrogram/methods/messages/send_dice.py                        | 2 +-
 pyrogram/methods/messages/send_document.py                    | 2 +-
 pyrogram/methods/messages/send_location.py                    | 2 +-
 pyrogram/methods/messages/send_media_group.py                 | 2 +-
 pyrogram/methods/messages/send_message.py                     | 2 +-
 pyrogram/methods/messages/send_photo.py                       | 2 +-
 pyrogram/methods/messages/send_poll.py                        | 2 +-
 pyrogram/methods/messages/send_reaction.py                    | 2 +-
 pyrogram/methods/messages/send_sticker.py                     | 2 +-
 pyrogram/methods/messages/send_venue.py                       | 2 +-
 pyrogram/methods/messages/send_video.py                       | 2 +-
 pyrogram/methods/messages/send_video_note.py                  | 2 +-
 pyrogram/methods/messages/send_voice.py                       | 2 +-
 pyrogram/methods/messages/stop_poll.py                        | 2 +-
 pyrogram/methods/messages/vote_poll.py                        | 2 +-
 pyrogram/methods/password/__init__.py                         | 2 +-
 pyrogram/methods/password/change_cloud_password.py            | 2 +-
 pyrogram/methods/password/enable_cloud_password.py            | 2 +-
 pyrogram/methods/password/remove_cloud_password.py            | 2 +-
 pyrogram/methods/users/__init__.py                            | 2 +-
 pyrogram/methods/users/block_user.py                          | 2 +-
 pyrogram/methods/users/delete_profile_photos.py               | 2 +-
 pyrogram/methods/users/get_common_chats.py                    | 2 +-
 pyrogram/methods/users/get_me.py                              | 2 +-
 pyrogram/methods/users/get_profile_photos.py                  | 2 +-
 pyrogram/methods/users/get_profile_photos_count.py            | 2 +-
 pyrogram/methods/users/get_users.py                           | 2 +-
 pyrogram/methods/users/iter_profile_photos.py                 | 2 +-
 pyrogram/methods/users/set_profile_photo.py                   | 2 +-
 pyrogram/methods/users/unblock_user.py                        | 2 +-
 pyrogram/methods/users/update_profile.py                      | 2 +-
 pyrogram/methods/users/update_username.py                     | 2 +-
 pyrogram/methods/utilities/__init__.py                        | 2 +-
 pyrogram/methods/utilities/add_handler.py                     | 2 +-
 pyrogram/methods/utilities/export_session_string.py           | 2 +-
 pyrogram/methods/utilities/idle.py                            | 2 +-
 pyrogram/methods/utilities/remove_handler.py                  | 2 +-
 pyrogram/methods/utilities/restart.py                         | 2 +-
 pyrogram/methods/utilities/run.py                             | 2 +-
 pyrogram/methods/utilities/start.py                           | 2 +-
 pyrogram/methods/utilities/stop.py                            | 2 +-
 pyrogram/methods/utilities/stop_transmission.py               | 2 +-
 pyrogram/mime_types.py                                        | 2 +-
 pyrogram/parser/__init__.py                                   | 2 +-
 pyrogram/parser/html.py                                       | 2 +-
 pyrogram/parser/markdown.py                                   | 2 +-
 pyrogram/parser/parser.py                                     | 2 +-
 pyrogram/parser/utils.py                                      | 2 +-
 pyrogram/raw/__init__.py                                      | 2 +-
 pyrogram/raw/core/__init__.py                                 | 2 +-
 pyrogram/raw/core/future_salt.py                              | 2 +-
 pyrogram/raw/core/future_salts.py                             | 2 +-
 pyrogram/raw/core/gzip_packed.py                              | 2 +-
 pyrogram/raw/core/list.py                                     | 2 +-
 pyrogram/raw/core/message.py                                  | 2 +-
 pyrogram/raw/core/msg_container.py                            | 2 +-
 pyrogram/raw/core/primitives/__init__.py                      | 2 +-
 pyrogram/raw/core/primitives/bool.py                          | 2 +-
 pyrogram/raw/core/primitives/bytes.py                         | 2 +-
 pyrogram/raw/core/primitives/double.py                        | 2 +-
 pyrogram/raw/core/primitives/int.py                           | 2 +-
 pyrogram/raw/core/primitives/string.py                        | 2 +-
 pyrogram/raw/core/primitives/vector.py                        | 2 +-
 pyrogram/raw/core/tl_object.py                                | 2 +-
 pyrogram/scaffold.py                                          | 2 +-
 pyrogram/session/__init__.py                                  | 2 +-
 pyrogram/session/auth.py                                      | 2 +-
 pyrogram/session/internals/__init__.py                        | 2 +-
 pyrogram/session/internals/data_center.py                     | 2 +-
 pyrogram/session/internals/msg_factory.py                     | 2 +-
 pyrogram/session/internals/msg_id.py                          | 2 +-
 pyrogram/session/internals/seq_no.py                          | 2 +-
 pyrogram/session/session.py                                   | 2 +-
 pyrogram/storage/__init__.py                                  | 2 +-
 pyrogram/storage/file_storage.py                              | 2 +-
 pyrogram/storage/memory_storage.py                            | 2 +-
 pyrogram/storage/sqlite_storage.py                            | 2 +-
 pyrogram/storage/storage.py                                   | 2 +-
 pyrogram/sync.py                                              | 2 +-
 pyrogram/syncer.py                                            | 2 +-
 pyrogram/types/__init__.py                                    | 2 +-
 pyrogram/types/authorization/__init__.py                      | 2 +-
 pyrogram/types/authorization/sent_code.py                     | 2 +-
 pyrogram/types/authorization/terms_of_service.py              | 2 +-
 pyrogram/types/bots_and_keyboards/__init__.py                 | 2 +-
 pyrogram/types/bots_and_keyboards/bot_command.py              | 2 +-
 pyrogram/types/bots_and_keyboards/callback_game.py            | 2 +-
 pyrogram/types/bots_and_keyboards/callback_query.py           | 2 +-
 pyrogram/types/bots_and_keyboards/force_reply.py              | 2 +-
 pyrogram/types/bots_and_keyboards/game_high_score.py          | 2 +-
 pyrogram/types/bots_and_keyboards/inline_keyboard_button.py   | 2 +-
 pyrogram/types/bots_and_keyboards/inline_keyboard_markup.py   | 2 +-
 pyrogram/types/bots_and_keyboards/keyboard_button.py          | 2 +-
 pyrogram/types/bots_and_keyboards/login_url.py                | 2 +-
 pyrogram/types/bots_and_keyboards/reply_keyboard_markup.py    | 2 +-
 pyrogram/types/bots_and_keyboards/reply_keyboard_remove.py    | 2 +-
 pyrogram/types/inline_mode/__init__.py                        | 2 +-
 pyrogram/types/inline_mode/chosen_inline_result.py            | 2 +-
 pyrogram/types/inline_mode/inline_query.py                    | 2 +-
 pyrogram/types/inline_mode/inline_query_result.py             | 2 +-
 pyrogram/types/inline_mode/inline_query_result_animation.py   | 2 +-
 pyrogram/types/inline_mode/inline_query_result_article.py     | 2 +-
 pyrogram/types/inline_mode/inline_query_result_photo.py       | 2 +-
 pyrogram/types/inline_mode/inline_query_result_video.py       | 2 +-
 pyrogram/types/input_media/__init__.py                        | 2 +-
 pyrogram/types/input_media/input_media.py                     | 2 +-
 pyrogram/types/input_media/input_media_animation.py           | 2 +-
 pyrogram/types/input_media/input_media_audio.py               | 2 +-
 pyrogram/types/input_media/input_media_document.py            | 2 +-
 pyrogram/types/input_media/input_media_photo.py               | 2 +-
 pyrogram/types/input_media/input_media_video.py               | 2 +-
 pyrogram/types/input_media/input_phone_contact.py             | 2 +-
 pyrogram/types/input_message_content/__init__.py              | 2 +-
 pyrogram/types/input_message_content/input_message_content.py | 2 +-
 .../types/input_message_content/input_text_message_content.py | 2 +-
 pyrogram/types/list.py                                        | 2 +-
 pyrogram/types/messages_and_media/__init__.py                 | 2 +-
 pyrogram/types/messages_and_media/animation.py                | 2 +-
 pyrogram/types/messages_and_media/audio.py                    | 2 +-
 pyrogram/types/messages_and_media/contact.py                  | 2 +-
 pyrogram/types/messages_and_media/dice.py                     | 2 +-
 pyrogram/types/messages_and_media/document.py                 | 2 +-
 pyrogram/types/messages_and_media/game.py                     | 2 +-
 pyrogram/types/messages_and_media/location.py                 | 2 +-
 pyrogram/types/messages_and_media/message.py                  | 2 +-
 pyrogram/types/messages_and_media/message_entity.py           | 2 +-
 pyrogram/types/messages_and_media/photo.py                    | 2 +-
 pyrogram/types/messages_and_media/poll.py                     | 2 +-
 pyrogram/types/messages_and_media/poll_option.py              | 2 +-
 pyrogram/types/messages_and_media/reaction.py                 | 2 +-
 pyrogram/types/messages_and_media/sticker.py                  | 2 +-
 pyrogram/types/messages_and_media/stripped_thumbnail.py       | 2 +-
 pyrogram/types/messages_and_media/thumbnail.py                | 2 +-
 pyrogram/types/messages_and_media/venue.py                    | 2 +-
 pyrogram/types/messages_and_media/video.py                    | 2 +-
 pyrogram/types/messages_and_media/video_note.py               | 2 +-
 pyrogram/types/messages_and_media/voice.py                    | 2 +-
 pyrogram/types/messages_and_media/webpage.py                  | 2 +-
 pyrogram/types/object.py                                      | 2 +-
 pyrogram/types/update.py                                      | 2 +-
 pyrogram/types/user_and_chats/__init__.py                     | 2 +-
 pyrogram/types/user_and_chats/chat.py                         | 2 +-
 pyrogram/types/user_and_chats/chat_admin_with_invite_links.py | 2 +-
 pyrogram/types/user_and_chats/chat_event.py                   | 2 +-
 pyrogram/types/user_and_chats/chat_event_filter.py            | 2 +-
 pyrogram/types/user_and_chats/chat_invite_link.py             | 2 +-
 pyrogram/types/user_and_chats/chat_join_request.py            | 2 +-
 pyrogram/types/user_and_chats/chat_member.py                  | 2 +-
 pyrogram/types/user_and_chats/chat_member_updated.py          | 2 +-
 pyrogram/types/user_and_chats/chat_permissions.py             | 2 +-
 pyrogram/types/user_and_chats/chat_photo.py                   | 2 +-
 pyrogram/types/user_and_chats/chat_preview.py                 | 2 +-
 pyrogram/types/user_and_chats/dialog.py                       | 2 +-
 pyrogram/types/user_and_chats/invite_link_importer.py         | 2 +-
 pyrogram/types/user_and_chats/restriction.py                  | 2 +-
 pyrogram/types/user_and_chats/user.py                         | 2 +-
 pyrogram/types/user_and_chats/voice_chat_ended.py             | 2 +-
 pyrogram/types/user_and_chats/voice_chat_members_invited.py   | 2 +-
 pyrogram/types/user_and_chats/voice_chat_scheduled.py         | 2 +-
 pyrogram/types/user_and_chats/voice_chat_started.py           | 2 +-
 pyrogram/utils.py                                             | 2 +-
 setup.py                                                      | 2 +-
 tests/__init__.py                                             | 2 +-
 tests/filters/__init__.py                                     | 2 +-
 tests/filters/test_command.py                                 | 2 +-
 tests/test_file_id.py                                         | 2 +-
 349 files changed, 350 insertions(+), 350 deletions(-)

diff --git a/NOTICE b/NOTICE
index 58defc6402..7a9aaab647 100644
--- a/NOTICE
+++ b/NOTICE
@@ -1,5 +1,5 @@
 Pyrogram - Telegram MTProto API Client Library for Python
-Copyright (C) 2017-2021 Dan 
+Copyright (C) 2017-present Dan 
 
 This file is part of Pyrogram.
 
diff --git a/compiler/__init__.py b/compiler/__init__.py
index 4ad4f32b47..46887cb7a5 100644
--- a/compiler/__init__.py
+++ b/compiler/__init__.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/compiler/api/__init__.py b/compiler/api/__init__.py
index 4ad4f32b47..46887cb7a5 100644
--- a/compiler/api/__init__.py
+++ b/compiler/api/__init__.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/compiler/api/compiler.py b/compiler/api/compiler.py
index d79fc80160..192d6a6759 100644
--- a/compiler/api/compiler.py
+++ b/compiler/api/compiler.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/compiler/docs/__init__.py b/compiler/docs/__init__.py
index 4ad4f32b47..46887cb7a5 100644
--- a/compiler/docs/__init__.py
+++ b/compiler/docs/__init__.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py
index 65f6743660..cb4de0a6b5 100644
--- a/compiler/docs/compiler.py
+++ b/compiler/docs/compiler.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/compiler/errors/__init__.py b/compiler/errors/__init__.py
index 4ad4f32b47..46887cb7a5 100644
--- a/compiler/errors/__init__.py
+++ b/compiler/errors/__init__.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/compiler/errors/compiler.py b/compiler/errors/compiler.py
index feda92316c..d2c1010456 100644
--- a/compiler/errors/compiler.py
+++ b/compiler/errors/compiler.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/compiler/errors/sort.py b/compiler/errors/sort.py
index 69fc0437a8..db94e35162 100644
--- a/compiler/errors/sort.py
+++ b/compiler/errors/sort.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/docs/source/conf.py b/docs/source/conf.py
index ae84c5b806..53ac9012c5 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index c0c7b21916..96ddb664a4 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
@@ -18,7 +18,7 @@
 
 __version__ = "1.2.9"
 __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)"
-__copyright__ = "Copyright (C) 2017-2021 Dan "
+__copyright__ = "Copyright (C) 2017-present Dan "
 
 from concurrent.futures.thread import ThreadPoolExecutor
 
diff --git a/pyrogram/client.py b/pyrogram/client.py
index 65514cf6cc..ce76920fa9 100644
--- a/pyrogram/client.py
+++ b/pyrogram/client.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/connection/__init__.py b/pyrogram/connection/__init__.py
index 4b63340ee6..4665ce913f 100644
--- a/pyrogram/connection/__init__.py
+++ b/pyrogram/connection/__init__.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/connection/connection.py b/pyrogram/connection/connection.py
index 3ab52d7105..2173c70b94 100644
--- a/pyrogram/connection/connection.py
+++ b/pyrogram/connection/connection.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/connection/transport/__init__.py b/pyrogram/connection/transport/__init__.py
index b856583cf0..2d08832a70 100644
--- a/pyrogram/connection/transport/__init__.py
+++ b/pyrogram/connection/transport/__init__.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/connection/transport/tcp/__init__.py b/pyrogram/connection/transport/tcp/__init__.py
index 6a5456de3f..3e23a88379 100644
--- a/pyrogram/connection/transport/tcp/__init__.py
+++ b/pyrogram/connection/transport/tcp/__init__.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/connection/transport/tcp/tcp.py b/pyrogram/connection/transport/tcp/tcp.py
index 6c3e760315..0b858c0265 100644
--- a/pyrogram/connection/transport/tcp/tcp.py
+++ b/pyrogram/connection/transport/tcp/tcp.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/connection/transport/tcp/tcp_abridged.py b/pyrogram/connection/transport/tcp/tcp_abridged.py
index 0282e66815..77d44cf41c 100644
--- a/pyrogram/connection/transport/tcp/tcp_abridged.py
+++ b/pyrogram/connection/transport/tcp/tcp_abridged.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/connection/transport/tcp/tcp_abridged_o.py b/pyrogram/connection/transport/tcp/tcp_abridged_o.py
index 9db148bf2f..12a832f6a9 100644
--- a/pyrogram/connection/transport/tcp/tcp_abridged_o.py
+++ b/pyrogram/connection/transport/tcp/tcp_abridged_o.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/connection/transport/tcp/tcp_full.py b/pyrogram/connection/transport/tcp/tcp_full.py
index 8c80f454be..8bd89000c8 100644
--- a/pyrogram/connection/transport/tcp/tcp_full.py
+++ b/pyrogram/connection/transport/tcp/tcp_full.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/connection/transport/tcp/tcp_intermediate.py b/pyrogram/connection/transport/tcp/tcp_intermediate.py
index 6b18ab15c7..b6aef335a9 100644
--- a/pyrogram/connection/transport/tcp/tcp_intermediate.py
+++ b/pyrogram/connection/transport/tcp/tcp_intermediate.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/connection/transport/tcp/tcp_intermediate_o.py b/pyrogram/connection/transport/tcp/tcp_intermediate_o.py
index c257fdd54e..5b267661db 100644
--- a/pyrogram/connection/transport/tcp/tcp_intermediate_o.py
+++ b/pyrogram/connection/transport/tcp/tcp_intermediate_o.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/crypto/__init__.py b/pyrogram/crypto/__init__.py
index 4ad4f32b47..46887cb7a5 100644
--- a/pyrogram/crypto/__init__.py
+++ b/pyrogram/crypto/__init__.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/crypto/aes.py b/pyrogram/crypto/aes.py
index 05aca1c087..532cd5e8d0 100644
--- a/pyrogram/crypto/aes.py
+++ b/pyrogram/crypto/aes.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/crypto/prime.py b/pyrogram/crypto/prime.py
index 82d1df758e..e919e22ce4 100644
--- a/pyrogram/crypto/prime.py
+++ b/pyrogram/crypto/prime.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/crypto/rsa.py b/pyrogram/crypto/rsa.py
index 8804f87a60..3fd7d67ba3 100644
--- a/pyrogram/crypto/rsa.py
+++ b/pyrogram/crypto/rsa.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/dispatcher.py b/pyrogram/dispatcher.py
index ccd8b9da82..0c425f4de3 100644
--- a/pyrogram/dispatcher.py
+++ b/pyrogram/dispatcher.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/emoji.py b/pyrogram/emoji.py
index 69e44fa574..19dd7e8172 100644
--- a/pyrogram/emoji.py
+++ b/pyrogram/emoji.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/errors/__init__.py b/pyrogram/errors/__init__.py
index c92f24b1eb..0ae393620e 100644
--- a/pyrogram/errors/__init__.py
+++ b/pyrogram/errors/__init__.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/errors/rpc_error.py b/pyrogram/errors/rpc_error.py
index a9fd66556d..444447f2bb 100644
--- a/pyrogram/errors/rpc_error.py
+++ b/pyrogram/errors/rpc_error.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/file_id.py b/pyrogram/file_id.py
index ef1319ecae..ee1ccc4a89 100644
--- a/pyrogram/file_id.py
+++ b/pyrogram/file_id.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/filters.py b/pyrogram/filters.py
index e583e0638e..46ca896f4d 100644
--- a/pyrogram/filters.py
+++ b/pyrogram/filters.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/handlers/__init__.py b/pyrogram/handlers/__init__.py
index 1312c6e59d..2b7ef0a201 100644
--- a/pyrogram/handlers/__init__.py
+++ b/pyrogram/handlers/__init__.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/handlers/callback_query_handler.py b/pyrogram/handlers/callback_query_handler.py
index e28e7216a6..b582d728c8 100644
--- a/pyrogram/handlers/callback_query_handler.py
+++ b/pyrogram/handlers/callback_query_handler.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/handlers/chat_join_request_handler.py b/pyrogram/handlers/chat_join_request_handler.py
index f26abefc95..03e2ac674e 100644
--- a/pyrogram/handlers/chat_join_request_handler.py
+++ b/pyrogram/handlers/chat_join_request_handler.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/handlers/chat_member_updated_handler.py b/pyrogram/handlers/chat_member_updated_handler.py
index d03eae11f3..8cfdd72696 100644
--- a/pyrogram/handlers/chat_member_updated_handler.py
+++ b/pyrogram/handlers/chat_member_updated_handler.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/handlers/chosen_inline_result_handler.py b/pyrogram/handlers/chosen_inline_result_handler.py
index 42b5bb79eb..81691a3577 100644
--- a/pyrogram/handlers/chosen_inline_result_handler.py
+++ b/pyrogram/handlers/chosen_inline_result_handler.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/handlers/deleted_messages_handler.py b/pyrogram/handlers/deleted_messages_handler.py
index e41158f93b..a336c6503b 100644
--- a/pyrogram/handlers/deleted_messages_handler.py
+++ b/pyrogram/handlers/deleted_messages_handler.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/handlers/disconnect_handler.py b/pyrogram/handlers/disconnect_handler.py
index 2302db5821..c471e8c7e6 100644
--- a/pyrogram/handlers/disconnect_handler.py
+++ b/pyrogram/handlers/disconnect_handler.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/handlers/handler.py b/pyrogram/handlers/handler.py
index 78786ab797..c666d042e2 100644
--- a/pyrogram/handlers/handler.py
+++ b/pyrogram/handlers/handler.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/handlers/inline_query_handler.py b/pyrogram/handlers/inline_query_handler.py
index 31f085b35f..0ce58d17a8 100644
--- a/pyrogram/handlers/inline_query_handler.py
+++ b/pyrogram/handlers/inline_query_handler.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/handlers/message_handler.py b/pyrogram/handlers/message_handler.py
index f20669afc8..144dbe9111 100644
--- a/pyrogram/handlers/message_handler.py
+++ b/pyrogram/handlers/message_handler.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/handlers/poll_handler.py b/pyrogram/handlers/poll_handler.py
index 09940c1252..0151f2b75c 100644
--- a/pyrogram/handlers/poll_handler.py
+++ b/pyrogram/handlers/poll_handler.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/handlers/raw_update_handler.py b/pyrogram/handlers/raw_update_handler.py
index 53413c3144..e12e8477e4 100644
--- a/pyrogram/handlers/raw_update_handler.py
+++ b/pyrogram/handlers/raw_update_handler.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/handlers/user_status_handler.py b/pyrogram/handlers/user_status_handler.py
index 23b9af2f97..caebac742b 100644
--- a/pyrogram/handlers/user_status_handler.py
+++ b/pyrogram/handlers/user_status_handler.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/__init__.py b/pyrogram/methods/__init__.py
index ee453ce3bb..ea71f6b178 100644
--- a/pyrogram/methods/__init__.py
+++ b/pyrogram/methods/__init__.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/advanced/__init__.py b/pyrogram/methods/advanced/__init__.py
index 934b0b9f21..a3cf461af7 100644
--- a/pyrogram/methods/advanced/__init__.py
+++ b/pyrogram/methods/advanced/__init__.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/advanced/resolve_peer.py b/pyrogram/methods/advanced/resolve_peer.py
index 3b3915aca6..2a39780cf2 100644
--- a/pyrogram/methods/advanced/resolve_peer.py
+++ b/pyrogram/methods/advanced/resolve_peer.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/advanced/save_file.py b/pyrogram/methods/advanced/save_file.py
index 8436156584..b612b1124f 100644
--- a/pyrogram/methods/advanced/save_file.py
+++ b/pyrogram/methods/advanced/save_file.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/advanced/send.py b/pyrogram/methods/advanced/send.py
index 9ac7ec24d6..8ef0849c63 100644
--- a/pyrogram/methods/advanced/send.py
+++ b/pyrogram/methods/advanced/send.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/auth/__init__.py b/pyrogram/methods/auth/__init__.py
index 2227be8102..ce585648da 100644
--- a/pyrogram/methods/auth/__init__.py
+++ b/pyrogram/methods/auth/__init__.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/auth/accept_terms_of_service.py b/pyrogram/methods/auth/accept_terms_of_service.py
index c8cfd36d71..5641a8ac72 100644
--- a/pyrogram/methods/auth/accept_terms_of_service.py
+++ b/pyrogram/methods/auth/accept_terms_of_service.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/auth/check_password.py b/pyrogram/methods/auth/check_password.py
index 1dd60f7ebf..1cfa526ba0 100644
--- a/pyrogram/methods/auth/check_password.py
+++ b/pyrogram/methods/auth/check_password.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/auth/connect.py b/pyrogram/methods/auth/connect.py
index 7376ddf60e..82cf661f90 100644
--- a/pyrogram/methods/auth/connect.py
+++ b/pyrogram/methods/auth/connect.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/auth/disconnect.py b/pyrogram/methods/auth/disconnect.py
index 4d516a119e..ddc1e7e1cd 100644
--- a/pyrogram/methods/auth/disconnect.py
+++ b/pyrogram/methods/auth/disconnect.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/auth/get_password_hint.py b/pyrogram/methods/auth/get_password_hint.py
index 44f1439c84..6ba3f280b4 100644
--- a/pyrogram/methods/auth/get_password_hint.py
+++ b/pyrogram/methods/auth/get_password_hint.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/auth/initialize.py b/pyrogram/methods/auth/initialize.py
index 0fb276f7fd..0b7608818b 100644
--- a/pyrogram/methods/auth/initialize.py
+++ b/pyrogram/methods/auth/initialize.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/auth/log_out.py b/pyrogram/methods/auth/log_out.py
index 06c9824bbd..7174545dc5 100644
--- a/pyrogram/methods/auth/log_out.py
+++ b/pyrogram/methods/auth/log_out.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/auth/recover_password.py b/pyrogram/methods/auth/recover_password.py
index 03887f4ed8..db877ec8d6 100644
--- a/pyrogram/methods/auth/recover_password.py
+++ b/pyrogram/methods/auth/recover_password.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/auth/resend_code.py b/pyrogram/methods/auth/resend_code.py
index cf8ce3be6d..18e835f53d 100644
--- a/pyrogram/methods/auth/resend_code.py
+++ b/pyrogram/methods/auth/resend_code.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/auth/send_code.py b/pyrogram/methods/auth/send_code.py
index 62fb256bc6..3f92ccebcb 100644
--- a/pyrogram/methods/auth/send_code.py
+++ b/pyrogram/methods/auth/send_code.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/auth/send_recovery_code.py b/pyrogram/methods/auth/send_recovery_code.py
index 4c28f1cf0b..078799783e 100644
--- a/pyrogram/methods/auth/send_recovery_code.py
+++ b/pyrogram/methods/auth/send_recovery_code.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/auth/sign_in.py b/pyrogram/methods/auth/sign_in.py
index 6e5e7c46fe..19c0fbc165 100644
--- a/pyrogram/methods/auth/sign_in.py
+++ b/pyrogram/methods/auth/sign_in.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/auth/sign_in_bot.py b/pyrogram/methods/auth/sign_in_bot.py
index 490ceced27..7cbcb1aca5 100644
--- a/pyrogram/methods/auth/sign_in_bot.py
+++ b/pyrogram/methods/auth/sign_in_bot.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/auth/sign_up.py b/pyrogram/methods/auth/sign_up.py
index bed07f8509..4b18a73292 100644
--- a/pyrogram/methods/auth/sign_up.py
+++ b/pyrogram/methods/auth/sign_up.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/auth/terminate.py b/pyrogram/methods/auth/terminate.py
index 70166fe428..46b6698263 100644
--- a/pyrogram/methods/auth/terminate.py
+++ b/pyrogram/methods/auth/terminate.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/bots/__init__.py b/pyrogram/methods/bots/__init__.py
index 1e6b91465e..0051c6c3fb 100644
--- a/pyrogram/methods/bots/__init__.py
+++ b/pyrogram/methods/bots/__init__.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/bots/answer_callback_query.py b/pyrogram/methods/bots/answer_callback_query.py
index ccb1f4449a..941389b74b 100644
--- a/pyrogram/methods/bots/answer_callback_query.py
+++ b/pyrogram/methods/bots/answer_callback_query.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/bots/answer_inline_query.py b/pyrogram/methods/bots/answer_inline_query.py
index 66bd739ffb..9683e1bb34 100644
--- a/pyrogram/methods/bots/answer_inline_query.py
+++ b/pyrogram/methods/bots/answer_inline_query.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/bots/get_game_high_scores.py b/pyrogram/methods/bots/get_game_high_scores.py
index 59ddb67ec5..cd0b09ba3d 100644
--- a/pyrogram/methods/bots/get_game_high_scores.py
+++ b/pyrogram/methods/bots/get_game_high_scores.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/bots/get_inline_bot_results.py b/pyrogram/methods/bots/get_inline_bot_results.py
index 73b4c5d786..800de93e4c 100644
--- a/pyrogram/methods/bots/get_inline_bot_results.py
+++ b/pyrogram/methods/bots/get_inline_bot_results.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/bots/request_callback_answer.py b/pyrogram/methods/bots/request_callback_answer.py
index 9d32e60f07..5db9dfe079 100644
--- a/pyrogram/methods/bots/request_callback_answer.py
+++ b/pyrogram/methods/bots/request_callback_answer.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/bots/send_game.py b/pyrogram/methods/bots/send_game.py
index f6e6a3409c..05b9094f58 100644
--- a/pyrogram/methods/bots/send_game.py
+++ b/pyrogram/methods/bots/send_game.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/bots/send_inline_bot_result.py b/pyrogram/methods/bots/send_inline_bot_result.py
index e91aac9789..de00546c13 100644
--- a/pyrogram/methods/bots/send_inline_bot_result.py
+++ b/pyrogram/methods/bots/send_inline_bot_result.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/bots/set_bot_commands.py b/pyrogram/methods/bots/set_bot_commands.py
index 3a3c8f54a8..02158c0cb6 100644
--- a/pyrogram/methods/bots/set_bot_commands.py
+++ b/pyrogram/methods/bots/set_bot_commands.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/bots/set_game_score.py b/pyrogram/methods/bots/set_game_score.py
index e8af3125ad..edb38dcfe1 100644
--- a/pyrogram/methods/bots/set_game_score.py
+++ b/pyrogram/methods/bots/set_game_score.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/chats/__init__.py b/pyrogram/methods/chats/__init__.py
index 70c02dee15..cba67600af 100644
--- a/pyrogram/methods/chats/__init__.py
+++ b/pyrogram/methods/chats/__init__.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/chats/add_chat_members.py b/pyrogram/methods/chats/add_chat_members.py
index 154f1ee1e4..70fb0f97c4 100644
--- a/pyrogram/methods/chats/add_chat_members.py
+++ b/pyrogram/methods/chats/add_chat_members.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/chats/archive_chats.py b/pyrogram/methods/chats/archive_chats.py
index df3f69c103..c65b762206 100644
--- a/pyrogram/methods/chats/archive_chats.py
+++ b/pyrogram/methods/chats/archive_chats.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/chats/ban_chat_member.py b/pyrogram/methods/chats/ban_chat_member.py
index 0d17fec699..bdf1175197 100644
--- a/pyrogram/methods/chats/ban_chat_member.py
+++ b/pyrogram/methods/chats/ban_chat_member.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/chats/create_channel.py b/pyrogram/methods/chats/create_channel.py
index 3e1fab82a6..b3b21eaea3 100644
--- a/pyrogram/methods/chats/create_channel.py
+++ b/pyrogram/methods/chats/create_channel.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/chats/create_group.py b/pyrogram/methods/chats/create_group.py
index f1833d80e6..2117d3699a 100644
--- a/pyrogram/methods/chats/create_group.py
+++ b/pyrogram/methods/chats/create_group.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/chats/create_supergroup.py b/pyrogram/methods/chats/create_supergroup.py
index 866a732b56..2c72e25b30 100644
--- a/pyrogram/methods/chats/create_supergroup.py
+++ b/pyrogram/methods/chats/create_supergroup.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/chats/delete_channel.py b/pyrogram/methods/chats/delete_channel.py
index 0a47fb43aa..3f9baa4ff6 100644
--- a/pyrogram/methods/chats/delete_channel.py
+++ b/pyrogram/methods/chats/delete_channel.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/chats/delete_chat_photo.py b/pyrogram/methods/chats/delete_chat_photo.py
index 7bd82337a9..4311658bd5 100644
--- a/pyrogram/methods/chats/delete_chat_photo.py
+++ b/pyrogram/methods/chats/delete_chat_photo.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/chats/delete_supergroup.py b/pyrogram/methods/chats/delete_supergroup.py
index 016be5463f..b9d3bdf7d0 100644
--- a/pyrogram/methods/chats/delete_supergroup.py
+++ b/pyrogram/methods/chats/delete_supergroup.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/chats/delete_user_history.py b/pyrogram/methods/chats/delete_user_history.py
index 8043432de3..ca9d0c5941 100644
--- a/pyrogram/methods/chats/delete_user_history.py
+++ b/pyrogram/methods/chats/delete_user_history.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/chats/get_chat.py b/pyrogram/methods/chats/get_chat.py
index 4420b5feb7..d9ecb166ba 100644
--- a/pyrogram/methods/chats/get_chat.py
+++ b/pyrogram/methods/chats/get_chat.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/chats/get_chat_event_log.py b/pyrogram/methods/chats/get_chat_event_log.py
index 46c031edb8..3e392a204d 100644
--- a/pyrogram/methods/chats/get_chat_event_log.py
+++ b/pyrogram/methods/chats/get_chat_event_log.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/chats/get_chat_member.py b/pyrogram/methods/chats/get_chat_member.py
index 711eafdd41..9987327c6b 100644
--- a/pyrogram/methods/chats/get_chat_member.py
+++ b/pyrogram/methods/chats/get_chat_member.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/chats/get_chat_members.py b/pyrogram/methods/chats/get_chat_members.py
index c984107c62..07d831a964 100644
--- a/pyrogram/methods/chats/get_chat_members.py
+++ b/pyrogram/methods/chats/get_chat_members.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/chats/get_chat_members_count.py b/pyrogram/methods/chats/get_chat_members_count.py
index 896b244d16..aa943eea2a 100644
--- a/pyrogram/methods/chats/get_chat_members_count.py
+++ b/pyrogram/methods/chats/get_chat_members_count.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/chats/get_chat_online_count.py b/pyrogram/methods/chats/get_chat_online_count.py
index 5d3d861f87..a8d1321446 100644
--- a/pyrogram/methods/chats/get_chat_online_count.py
+++ b/pyrogram/methods/chats/get_chat_online_count.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/chats/get_dialogs.py b/pyrogram/methods/chats/get_dialogs.py
index 4e3b11113e..7a9d6a485f 100644
--- a/pyrogram/methods/chats/get_dialogs.py
+++ b/pyrogram/methods/chats/get_dialogs.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/chats/get_dialogs_count.py b/pyrogram/methods/chats/get_dialogs_count.py
index 260cc4f6ba..a8598e5fcc 100644
--- a/pyrogram/methods/chats/get_dialogs_count.py
+++ b/pyrogram/methods/chats/get_dialogs_count.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/chats/get_nearby_chats.py b/pyrogram/methods/chats/get_nearby_chats.py
index 22ff4fc07f..dcceb4398b 100644
--- a/pyrogram/methods/chats/get_nearby_chats.py
+++ b/pyrogram/methods/chats/get_nearby_chats.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/chats/get_send_as_chats.py b/pyrogram/methods/chats/get_send_as_chats.py
index 6eb6c4a684..4d2fae4824 100644
--- a/pyrogram/methods/chats/get_send_as_chats.py
+++ b/pyrogram/methods/chats/get_send_as_chats.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/chats/iter_chat_members.py b/pyrogram/methods/chats/iter_chat_members.py
index c4144384ab..e9fa5b8277 100644
--- a/pyrogram/methods/chats/iter_chat_members.py
+++ b/pyrogram/methods/chats/iter_chat_members.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/chats/iter_dialogs.py b/pyrogram/methods/chats/iter_dialogs.py
index 124eff52f1..2584d98d63 100644
--- a/pyrogram/methods/chats/iter_dialogs.py
+++ b/pyrogram/methods/chats/iter_dialogs.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/chats/join_chat.py b/pyrogram/methods/chats/join_chat.py
index 41d0aa9313..ca720d6d0b 100644
--- a/pyrogram/methods/chats/join_chat.py
+++ b/pyrogram/methods/chats/join_chat.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/chats/leave_chat.py b/pyrogram/methods/chats/leave_chat.py
index 84ab4ebc04..3271a1f952 100644
--- a/pyrogram/methods/chats/leave_chat.py
+++ b/pyrogram/methods/chats/leave_chat.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/chats/mark_chat_unread.py b/pyrogram/methods/chats/mark_chat_unread.py
index a85cfccbcd..4a48c5d6fa 100644
--- a/pyrogram/methods/chats/mark_chat_unread.py
+++ b/pyrogram/methods/chats/mark_chat_unread.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/chats/pin_chat_message.py b/pyrogram/methods/chats/pin_chat_message.py
index 11983f5727..d4d6b040f8 100644
--- a/pyrogram/methods/chats/pin_chat_message.py
+++ b/pyrogram/methods/chats/pin_chat_message.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/chats/promote_chat_member.py b/pyrogram/methods/chats/promote_chat_member.py
index e45b58764c..8ab5fe29e8 100644
--- a/pyrogram/methods/chats/promote_chat_member.py
+++ b/pyrogram/methods/chats/promote_chat_member.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/chats/restrict_chat_member.py b/pyrogram/methods/chats/restrict_chat_member.py
index d0cc933831..7c74d68536 100644
--- a/pyrogram/methods/chats/restrict_chat_member.py
+++ b/pyrogram/methods/chats/restrict_chat_member.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/chats/set_administrator_title.py b/pyrogram/methods/chats/set_administrator_title.py
index e0a32bdf0a..382d1faccc 100644
--- a/pyrogram/methods/chats/set_administrator_title.py
+++ b/pyrogram/methods/chats/set_administrator_title.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/chats/set_chat_description.py b/pyrogram/methods/chats/set_chat_description.py
index 0a7bdf7f87..6d2575f207 100644
--- a/pyrogram/methods/chats/set_chat_description.py
+++ b/pyrogram/methods/chats/set_chat_description.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/chats/set_chat_permissions.py b/pyrogram/methods/chats/set_chat_permissions.py
index fe208818b1..2c59190657 100644
--- a/pyrogram/methods/chats/set_chat_permissions.py
+++ b/pyrogram/methods/chats/set_chat_permissions.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/chats/set_chat_photo.py b/pyrogram/methods/chats/set_chat_photo.py
index e111449040..266253d632 100644
--- a/pyrogram/methods/chats/set_chat_photo.py
+++ b/pyrogram/methods/chats/set_chat_photo.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/chats/set_chat_protected_content.py b/pyrogram/methods/chats/set_chat_protected_content.py
index 8057873da3..d63e381d98 100644
--- a/pyrogram/methods/chats/set_chat_protected_content.py
+++ b/pyrogram/methods/chats/set_chat_protected_content.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/chats/set_chat_title.py b/pyrogram/methods/chats/set_chat_title.py
index 0cc4c6d2e4..62649f3850 100644
--- a/pyrogram/methods/chats/set_chat_title.py
+++ b/pyrogram/methods/chats/set_chat_title.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/chats/set_send_as_chat.py b/pyrogram/methods/chats/set_send_as_chat.py
index ccf13952dc..3fc686bbcf 100644
--- a/pyrogram/methods/chats/set_send_as_chat.py
+++ b/pyrogram/methods/chats/set_send_as_chat.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/chats/set_slow_mode.py b/pyrogram/methods/chats/set_slow_mode.py
index 084e775c14..7e6739bacb 100644
--- a/pyrogram/methods/chats/set_slow_mode.py
+++ b/pyrogram/methods/chats/set_slow_mode.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/chats/unarchive_chats.py b/pyrogram/methods/chats/unarchive_chats.py
index e49c7afdb3..32867798ce 100644
--- a/pyrogram/methods/chats/unarchive_chats.py
+++ b/pyrogram/methods/chats/unarchive_chats.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/chats/unban_chat_member.py b/pyrogram/methods/chats/unban_chat_member.py
index 9f81f9c7ce..c7be7b58f6 100644
--- a/pyrogram/methods/chats/unban_chat_member.py
+++ b/pyrogram/methods/chats/unban_chat_member.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/chats/unpin_all_chat_messages.py b/pyrogram/methods/chats/unpin_all_chat_messages.py
index 2996f511de..3028120ae2 100644
--- a/pyrogram/methods/chats/unpin_all_chat_messages.py
+++ b/pyrogram/methods/chats/unpin_all_chat_messages.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/chats/unpin_chat_message.py b/pyrogram/methods/chats/unpin_chat_message.py
index fe4b71a244..83654452cc 100644
--- a/pyrogram/methods/chats/unpin_chat_message.py
+++ b/pyrogram/methods/chats/unpin_chat_message.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/chats/update_chat_username.py b/pyrogram/methods/chats/update_chat_username.py
index 297d83a24c..a5bf9958d3 100644
--- a/pyrogram/methods/chats/update_chat_username.py
+++ b/pyrogram/methods/chats/update_chat_username.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/contacts/__init__.py b/pyrogram/methods/contacts/__init__.py
index b542b665ea..5849ce4389 100644
--- a/pyrogram/methods/contacts/__init__.py
+++ b/pyrogram/methods/contacts/__init__.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/contacts/add_contact.py b/pyrogram/methods/contacts/add_contact.py
index 79b85b9c54..cceb273d37 100644
--- a/pyrogram/methods/contacts/add_contact.py
+++ b/pyrogram/methods/contacts/add_contact.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/contacts/delete_contacts.py b/pyrogram/methods/contacts/delete_contacts.py
index 6b73fa7434..273407475a 100644
--- a/pyrogram/methods/contacts/delete_contacts.py
+++ b/pyrogram/methods/contacts/delete_contacts.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/contacts/get_contacts.py b/pyrogram/methods/contacts/get_contacts.py
index 9b546a16f2..98ed11502d 100644
--- a/pyrogram/methods/contacts/get_contacts.py
+++ b/pyrogram/methods/contacts/get_contacts.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/contacts/get_contacts_count.py b/pyrogram/methods/contacts/get_contacts_count.py
index f58fb96c63..b29fed7ac1 100644
--- a/pyrogram/methods/contacts/get_contacts_count.py
+++ b/pyrogram/methods/contacts/get_contacts_count.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/contacts/import_contacts.py b/pyrogram/methods/contacts/import_contacts.py
index 9ba190f332..b7df855439 100644
--- a/pyrogram/methods/contacts/import_contacts.py
+++ b/pyrogram/methods/contacts/import_contacts.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/decorators/__init__.py b/pyrogram/methods/decorators/__init__.py
index 384560fe3e..da0ed9ab13 100644
--- a/pyrogram/methods/decorators/__init__.py
+++ b/pyrogram/methods/decorators/__init__.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/decorators/on_callback_query.py b/pyrogram/methods/decorators/on_callback_query.py
index 884fbb95f3..fb0b716f60 100644
--- a/pyrogram/methods/decorators/on_callback_query.py
+++ b/pyrogram/methods/decorators/on_callback_query.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/decorators/on_chat_join_request.py b/pyrogram/methods/decorators/on_chat_join_request.py
index 93d48c1059..0629901814 100644
--- a/pyrogram/methods/decorators/on_chat_join_request.py
+++ b/pyrogram/methods/decorators/on_chat_join_request.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/decorators/on_chat_member_updated.py b/pyrogram/methods/decorators/on_chat_member_updated.py
index 080daa1fc2..9c10debfdd 100644
--- a/pyrogram/methods/decorators/on_chat_member_updated.py
+++ b/pyrogram/methods/decorators/on_chat_member_updated.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/decorators/on_chosen_inline_result.py b/pyrogram/methods/decorators/on_chosen_inline_result.py
index 4e972a06a3..a2775c9bea 100644
--- a/pyrogram/methods/decorators/on_chosen_inline_result.py
+++ b/pyrogram/methods/decorators/on_chosen_inline_result.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/decorators/on_deleted_messages.py b/pyrogram/methods/decorators/on_deleted_messages.py
index d093310c78..3bf88b088a 100644
--- a/pyrogram/methods/decorators/on_deleted_messages.py
+++ b/pyrogram/methods/decorators/on_deleted_messages.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/decorators/on_disconnect.py b/pyrogram/methods/decorators/on_disconnect.py
index b6dd821c32..5e4f75014b 100644
--- a/pyrogram/methods/decorators/on_disconnect.py
+++ b/pyrogram/methods/decorators/on_disconnect.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/decorators/on_inline_query.py b/pyrogram/methods/decorators/on_inline_query.py
index 60463f66d8..4910670e33 100644
--- a/pyrogram/methods/decorators/on_inline_query.py
+++ b/pyrogram/methods/decorators/on_inline_query.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/decorators/on_message.py b/pyrogram/methods/decorators/on_message.py
index ec23084f7d..634ca2e335 100644
--- a/pyrogram/methods/decorators/on_message.py
+++ b/pyrogram/methods/decorators/on_message.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/decorators/on_poll.py b/pyrogram/methods/decorators/on_poll.py
index fc49b1925a..2694040372 100644
--- a/pyrogram/methods/decorators/on_poll.py
+++ b/pyrogram/methods/decorators/on_poll.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/decorators/on_raw_update.py b/pyrogram/methods/decorators/on_raw_update.py
index 0ce6bf5df9..f61b156b90 100644
--- a/pyrogram/methods/decorators/on_raw_update.py
+++ b/pyrogram/methods/decorators/on_raw_update.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/decorators/on_user_status.py b/pyrogram/methods/decorators/on_user_status.py
index fda3fb800c..38760c2f4e 100644
--- a/pyrogram/methods/decorators/on_user_status.py
+++ b/pyrogram/methods/decorators/on_user_status.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/invite_links/__init__.py b/pyrogram/methods/invite_links/__init__.py
index ccdad31297..c2183d9b55 100644
--- a/pyrogram/methods/invite_links/__init__.py
+++ b/pyrogram/methods/invite_links/__init__.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/invite_links/approve_chat_join_request.py b/pyrogram/methods/invite_links/approve_chat_join_request.py
index 013b64feaf..0bb2f604ca 100644
--- a/pyrogram/methods/invite_links/approve_chat_join_request.py
+++ b/pyrogram/methods/invite_links/approve_chat_join_request.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/invite_links/create_chat_invite_link.py b/pyrogram/methods/invite_links/create_chat_invite_link.py
index b90b9b1524..4e6aded0b3 100644
--- a/pyrogram/methods/invite_links/create_chat_invite_link.py
+++ b/pyrogram/methods/invite_links/create_chat_invite_link.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/invite_links/decline_chat_join_request.py b/pyrogram/methods/invite_links/decline_chat_join_request.py
index 5a0f942c1c..3fc01e2690 100644
--- a/pyrogram/methods/invite_links/decline_chat_join_request.py
+++ b/pyrogram/methods/invite_links/decline_chat_join_request.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/invite_links/delete_chat_admin_invite_links.py b/pyrogram/methods/invite_links/delete_chat_admin_invite_links.py
index 94a3740890..fda2aadbcb 100644
--- a/pyrogram/methods/invite_links/delete_chat_admin_invite_links.py
+++ b/pyrogram/methods/invite_links/delete_chat_admin_invite_links.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/invite_links/delete_chat_invite_link.py b/pyrogram/methods/invite_links/delete_chat_invite_link.py
index 987cc78129..eeab4e3537 100644
--- a/pyrogram/methods/invite_links/delete_chat_invite_link.py
+++ b/pyrogram/methods/invite_links/delete_chat_invite_link.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/invite_links/edit_chat_invite_link.py b/pyrogram/methods/invite_links/edit_chat_invite_link.py
index ea4be32f55..5c9dc9ff74 100644
--- a/pyrogram/methods/invite_links/edit_chat_invite_link.py
+++ b/pyrogram/methods/invite_links/edit_chat_invite_link.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/invite_links/export_chat_invite_link.py b/pyrogram/methods/invite_links/export_chat_invite_link.py
index ee59750cbb..061ccdf349 100644
--- a/pyrogram/methods/invite_links/export_chat_invite_link.py
+++ b/pyrogram/methods/invite_links/export_chat_invite_link.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/invite_links/get_chat_admin_invite_links.py b/pyrogram/methods/invite_links/get_chat_admin_invite_links.py
index ade3a84be2..8687795d70 100644
--- a/pyrogram/methods/invite_links/get_chat_admin_invite_links.py
+++ b/pyrogram/methods/invite_links/get_chat_admin_invite_links.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/invite_links/get_chat_admin_invite_links_count.py b/pyrogram/methods/invite_links/get_chat_admin_invite_links_count.py
index 5c99405a53..789f551a2f 100644
--- a/pyrogram/methods/invite_links/get_chat_admin_invite_links_count.py
+++ b/pyrogram/methods/invite_links/get_chat_admin_invite_links_count.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/invite_links/get_chat_admins_with_invite_links.py b/pyrogram/methods/invite_links/get_chat_admins_with_invite_links.py
index 1f2cc1c197..8b048a58b5 100644
--- a/pyrogram/methods/invite_links/get_chat_admins_with_invite_links.py
+++ b/pyrogram/methods/invite_links/get_chat_admins_with_invite_links.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/invite_links/get_chat_invite_link.py b/pyrogram/methods/invite_links/get_chat_invite_link.py
index c5e0339901..a5361bab6f 100644
--- a/pyrogram/methods/invite_links/get_chat_invite_link.py
+++ b/pyrogram/methods/invite_links/get_chat_invite_link.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/invite_links/get_chat_invite_link_members.py b/pyrogram/methods/invite_links/get_chat_invite_link_members.py
index e48422ea30..5d6e920891 100644
--- a/pyrogram/methods/invite_links/get_chat_invite_link_members.py
+++ b/pyrogram/methods/invite_links/get_chat_invite_link_members.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/invite_links/get_chat_invite_link_members_count.py b/pyrogram/methods/invite_links/get_chat_invite_link_members_count.py
index bd6a2fcc81..a4f5bbb710 100644
--- a/pyrogram/methods/invite_links/get_chat_invite_link_members_count.py
+++ b/pyrogram/methods/invite_links/get_chat_invite_link_members_count.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/invite_links/revoke_chat_invite_link.py b/pyrogram/methods/invite_links/revoke_chat_invite_link.py
index b49a51dd4c..cb3c39cdc4 100644
--- a/pyrogram/methods/invite_links/revoke_chat_invite_link.py
+++ b/pyrogram/methods/invite_links/revoke_chat_invite_link.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/messages/__init__.py b/pyrogram/methods/messages/__init__.py
index 84cc143db9..6b78eeb8ce 100644
--- a/pyrogram/methods/messages/__init__.py
+++ b/pyrogram/methods/messages/__init__.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/messages/copy_media_group.py b/pyrogram/methods/messages/copy_media_group.py
index 4866d82457..72d8d04bf3 100644
--- a/pyrogram/methods/messages/copy_media_group.py
+++ b/pyrogram/methods/messages/copy_media_group.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/messages/copy_message.py b/pyrogram/methods/messages/copy_message.py
index 5e8869496d..1295edef44 100644
--- a/pyrogram/methods/messages/copy_message.py
+++ b/pyrogram/methods/messages/copy_message.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/messages/delete_messages.py b/pyrogram/methods/messages/delete_messages.py
index ba9da8967e..b9807dc53d 100644
--- a/pyrogram/methods/messages/delete_messages.py
+++ b/pyrogram/methods/messages/delete_messages.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/messages/download_media.py b/pyrogram/methods/messages/download_media.py
index be5da0601b..c1472e9558 100644
--- a/pyrogram/methods/messages/download_media.py
+++ b/pyrogram/methods/messages/download_media.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/messages/edit_inline_caption.py b/pyrogram/methods/messages/edit_inline_caption.py
index b291589731..2f009a0586 100644
--- a/pyrogram/methods/messages/edit_inline_caption.py
+++ b/pyrogram/methods/messages/edit_inline_caption.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/messages/edit_inline_media.py b/pyrogram/methods/messages/edit_inline_media.py
index 13b2736649..6b8c834ad4 100644
--- a/pyrogram/methods/messages/edit_inline_media.py
+++ b/pyrogram/methods/messages/edit_inline_media.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/messages/edit_inline_reply_markup.py b/pyrogram/methods/messages/edit_inline_reply_markup.py
index 5966d4b7d2..614da61718 100644
--- a/pyrogram/methods/messages/edit_inline_reply_markup.py
+++ b/pyrogram/methods/messages/edit_inline_reply_markup.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/messages/edit_inline_text.py b/pyrogram/methods/messages/edit_inline_text.py
index e9a79e1407..996dd4f5be 100644
--- a/pyrogram/methods/messages/edit_inline_text.py
+++ b/pyrogram/methods/messages/edit_inline_text.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/messages/edit_message_caption.py b/pyrogram/methods/messages/edit_message_caption.py
index 53e0911c2d..9abdfbe972 100644
--- a/pyrogram/methods/messages/edit_message_caption.py
+++ b/pyrogram/methods/messages/edit_message_caption.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/messages/edit_message_media.py b/pyrogram/methods/messages/edit_message_media.py
index a0d365dcc9..4e443e71c0 100644
--- a/pyrogram/methods/messages/edit_message_media.py
+++ b/pyrogram/methods/messages/edit_message_media.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/messages/edit_message_reply_markup.py b/pyrogram/methods/messages/edit_message_reply_markup.py
index aefb87e9e5..2bbe1bc9b0 100644
--- a/pyrogram/methods/messages/edit_message_reply_markup.py
+++ b/pyrogram/methods/messages/edit_message_reply_markup.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/messages/edit_message_text.py b/pyrogram/methods/messages/edit_message_text.py
index 443d71f6af..5d6ba9cfa9 100644
--- a/pyrogram/methods/messages/edit_message_text.py
+++ b/pyrogram/methods/messages/edit_message_text.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/messages/forward_messages.py b/pyrogram/methods/messages/forward_messages.py
index fc27805980..caa627d8ad 100644
--- a/pyrogram/methods/messages/forward_messages.py
+++ b/pyrogram/methods/messages/forward_messages.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/messages/get_discussion_message.py b/pyrogram/methods/messages/get_discussion_message.py
index cda7ae5212..c7d47261fa 100644
--- a/pyrogram/methods/messages/get_discussion_message.py
+++ b/pyrogram/methods/messages/get_discussion_message.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/messages/get_history.py b/pyrogram/methods/messages/get_history.py
index 98a2f77e6c..d80a6b8b2d 100644
--- a/pyrogram/methods/messages/get_history.py
+++ b/pyrogram/methods/messages/get_history.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/messages/get_history_count.py b/pyrogram/methods/messages/get_history_count.py
index 6552d527f0..9facdbebac 100644
--- a/pyrogram/methods/messages/get_history_count.py
+++ b/pyrogram/methods/messages/get_history_count.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/messages/get_media_group.py b/pyrogram/methods/messages/get_media_group.py
index b429224f56..273f5fffcd 100644
--- a/pyrogram/methods/messages/get_media_group.py
+++ b/pyrogram/methods/messages/get_media_group.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/messages/get_messages.py b/pyrogram/methods/messages/get_messages.py
index cb4d2abbe0..b9c0fbf7ca 100644
--- a/pyrogram/methods/messages/get_messages.py
+++ b/pyrogram/methods/messages/get_messages.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/messages/inline_session.py b/pyrogram/methods/messages/inline_session.py
index 354cb70f69..c4ac50aa89 100644
--- a/pyrogram/methods/messages/inline_session.py
+++ b/pyrogram/methods/messages/inline_session.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/messages/iter_history.py b/pyrogram/methods/messages/iter_history.py
index 9fecc8743e..2e60dfde90 100644
--- a/pyrogram/methods/messages/iter_history.py
+++ b/pyrogram/methods/messages/iter_history.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/messages/read_history.py b/pyrogram/methods/messages/read_history.py
index e7477397da..881b59ad7c 100644
--- a/pyrogram/methods/messages/read_history.py
+++ b/pyrogram/methods/messages/read_history.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/messages/retract_vote.py b/pyrogram/methods/messages/retract_vote.py
index 2f9ec33ced..4baba81157 100644
--- a/pyrogram/methods/messages/retract_vote.py
+++ b/pyrogram/methods/messages/retract_vote.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/messages/search_global.py b/pyrogram/methods/messages/search_global.py
index b016653d75..c3fb7acbac 100644
--- a/pyrogram/methods/messages/search_global.py
+++ b/pyrogram/methods/messages/search_global.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/messages/search_global_count.py b/pyrogram/methods/messages/search_global_count.py
index 113ef81362..78b6965402 100644
--- a/pyrogram/methods/messages/search_global_count.py
+++ b/pyrogram/methods/messages/search_global_count.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/messages/search_messages.py b/pyrogram/methods/messages/search_messages.py
index b62d4cfe47..91bd6a5afc 100644
--- a/pyrogram/methods/messages/search_messages.py
+++ b/pyrogram/methods/messages/search_messages.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/messages/search_messages_count.py b/pyrogram/methods/messages/search_messages_count.py
index 02e0c80366..3004eb39ce 100644
--- a/pyrogram/methods/messages/search_messages_count.py
+++ b/pyrogram/methods/messages/search_messages_count.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/messages/send_animation.py b/pyrogram/methods/messages/send_animation.py
index 3a95a95065..01856a79ef 100644
--- a/pyrogram/methods/messages/send_animation.py
+++ b/pyrogram/methods/messages/send_animation.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/messages/send_audio.py b/pyrogram/methods/messages/send_audio.py
index 6ca16fe161..4ce02c31a1 100644
--- a/pyrogram/methods/messages/send_audio.py
+++ b/pyrogram/methods/messages/send_audio.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/messages/send_cached_media.py b/pyrogram/methods/messages/send_cached_media.py
index 4e7b81495b..885824b4de 100644
--- a/pyrogram/methods/messages/send_cached_media.py
+++ b/pyrogram/methods/messages/send_cached_media.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/messages/send_chat_action.py b/pyrogram/methods/messages/send_chat_action.py
index f618670d7e..fc79c01553 100644
--- a/pyrogram/methods/messages/send_chat_action.py
+++ b/pyrogram/methods/messages/send_chat_action.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/messages/send_contact.py b/pyrogram/methods/messages/send_contact.py
index 5ce3b01399..f396577797 100644
--- a/pyrogram/methods/messages/send_contact.py
+++ b/pyrogram/methods/messages/send_contact.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/messages/send_dice.py b/pyrogram/methods/messages/send_dice.py
index 5d89b03cba..dfc9d5b895 100644
--- a/pyrogram/methods/messages/send_dice.py
+++ b/pyrogram/methods/messages/send_dice.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/messages/send_document.py b/pyrogram/methods/messages/send_document.py
index 82be26d1d0..bc407fe2b3 100644
--- a/pyrogram/methods/messages/send_document.py
+++ b/pyrogram/methods/messages/send_document.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/messages/send_location.py b/pyrogram/methods/messages/send_location.py
index 1bc3b0326c..d2b6616dae 100644
--- a/pyrogram/methods/messages/send_location.py
+++ b/pyrogram/methods/messages/send_location.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/messages/send_media_group.py b/pyrogram/methods/messages/send_media_group.py
index 9f4c5e54e1..13d4d62a8a 100644
--- a/pyrogram/methods/messages/send_media_group.py
+++ b/pyrogram/methods/messages/send_media_group.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/messages/send_message.py b/pyrogram/methods/messages/send_message.py
index 5277ec1fac..c0653a38d4 100644
--- a/pyrogram/methods/messages/send_message.py
+++ b/pyrogram/methods/messages/send_message.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/messages/send_photo.py b/pyrogram/methods/messages/send_photo.py
index ff69558974..c70be84e2f 100644
--- a/pyrogram/methods/messages/send_photo.py
+++ b/pyrogram/methods/messages/send_photo.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/messages/send_poll.py b/pyrogram/methods/messages/send_poll.py
index 6fbb0aab95..a412bcdff0 100644
--- a/pyrogram/methods/messages/send_poll.py
+++ b/pyrogram/methods/messages/send_poll.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/messages/send_reaction.py b/pyrogram/methods/messages/send_reaction.py
index a65cb5bcee..9e6c353cd1 100644
--- a/pyrogram/methods/messages/send_reaction.py
+++ b/pyrogram/methods/messages/send_reaction.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/messages/send_sticker.py b/pyrogram/methods/messages/send_sticker.py
index 4aa3ce288a..641cc6359a 100644
--- a/pyrogram/methods/messages/send_sticker.py
+++ b/pyrogram/methods/messages/send_sticker.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/messages/send_venue.py b/pyrogram/methods/messages/send_venue.py
index 557aa673b4..7941485ebc 100644
--- a/pyrogram/methods/messages/send_venue.py
+++ b/pyrogram/methods/messages/send_venue.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/messages/send_video.py b/pyrogram/methods/messages/send_video.py
index 8eff5d9777..cccc07ab98 100644
--- a/pyrogram/methods/messages/send_video.py
+++ b/pyrogram/methods/messages/send_video.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/messages/send_video_note.py b/pyrogram/methods/messages/send_video_note.py
index 231e149820..1e52f3ab14 100644
--- a/pyrogram/methods/messages/send_video_note.py
+++ b/pyrogram/methods/messages/send_video_note.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/messages/send_voice.py b/pyrogram/methods/messages/send_voice.py
index 3782d41be8..80eb386d2c 100644
--- a/pyrogram/methods/messages/send_voice.py
+++ b/pyrogram/methods/messages/send_voice.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/messages/stop_poll.py b/pyrogram/methods/messages/stop_poll.py
index 8853c5d6b9..113f1618aa 100644
--- a/pyrogram/methods/messages/stop_poll.py
+++ b/pyrogram/methods/messages/stop_poll.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/messages/vote_poll.py b/pyrogram/methods/messages/vote_poll.py
index 0c18bc215a..d6753a275a 100644
--- a/pyrogram/methods/messages/vote_poll.py
+++ b/pyrogram/methods/messages/vote_poll.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/password/__init__.py b/pyrogram/methods/password/__init__.py
index 30f3950c78..e8d42926a4 100644
--- a/pyrogram/methods/password/__init__.py
+++ b/pyrogram/methods/password/__init__.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/password/change_cloud_password.py b/pyrogram/methods/password/change_cloud_password.py
index 1f1f4e0364..f950c65a70 100644
--- a/pyrogram/methods/password/change_cloud_password.py
+++ b/pyrogram/methods/password/change_cloud_password.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/password/enable_cloud_password.py b/pyrogram/methods/password/enable_cloud_password.py
index ad12cdc440..7073af59c2 100644
--- a/pyrogram/methods/password/enable_cloud_password.py
+++ b/pyrogram/methods/password/enable_cloud_password.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/password/remove_cloud_password.py b/pyrogram/methods/password/remove_cloud_password.py
index 3d792835eb..18ae31e5c1 100644
--- a/pyrogram/methods/password/remove_cloud_password.py
+++ b/pyrogram/methods/password/remove_cloud_password.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/users/__init__.py b/pyrogram/methods/users/__init__.py
index d9e61e694c..e8005b83ea 100644
--- a/pyrogram/methods/users/__init__.py
+++ b/pyrogram/methods/users/__init__.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/users/block_user.py b/pyrogram/methods/users/block_user.py
index 6d7c8cdadb..0efda2b9a8 100644
--- a/pyrogram/methods/users/block_user.py
+++ b/pyrogram/methods/users/block_user.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/users/delete_profile_photos.py b/pyrogram/methods/users/delete_profile_photos.py
index 7f2c3a729e..64f3ce7763 100644
--- a/pyrogram/methods/users/delete_profile_photos.py
+++ b/pyrogram/methods/users/delete_profile_photos.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/users/get_common_chats.py b/pyrogram/methods/users/get_common_chats.py
index 6674d229b9..7969647a0c 100644
--- a/pyrogram/methods/users/get_common_chats.py
+++ b/pyrogram/methods/users/get_common_chats.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/users/get_me.py b/pyrogram/methods/users/get_me.py
index 3fab57e34d..b7ebf2d745 100644
--- a/pyrogram/methods/users/get_me.py
+++ b/pyrogram/methods/users/get_me.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/users/get_profile_photos.py b/pyrogram/methods/users/get_profile_photos.py
index fb6f93ca31..fb8c75c497 100644
--- a/pyrogram/methods/users/get_profile_photos.py
+++ b/pyrogram/methods/users/get_profile_photos.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/users/get_profile_photos_count.py b/pyrogram/methods/users/get_profile_photos_count.py
index d27c9da598..dcb7ee4b4c 100644
--- a/pyrogram/methods/users/get_profile_photos_count.py
+++ b/pyrogram/methods/users/get_profile_photos_count.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/users/get_users.py b/pyrogram/methods/users/get_users.py
index 3758eaea82..43ae008423 100644
--- a/pyrogram/methods/users/get_users.py
+++ b/pyrogram/methods/users/get_users.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/users/iter_profile_photos.py b/pyrogram/methods/users/iter_profile_photos.py
index 3ebd7f5020..eda77a8889 100644
--- a/pyrogram/methods/users/iter_profile_photos.py
+++ b/pyrogram/methods/users/iter_profile_photos.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/users/set_profile_photo.py b/pyrogram/methods/users/set_profile_photo.py
index 822500edc5..9b1c5c04c5 100644
--- a/pyrogram/methods/users/set_profile_photo.py
+++ b/pyrogram/methods/users/set_profile_photo.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/users/unblock_user.py b/pyrogram/methods/users/unblock_user.py
index 2492aeba72..4218bb1e55 100644
--- a/pyrogram/methods/users/unblock_user.py
+++ b/pyrogram/methods/users/unblock_user.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/users/update_profile.py b/pyrogram/methods/users/update_profile.py
index a505c70877..4dc81204e4 100644
--- a/pyrogram/methods/users/update_profile.py
+++ b/pyrogram/methods/users/update_profile.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/users/update_username.py b/pyrogram/methods/users/update_username.py
index 865f3ed9eb..ce61402a7f 100644
--- a/pyrogram/methods/users/update_username.py
+++ b/pyrogram/methods/users/update_username.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/utilities/__init__.py b/pyrogram/methods/utilities/__init__.py
index 40034e6f0d..80a5f7419d 100644
--- a/pyrogram/methods/utilities/__init__.py
+++ b/pyrogram/methods/utilities/__init__.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/utilities/add_handler.py b/pyrogram/methods/utilities/add_handler.py
index 466e643139..d0ef15ff64 100644
--- a/pyrogram/methods/utilities/add_handler.py
+++ b/pyrogram/methods/utilities/add_handler.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/utilities/export_session_string.py b/pyrogram/methods/utilities/export_session_string.py
index 545520a26e..cd1741bdb4 100644
--- a/pyrogram/methods/utilities/export_session_string.py
+++ b/pyrogram/methods/utilities/export_session_string.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/utilities/idle.py b/pyrogram/methods/utilities/idle.py
index 0b3c9a1eff..c708d6f1fb 100644
--- a/pyrogram/methods/utilities/idle.py
+++ b/pyrogram/methods/utilities/idle.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/utilities/remove_handler.py b/pyrogram/methods/utilities/remove_handler.py
index 8447f8a9db..12be00b481 100644
--- a/pyrogram/methods/utilities/remove_handler.py
+++ b/pyrogram/methods/utilities/remove_handler.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/utilities/restart.py b/pyrogram/methods/utilities/restart.py
index 9c9e510935..e750137fa4 100644
--- a/pyrogram/methods/utilities/restart.py
+++ b/pyrogram/methods/utilities/restart.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/utilities/run.py b/pyrogram/methods/utilities/run.py
index 53f66f44ba..40decbbd82 100644
--- a/pyrogram/methods/utilities/run.py
+++ b/pyrogram/methods/utilities/run.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/utilities/start.py b/pyrogram/methods/utilities/start.py
index 62e8acfc5d..a8144d3ff5 100644
--- a/pyrogram/methods/utilities/start.py
+++ b/pyrogram/methods/utilities/start.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/utilities/stop.py b/pyrogram/methods/utilities/stop.py
index 4f338d3a2b..3536caa1e2 100644
--- a/pyrogram/methods/utilities/stop.py
+++ b/pyrogram/methods/utilities/stop.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/methods/utilities/stop_transmission.py b/pyrogram/methods/utilities/stop_transmission.py
index 6f76f11f47..70bd58d450 100644
--- a/pyrogram/methods/utilities/stop_transmission.py
+++ b/pyrogram/methods/utilities/stop_transmission.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/mime_types.py b/pyrogram/mime_types.py
index 819e07738e..2f6c86aa8b 100644
--- a/pyrogram/mime_types.py
+++ b/pyrogram/mime_types.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/parser/__init__.py b/pyrogram/parser/__init__.py
index c1c03d59d7..00c7acae76 100644
--- a/pyrogram/parser/__init__.py
+++ b/pyrogram/parser/__init__.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/parser/html.py b/pyrogram/parser/html.py
index 805ca02153..c169dd1a06 100644
--- a/pyrogram/parser/html.py
+++ b/pyrogram/parser/html.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/parser/markdown.py b/pyrogram/parser/markdown.py
index 5d14f8e8cb..898ac3d6a9 100644
--- a/pyrogram/parser/markdown.py
+++ b/pyrogram/parser/markdown.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/parser/parser.py b/pyrogram/parser/parser.py
index 24663ab6df..2ea9e4f284 100644
--- a/pyrogram/parser/parser.py
+++ b/pyrogram/parser/parser.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/parser/utils.py b/pyrogram/parser/utils.py
index db0fc1756d..0d60402894 100644
--- a/pyrogram/parser/utils.py
+++ b/pyrogram/parser/utils.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/raw/__init__.py b/pyrogram/raw/__init__.py
index deef4038fc..a1435c77c6 100644
--- a/pyrogram/raw/__init__.py
+++ b/pyrogram/raw/__init__.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/raw/core/__init__.py b/pyrogram/raw/core/__init__.py
index 8b284c65d0..95c8123104 100644
--- a/pyrogram/raw/core/__init__.py
+++ b/pyrogram/raw/core/__init__.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/raw/core/future_salt.py b/pyrogram/raw/core/future_salt.py
index 54a1296361..ecef5e2ff3 100644
--- a/pyrogram/raw/core/future_salt.py
+++ b/pyrogram/raw/core/future_salt.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/raw/core/future_salts.py b/pyrogram/raw/core/future_salts.py
index 9fa2f8e97e..b0ed334356 100644
--- a/pyrogram/raw/core/future_salts.py
+++ b/pyrogram/raw/core/future_salts.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/raw/core/gzip_packed.py b/pyrogram/raw/core/gzip_packed.py
index 8525338ac8..685594d87a 100644
--- a/pyrogram/raw/core/gzip_packed.py
+++ b/pyrogram/raw/core/gzip_packed.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/raw/core/list.py b/pyrogram/raw/core/list.py
index 74d39c03bf..780fd92c3e 100644
--- a/pyrogram/raw/core/list.py
+++ b/pyrogram/raw/core/list.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/raw/core/message.py b/pyrogram/raw/core/message.py
index 6d50ecf318..1357cf8690 100644
--- a/pyrogram/raw/core/message.py
+++ b/pyrogram/raw/core/message.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/raw/core/msg_container.py b/pyrogram/raw/core/msg_container.py
index ddebce0c38..454a150741 100644
--- a/pyrogram/raw/core/msg_container.py
+++ b/pyrogram/raw/core/msg_container.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/raw/core/primitives/__init__.py b/pyrogram/raw/core/primitives/__init__.py
index 575b36966e..88f2fa5363 100644
--- a/pyrogram/raw/core/primitives/__init__.py
+++ b/pyrogram/raw/core/primitives/__init__.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/raw/core/primitives/bool.py b/pyrogram/raw/core/primitives/bool.py
index 480a7224ae..02cc12d131 100644
--- a/pyrogram/raw/core/primitives/bool.py
+++ b/pyrogram/raw/core/primitives/bool.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/raw/core/primitives/bytes.py b/pyrogram/raw/core/primitives/bytes.py
index 47f914e08b..eace1feb99 100644
--- a/pyrogram/raw/core/primitives/bytes.py
+++ b/pyrogram/raw/core/primitives/bytes.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/raw/core/primitives/double.py b/pyrogram/raw/core/primitives/double.py
index 232f35c9b7..bb7878bf1e 100644
--- a/pyrogram/raw/core/primitives/double.py
+++ b/pyrogram/raw/core/primitives/double.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/raw/core/primitives/int.py b/pyrogram/raw/core/primitives/int.py
index aed653ad76..5d5ec177d1 100644
--- a/pyrogram/raw/core/primitives/int.py
+++ b/pyrogram/raw/core/primitives/int.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/raw/core/primitives/string.py b/pyrogram/raw/core/primitives/string.py
index c5d19205d6..66f992717b 100644
--- a/pyrogram/raw/core/primitives/string.py
+++ b/pyrogram/raw/core/primitives/string.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/raw/core/primitives/vector.py b/pyrogram/raw/core/primitives/vector.py
index a150fba76d..c6c6e8e5d4 100644
--- a/pyrogram/raw/core/primitives/vector.py
+++ b/pyrogram/raw/core/primitives/vector.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/raw/core/tl_object.py b/pyrogram/raw/core/tl_object.py
index f0dd596a3f..b094f7d035 100644
--- a/pyrogram/raw/core/tl_object.py
+++ b/pyrogram/raw/core/tl_object.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/scaffold.py b/pyrogram/scaffold.py
index 2bd2308690..d68ddf787b 100644
--- a/pyrogram/scaffold.py
+++ b/pyrogram/scaffold.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/session/__init__.py b/pyrogram/session/__init__.py
index 96e06e0f58..b414049b2b 100644
--- a/pyrogram/session/__init__.py
+++ b/pyrogram/session/__init__.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/session/auth.py b/pyrogram/session/auth.py
index 6b1ad9530d..d4083b2179 100644
--- a/pyrogram/session/auth.py
+++ b/pyrogram/session/auth.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/session/internals/__init__.py b/pyrogram/session/internals/__init__.py
index 7f8ef15b82..31dcc80f12 100644
--- a/pyrogram/session/internals/__init__.py
+++ b/pyrogram/session/internals/__init__.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/session/internals/data_center.py b/pyrogram/session/internals/data_center.py
index 3c9e95675c..4fce19aa24 100644
--- a/pyrogram/session/internals/data_center.py
+++ b/pyrogram/session/internals/data_center.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/session/internals/msg_factory.py b/pyrogram/session/internals/msg_factory.py
index 2adb22b34a..837f17d0e4 100644
--- a/pyrogram/session/internals/msg_factory.py
+++ b/pyrogram/session/internals/msg_factory.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/session/internals/msg_id.py b/pyrogram/session/internals/msg_id.py
index 1af7d42f0a..871a10b6c8 100644
--- a/pyrogram/session/internals/msg_id.py
+++ b/pyrogram/session/internals/msg_id.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/session/internals/seq_no.py b/pyrogram/session/internals/seq_no.py
index 0725809c97..0abc4a2f72 100644
--- a/pyrogram/session/internals/seq_no.py
+++ b/pyrogram/session/internals/seq_no.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py
index 8df167a774..751d6e0008 100644
--- a/pyrogram/session/session.py
+++ b/pyrogram/session/session.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/storage/__init__.py b/pyrogram/storage/__init__.py
index a73bd5d4df..2a43309a1b 100644
--- a/pyrogram/storage/__init__.py
+++ b/pyrogram/storage/__init__.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/storage/file_storage.py b/pyrogram/storage/file_storage.py
index 8910a2563b..8ba8910cea 100644
--- a/pyrogram/storage/file_storage.py
+++ b/pyrogram/storage/file_storage.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/storage/memory_storage.py b/pyrogram/storage/memory_storage.py
index 5f097a1c50..1035356dc9 100644
--- a/pyrogram/storage/memory_storage.py
+++ b/pyrogram/storage/memory_storage.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/storage/sqlite_storage.py b/pyrogram/storage/sqlite_storage.py
index 73bbb53408..106896f601 100644
--- a/pyrogram/storage/sqlite_storage.py
+++ b/pyrogram/storage/sqlite_storage.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/storage/storage.py b/pyrogram/storage/storage.py
index 3690cafd73..578dee1755 100644
--- a/pyrogram/storage/storage.py
+++ b/pyrogram/storage/storage.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/sync.py b/pyrogram/sync.py
index d94d490c41..6db937b3a7 100644
--- a/pyrogram/sync.py
+++ b/pyrogram/sync.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/syncer.py b/pyrogram/syncer.py
index dbd8dc6495..3ff1bfc9fd 100644
--- a/pyrogram/syncer.py
+++ b/pyrogram/syncer.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/__init__.py b/pyrogram/types/__init__.py
index bad120f105..e0859bbabd 100644
--- a/pyrogram/types/__init__.py
+++ b/pyrogram/types/__init__.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/authorization/__init__.py b/pyrogram/types/authorization/__init__.py
index d5b491da57..b31ad26a53 100644
--- a/pyrogram/types/authorization/__init__.py
+++ b/pyrogram/types/authorization/__init__.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/authorization/sent_code.py b/pyrogram/types/authorization/sent_code.py
index 1f4399013f..a471445af2 100644
--- a/pyrogram/types/authorization/sent_code.py
+++ b/pyrogram/types/authorization/sent_code.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/authorization/terms_of_service.py b/pyrogram/types/authorization/terms_of_service.py
index db465e643a..7e1376ea1d 100644
--- a/pyrogram/types/authorization/terms_of_service.py
+++ b/pyrogram/types/authorization/terms_of_service.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/bots_and_keyboards/__init__.py b/pyrogram/types/bots_and_keyboards/__init__.py
index fa6aa050f7..ccf84993c7 100644
--- a/pyrogram/types/bots_and_keyboards/__init__.py
+++ b/pyrogram/types/bots_and_keyboards/__init__.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/bots_and_keyboards/bot_command.py b/pyrogram/types/bots_and_keyboards/bot_command.py
index f2db126a53..66412f09cf 100644
--- a/pyrogram/types/bots_and_keyboards/bot_command.py
+++ b/pyrogram/types/bots_and_keyboards/bot_command.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/bots_and_keyboards/callback_game.py b/pyrogram/types/bots_and_keyboards/callback_game.py
index a89cbbd780..3bd89270ff 100644
--- a/pyrogram/types/bots_and_keyboards/callback_game.py
+++ b/pyrogram/types/bots_and_keyboards/callback_game.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/bots_and_keyboards/callback_query.py b/pyrogram/types/bots_and_keyboards/callback_query.py
index 8887594eb7..8cdd424a64 100644
--- a/pyrogram/types/bots_and_keyboards/callback_query.py
+++ b/pyrogram/types/bots_and_keyboards/callback_query.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/bots_and_keyboards/force_reply.py b/pyrogram/types/bots_and_keyboards/force_reply.py
index c52bb76651..025176f2e3 100644
--- a/pyrogram/types/bots_and_keyboards/force_reply.py
+++ b/pyrogram/types/bots_and_keyboards/force_reply.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/bots_and_keyboards/game_high_score.py b/pyrogram/types/bots_and_keyboards/game_high_score.py
index b9a77d332c..7de78ce9e6 100644
--- a/pyrogram/types/bots_and_keyboards/game_high_score.py
+++ b/pyrogram/types/bots_and_keyboards/game_high_score.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/bots_and_keyboards/inline_keyboard_button.py b/pyrogram/types/bots_and_keyboards/inline_keyboard_button.py
index 019999fbc2..9779ec3960 100644
--- a/pyrogram/types/bots_and_keyboards/inline_keyboard_button.py
+++ b/pyrogram/types/bots_and_keyboards/inline_keyboard_button.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/bots_and_keyboards/inline_keyboard_markup.py b/pyrogram/types/bots_and_keyboards/inline_keyboard_markup.py
index 1571f1ba32..e0fd323059 100644
--- a/pyrogram/types/bots_and_keyboards/inline_keyboard_markup.py
+++ b/pyrogram/types/bots_and_keyboards/inline_keyboard_markup.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/bots_and_keyboards/keyboard_button.py b/pyrogram/types/bots_and_keyboards/keyboard_button.py
index 204c696a72..626df749c0 100644
--- a/pyrogram/types/bots_and_keyboards/keyboard_button.py
+++ b/pyrogram/types/bots_and_keyboards/keyboard_button.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/bots_and_keyboards/login_url.py b/pyrogram/types/bots_and_keyboards/login_url.py
index 52ef8dc8ee..a0af5a1af0 100644
--- a/pyrogram/types/bots_and_keyboards/login_url.py
+++ b/pyrogram/types/bots_and_keyboards/login_url.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/bots_and_keyboards/reply_keyboard_markup.py b/pyrogram/types/bots_and_keyboards/reply_keyboard_markup.py
index 21fa6ceb0d..b619216a13 100644
--- a/pyrogram/types/bots_and_keyboards/reply_keyboard_markup.py
+++ b/pyrogram/types/bots_and_keyboards/reply_keyboard_markup.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/bots_and_keyboards/reply_keyboard_remove.py b/pyrogram/types/bots_and_keyboards/reply_keyboard_remove.py
index 9bf16528f9..479efe9027 100644
--- a/pyrogram/types/bots_and_keyboards/reply_keyboard_remove.py
+++ b/pyrogram/types/bots_and_keyboards/reply_keyboard_remove.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/inline_mode/__init__.py b/pyrogram/types/inline_mode/__init__.py
index d22937b929..c1f5b87f87 100644
--- a/pyrogram/types/inline_mode/__init__.py
+++ b/pyrogram/types/inline_mode/__init__.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/inline_mode/chosen_inline_result.py b/pyrogram/types/inline_mode/chosen_inline_result.py
index 578ef64a1b..623d737933 100644
--- a/pyrogram/types/inline_mode/chosen_inline_result.py
+++ b/pyrogram/types/inline_mode/chosen_inline_result.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/inline_mode/inline_query.py b/pyrogram/types/inline_mode/inline_query.py
index 5c718c33c9..2522b6934f 100644
--- a/pyrogram/types/inline_mode/inline_query.py
+++ b/pyrogram/types/inline_mode/inline_query.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/inline_mode/inline_query_result.py b/pyrogram/types/inline_mode/inline_query_result.py
index 919b7df565..48d633c141 100644
--- a/pyrogram/types/inline_mode/inline_query_result.py
+++ b/pyrogram/types/inline_mode/inline_query_result.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/inline_mode/inline_query_result_animation.py b/pyrogram/types/inline_mode/inline_query_result_animation.py
index 45af1ed38a..2281ae1cfd 100644
--- a/pyrogram/types/inline_mode/inline_query_result_animation.py
+++ b/pyrogram/types/inline_mode/inline_query_result_animation.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/inline_mode/inline_query_result_article.py b/pyrogram/types/inline_mode/inline_query_result_article.py
index 0b5e6008b5..73260dd9cc 100644
--- a/pyrogram/types/inline_mode/inline_query_result_article.py
+++ b/pyrogram/types/inline_mode/inline_query_result_article.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/inline_mode/inline_query_result_photo.py b/pyrogram/types/inline_mode/inline_query_result_photo.py
index f86c671898..679ee42947 100644
--- a/pyrogram/types/inline_mode/inline_query_result_photo.py
+++ b/pyrogram/types/inline_mode/inline_query_result_photo.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/inline_mode/inline_query_result_video.py b/pyrogram/types/inline_mode/inline_query_result_video.py
index cf035408df..cd5d845437 100644
--- a/pyrogram/types/inline_mode/inline_query_result_video.py
+++ b/pyrogram/types/inline_mode/inline_query_result_video.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/input_media/__init__.py b/pyrogram/types/input_media/__init__.py
index 655a968c73..a03d1e21f4 100644
--- a/pyrogram/types/input_media/__init__.py
+++ b/pyrogram/types/input_media/__init__.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/input_media/input_media.py b/pyrogram/types/input_media/input_media.py
index 16a72bdadd..8e2f3a7b1a 100644
--- a/pyrogram/types/input_media/input_media.py
+++ b/pyrogram/types/input_media/input_media.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/input_media/input_media_animation.py b/pyrogram/types/input_media/input_media_animation.py
index 9c5767fe78..76c146c510 100644
--- a/pyrogram/types/input_media/input_media_animation.py
+++ b/pyrogram/types/input_media/input_media_animation.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/input_media/input_media_audio.py b/pyrogram/types/input_media/input_media_audio.py
index 3a66b5ee58..4816659d5a 100644
--- a/pyrogram/types/input_media/input_media_audio.py
+++ b/pyrogram/types/input_media/input_media_audio.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/input_media/input_media_document.py b/pyrogram/types/input_media/input_media_document.py
index 3146006048..3b3a38fdf6 100644
--- a/pyrogram/types/input_media/input_media_document.py
+++ b/pyrogram/types/input_media/input_media_document.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/input_media/input_media_photo.py b/pyrogram/types/input_media/input_media_photo.py
index e84a7616c0..f733a7b0bc 100644
--- a/pyrogram/types/input_media/input_media_photo.py
+++ b/pyrogram/types/input_media/input_media_photo.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/input_media/input_media_video.py b/pyrogram/types/input_media/input_media_video.py
index 1199d86265..48c79f411d 100644
--- a/pyrogram/types/input_media/input_media_video.py
+++ b/pyrogram/types/input_media/input_media_video.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/input_media/input_phone_contact.py b/pyrogram/types/input_media/input_phone_contact.py
index e0169a5d81..0608cf2179 100644
--- a/pyrogram/types/input_media/input_phone_contact.py
+++ b/pyrogram/types/input_media/input_phone_contact.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/input_message_content/__init__.py b/pyrogram/types/input_message_content/__init__.py
index 233f53b07a..b445f3ba6a 100644
--- a/pyrogram/types/input_message_content/__init__.py
+++ b/pyrogram/types/input_message_content/__init__.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/input_message_content/input_message_content.py b/pyrogram/types/input_message_content/input_message_content.py
index 99bed67de9..2b46393259 100644
--- a/pyrogram/types/input_message_content/input_message_content.py
+++ b/pyrogram/types/input_message_content/input_message_content.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/input_message_content/input_text_message_content.py b/pyrogram/types/input_message_content/input_text_message_content.py
index a053d818bf..ef54c0b23a 100644
--- a/pyrogram/types/input_message_content/input_text_message_content.py
+++ b/pyrogram/types/input_message_content/input_text_message_content.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/list.py b/pyrogram/types/list.py
index fe91c4c6d0..da87d5275f 100644
--- a/pyrogram/types/list.py
+++ b/pyrogram/types/list.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/messages_and_media/__init__.py b/pyrogram/types/messages_and_media/__init__.py
index 9f31dde066..944d7e6e99 100644
--- a/pyrogram/types/messages_and_media/__init__.py
+++ b/pyrogram/types/messages_and_media/__init__.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/messages_and_media/animation.py b/pyrogram/types/messages_and_media/animation.py
index 96a57469e1..ce901734e6 100644
--- a/pyrogram/types/messages_and_media/animation.py
+++ b/pyrogram/types/messages_and_media/animation.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/messages_and_media/audio.py b/pyrogram/types/messages_and_media/audio.py
index e590d2e367..60b37a59e7 100644
--- a/pyrogram/types/messages_and_media/audio.py
+++ b/pyrogram/types/messages_and_media/audio.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/messages_and_media/contact.py b/pyrogram/types/messages_and_media/contact.py
index fe6f55fd60..cec03329a5 100644
--- a/pyrogram/types/messages_and_media/contact.py
+++ b/pyrogram/types/messages_and_media/contact.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/messages_and_media/dice.py b/pyrogram/types/messages_and_media/dice.py
index aa67470cc4..2c683ec828 100644
--- a/pyrogram/types/messages_and_media/dice.py
+++ b/pyrogram/types/messages_and_media/dice.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/messages_and_media/document.py b/pyrogram/types/messages_and_media/document.py
index 76ebed9b0f..333d38abb7 100644
--- a/pyrogram/types/messages_and_media/document.py
+++ b/pyrogram/types/messages_and_media/document.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/messages_and_media/game.py b/pyrogram/types/messages_and_media/game.py
index 5e76f5b6a3..1452d79eb3 100644
--- a/pyrogram/types/messages_and_media/game.py
+++ b/pyrogram/types/messages_and_media/game.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/messages_and_media/location.py b/pyrogram/types/messages_and_media/location.py
index 6cd0e9f189..664890cb76 100644
--- a/pyrogram/types/messages_and_media/location.py
+++ b/pyrogram/types/messages_and_media/location.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py
index 1d70d773e6..02bf2d3d98 100644
--- a/pyrogram/types/messages_and_media/message.py
+++ b/pyrogram/types/messages_and_media/message.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/messages_and_media/message_entity.py b/pyrogram/types/messages_and_media/message_entity.py
index 864bf54778..2595fc66bc 100644
--- a/pyrogram/types/messages_and_media/message_entity.py
+++ b/pyrogram/types/messages_and_media/message_entity.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/messages_and_media/photo.py b/pyrogram/types/messages_and_media/photo.py
index a512d17b82..fc496cd455 100644
--- a/pyrogram/types/messages_and_media/photo.py
+++ b/pyrogram/types/messages_and_media/photo.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/messages_and_media/poll.py b/pyrogram/types/messages_and_media/poll.py
index 832b81e258..8d46557d65 100644
--- a/pyrogram/types/messages_and_media/poll.py
+++ b/pyrogram/types/messages_and_media/poll.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/messages_and_media/poll_option.py b/pyrogram/types/messages_and_media/poll_option.py
index 25eff66387..a40926b077 100644
--- a/pyrogram/types/messages_and_media/poll_option.py
+++ b/pyrogram/types/messages_and_media/poll_option.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/messages_and_media/reaction.py b/pyrogram/types/messages_and_media/reaction.py
index 9d4baa54fa..83dda5829b 100644
--- a/pyrogram/types/messages_and_media/reaction.py
+++ b/pyrogram/types/messages_and_media/reaction.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/messages_and_media/sticker.py b/pyrogram/types/messages_and_media/sticker.py
index b7e4ecb5f4..be494e3078 100644
--- a/pyrogram/types/messages_and_media/sticker.py
+++ b/pyrogram/types/messages_and_media/sticker.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/messages_and_media/stripped_thumbnail.py b/pyrogram/types/messages_and_media/stripped_thumbnail.py
index 9c832ffb80..e9756607b9 100644
--- a/pyrogram/types/messages_and_media/stripped_thumbnail.py
+++ b/pyrogram/types/messages_and_media/stripped_thumbnail.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/messages_and_media/thumbnail.py b/pyrogram/types/messages_and_media/thumbnail.py
index 1a55c18ce4..b9cb93e127 100644
--- a/pyrogram/types/messages_and_media/thumbnail.py
+++ b/pyrogram/types/messages_and_media/thumbnail.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/messages_and_media/venue.py b/pyrogram/types/messages_and_media/venue.py
index bdf385e504..8a26f60061 100644
--- a/pyrogram/types/messages_and_media/venue.py
+++ b/pyrogram/types/messages_and_media/venue.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/messages_and_media/video.py b/pyrogram/types/messages_and_media/video.py
index b64dcfb6b9..1cdf055fc0 100644
--- a/pyrogram/types/messages_and_media/video.py
+++ b/pyrogram/types/messages_and_media/video.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/messages_and_media/video_note.py b/pyrogram/types/messages_and_media/video_note.py
index 9a5685fc8a..3a9e8a616f 100644
--- a/pyrogram/types/messages_and_media/video_note.py
+++ b/pyrogram/types/messages_and_media/video_note.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/messages_and_media/voice.py b/pyrogram/types/messages_and_media/voice.py
index 640ad8ccf6..4175b7ba1e 100644
--- a/pyrogram/types/messages_and_media/voice.py
+++ b/pyrogram/types/messages_and_media/voice.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/messages_and_media/webpage.py b/pyrogram/types/messages_and_media/webpage.py
index 08432eabfc..34e51d88cc 100644
--- a/pyrogram/types/messages_and_media/webpage.py
+++ b/pyrogram/types/messages_and_media/webpage.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/object.py b/pyrogram/types/object.py
index daeb89af74..015eeca89a 100644
--- a/pyrogram/types/object.py
+++ b/pyrogram/types/object.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/update.py b/pyrogram/types/update.py
index 1937c94b93..d3e45b4abd 100644
--- a/pyrogram/types/update.py
+++ b/pyrogram/types/update.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/user_and_chats/__init__.py b/pyrogram/types/user_and_chats/__init__.py
index 22455486b8..4667628a0e 100644
--- a/pyrogram/types/user_and_chats/__init__.py
+++ b/pyrogram/types/user_and_chats/__init__.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/user_and_chats/chat.py b/pyrogram/types/user_and_chats/chat.py
index 6ccd946524..a9d396eb21 100644
--- a/pyrogram/types/user_and_chats/chat.py
+++ b/pyrogram/types/user_and_chats/chat.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/user_and_chats/chat_admin_with_invite_links.py b/pyrogram/types/user_and_chats/chat_admin_with_invite_links.py
index c7e617e941..385a38da43 100644
--- a/pyrogram/types/user_and_chats/chat_admin_with_invite_links.py
+++ b/pyrogram/types/user_and_chats/chat_admin_with_invite_links.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/user_and_chats/chat_event.py b/pyrogram/types/user_and_chats/chat_event.py
index 3d0bbe19d1..374dac50f9 100644
--- a/pyrogram/types/user_and_chats/chat_event.py
+++ b/pyrogram/types/user_and_chats/chat_event.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/user_and_chats/chat_event_filter.py b/pyrogram/types/user_and_chats/chat_event_filter.py
index edd003eba8..d88300bae5 100644
--- a/pyrogram/types/user_and_chats/chat_event_filter.py
+++ b/pyrogram/types/user_and_chats/chat_event_filter.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/user_and_chats/chat_invite_link.py b/pyrogram/types/user_and_chats/chat_invite_link.py
index 1f8a9256fc..9dddea4835 100644
--- a/pyrogram/types/user_and_chats/chat_invite_link.py
+++ b/pyrogram/types/user_and_chats/chat_invite_link.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/user_and_chats/chat_join_request.py b/pyrogram/types/user_and_chats/chat_join_request.py
index 4ed7c1ed38..a7001da5d9 100644
--- a/pyrogram/types/user_and_chats/chat_join_request.py
+++ b/pyrogram/types/user_and_chats/chat_join_request.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/user_and_chats/chat_member.py b/pyrogram/types/user_and_chats/chat_member.py
index a6f2f4ccb9..ff55081587 100644
--- a/pyrogram/types/user_and_chats/chat_member.py
+++ b/pyrogram/types/user_and_chats/chat_member.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/user_and_chats/chat_member_updated.py b/pyrogram/types/user_and_chats/chat_member_updated.py
index a8d7abcafb..5fa7d84e6c 100644
--- a/pyrogram/types/user_and_chats/chat_member_updated.py
+++ b/pyrogram/types/user_and_chats/chat_member_updated.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/user_and_chats/chat_permissions.py b/pyrogram/types/user_and_chats/chat_permissions.py
index a5bc3011b1..e209625af3 100644
--- a/pyrogram/types/user_and_chats/chat_permissions.py
+++ b/pyrogram/types/user_and_chats/chat_permissions.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/user_and_chats/chat_photo.py b/pyrogram/types/user_and_chats/chat_photo.py
index 959d1732e0..b3aba61dd0 100644
--- a/pyrogram/types/user_and_chats/chat_photo.py
+++ b/pyrogram/types/user_and_chats/chat_photo.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/user_and_chats/chat_preview.py b/pyrogram/types/user_and_chats/chat_preview.py
index f4a8540fda..e251c86581 100644
--- a/pyrogram/types/user_and_chats/chat_preview.py
+++ b/pyrogram/types/user_and_chats/chat_preview.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/user_and_chats/dialog.py b/pyrogram/types/user_and_chats/dialog.py
index 6c4a408a14..7259a89a57 100644
--- a/pyrogram/types/user_and_chats/dialog.py
+++ b/pyrogram/types/user_and_chats/dialog.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/user_and_chats/invite_link_importer.py b/pyrogram/types/user_and_chats/invite_link_importer.py
index 4517ae3c8d..f933cfa2b0 100644
--- a/pyrogram/types/user_and_chats/invite_link_importer.py
+++ b/pyrogram/types/user_and_chats/invite_link_importer.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/user_and_chats/restriction.py b/pyrogram/types/user_and_chats/restriction.py
index e3acd250be..8f7a1b7269 100644
--- a/pyrogram/types/user_and_chats/restriction.py
+++ b/pyrogram/types/user_and_chats/restriction.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/user_and_chats/user.py b/pyrogram/types/user_and_chats/user.py
index cc0c2036ce..8579ba157d 100644
--- a/pyrogram/types/user_and_chats/user.py
+++ b/pyrogram/types/user_and_chats/user.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/user_and_chats/voice_chat_ended.py b/pyrogram/types/user_and_chats/voice_chat_ended.py
index febb813729..b6b05feaf3 100644
--- a/pyrogram/types/user_and_chats/voice_chat_ended.py
+++ b/pyrogram/types/user_and_chats/voice_chat_ended.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/user_and_chats/voice_chat_members_invited.py b/pyrogram/types/user_and_chats/voice_chat_members_invited.py
index 0a093cbf92..0fd4249ba5 100644
--- a/pyrogram/types/user_and_chats/voice_chat_members_invited.py
+++ b/pyrogram/types/user_and_chats/voice_chat_members_invited.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/user_and_chats/voice_chat_scheduled.py b/pyrogram/types/user_and_chats/voice_chat_scheduled.py
index fc7a5f1e5f..82d2125e7f 100644
--- a/pyrogram/types/user_and_chats/voice_chat_scheduled.py
+++ b/pyrogram/types/user_and_chats/voice_chat_scheduled.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/types/user_and_chats/voice_chat_started.py b/pyrogram/types/user_and_chats/voice_chat_started.py
index 6ac546dce9..e260e784fb 100644
--- a/pyrogram/types/user_and_chats/voice_chat_started.py
+++ b/pyrogram/types/user_and_chats/voice_chat_started.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/pyrogram/utils.py b/pyrogram/utils.py
index 9d53d8bfba..6a0c8bac91 100644
--- a/pyrogram/utils.py
+++ b/pyrogram/utils.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/setup.py b/setup.py
index 5cbd5eb208..1a24654ce8 100644
--- a/setup.py
+++ b/setup.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/tests/__init__.py b/tests/__init__.py
index 4ad4f32b47..46887cb7a5 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/tests/filters/__init__.py b/tests/filters/__init__.py
index 56187dd8b2..e93649f521 100644
--- a/tests/filters/__init__.py
+++ b/tests/filters/__init__.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/tests/filters/test_command.py b/tests/filters/test_command.py
index 19e21d9dd5..234ed69fc3 100644
--- a/tests/filters/test_command.py
+++ b/tests/filters/test_command.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #
diff --git a/tests/test_file_id.py b/tests/test_file_id.py
index 493590ab11..96152e48ca 100644
--- a/tests/test_file_id.py
+++ b/tests/test_file_id.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2021 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #

From 44228f247271e0f3f678fe800716a26cc72b8e4b Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Fri, 7 Jan 2022 10:26:55 +0100
Subject: [PATCH 0687/1185] Update Pyrogram to v1.3.0

---
 pyrogram/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index 96ddb664a4..a0d4345815 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -16,7 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-__version__ = "1.2.9"
+__version__ = "1.3.0"
 __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)"
 __copyright__ = "Copyright (C) 2017-present Dan "
 

From cb6cef37e644edd3effa6fca58b79772daeee8b4 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Fri, 7 Jan 2022 12:21:24 +0100
Subject: [PATCH 0688/1185] Update copyright year

---
 pyrogram/crypto/mtproto.py                    | 26 +++++++++----------
 .../inline_mode/inline_query_result_audio.py  |  2 +-
 2 files changed, 14 insertions(+), 14 deletions(-)

diff --git a/pyrogram/crypto/mtproto.py b/pyrogram/crypto/mtproto.py
index 2fc3b9f848..c893e3e538 100644
--- a/pyrogram/crypto/mtproto.py
+++ b/pyrogram/crypto/mtproto.py
@@ -1,20 +1,20 @@
-# Pyrogram - Telegram MTProto API Client Library for Python
-# Copyright (C) 2017-2018 Dan Tès 
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
 #
-# This file is part of Pyrogram.
+#  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 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.
+#  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 .
+#  You should have received a copy of the GNU Lesser General Public License
+#  along with Pyrogram.  If not, see .
 
 import bisect
 from hashlib import sha256
diff --git a/pyrogram/types/inline_mode/inline_query_result_audio.py b/pyrogram/types/inline_mode/inline_query_result_audio.py
index 6ca8c34c19..e36ecb1124 100644
--- a/pyrogram/types/inline_mode/inline_query_result_audio.py
+++ b/pyrogram/types/inline_mode/inline_query_result_audio.py
@@ -1,5 +1,5 @@
 #  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-2020 Dan 
+#  Copyright (C) 2017-present Dan 
 #
 #  This file is part of Pyrogram.
 #

From 2ca38d0ee9cbd7abbdc8ac9e95b624adf3df8eb1 Mon Sep 17 00:00:00 2001
From: Lorenzo Delmonte 
Date: Fri, 7 Jan 2022 12:31:15 +0100
Subject: [PATCH 0689/1185] Fix typo in OpenCollective link (#856)

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index e51ee0eb0b..455908676c 100644
--- a/README.md
+++ b/README.md
@@ -46,7 +46,7 @@ If you'd like to support Pyrogram, you can consider:
 
 - [Become a GitHub sponsor](https://github.com/sponsors/delivrance).
 - [Become a LiberaPay patron](https://liberapay.com/delivrance).
-- [Become an OpenCollective backer](https://opencollective.com/pyrogram>).
+- [Become an OpenCollective backer](https://opencollective.com/pyrogram).
 
 ### Key Features
 

From 1d7c57e6699bc178b8f60eef7009a95fae634741 Mon Sep 17 00:00:00 2001
From: SUBIN <64341611+subinps@users.noreply.github.com>
Date: Fri, 7 Jan 2022 21:35:34 +0530
Subject: [PATCH 0690/1185] Add missing parameter protect_content (#859)

---
 pyrogram/methods/messages/send_cached_media.py | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/pyrogram/methods/messages/send_cached_media.py b/pyrogram/methods/messages/send_cached_media.py
index 885824b4de..84fe9b17ee 100644
--- a/pyrogram/methods/messages/send_cached_media.py
+++ b/pyrogram/methods/messages/send_cached_media.py
@@ -35,6 +35,7 @@ async def send_cached_media(
         disable_notification: bool = None,
         reply_to_message_id: int = None,
         schedule_date: int = None,
+        protect_content: bool = None,
         reply_markup: Union[
             "types.InlineKeyboardMarkup",
             "types.ReplyKeyboardMarkup",
@@ -80,6 +81,9 @@ async def send_cached_media(
 
             schedule_date (``int``, *optional*):
                 Date when the message will be automatically sent. Unix time.
+            
+            protect_content (``bool``, *optional*):
+                Protects the contents of the sent message from forwarding and saving.
 
             reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*):
                 Additional interface options. An object for an inline keyboard, custom reply keyboard,
@@ -102,6 +106,7 @@ async def send_cached_media(
                 reply_to_msg_id=reply_to_message_id,
                 random_id=self.rnd_id(),
                 schedule_date=schedule_date,
+                noforwards=protect_content,
                 reply_markup=await reply_markup.write(self) if reply_markup else None,
                 **await utils.parse_text_entities(self, caption, parse_mode, caption_entities)
             )

From 7fb35fbad51a4b01dc4c3251772e13054713c506 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Fri, 7 Jan 2022 17:06:45 +0100
Subject: [PATCH 0691/1185] Update Pyrogram to v1.3.1

---
 pyrogram/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index a0d4345815..5728696fc8 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -16,7 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-__version__ = "1.3.0"
+__version__ = "1.3.1"
 __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)"
 __copyright__ = "Copyright (C) 2017-present Dan "
 

From fbdc6613f26a378dc7c033a90fc63ca3235556e9 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Mon, 10 Jan 2022 14:31:17 +0100
Subject: [PATCH 0692/1185] Fix can_send_other_messages permission being
 inverted Fixes #868

---
 .../methods/chats/set_chat_permissions.py     | 22 +++++++++----------
 1 file changed, 11 insertions(+), 11 deletions(-)

diff --git a/pyrogram/methods/chats/set_chat_permissions.py b/pyrogram/methods/chats/set_chat_permissions.py
index 2c59190657..2ff2b67831 100644
--- a/pyrogram/methods/chats/set_chat_permissions.py
+++ b/pyrogram/methods/chats/set_chat_permissions.py
@@ -67,17 +67,17 @@ async def set_chat_permissions(
                 peer=await self.resolve_peer(chat_id),
                 banned_rights=raw.types.ChatBannedRights(
                     until_date=0,
-                    send_messages=True if not permissions.can_send_messages else None,
-                    send_media=True if not permissions.can_send_media_messages else None,
-                    send_stickers=permissions.can_send_other_messages,
-                    send_gifs=permissions.can_send_other_messages,
-                    send_games=permissions.can_send_other_messages,
-                    send_inline=permissions.can_send_other_messages,
-                    embed_links=True if not permissions.can_add_web_page_previews else None,
-                    send_polls=True if not permissions.can_send_polls else None,
-                    change_info=True if not permissions.can_change_info else None,
-                    invite_users=True if not permissions.can_invite_users else None,
-                    pin_messages=True if not permissions.can_pin_messages else None,
+                    send_messages=not permissions.can_send_messages,
+                    send_media=not permissions.can_send_media_messages,
+                    send_stickers=not permissions.can_send_other_messages,
+                    send_gifs=not permissions.can_send_other_messages,
+                    send_games=not permissions.can_send_other_messages,
+                    send_inline=not permissions.can_send_other_messages,
+                    embed_links=not permissions.can_add_web_page_previews,
+                    send_polls=not permissions.can_send_polls,
+                    change_info=not permissions.can_change_info,
+                    invite_users=not permissions.can_invite_users,
+                    pin_messages=not permissions.can_pin_messages,
                 )
             )
         )

From c7da4a8495676d8872611623345cbfadc8a26262 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Mon, 10 Jan 2022 14:32:10 +0100
Subject: [PATCH 0693/1185] Update Pyrogram to v1.3.2

---
 pyrogram/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index 5728696fc8..fad89499b9 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -16,7 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-__version__ = "1.3.1"
+__version__ = "1.3.2"
 __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)"
 __copyright__ = "Copyright (C) 2017-present Dan "
 

From de9705f1268cba58845d07415b3d8081d449decd Mon Sep 17 00:00:00 2001
From: Sam <25792361+sam01101@users.noreply.github.com>
Date: Tue, 11 Jan 2022 23:40:37 +0800
Subject: [PATCH 0694/1185] Fix core types and compiler (#871)

- Add missing ID to FutureSalts
- Have vector flags read to None instead of [] for non-existent lists
---
 compiler/api/compiler.py          | 2 +-
 pyrogram/raw/core/future_salts.py | 1 +
 2 files changed, 2 insertions(+), 1 deletion(-)

diff --git a/compiler/api/compiler.py b/compiler/api/compiler.py
index 192d6a6759..c173a93193 100644
--- a/compiler/api/compiler.py
+++ b/compiler/api/compiler.py
@@ -447,7 +447,7 @@ def start(format: bool = False):
                     )
 
                     read_types += "\n        "
-                    read_types += "{} = TLObject.read(b{}) if flags & (1 << {}) else []\n        ".format(
+                    read_types += "{} = TLObject.read(b{}) if flags & (1 << {}) else None\n        ".format(
                         arg_name, f", {sub_type.title()}" if sub_type in CORE_TYPES else "", index
                     )
                 else:
diff --git a/pyrogram/raw/core/future_salts.py b/pyrogram/raw/core/future_salts.py
index b0ed334356..31eb16e277 100644
--- a/pyrogram/raw/core/future_salts.py
+++ b/pyrogram/raw/core/future_salts.py
@@ -48,6 +48,7 @@ def read(data: BytesIO, *args: Any) -> "FutureSalts":
 
     def write(self, *args: Any) -> bytes:
         b = BytesIO()
+        b.write(Int(self.ID, False))
 
         b.write(Long(self.req_msg_id))
         b.write(Int(self.now))

From 10c512d39cad34f453a179b336d04b98b12bc463 Mon Sep 17 00:00:00 2001
From: Danipulok <45077699+Danipulok@users.noreply.github.com>
Date: Tue, 11 Jan 2022 17:42:04 +0200
Subject: [PATCH 0695/1185] Remove unnecessary method call in get_media_group
 (#860)

---
 pyrogram/methods/messages/get_media_group.py | 7 ++-----
 1 file changed, 2 insertions(+), 5 deletions(-)

diff --git a/pyrogram/methods/messages/get_media_group.py b/pyrogram/methods/messages/get_media_group.py
index 273f5fffcd..3b33bfd4a9 100644
--- a/pyrogram/methods/messages/get_media_group.py
+++ b/pyrogram/methods/messages/get_media_group.py
@@ -51,13 +51,10 @@ async def get_media_group(
                 In case target message doesn't belong to a media group.
         """
 
-        # There can be maximum 10 items in a media group. 
-        messages = await self.get_messages(chat_id, [msg_id for msg_id in range(message_id - 9, message_id + 10)],
-                                           replies=0)
-
         if message_id <= 0:
             raise ValueError("Passed message_id is negative or equal to zero.")
 
+        # Get messages with id from `id - 9` to `id + 10` to get all possible media group messages.
         messages = await self.get_messages(
             chat_id=chat_id,
             message_ids=[msg_id for msg_id in range(message_id - 9, message_id + 10)],
@@ -65,7 +62,7 @@ async def get_media_group(
         )
 
         # There can be maximum 10 items in a media group.
-        # The if/else condition to fix the problem of getting correct `media_group_id` when it has `message_id` less then 10.
+        # If/else condition to fix the problem of getting correct `media_group_id` when `message_id` is less than 10.
         media_group_id = messages[9].media_group_id if len(messages) == 19 else messages[message_id - 1].media_group_id
 
         if media_group_id is None:

From 5ec9743a1a8480449f5a38c7ff38d26f652f0201 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Tue, 11 Jan 2022 16:43:24 +0100
Subject: [PATCH 0696/1185] Minor style fix

---
 pyrogram/raw/core/future_salts.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/pyrogram/raw/core/future_salts.py b/pyrogram/raw/core/future_salts.py
index 31eb16e277..88216387f2 100644
--- a/pyrogram/raw/core/future_salts.py
+++ b/pyrogram/raw/core/future_salts.py
@@ -48,6 +48,7 @@ def read(data: BytesIO, *args: Any) -> "FutureSalts":
 
     def write(self, *args: Any) -> bytes:
         b = BytesIO()
+
         b.write(Int(self.ID, False))
 
         b.write(Long(self.req_msg_id))

From 14ae9d314b647e262d37fd057ebc6cfa932afce1 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Tue, 11 Jan 2022 16:44:09 +0100
Subject: [PATCH 0697/1185] Update Pyrogram to v1.3.3

---
 pyrogram/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index fad89499b9..02eef373ef 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -16,7 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-__version__ = "1.3.2"
+__version__ = "1.3.3"
 __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)"
 __copyright__ = "Copyright (C) 2017-present Dan "
 

From 527ec23c20164736203eb5a7e3109368c9629aed Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Tue, 11 Jan 2022 16:58:07 +0100
Subject: [PATCH 0698/1185] Revert reading non-existent flag vectors from None
 to []

---
 compiler/api/compiler.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/compiler/api/compiler.py b/compiler/api/compiler.py
index c173a93193..192d6a6759 100644
--- a/compiler/api/compiler.py
+++ b/compiler/api/compiler.py
@@ -447,7 +447,7 @@ def start(format: bool = False):
                     )
 
                     read_types += "\n        "
-                    read_types += "{} = TLObject.read(b{}) if flags & (1 << {}) else None\n        ".format(
+                    read_types += "{} = TLObject.read(b{}) if flags & (1 << {}) else []\n        ".format(
                         arg_name, f", {sub_type.title()}" if sub_type in CORE_TYPES else "", index
                     )
                 else:

From db9489b318114ff45c977392655d55cb8a28aab9 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Tue, 11 Jan 2022 16:58:36 +0100
Subject: [PATCH 0699/1185] Update Pyrogram to v1.3.4

---
 pyrogram/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index 02eef373ef..6d1a462cb7 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -16,7 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-__version__ = "1.3.3"
+__version__ = "1.3.4"
 __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)"
 __copyright__ = "Copyright (C) 2017-present Dan "
 

From a6299f8401fa5fcdc504380899518c5a4870a12c Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Tue, 11 Jan 2022 17:24:17 +0100
Subject: [PATCH 0700/1185] Use a proper condition check when dealing with flag
 vectors When reading flag vectors, non-existent vectors are being translated
 to [] (empty list). When writing them, the flag condition was strictly
 checking for None and an empty list [] would result in an empty vector being
 serialized, which should not happen. Related to #871.

---
 compiler/api/compiler.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/compiler/api/compiler.py b/compiler/api/compiler.py
index 192d6a6759..150fa49d37 100644
--- a/compiler/api/compiler.py
+++ b/compiler/api/compiler.py
@@ -408,7 +408,7 @@ def start(format: bool = False):
                     flag = FLAGS_RE_2.match(i[1])
 
                     if flag:
-                        if flag.group(2) == "true":
+                        if flag.group(2) == "true" or flag.group(2).startswith("Vector"):
                             write_flags.append(f"flags |= (1 << {flag.group(1)}) if self.{i[0]} else 0")
                         else:
                             write_flags.append(f"flags |= (1 << {flag.group(1)}) if self.{i[0]} is not None else 0")
@@ -441,7 +441,7 @@ def start(format: bool = False):
                     sub_type = arg_type.split("<")[1][:-1]
 
                     write_types += "\n        "
-                    write_types += f"if self.{arg_name} is not None:\n            "
+                    write_types += f"if self.{arg_name}:\n            "
                     write_types += "b.write(Vector(self.{}{}))\n        ".format(
                         arg_name, f", {sub_type.title()}" if sub_type in CORE_TYPES else ""
                     )

From e67fd6efbb93e5ba6307fe4c20ea8051c5583d85 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Tue, 11 Jan 2022 17:25:01 +0100
Subject: [PATCH 0701/1185] Update Pyrogram to v.1.3.5

---
 pyrogram/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index 6d1a462cb7..2c85c00fbf 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -16,7 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-__version__ = "1.3.4"
+__version__ = "1.3.5"
 __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)"
 __copyright__ = "Copyright (C) 2017-present Dan "
 

From 1162e89f26cc5f623ba6edb851129afd8ce2cab7 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Thu, 20 Jan 2022 09:43:29 +0100
Subject: [PATCH 0702/1185] Better handling of expiring server salts

---
 pyrogram/session/session.py | 62 +++++--------------------------------
 1 file changed, 7 insertions(+), 55 deletions(-)

diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py
index 751d6e0008..6455e958a1 100644
--- a/pyrogram/session/session.py
+++ b/pyrogram/session/session.py
@@ -19,8 +19,6 @@
 import asyncio
 import logging
 import os
-import time
-from datetime import datetime, timedelta
 from hashlib import sha1
 from io import BytesIO
 
@@ -33,7 +31,7 @@
     SecurityCheckMismatch
 )
 from pyrogram.raw.all import layer
-from pyrogram.raw.core import TLObject, MsgContainer, Int, FutureSalt, FutureSalts
+from pyrogram.raw.core import TLObject, MsgContainer, Int, FutureSalts
 from .internals import MsgId, MsgFactory
 
 log = logging.getLogger(__name__)
@@ -76,7 +74,7 @@ def __init__(
         self.session_id = os.urandom(8)
         self.msg_factory = MsgFactory()
 
-        self.current_salt = None
+        self.salt = 0
 
         self.pending_acks = set()
 
@@ -87,9 +85,6 @@ def __init__(
         self.ping_task = None
         self.ping_task_event = asyncio.Event()
 
-        self.next_salt_task = None
-        self.next_salt_task_event = asyncio.Event()
-
         self.network_task = None
 
         self.is_connected = asyncio.Event()
@@ -111,19 +106,7 @@ async def start(self):
 
                 self.network_task = self.loop.create_task(self.network_worker())
 
-                self.current_salt = FutureSalt(0, 0, 0)
-                self.current_salt = FutureSalt(
-                    0, 0,
-                    (await self._send(
-                        raw.functions.Ping(ping_id=0),
-                        timeout=self.START_TIMEOUT
-                    )).new_server_salt
-                )
-                self.current_salt = (await self._send(
-                    raw.functions.GetFutureSalts(num=1),
-                    timeout=self.START_TIMEOUT)).salts[0]
-
-                self.next_salt_task = self.loop.create_task(self.next_salt_worker())
+                await self._send(raw.functions.Ping(ping_id=0), timeout=self.START_TIMEOUT)
 
                 if not self.is_cdn:
                     await self._send(
@@ -168,16 +151,11 @@ async def stop(self):
         self.is_connected.clear()
 
         self.ping_task_event.set()
-        self.next_salt_task_event.set()
 
         if self.ping_task is not None:
             await self.ping_task
 
-        if self.next_salt_task is not None:
-            await self.next_salt_task
-
         self.ping_task_event.clear()
-        self.next_salt_task_event.clear()
 
         self.connection.close()
 
@@ -288,35 +266,6 @@ async def ping_worker(self):
 
         log.info("PingTask stopped")
 
-    async def next_salt_worker(self):
-        log.info("NextSaltTask started")
-
-        while True:
-            now = datetime.fromtimestamp(time.perf_counter() - MsgId.reference_clock + MsgId.server_time)
-
-            # Seconds to wait until middle-overlap, which is
-            # 15 minutes before/after the current/next salt end/start time
-            valid_until = datetime.fromtimestamp(self.current_salt.valid_until)
-            dt = (valid_until - now).total_seconds() - 900
-
-            minutes, seconds = divmod(int(dt), 60)
-            log.info(f"Next salt in {minutes:.0f}m {seconds:.0f}s (at {now + timedelta(seconds=dt)})")
-
-            try:
-                await asyncio.wait_for(self.next_salt_task_event.wait(), dt)
-            except asyncio.TimeoutError:
-                pass
-            else:
-                break
-
-            try:
-                self.current_salt = (await self._send(raw.functions.GetFutureSalts(num=1))).salts[0]
-            except (OSError, TimeoutError, RPCError):
-                self.connection.close()
-                break
-
-        log.info("NextSaltTask stopped")
-
     async def network_worker(self):
         log.info("NetworkTask started")
 
@@ -352,7 +301,7 @@ async def _send(self, data: TLObject, wait_response: bool = True, timeout: float
             pyrogram.crypto_executor,
             mtproto.pack,
             message,
-            self.current_salt.salt,
+            self.salt,
             self.session_id,
             self.auth_key,
             self.auth_key_id
@@ -381,6 +330,9 @@ async def _send(self, data: TLObject, wait_response: bool = True, timeout: float
                 RPCError.raise_it(result, type(data))
             elif isinstance(result, raw.types.BadMsgNotification):
                 raise BadMsgNotification(result.error_code)
+            elif isinstance(result, raw.types.BadServerSalt):
+                self.salt = result.new_server_salt
+                return await self._send(data, wait_response, timeout)
             else:
                 return result
 

From 23b02087c27f8fb5f43fded650df9f3d14381c1c Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Thu, 20 Jan 2022 09:44:21 +0100
Subject: [PATCH 0703/1185] Update Pyrogram to v1.3.6

---
 pyrogram/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index 2c85c00fbf..b3b2ccdf8a 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -16,7 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-__version__ = "1.3.5"
+__version__ = "1.3.6"
 __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)"
 __copyright__ = "Copyright (C) 2017-present Dan "
 

From 7d444381c743a2c7ce587b3b7fc4f32afa3e02aa Mon Sep 17 00:00:00 2001
From: Alisson Lauffer 
Date: Fri, 21 Jan 2022 06:26:52 -0300
Subject: [PATCH 0704/1185] Fix spoiler html unparsing (#862)

- The current spoiler implementaion unparses both strikethrough and spoiler tags with , making them indistinguishable
---
 pyrogram/parser/html.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/pyrogram/parser/html.py b/pyrogram/parser/html.py
index c169dd1a06..b70a189f57 100644
--- a/pyrogram/parser/html.py
+++ b/pyrogram/parser/html.py
@@ -155,10 +155,10 @@ def unparse(text: str, entities: list):
             start = entity.offset
             end = start + entity.length
 
-            if entity_type in ("bold", "italic", "underline", "strikethrough", "spoiler"):
+            if entity_type in ("bold", "italic", "underline", "strikethrough"):
                 start_tag = f"<{entity_type[0]}>"
                 end_tag = f""
-            elif entity_type in ("code", "pre", "blockquote"):
+            elif entity_type in ("code", "pre", "blockquote", "spoiler"):
                 start_tag = f"<{entity_type}>"
                 end_tag = f""
             elif entity_type == "text_link":

From 4af9e30cfd6f2ef8157f041744050c568e4fa20f Mon Sep 17 00:00:00 2001
From: Shrimadhav U K 
Date: Sat, 29 Jan 2022 15:43:09 +0530
Subject: [PATCH 0705/1185] Fix caption being "None" when passing None (#879)

---
 pyrogram/parser/parser.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/parser/parser.py b/pyrogram/parser/parser.py
index 2ea9e4f284..d294e04d51 100644
--- a/pyrogram/parser/parser.py
+++ b/pyrogram/parser/parser.py
@@ -31,7 +31,7 @@ def __init__(self, client: Optional["pyrogram.Client"]):
         self.markdown = Markdown(client)
 
     async def parse(self, text: str, mode: Optional[str] = object):
-        text = str(text).strip()
+        text = str(text if text else "").strip()
 
         if mode == object:
             if self.client:

From 244606eed619682ce4c089c40181e9c31c28f7e8 Mon Sep 17 00:00:00 2001
From: W4RR10R <46273006+arunpt@users.noreply.github.com>
Date: Sat, 29 Jan 2022 16:06:15 +0530
Subject: [PATCH 0706/1185] Add approve() and decline() bound methods to
 ChatJoinRequest (#863)

* Bound method approve() and decline()

* Style fixes

Co-authored-by: ArUn Pt <46273006+CW4RR10R@users.noreply.github.com>
Co-authored-by: Dan <14043624+delivrance@users.noreply.github.com>
---
 .../types/user_and_chats/chat_join_request.py | 56 +++++++++++++++++++
 1 file changed, 56 insertions(+)

diff --git a/pyrogram/types/user_and_chats/chat_join_request.py b/pyrogram/types/user_and_chats/chat_join_request.py
index a7001da5d9..fe051de4f7 100644
--- a/pyrogram/types/user_and_chats/chat_join_request.py
+++ b/pyrogram/types/user_and_chats/chat_join_request.py
@@ -80,3 +80,59 @@ def _parse(
             invite_link=types.ChatInviteLink._parse(client, update.invite, users),
             client=client
         )
+
+    async def approve(self) -> bool:
+        """Bound method *approve* of :obj:`~pyrogram.types.ChatJoinRequest`.
+        
+        Use as a shortcut for:
+        
+        .. code-block:: python
+
+            client.approve_chat_join_request(
+                chat_id=request.chat.id,
+                user_id=request.from_user.id
+            )
+            
+        Example:
+            .. code-block:: python
+
+                request.approve()
+                
+        Returns:
+            ``bool``: True on success.
+        
+        Raises:
+            RPCError: In case of a Telegram RPC error.
+        """
+        return await self._client.approve_chat_join_request(
+            chat_id=self.chat.id,
+            user_id=self.from_user.id
+        )
+
+    async def decline(self) -> bool:
+        """Bound method *decline* of :obj:`~pyrogram.types.ChatJoinRequest`.
+        
+        Use as a shortcut for:
+        
+        .. code-block:: python
+
+            client.decline_chat_join_request(
+                chat_id=request.chat.id,
+                user_id=request.from_user.id
+            )
+            
+        Example:
+            .. code-block:: python
+
+                request.decline()
+                
+        Returns:
+            ``bool``: True on success.
+        
+        Raises:
+            RPCError: In case of a Telegram RPC error.
+        """
+        return await self._client.decline_chat_join_request(
+            chat_id=self.chat.id,
+            user_id=self.from_user.id
+        )

From 149685f9d3802130af31721171f9c7db4dabec57 Mon Sep 17 00:00:00 2001
From: Mahesh <44301650+Mahesh0253@users.noreply.github.com>
Date: Sat, 29 Jan 2022 16:24:00 +0530
Subject: [PATCH 0707/1185] Add placeholder in ForceReply & ReplyKeyboardMarkup
 (#717)

* Added placeholder

* Fix docs
---
 pyrogram/types/bots_and_keyboards/force_reply.py    | 13 ++++++++++---
 .../bots_and_keyboards/reply_keyboard_markup.py     | 13 ++++++++++---
 2 files changed, 20 insertions(+), 6 deletions(-)

diff --git a/pyrogram/types/bots_and_keyboards/force_reply.py b/pyrogram/types/bots_and_keyboards/force_reply.py
index 025176f2e3..4cb137d845 100644
--- a/pyrogram/types/bots_and_keyboards/force_reply.py
+++ b/pyrogram/types/bots_and_keyboards/force_reply.py
@@ -36,24 +36,31 @@ class ForceReply(Object):
             Use this parameter if you want to force reply from specific users only. Targets:
             1) users that are @mentioned in the text of the Message object;
             2) if the bot's message is a reply (has reply_to_message_id), sender of the original message.
+
+        placeholder (``str``, *optional*):
+            The placeholder to be shown in the input field when the reply is active; 1-64 characters.
     """
 
     def __init__(
         self,
-        selective: bool = None
+        selective: bool = None,
+        placeholder: str = None
     ):
         super().__init__()
 
         self.selective = selective
+        self.placeholder = placeholder
 
     @staticmethod
     def read(b):
         return ForceReply(
-            selective=b.selective
+            selective=b.selective,
+            placeholder=b.placeholder
         )
 
     async def write(self, _: "pyrogram.Client"):
         return raw.types.ReplyKeyboardForceReply(
             single_use=True,
-            selective=self.selective or None
+            selective=self.selective or None,
+            placeholder=self.placeholder or None
         )
diff --git a/pyrogram/types/bots_and_keyboards/reply_keyboard_markup.py b/pyrogram/types/bots_and_keyboards/reply_keyboard_markup.py
index b619216a13..b62f6dcff7 100644
--- a/pyrogram/types/bots_and_keyboards/reply_keyboard_markup.py
+++ b/pyrogram/types/bots_and_keyboards/reply_keyboard_markup.py
@@ -47,6 +47,9 @@ class ReplyKeyboardMarkup(Object):
             2) if the bot's message is a reply (has reply_to_message_id), sender of the original message.
             Example: A user requests to change the bot's language, bot replies to the request with a keyboard to
             select the new language. Other users in the group don't see the keyboard.
+
+        placeholder (``str``, *optional*):
+            The placeholder to be shown in the input field when the keyboard is active; 1-64 characters.
     """
 
     def __init__(
@@ -54,7 +57,8 @@ def __init__(
         keyboard: List[List[Union["types.KeyboardButton", str]]],
         resize_keyboard: bool = None,
         one_time_keyboard: bool = None,
-        selective: bool = None
+        selective: bool = None,
+        placeholder: str = None
     ):
         super().__init__()
 
@@ -62,6 +66,7 @@ def __init__(
         self.resize_keyboard = resize_keyboard
         self.one_time_keyboard = one_time_keyboard
         self.selective = selective
+        self.placeholder = placeholder
 
     @staticmethod
     def read(kb):
@@ -79,7 +84,8 @@ def read(kb):
             keyboard=keyboard,
             resize_keyboard=kb.resize,
             one_time_keyboard=kb.single_use,
-            selective=kb.selective
+            selective=kb.selective,
+            placeholder=kb.placeholder
         )
 
     async def write(self, _: "pyrogram.Client"):
@@ -93,5 +99,6 @@ async def write(self, _: "pyrogram.Client"):
             ) for i in self.keyboard],
             resize=self.resize_keyboard or None,
             single_use=self.one_time_keyboard or None,
-            selective=self.selective or None
+            selective=self.selective or None,
+            placeholder=self.placeholder or None
         )

From b1250e65751da992369cae8701eee5caa39c3fe7 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sat, 29 Jan 2022 13:02:32 +0100
Subject: [PATCH 0708/1185] Fix accessing non-existent attribute Closes #865

---
 pyrogram/types/user_and_chats/chat.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/pyrogram/types/user_and_chats/chat.py b/pyrogram/types/user_and_chats/chat.py
index a9d396eb21..6dc3fb8264 100644
--- a/pyrogram/types/user_and_chats/chat.py
+++ b/pyrogram/types/user_and_chats/chat.py
@@ -247,7 +247,8 @@ def _parse_channel_chat(client, channel: raw.types.Channel) -> "Chat":
             is_fake=getattr(channel, "fake", None),
             title=channel.title,
             username=getattr(channel, "username", None),
-            photo=types.ChatPhoto._parse(client, getattr(channel, "photo", None), peer_id, channel.access_hash),
+            photo=types.ChatPhoto._parse(client, getattr(channel, "photo", None), peer_id,
+                                         getattr(channel, "access_hash", 0)),
             restrictions=types.List([types.Restriction._parse(r) for r in restriction_reason]) or None,
             permissions=types.ChatPermissions._parse(getattr(channel, "default_banned_rights", None)),
             members_count=getattr(channel, "participants_count", None),

From 3e79d7dfce9a4b259a2ee1833eb5b0d47560e9ba Mon Sep 17 00:00:00 2001
From: Pietro De Nicolao 
Date: Sat, 29 Jan 2022 13:39:25 +0100
Subject: [PATCH 0709/1185] Add py.typed file for enhanced type hinting (#838)

Fixes #781

* fix: add py.typed file

Comply with PEP 561 and enable type checkers.
Fixes #781.

* chore: add py.typed to package_data in setup.py

* Style fixes

Co-authored-by: Dan <14043624+delivrance@users.noreply.github.com>
---
 pyrogram/py.typed | 0
 setup.py          | 3 +++
 2 files changed, 3 insertions(+)
 create mode 100644 pyrogram/py.typed

diff --git a/pyrogram/py.typed b/pyrogram/py.typed
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/setup.py b/setup.py
index 1a24654ce8..66ede5066d 100644
--- a/setup.py
+++ b/setup.py
@@ -172,6 +172,9 @@ def run(self):
         "Documentation": "https://docs.pyrogram.org",
     },
     python_requires="~=3.6",
+    package_data = {
+        "pyrogram": ["py.typed"],
+    },
     packages=find_packages(exclude=["compiler*", "tests*"]),
     zip_safe=False,
     install_requires=requires,

From f1298dfdc67c51bf31cf436db1512749b5447e60 Mon Sep 17 00:00:00 2001
From: Roj 
Date: Sat, 29 Jan 2022 16:08:15 +0300
Subject: [PATCH 0710/1185] Add video_start_ts parameter to set_chat_photo
 (#770)

* Add `video_start_ts` parameter to `set_chat_photo`

* Docstrings update

Co-authored-by: Dan <14043624+delivrance@users.noreply.github.com>
---
 pyrogram/methods/chats/set_chat_photo.py | 14 ++++++++++----
 1 file changed, 10 insertions(+), 4 deletions(-)

diff --git a/pyrogram/methods/chats/set_chat_photo.py b/pyrogram/methods/chats/set_chat_photo.py
index 266253d632..a1fee7f62f 100644
--- a/pyrogram/methods/chats/set_chat_photo.py
+++ b/pyrogram/methods/chats/set_chat_photo.py
@@ -31,7 +31,8 @@ async def set_chat_photo(
         chat_id: Union[int, str],
         *,
         photo: Union[str, BinaryIO] = None,
-        video: Union[str, BinaryIO] = None
+        video: Union[str, BinaryIO] = None,
+        video_start_ts: float = None,
     ) -> bool:
         """Set a new chat photo or video (H.264/MPEG-4 AVC video, max 5 seconds).
 
@@ -54,6 +55,9 @@ async def set_chat_photo(
                 from your local machine or a binary file-like object with its attribute
                 ".name" set for in-memory uploads.
 
+            video_start_ts (``float``, *optional*):
+                The timestamp in seconds of the video frame to use as photo profile preview.
+
         Returns:
             ``bool``: True on success.
 
@@ -82,7 +86,8 @@ async def set_chat_photo(
             if os.path.isfile(photo):
                 photo = raw.types.InputChatUploadedPhoto(
                     file=await self.save_file(photo),
-                    video=await self.save_file(video)
+                    video=await self.save_file(video),
+                    video_start_ts=video_start_ts,
                 )
             else:
                 photo = utils.get_input_media_from_file_id(photo, FileType.PHOTO)
@@ -90,14 +95,15 @@ async def set_chat_photo(
         else:
             photo = raw.types.InputChatUploadedPhoto(
                 file=await self.save_file(photo),
-                video=await self.save_file(video)
+                video=await self.save_file(video),
+                video_start_ts=video_start_ts,
             )
 
         if isinstance(peer, raw.types.InputPeerChat):
             await self.send(
                 raw.functions.messages.EditChatPhoto(
                     chat_id=peer.chat_id,
-                    photo=photo
+                    photo=photo,
                 )
             )
         elif isinstance(peer, raw.types.InputPeerChannel):

From 333d22afcad3d94268f0370b06950f40977d97e8 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sat, 29 Jan 2022 14:45:35 +0100
Subject: [PATCH 0711/1185] Update API schema to Layer 137

---
 compiler/api/source/main_api.tl | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl
index 3cfee8a5e0..824fab45d1 100644
--- a/compiler/api/source/main_api.tl
+++ b/compiler/api/source/main_api.tl
@@ -1291,7 +1291,7 @@ messageUserReaction#932844fa user_id:long reaction:string = MessageUserReaction;
 
 messages.messageReactionsList#a366923c flags:# count:int reactions:Vector users:Vector next_offset:flags.0?string = messages.MessageReactionsList;
 
-availableReaction#21d7c4b flags:# inactive:flags.0?true reaction:string title:string static_icon:Document appear_animation:Document select_animation:Document activate_animation:Document effect_animation:Document = AvailableReaction;
+availableReaction#c077ec01 flags:# inactive:flags.0?true reaction:string title:string static_icon:Document appear_animation:Document select_animation:Document activate_animation:Document effect_animation:Document around_animation:flags.1?Document center_icon:flags.1?Document = AvailableReaction;
 
 messages.availableReactionsNotModified#9f071957 = messages.AvailableReactions;
 messages.availableReactions#768e3aad hash:int reactions:Vector = messages.AvailableReactions;
@@ -1728,4 +1728,4 @@ stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel
 stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages;
 stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats;
 
-// LAYER 136
\ No newline at end of file
+// LAYER 137
\ No newline at end of file

From c7888437e881d6188c710fe405f0ae8334755611 Mon Sep 17 00:00:00 2001
From: Andrea Princic <48788808+Princic-1837592@users.noreply.github.com>
Date: Sat, 29 Jan 2022 18:50:51 +0100
Subject: [PATCH 0712/1185] Fixed error while unparsing consecutive entities
 (#885)

---
 pyrogram/parser/html.py | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/pyrogram/parser/html.py b/pyrogram/parser/html.py
index b70a189f57..81c761ac94 100644
--- a/pyrogram/parser/html.py
+++ b/pyrogram/parser/html.py
@@ -175,10 +175,7 @@ def unparse(text: str, entities: list):
             entities_offsets.append((start_tag, start,))
             entities_offsets.append((end_tag, end,))
 
-        # sorting by offset (desc)
-        entities_offsets.sort(key=lambda x: -x[1])
-
-        for entity, offset in entities_offsets:
+        for entity, offset in reversed(entities_offsets):
             text = text[:offset] + entity + text[offset:]
 
         return utils.remove_surrogates(text)

From 6f9e77bc2c54ca6bc928592645c48317a74981f5 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Mon, 31 Jan 2022 20:42:44 +0100
Subject: [PATCH 0713/1185] Do not handle messages with a pending ack

---
 pyrogram/session/session.py | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py
index 6455e958a1..c156c95377 100644
--- a/pyrogram/session/session.py
+++ b/pyrogram/session/session.py
@@ -208,7 +208,9 @@ async def handle_packet(self, packet):
                 MsgId.set_server_time(msg.msg_id / (2 ** 32))
 
             if msg.seq_no % 2 != 0:
-                if msg.msg_id not in self.pending_acks:
+                if msg.msg_id in self.pending_acks:
+                    continue
+                else:
                     self.pending_acks.add(msg.msg_id)
 
             if isinstance(msg.body, (raw.types.MsgDetailedInfo, raw.types.MsgNewDetailedInfo)):

From 3a911956b064372c79c9856c2115909f946e4269 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Mon, 31 Jan 2022 20:45:04 +0100
Subject: [PATCH 0714/1185] Update message for automatic sleeps

---
 pyrogram/session/session.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py
index c156c95377..05d1fd4a3f 100644
--- a/pyrogram/session/session.py
+++ b/pyrogram/session/session.py
@@ -366,7 +366,8 @@ async def send(
                 if amount > sleep_threshold >= 0:
                     raise
 
-                log.warning(f'[{self.client.session_name}] Sleeping for {amount}s (required by "{query}")')
+                log.warning(f'[{self.client.session_name}] Waiting for {amount} seconds before continuing '
+                            f'(required by "{query}")')
 
                 await asyncio.sleep(amount)
             except (OSError, TimeoutError, InternalServerError, ServiceUnavailable) as e:

From a8cc77d903fe0f92303d360b067983509bac835c Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Mon, 31 Jan 2022 20:46:53 +0100
Subject: [PATCH 0715/1185] Update Pyrogram to v1.3.7

---
 pyrogram/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index b3b2ccdf8a..81670ac9b2 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -16,7 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-__version__ = "1.3.6"
+__version__ = "1.3.7"
 __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)"
 __copyright__ = "Copyright (C) 2017-present Dan "
 

From afad3a7704d122fdc4fc941f6e2746da3c988cb2 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Mon, 31 Jan 2022 21:26:30 +0100
Subject: [PATCH 0716/1185] Switch from issue templates to issue forms

---
 .github/ISSUE_TEMPLATE/bug_report.md       | 28 ------------
 .github/ISSUE_TEMPLATE/bug_report.yml      | 51 ++++++++++++++++++++++
 .github/ISSUE_TEMPLATE/feature_request.md  | 14 ------
 .github/ISSUE_TEMPLATE/feature_request.yml | 20 +++++++++
 4 files changed, 71 insertions(+), 42 deletions(-)
 delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.md
 create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml
 delete mode 100644 .github/ISSUE_TEMPLATE/feature_request.md
 create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml

diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
deleted file mode 100644
index e3bc5909d1..0000000000
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ /dev/null
@@ -1,28 +0,0 @@
----
-name: Bug Report
-about: Create a bug report affecting the framework or the documentation
----
-
-
-
-## Checklist
-- [ ] I am sure the error is coming from Pyrogram's code and not elsewhere.
-- [ ] I have searched in the issue tracker for similar bug reports, including closed ones.
-- [ ] I ran `pip3 install -U https://github.com/pyrogram/pyrogram/archive/master.zip` and reproduced the issue using the latest development version.
-
-## Description
-A **clear** and **concise** description of the problem. Code snippets must be
-[minimal, reproducible](https://stackoverflow.com/help/minimal-reproducible-example) and properly formatted.
-
-``` python
-from pyrogram import Client
-...
-```
-
-## Traceback
-The full traceback (if applicable).
-
-```
-Traceback (most recent call last):
-  File "main.py", line 1, in 
-```
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml
new file mode 100644
index 0000000000..b03ff7dc5e
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.yml
@@ -0,0 +1,51 @@
+name: Bug report
+description: Report issues affecting the framework or the documentation
+body:
+  - type: checkboxes
+    attributes:
+      label: Checklist
+      options:
+        - label: I am sure the error is coming from Pyrogram's code and not elsewhere
+          required: true
+        - label: I have searched in the issue tracker for similar bug reports, including closed ones
+          required: true
+        - label: I ran `pip3 install -U https://github.com/pyrogram/pyrogram/archive/master.zip` and reproduced the issue using the latest development version
+          required: true
+
+  - type: textarea
+    attributes:
+      label: Description
+      description: Provide a clear and concise description of the issue
+      placeholder: Description...
+    validations:
+      required: true
+
+  - type: textarea
+    attributes:
+      label: Steps to reproduce
+      description: Explain precisely how to reproduce the issue
+      placeholder: |
+        1.
+        2.
+        3.
+    validations:
+      required: true
+
+  - type: textarea
+    attributes:
+      label: Code example
+      description: Provide a [minimal, reproducible](https://stackoverflow.com/help/minimal-reproducible-example) and properly formatted example (if applicable)
+      placeholder: |
+        from pyrogram import Client
+        ...
+      render: python
+
+  - type: textarea
+    attributes:
+      label: Logs
+      description: Provide the complete traceback (if applicable)
+      placeholder: |
+        Traceback (most recent call last):
+        File "main.py", line 1, in 
+        ...
+      render: shell
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
deleted file mode 100644
index bf5e6a21f3..0000000000
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ /dev/null
@@ -1,14 +0,0 @@
----
-name: Feature Request
-about: Suggest ideas, new features or enhancements
-labels: "enhancement"
----
-
-
-
-## Checklist
-- [ ] I believe the idea is awesome and would benefit the framework.
-- [ ] I have searched in the issue tracker for similar requests, including closed ones.
-
-## Description
-A detailed description of the request.
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml
new file mode 100644
index 0000000000..59202d14a9
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.yml
@@ -0,0 +1,20 @@
+name: Feature request
+description: Suggest ideas, new features or enhancements
+labels: [enhancement]
+body:
+  - type: checkboxes
+    attributes:
+      label: Checklist
+      options:
+        - label: I believe the idea is awesome and would benefit the framework
+          required: true
+        - label: I have searched in the issue tracker for similar requests, including closed ones
+          required: true
+
+  - type: textarea
+    attributes:
+      label: Description
+      description: Provide a detailed description of the request
+      placeholder: Description...
+    validations:
+      required: true
\ No newline at end of file

From 51cf103c1533c21638dc6c2dd8d353e0ddbbbb47 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Mon, 31 Jan 2022 21:27:58 +0100
Subject: [PATCH 0717/1185] Update emoji.py

---
 pyrogram/emoji.py | 1102 +++++++++++++++++++++++++--------------------
 1 file changed, 608 insertions(+), 494 deletions(-)

diff --git a/pyrogram/emoji.py b/pyrogram/emoji.py
index 19dd7e8172..d135faf74d 100644
--- a/pyrogram/emoji.py
+++ b/pyrogram/emoji.py
@@ -26,6 +26,7 @@
 FACE_WITH_TEARS_OF_JOY = "\U0001f602"
 SLIGHTLY_SMILING_FACE = "\U0001f642"
 UPSIDE_DOWN_FACE = "\U0001f643"
+MELTING_FACE = "\U0001fae0"
 WINKING_FACE = "\U0001f609"
 SMILING_FACE_WITH_SMILING_EYES = "\U0001f60a"
 SMILING_FACE_WITH_HALO = "\U0001f607"
@@ -44,19 +45,25 @@
 ZANY_FACE = "\U0001f92a"
 SQUINTING_FACE_WITH_TONGUE = "\U0001f61d"
 MONEY_MOUTH_FACE = "\U0001f911"
-HUGGING_FACE = "\U0001f917"
+SMILING_FACE_WITH_OPEN_HANDS = "\U0001f917"
 FACE_WITH_HAND_OVER_MOUTH = "\U0001f92d"
+FACE_WITH_OPEN_EYES_AND_HAND_OVER_MOUTH = "\U0001fae2"
+FACE_WITH_PEEKING_EYE = "\U0001fae3"
 SHUSHING_FACE = "\U0001f92b"
 THINKING_FACE = "\U0001f914"
+SALUTING_FACE = "\U0001fae1"
 ZIPPER_MOUTH_FACE = "\U0001f910"
 FACE_WITH_RAISED_EYEBROW = "\U0001f928"
 NEUTRAL_FACE = "\U0001f610"
 EXPRESSIONLESS_FACE = "\U0001f611"
 FACE_WITHOUT_MOUTH = "\U0001f636"
+DOTTED_LINE_FACE = "\U0001fae5"
+FACE_IN_CLOUDS = "\U0001f636\u200d\U0001f32b\ufe0f"
 SMIRKING_FACE = "\U0001f60f"
 UNAMUSED_FACE = "\U0001f612"
 FACE_WITH_ROLLING_EYES = "\U0001f644"
 GRIMACING_FACE = "\U0001f62c"
+FACE_EXHALING = "\U0001f62e\u200d\U0001f4a8"
 LYING_FACE = "\U0001f925"
 RELIEVED_FACE = "\U0001f60c"
 PENSIVE_FACE = "\U0001f614"
@@ -72,7 +79,8 @@
 HOT_FACE = "\U0001f975"
 COLD_FACE = "\U0001f976"
 WOOZY_FACE = "\U0001f974"
-DIZZY_FACE = "\U0001f635"
+FACE_WITH_CROSSED_OUT_EYES = "\U0001f635"
+FACE_WITH_SPIRAL_EYES = "\U0001f635\u200d\U0001f4ab"
 EXPLODING_HEAD = "\U0001f92f"
 COWBOY_HAT_FACE = "\U0001f920"
 PARTYING_FACE = "\U0001f973"
@@ -81,6 +89,7 @@
 NERD_FACE = "\U0001f913"
 FACE_WITH_MONOCLE = "\U0001f9d0"
 CONFUSED_FACE = "\U0001f615"
+FACE_WITH_DIAGONAL_MOUTH = "\U0001fae4"
 WORRIED_FACE = "\U0001f61f"
 SLIGHTLY_FROWNING_FACE = "\U0001f641"
 FROWNING_FACE = "\u2639\ufe0f"
@@ -89,6 +98,7 @@
 ASTONISHED_FACE = "\U0001f632"
 FLUSHED_FACE = "\U0001f633"
 PLEADING_FACE = "\U0001f97a"
+FACE_HOLDING_BACK_TEARS = "\U0001f979"
 FROWNING_FACE_WITH_OPEN_MOUTH = "\U0001f626"
 ANGUISHED_FACE = "\U0001f627"
 FEARFUL_FACE = "\U0001f628"
@@ -105,7 +115,7 @@
 TIRED_FACE = "\U0001f62b"
 YAWNING_FACE = "\U0001f971"
 FACE_WITH_STEAM_FROM_NOSE = "\U0001f624"
-POUTING_FACE = "\U0001f621"
+ENRAGED_FACE = "\U0001f621"
 ANGRY_FACE = "\U0001f620"
 FACE_WITH_SYMBOLS_ON_MOUTH = "\U0001f92c"
 SMILING_FACE_WITH_HORNS = "\U0001f608"
@@ -144,6 +154,8 @@
 HEART_DECORATION = "\U0001f49f"
 HEART_EXCLAMATION = "\u2763\ufe0f"
 BROKEN_HEART = "\U0001f494"
+HEART_ON_FIRE = "\u2764\ufe0f\u200d\U0001f525"
+MENDING_HEART = "\u2764\ufe0f\u200d\U0001fa79"
 RED_HEART = "\u2764\ufe0f"
 ORANGE_HEART = "\U0001f9e1"
 YELLOW_HEART = "\U0001f49b"
@@ -197,6 +209,30 @@
 VULCAN_SALUTE_MEDIUM_SKIN_TONE = "\U0001f596\U0001f3fd"
 VULCAN_SALUTE_MEDIUM_DARK_SKIN_TONE = "\U0001f596\U0001f3fe"
 VULCAN_SALUTE_DARK_SKIN_TONE = "\U0001f596\U0001f3ff"
+RIGHTWARDS_HAND = "\U0001faf1"
+RIGHTWARDS_HAND_LIGHT_SKIN_TONE = "\U0001faf1\U0001f3fb"
+RIGHTWARDS_HAND_MEDIUM_LIGHT_SKIN_TONE = "\U0001faf1\U0001f3fc"
+RIGHTWARDS_HAND_MEDIUM_SKIN_TONE = "\U0001faf1\U0001f3fd"
+RIGHTWARDS_HAND_MEDIUM_DARK_SKIN_TONE = "\U0001faf1\U0001f3fe"
+RIGHTWARDS_HAND_DARK_SKIN_TONE = "\U0001faf1\U0001f3ff"
+LEFTWARDS_HAND = "\U0001faf2"
+LEFTWARDS_HAND_LIGHT_SKIN_TONE = "\U0001faf2\U0001f3fb"
+LEFTWARDS_HAND_MEDIUM_LIGHT_SKIN_TONE = "\U0001faf2\U0001f3fc"
+LEFTWARDS_HAND_MEDIUM_SKIN_TONE = "\U0001faf2\U0001f3fd"
+LEFTWARDS_HAND_MEDIUM_DARK_SKIN_TONE = "\U0001faf2\U0001f3fe"
+LEFTWARDS_HAND_DARK_SKIN_TONE = "\U0001faf2\U0001f3ff"
+PALM_DOWN_HAND = "\U0001faf3"
+PALM_DOWN_HAND_LIGHT_SKIN_TONE = "\U0001faf3\U0001f3fb"
+PALM_DOWN_HAND_MEDIUM_LIGHT_SKIN_TONE = "\U0001faf3\U0001f3fc"
+PALM_DOWN_HAND_MEDIUM_SKIN_TONE = "\U0001faf3\U0001f3fd"
+PALM_DOWN_HAND_MEDIUM_DARK_SKIN_TONE = "\U0001faf3\U0001f3fe"
+PALM_DOWN_HAND_DARK_SKIN_TONE = "\U0001faf3\U0001f3ff"
+PALM_UP_HAND = "\U0001faf4"
+PALM_UP_HAND_LIGHT_SKIN_TONE = "\U0001faf4\U0001f3fb"
+PALM_UP_HAND_MEDIUM_LIGHT_SKIN_TONE = "\U0001faf4\U0001f3fc"
+PALM_UP_HAND_MEDIUM_SKIN_TONE = "\U0001faf4\U0001f3fd"
+PALM_UP_HAND_MEDIUM_DARK_SKIN_TONE = "\U0001faf4\U0001f3fe"
+PALM_UP_HAND_DARK_SKIN_TONE = "\U0001faf4\U0001f3ff"
 OK_HAND = "\U0001f44c"
 OK_HAND_LIGHT_SKIN_TONE = "\U0001f44c\U0001f3fb"
 OK_HAND_MEDIUM_LIGHT_SKIN_TONE = "\U0001f44c\U0001f3fc"
@@ -227,6 +263,12 @@
 CROSSED_FINGERS_MEDIUM_SKIN_TONE = "\U0001f91e\U0001f3fd"
 CROSSED_FINGERS_MEDIUM_DARK_SKIN_TONE = "\U0001f91e\U0001f3fe"
 CROSSED_FINGERS_DARK_SKIN_TONE = "\U0001f91e\U0001f3ff"
+HAND_WITH_INDEX_FINGER_AND_THUMB_CROSSED = "\U0001faf0"
+HAND_WITH_INDEX_FINGER_AND_THUMB_CROSSED_LIGHT_SKIN_TONE = "\U0001faf0\U0001f3fb"
+HAND_WITH_INDEX_FINGER_AND_THUMB_CROSSED_MEDIUM_LIGHT_SKIN_TONE = "\U0001faf0\U0001f3fc"
+HAND_WITH_INDEX_FINGER_AND_THUMB_CROSSED_MEDIUM_SKIN_TONE = "\U0001faf0\U0001f3fd"
+HAND_WITH_INDEX_FINGER_AND_THUMB_CROSSED_MEDIUM_DARK_SKIN_TONE = "\U0001faf0\U0001f3fe"
+HAND_WITH_INDEX_FINGER_AND_THUMB_CROSSED_DARK_SKIN_TONE = "\U0001faf0\U0001f3ff"
 LOVE_YOU_GESTURE = "\U0001f91f"
 LOVE_YOU_GESTURE_LIGHT_SKIN_TONE = "\U0001f91f\U0001f3fb"
 LOVE_YOU_GESTURE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f91f\U0001f3fc"
@@ -281,6 +323,12 @@
 INDEX_POINTING_UP_MEDIUM_SKIN_TONE = "\u261d\U0001f3fd"
 INDEX_POINTING_UP_MEDIUM_DARK_SKIN_TONE = "\u261d\U0001f3fe"
 INDEX_POINTING_UP_DARK_SKIN_TONE = "\u261d\U0001f3ff"
+INDEX_POINTING_AT_THE_VIEWER = "\U0001faf5"
+INDEX_POINTING_AT_THE_VIEWER_LIGHT_SKIN_TONE = "\U0001faf5\U0001f3fb"
+INDEX_POINTING_AT_THE_VIEWER_MEDIUM_LIGHT_SKIN_TONE = "\U0001faf5\U0001f3fc"
+INDEX_POINTING_AT_THE_VIEWER_MEDIUM_SKIN_TONE = "\U0001faf5\U0001f3fd"
+INDEX_POINTING_AT_THE_VIEWER_MEDIUM_DARK_SKIN_TONE = "\U0001faf5\U0001f3fe"
+INDEX_POINTING_AT_THE_VIEWER_DARK_SKIN_TONE = "\U0001faf5\U0001f3ff"
 THUMBS_UP = "\U0001f44d"
 THUMBS_UP_LIGHT_SKIN_TONE = "\U0001f44d\U0001f3fb"
 THUMBS_UP_MEDIUM_LIGHT_SKIN_TONE = "\U0001f44d\U0001f3fc"
@@ -329,6 +377,12 @@
 RAISING_HANDS_MEDIUM_SKIN_TONE = "\U0001f64c\U0001f3fd"
 RAISING_HANDS_MEDIUM_DARK_SKIN_TONE = "\U0001f64c\U0001f3fe"
 RAISING_HANDS_DARK_SKIN_TONE = "\U0001f64c\U0001f3ff"
+HEART_HANDS = "\U0001faf6"
+HEART_HANDS_LIGHT_SKIN_TONE = "\U0001faf6\U0001f3fb"
+HEART_HANDS_MEDIUM_LIGHT_SKIN_TONE = "\U0001faf6\U0001f3fc"
+HEART_HANDS_MEDIUM_SKIN_TONE = "\U0001faf6\U0001f3fd"
+HEART_HANDS_MEDIUM_DARK_SKIN_TONE = "\U0001faf6\U0001f3fe"
+HEART_HANDS_DARK_SKIN_TONE = "\U0001faf6\U0001f3ff"
 OPEN_HANDS = "\U0001f450"
 OPEN_HANDS_LIGHT_SKIN_TONE = "\U0001f450\U0001f3fb"
 OPEN_HANDS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f450\U0001f3fc"
@@ -342,6 +396,31 @@
 PALMS_UP_TOGETHER_MEDIUM_DARK_SKIN_TONE = "\U0001f932\U0001f3fe"
 PALMS_UP_TOGETHER_DARK_SKIN_TONE = "\U0001f932\U0001f3ff"
 HANDSHAKE = "\U0001f91d"
+HANDSHAKE_LIGHT_SKIN_TONE = "\U0001f91d\U0001f3fb"
+HANDSHAKE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f91d\U0001f3fc"
+HANDSHAKE_MEDIUM_SKIN_TONE = "\U0001f91d\U0001f3fd"
+HANDSHAKE_MEDIUM_DARK_SKIN_TONE = "\U0001f91d\U0001f3fe"
+HANDSHAKE_DARK_SKIN_TONE = "\U0001f91d\U0001f3ff"
+HANDSHAKE_LIGHT_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = "\U0001faf1\U0001f3fb\u200d\U0001faf2\U0001f3fc"
+HANDSHAKE_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = "\U0001faf1\U0001f3fb\u200d\U0001faf2\U0001f3fd"
+HANDSHAKE_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = "\U0001faf1\U0001f3fb\u200d\U0001faf2\U0001f3fe"
+HANDSHAKE_LIGHT_SKIN_TONE_DARK_SKIN_TONE = "\U0001faf1\U0001f3fb\u200d\U0001faf2\U0001f3ff"
+HANDSHAKE_MEDIUM_LIGHT_SKIN_TONE_LIGHT_SKIN_TONE = "\U0001faf1\U0001f3fc\u200d\U0001faf2\U0001f3fb"
+HANDSHAKE_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = "\U0001faf1\U0001f3fc\u200d\U0001faf2\U0001f3fd"
+HANDSHAKE_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = "\U0001faf1\U0001f3fc\u200d\U0001faf2\U0001f3fe"
+HANDSHAKE_MEDIUM_LIGHT_SKIN_TONE_DARK_SKIN_TONE = "\U0001faf1\U0001f3fc\u200d\U0001faf2\U0001f3ff"
+HANDSHAKE_MEDIUM_SKIN_TONE_LIGHT_SKIN_TONE = "\U0001faf1\U0001f3fd\u200d\U0001faf2\U0001f3fb"
+HANDSHAKE_MEDIUM_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = "\U0001faf1\U0001f3fd\u200d\U0001faf2\U0001f3fc"
+HANDSHAKE_MEDIUM_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = "\U0001faf1\U0001f3fd\u200d\U0001faf2\U0001f3fe"
+HANDSHAKE_MEDIUM_SKIN_TONE_DARK_SKIN_TONE = "\U0001faf1\U0001f3fd\u200d\U0001faf2\U0001f3ff"
+HANDSHAKE_MEDIUM_DARK_SKIN_TONE_LIGHT_SKIN_TONE = "\U0001faf1\U0001f3fe\u200d\U0001faf2\U0001f3fb"
+HANDSHAKE_MEDIUM_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = "\U0001faf1\U0001f3fe\u200d\U0001faf2\U0001f3fc"
+HANDSHAKE_MEDIUM_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = "\U0001faf1\U0001f3fe\u200d\U0001faf2\U0001f3fd"
+HANDSHAKE_MEDIUM_DARK_SKIN_TONE_DARK_SKIN_TONE = "\U0001faf1\U0001f3fe\u200d\U0001faf2\U0001f3ff"
+HANDSHAKE_DARK_SKIN_TONE_LIGHT_SKIN_TONE = "\U0001faf1\U0001f3ff\u200d\U0001faf2\U0001f3fb"
+HANDSHAKE_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = "\U0001faf1\U0001f3ff\u200d\U0001faf2\U0001f3fc"
+HANDSHAKE_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = "\U0001faf1\U0001f3ff\u200d\U0001faf2\U0001f3fd"
+HANDSHAKE_DARK_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = "\U0001faf1\U0001f3ff\u200d\U0001faf2\U0001f3fe"
 FOLDED_HANDS = "\U0001f64f"
 FOLDED_HANDS_LIGHT_SKIN_TONE = "\U0001f64f\U0001f3fb"
 FOLDED_HANDS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f64f\U0001f3fc"
@@ -413,6 +492,7 @@
 EYE = "\U0001f441\ufe0f"
 TONGUE = "\U0001f445"
 MOUTH = "\U0001f444"
+BITING_LIP = "\U0001fae6"
 BABY = "\U0001f476"
 BABY_LIGHT_SKIN_TONE = "\U0001f476\U0001f3fb"
 BABY_MEDIUM_LIGHT_SKIN_TONE = "\U0001f476\U0001f3fc"
@@ -461,6 +541,18 @@
 PERSON_MEDIUM_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fd"
 PERSON_MEDIUM_DARK_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fe"
 PERSON_DARK_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3ff"
+MAN_BEARD = "\U0001f9d4\u200d\u2642\ufe0f"
+MAN_LIGHT_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fb\u200d\u2642\ufe0f"
+MAN_MEDIUM_LIGHT_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fc\u200d\u2642\ufe0f"
+MAN_MEDIUM_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fd\u200d\u2642\ufe0f"
+MAN_MEDIUM_DARK_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fe\u200d\u2642\ufe0f"
+MAN_DARK_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3ff\u200d\u2642\ufe0f"
+WOMAN_BEARD = "\U0001f9d4\u200d\u2640\ufe0f"
+WOMAN_LIGHT_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fb\u200d\u2640\ufe0f"
+WOMAN_MEDIUM_LIGHT_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fc\u200d\u2640\ufe0f"
+WOMAN_MEDIUM_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fd\u200d\u2640\ufe0f"
+WOMAN_MEDIUM_DARK_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fe\u200d\u2640\ufe0f"
+WOMAN_DARK_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3ff\u200d\u2640\ufe0f"
 MAN_RED_HAIR = "\U0001f468\u200d\U0001f9b0"
 MAN_LIGHT_SKIN_TONE_RED_HAIR = "\U0001f468\U0001f3fb\u200d\U0001f9b0"
 MAN_MEDIUM_LIGHT_SKIN_TONE_RED_HAIR = "\U0001f468\U0001f3fc\u200d\U0001f9b0"
@@ -1115,6 +1207,12 @@
 WOMAN_CONSTRUCTION_WORKER_MEDIUM_SKIN_TONE = "\U0001f477\U0001f3fd\u200d\u2640\ufe0f"
 WOMAN_CONSTRUCTION_WORKER_MEDIUM_DARK_SKIN_TONE = "\U0001f477\U0001f3fe\u200d\u2640\ufe0f"
 WOMAN_CONSTRUCTION_WORKER_DARK_SKIN_TONE = "\U0001f477\U0001f3ff\u200d\u2640\ufe0f"
+PERSON_WITH_CROWN = "\U0001fac5"
+PERSON_WITH_CROWN_LIGHT_SKIN_TONE = "\U0001fac5\U0001f3fb"
+PERSON_WITH_CROWN_MEDIUM_LIGHT_SKIN_TONE = "\U0001fac5\U0001f3fc"
+PERSON_WITH_CROWN_MEDIUM_SKIN_TONE = "\U0001fac5\U0001f3fd"
+PERSON_WITH_CROWN_MEDIUM_DARK_SKIN_TONE = "\U0001fac5\U0001f3fe"
+PERSON_WITH_CROWN_DARK_SKIN_TONE = "\U0001fac5\U0001f3ff"
 PRINCE = "\U0001f934"
 PRINCE_LIGHT_SKIN_TONE = "\U0001f934\U0001f3fb"
 PRINCE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f934\U0001f3fc"
@@ -1199,6 +1297,18 @@
 PREGNANT_WOMAN_MEDIUM_SKIN_TONE = "\U0001f930\U0001f3fd"
 PREGNANT_WOMAN_MEDIUM_DARK_SKIN_TONE = "\U0001f930\U0001f3fe"
 PREGNANT_WOMAN_DARK_SKIN_TONE = "\U0001f930\U0001f3ff"
+PREGNANT_MAN = "\U0001fac3"
+PREGNANT_MAN_LIGHT_SKIN_TONE = "\U0001fac3\U0001f3fb"
+PREGNANT_MAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001fac3\U0001f3fc"
+PREGNANT_MAN_MEDIUM_SKIN_TONE = "\U0001fac3\U0001f3fd"
+PREGNANT_MAN_MEDIUM_DARK_SKIN_TONE = "\U0001fac3\U0001f3fe"
+PREGNANT_MAN_DARK_SKIN_TONE = "\U0001fac3\U0001f3ff"
+PREGNANT_PERSON = "\U0001fac4"
+PREGNANT_PERSON_LIGHT_SKIN_TONE = "\U0001fac4\U0001f3fb"
+PREGNANT_PERSON_MEDIUM_LIGHT_SKIN_TONE = "\U0001fac4\U0001f3fc"
+PREGNANT_PERSON_MEDIUM_SKIN_TONE = "\U0001fac4\U0001f3fd"
+PREGNANT_PERSON_MEDIUM_DARK_SKIN_TONE = "\U0001fac4\U0001f3fe"
+PREGNANT_PERSON_DARK_SKIN_TONE = "\U0001fac4\U0001f3ff"
 BREAST_FEEDING = "\U0001f931"
 BREAST_FEEDING_LIGHT_SKIN_TONE = "\U0001f931\U0001f3fb"
 BREAST_FEEDING_MEDIUM_LIGHT_SKIN_TONE = "\U0001f931\U0001f3fc"
@@ -1379,6 +1489,7 @@
 ZOMBIE = "\U0001f9df"
 MAN_ZOMBIE = "\U0001f9df\u200d\u2642\ufe0f"
 WOMAN_ZOMBIE = "\U0001f9df\u200d\u2640\ufe0f"
+TROLL = "\U0001f9cc"
 PERSON_GETTING_MASSAGE = "\U0001f486"
 PERSON_GETTING_MASSAGE_LIGHT_SKIN_TONE = "\U0001f486\U0001f3fb"
 PERSON_GETTING_MASSAGE_MEDIUM_LIGHT_SKIN_TONE = "\U0001f486\U0001f3fc"
@@ -1775,6 +1886,8 @@
 WOMAN_CARTWHEELING_MEDIUM_DARK_SKIN_TONE = "\U0001f938\U0001f3fe\u200d\u2640\ufe0f"
 WOMAN_CARTWHEELING_DARK_SKIN_TONE = "\U0001f938\U0001f3ff\u200d\u2640\ufe0f"
 PEOPLE_WRESTLING = "\U0001f93c"
+MEN_WRESTLING = "\U0001f93c\u200d\u2642\ufe0f"
+WOMEN_WRESTLING = "\U0001f93c\u200d\u2640\ufe0f"
 PERSON_PLAYING_WATER_POLO = "\U0001f93d"
 PERSON_PLAYING_WATER_POLO_LIGHT_SKIN_TONE = "\U0001f93d\U0001f3fb"
 PERSON_PLAYING_WATER_POLO_MEDIUM_LIGHT_SKIN_TONE = "\U0001f93d\U0001f3fc"
@@ -2028,21 +2141,388 @@
     "\U0001f468\U0001f3ff\u200d\U0001f91d\u200d\U0001f468\U0001f3fe"
 MEN_HOLDING_HANDS_DARK_SKIN_TONE = "\U0001f46c\U0001f3ff"
 KISS = "\U0001f48f"
+KISS_LIGHT_SKIN_TONE = "\U0001f48f\U0001f3fb"
+KISS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f48f\U0001f3fc"
+KISS_MEDIUM_SKIN_TONE = "\U0001f48f\U0001f3fd"
+KISS_MEDIUM_DARK_SKIN_TONE = "\U0001f48f\U0001f3fe"
+KISS_DARK_SKIN_TONE = "\U0001f48f\U0001f3ff"
+KISS_PERSON_PERSON_LIGHT_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
+    "\U0001f9d1\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fc"
+KISS_PERSON_PERSON_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \
+    "\U0001f9d1\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fd"
+KISS_PERSON_PERSON_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
+    "\U0001f9d1\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fe"
+KISS_PERSON_PERSON_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \
+    "\U0001f9d1\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3ff"
+KISS_PERSON_PERSON_MEDIUM_LIGHT_SKIN_TONE_LIGHT_SKIN_TONE = \
+    "\U0001f9d1\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fb"
+KISS_PERSON_PERSON_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \
+    "\U0001f9d1\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fd"
+KISS_PERSON_PERSON_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
+    "\U0001f9d1\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fe"
+KISS_PERSON_PERSON_MEDIUM_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \
+    "\U0001f9d1\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3ff"
+KISS_PERSON_PERSON_MEDIUM_SKIN_TONE_LIGHT_SKIN_TONE = \
+    "\U0001f9d1\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fb"
+KISS_PERSON_PERSON_MEDIUM_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
+    "\U0001f9d1\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fc"
+KISS_PERSON_PERSON_MEDIUM_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
+    "\U0001f9d1\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fe"
+KISS_PERSON_PERSON_MEDIUM_SKIN_TONE_DARK_SKIN_TONE = \
+    "\U0001f9d1\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3ff"
+KISS_PERSON_PERSON_MEDIUM_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \
+    "\U0001f9d1\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fb"
+KISS_PERSON_PERSON_MEDIUM_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
+    "\U0001f9d1\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fc"
+KISS_PERSON_PERSON_MEDIUM_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \
+    "\U0001f9d1\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fd"
+KISS_PERSON_PERSON_MEDIUM_DARK_SKIN_TONE_DARK_SKIN_TONE = \
+    "\U0001f9d1\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3ff"
+KISS_PERSON_PERSON_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \
+    "\U0001f9d1\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fb"
+KISS_PERSON_PERSON_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
+    "\U0001f9d1\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fc"
+KISS_PERSON_PERSON_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \
+    "\U0001f9d1\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fd"
+KISS_PERSON_PERSON_DARK_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
+    "\U0001f9d1\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fe"
 KISS_WOMAN_MAN = "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468"
+KISS_WOMAN_MAN_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb"
+KISS_WOMAN_MAN_LIGHT_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
+    "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc"
+KISS_WOMAN_MAN_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \
+    "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd"
+KISS_WOMAN_MAN_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
+    "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe"
+KISS_WOMAN_MAN_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \
+    "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff"
+KISS_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE_LIGHT_SKIN_TONE = \
+    "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb"
+KISS_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE = \
+    "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc"
+KISS_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \
+    "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd"
+KISS_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
+    "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe"
+KISS_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \
+    "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff"
+KISS_WOMAN_MAN_MEDIUM_SKIN_TONE_LIGHT_SKIN_TONE = \
+    "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb"
+KISS_WOMAN_MAN_MEDIUM_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
+    "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc"
+KISS_WOMAN_MAN_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd"
+KISS_WOMAN_MAN_MEDIUM_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
+    "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe"
+KISS_WOMAN_MAN_MEDIUM_SKIN_TONE_DARK_SKIN_TONE = \
+    "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff"
+KISS_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \
+    "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb"
+KISS_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
+    "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc"
+KISS_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \
+    "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd"
+KISS_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE = \
+    "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe"
+KISS_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE_DARK_SKIN_TONE = \
+    "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff"
+KISS_WOMAN_MAN_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \
+    "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb"
+KISS_WOMAN_MAN_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
+    "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc"
+KISS_WOMAN_MAN_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \
+    "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd"
+KISS_WOMAN_MAN_DARK_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
+    "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe"
+KISS_WOMAN_MAN_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff"
 KISS_MAN_MAN = "\U0001f468\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468"
+KISS_MAN_MAN_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb"
+KISS_MAN_MAN_LIGHT_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
+    "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc"
+KISS_MAN_MAN_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \
+    "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd"
+KISS_MAN_MAN_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
+    "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe"
+KISS_MAN_MAN_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \
+    "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff"
+KISS_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE_LIGHT_SKIN_TONE = \
+    "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb"
+KISS_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE = \
+    "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc"
+KISS_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \
+    "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd"
+KISS_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
+    "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe"
+KISS_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \
+    "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff"
+KISS_MAN_MAN_MEDIUM_SKIN_TONE_LIGHT_SKIN_TONE = \
+    "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb"
+KISS_MAN_MAN_MEDIUM_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
+    "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc"
+KISS_MAN_MAN_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd"
+KISS_MAN_MAN_MEDIUM_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
+    "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe"
+KISS_MAN_MAN_MEDIUM_SKIN_TONE_DARK_SKIN_TONE = \
+    "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff"
+KISS_MAN_MAN_MEDIUM_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \
+    "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb"
+KISS_MAN_MAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
+    "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc"
+KISS_MAN_MAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \
+    "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd"
+KISS_MAN_MAN_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe"
+KISS_MAN_MAN_MEDIUM_DARK_SKIN_TONE_DARK_SKIN_TONE = \
+    "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff"
+KISS_MAN_MAN_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \
+    "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb"
+KISS_MAN_MAN_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
+    "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc"
+KISS_MAN_MAN_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \
+    "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd"
+KISS_MAN_MAN_DARK_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
+    "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe"
+KISS_MAN_MAN_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff"
 KISS_WOMAN_WOMAN = "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469"
+KISS_WOMAN_WOMAN_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fb"
+KISS_WOMAN_WOMAN_LIGHT_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
+    "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fc"
+KISS_WOMAN_WOMAN_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \
+    "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fd"
+KISS_WOMAN_WOMAN_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
+    "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fe"
+KISS_WOMAN_WOMAN_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \
+    "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3ff"
+KISS_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE_LIGHT_SKIN_TONE = \
+    "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fb"
+KISS_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE = \
+    "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fc"
+KISS_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \
+    "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fd"
+KISS_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
+    "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fe"
+KISS_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \
+    "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3ff"
+KISS_WOMAN_WOMAN_MEDIUM_SKIN_TONE_LIGHT_SKIN_TONE = \
+    "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fb"
+KISS_WOMAN_WOMAN_MEDIUM_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
+    "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fc"
+KISS_WOMAN_WOMAN_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fd"
+KISS_WOMAN_WOMAN_MEDIUM_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
+    "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fe"
+KISS_WOMAN_WOMAN_MEDIUM_SKIN_TONE_DARK_SKIN_TONE = \
+    "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3ff"
+KISS_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \
+    "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fb"
+KISS_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
+    "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fc"
+KISS_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \
+    "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fd"
+KISS_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE = \
+    "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fe"
+KISS_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE_DARK_SKIN_TONE = \
+    "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3ff"
+KISS_WOMAN_WOMAN_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \
+    "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fb"
+KISS_WOMAN_WOMAN_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
+    "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fc"
+KISS_WOMAN_WOMAN_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \
+    "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fd"
+KISS_WOMAN_WOMAN_DARK_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
+    "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fe"
+KISS_WOMAN_WOMAN_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3ff"
 COUPLE_WITH_HEART = "\U0001f491"
+COUPLE_WITH_HEART_LIGHT_SKIN_TONE = "\U0001f491\U0001f3fb"
+COUPLE_WITH_HEART_MEDIUM_LIGHT_SKIN_TONE = "\U0001f491\U0001f3fc"
+COUPLE_WITH_HEART_MEDIUM_SKIN_TONE = "\U0001f491\U0001f3fd"
+COUPLE_WITH_HEART_MEDIUM_DARK_SKIN_TONE = "\U0001f491\U0001f3fe"
+COUPLE_WITH_HEART_DARK_SKIN_TONE = "\U0001f491\U0001f3ff"
+COUPLE_WITH_HEART_PERSON_PERSON_LIGHT_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
+    "\U0001f9d1\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fc"
+COUPLE_WITH_HEART_PERSON_PERSON_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \
+    "\U0001f9d1\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fd"
+COUPLE_WITH_HEART_PERSON_PERSON_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
+    "\U0001f9d1\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fe"
+COUPLE_WITH_HEART_PERSON_PERSON_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \
+    "\U0001f9d1\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3ff"
+COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_LIGHT_SKIN_TONE_LIGHT_SKIN_TONE = \
+    "\U0001f9d1\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fb"
+COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \
+    "\U0001f9d1\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fd"
+COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
+    "\U0001f9d1\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fe"
+COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \
+    "\U0001f9d1\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3ff"
+COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_SKIN_TONE_LIGHT_SKIN_TONE = \
+    "\U0001f9d1\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fb"
+COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
+    "\U0001f9d1\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fc"
+COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
+    "\U0001f9d1\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fe"
+COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_SKIN_TONE_DARK_SKIN_TONE = \
+    "\U0001f9d1\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3ff"
+COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \
+    "\U0001f9d1\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fb"
+COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
+    "\U0001f9d1\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fc"
+COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \
+    "\U0001f9d1\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fd"
+COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_DARK_SKIN_TONE_DARK_SKIN_TONE = \
+    "\U0001f9d1\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3ff"
+COUPLE_WITH_HEART_PERSON_PERSON_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \
+    "\U0001f9d1\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fb"
+COUPLE_WITH_HEART_PERSON_PERSON_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
+    "\U0001f9d1\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fc"
+COUPLE_WITH_HEART_PERSON_PERSON_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \
+    "\U0001f9d1\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fd"
+COUPLE_WITH_HEART_PERSON_PERSON_DARK_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
+    "\U0001f9d1\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fe"
 COUPLE_WITH_HEART_WOMAN_MAN = "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f468"
-COUPLE_WITH_HEART_MAN_MAN = "\U0001f468\u200d\u2764\ufe0f\u200d\U0001f468"
-COUPLE_WITH_HEART_WOMAN_WOMAN = "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f469"
-FAMILY = "\U0001f46a"
-FAMILY_MAN_WOMAN_BOY = "\U0001f468\u200d\U0001f469\u200d\U0001f466"
-FAMILY_MAN_WOMAN_GIRL = "\U0001f468\u200d\U0001f469\u200d\U0001f467"
-FAMILY_MAN_WOMAN_GIRL_BOY = "\U0001f468\u200d\U0001f469\u200d\U0001f467\u200d\U0001f466"
-FAMILY_MAN_WOMAN_BOY_BOY = "\U0001f468\u200d\U0001f469\u200d\U0001f466\u200d\U0001f466"
-FAMILY_MAN_WOMAN_GIRL_GIRL = "\U0001f468\u200d\U0001f469\u200d\U0001f467\u200d\U0001f467"
-FAMILY_MAN_MAN_BOY = "\U0001f468\u200d\U0001f468\u200d\U0001f466"
-FAMILY_MAN_MAN_GIRL = "\U0001f468\u200d\U0001f468\u200d\U0001f467"
+COUPLE_WITH_HEART_WOMAN_MAN_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb"
+COUPLE_WITH_HEART_WOMAN_MAN_LIGHT_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
+    "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc"
+COUPLE_WITH_HEART_WOMAN_MAN_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \
+    "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd"
+COUPLE_WITH_HEART_WOMAN_MAN_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
+    "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe"
+COUPLE_WITH_HEART_WOMAN_MAN_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \
+    "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff"
+COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE_LIGHT_SKIN_TONE = \
+    "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb"
+COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc"
+COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \
+    "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd"
+COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
+    "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe"
+COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \
+    "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff"
+COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_SKIN_TONE_LIGHT_SKIN_TONE = \
+    "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb"
+COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
+    "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc"
+COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd"
+COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
+    "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe"
+COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_SKIN_TONE_DARK_SKIN_TONE = \
+    "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff"
+COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \
+    "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb"
+COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
+    "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc"
+COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \
+    "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd"
+COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe"
+COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE_DARK_SKIN_TONE = \
+    "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff"
+COUPLE_WITH_HEART_WOMAN_MAN_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \
+    "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb"
+COUPLE_WITH_HEART_WOMAN_MAN_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
+    "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc"
+COUPLE_WITH_HEART_WOMAN_MAN_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \
+    "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd"
+COUPLE_WITH_HEART_WOMAN_MAN_DARK_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
+    "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe"
+COUPLE_WITH_HEART_WOMAN_MAN_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff"
+COUPLE_WITH_HEART_MAN_MAN = "\U0001f468\u200d\u2764\ufe0f\u200d\U0001f468"
+COUPLE_WITH_HEART_MAN_MAN_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb"
+COUPLE_WITH_HEART_MAN_MAN_LIGHT_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
+    "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc"
+COUPLE_WITH_HEART_MAN_MAN_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \
+    "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd"
+COUPLE_WITH_HEART_MAN_MAN_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
+    "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe"
+COUPLE_WITH_HEART_MAN_MAN_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \
+    "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff"
+COUPLE_WITH_HEART_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE_LIGHT_SKIN_TONE = \
+    "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb"
+COUPLE_WITH_HEART_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc"
+COUPLE_WITH_HEART_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \
+    "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd"
+COUPLE_WITH_HEART_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
+    "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe"
+COUPLE_WITH_HEART_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \
+    "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff"
+COUPLE_WITH_HEART_MAN_MAN_MEDIUM_SKIN_TONE_LIGHT_SKIN_TONE = \
+    "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb"
+COUPLE_WITH_HEART_MAN_MAN_MEDIUM_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
+    "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc"
+COUPLE_WITH_HEART_MAN_MAN_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd"
+COUPLE_WITH_HEART_MAN_MAN_MEDIUM_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
+    "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe"
+COUPLE_WITH_HEART_MAN_MAN_MEDIUM_SKIN_TONE_DARK_SKIN_TONE = \
+    "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff"
+COUPLE_WITH_HEART_MAN_MAN_MEDIUM_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \
+    "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb"
+COUPLE_WITH_HEART_MAN_MAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
+    "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc"
+COUPLE_WITH_HEART_MAN_MAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \
+    "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd"
+COUPLE_WITH_HEART_MAN_MAN_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe"
+COUPLE_WITH_HEART_MAN_MAN_MEDIUM_DARK_SKIN_TONE_DARK_SKIN_TONE = \
+    "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff"
+COUPLE_WITH_HEART_MAN_MAN_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \
+    "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb"
+COUPLE_WITH_HEART_MAN_MAN_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
+    "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc"
+COUPLE_WITH_HEART_MAN_MAN_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \
+    "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd"
+COUPLE_WITH_HEART_MAN_MAN_DARK_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
+    "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe"
+COUPLE_WITH_HEART_MAN_MAN_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff"
+COUPLE_WITH_HEART_WOMAN_WOMAN = "\U0001f469\u200d\u2764\ufe0f\u200d\U0001f469"
+COUPLE_WITH_HEART_WOMAN_WOMAN_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fb"
+COUPLE_WITH_HEART_WOMAN_WOMAN_LIGHT_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
+    "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fc"
+COUPLE_WITH_HEART_WOMAN_WOMAN_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \
+    "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fd"
+COUPLE_WITH_HEART_WOMAN_WOMAN_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
+    "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fe"
+COUPLE_WITH_HEART_WOMAN_WOMAN_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \
+    "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3ff"
+COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE_LIGHT_SKIN_TONE = \
+    "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fb"
+COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE = \
+    "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fc"
+COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \
+    "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fd"
+COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
+    "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fe"
+COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \
+    "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3ff"
+COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_SKIN_TONE_LIGHT_SKIN_TONE = \
+    "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fb"
+COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
+    "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fc"
+COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fd"
+COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
+    "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fe"
+COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_SKIN_TONE_DARK_SKIN_TONE = \
+    "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3ff"
+COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \
+    "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fb"
+COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
+    "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fc"
+COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \
+    "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fd"
+COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE = \
+    "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fe"
+COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE_DARK_SKIN_TONE = \
+    "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3ff"
+COUPLE_WITH_HEART_WOMAN_WOMAN_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \
+    "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fb"
+COUPLE_WITH_HEART_WOMAN_WOMAN_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
+    "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fc"
+COUPLE_WITH_HEART_WOMAN_WOMAN_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \
+    "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fd"
+COUPLE_WITH_HEART_WOMAN_WOMAN_DARK_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
+    "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fe"
+COUPLE_WITH_HEART_WOMAN_WOMAN_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3ff"
+FAMILY = "\U0001f46a"
+FAMILY_MAN_WOMAN_BOY = "\U0001f468\u200d\U0001f469\u200d\U0001f466"
+FAMILY_MAN_WOMAN_GIRL = "\U0001f468\u200d\U0001f469\u200d\U0001f467"
+FAMILY_MAN_WOMAN_GIRL_BOY = "\U0001f468\u200d\U0001f469\u200d\U0001f467\u200d\U0001f466"
+FAMILY_MAN_WOMAN_BOY_BOY = "\U0001f468\u200d\U0001f469\u200d\U0001f466\u200d\U0001f466"
+FAMILY_MAN_WOMAN_GIRL_GIRL = "\U0001f468\u200d\U0001f469\u200d\U0001f467\u200d\U0001f467"
+FAMILY_MAN_MAN_BOY = "\U0001f468\u200d\U0001f468\u200d\U0001f466"
+FAMILY_MAN_MAN_GIRL = "\U0001f468\u200d\U0001f468\u200d\U0001f467"
 FAMILY_MAN_MAN_GIRL_BOY = "\U0001f468\u200d\U0001f468\u200d\U0001f467\u200d\U0001f466"
 FAMILY_MAN_MAN_BOY_BOY = "\U0001f468\u200d\U0001f468\u200d\U0001f466\u200d\U0001f466"
 FAMILY_MAN_MAN_GIRL_GIRL = "\U0001f468\u200d\U0001f468\u200d\U0001f467\u200d\U0001f467"
@@ -2176,6 +2656,7 @@
 SHARK = "\U0001f988"
 OCTOPUS = "\U0001f419"
 SPIRAL_SHELL = "\U0001f41a"
+CORAL = "\U0001fab8"
 SNAIL = "\U0001f40c"
 BUTTERFLY = "\U0001f98b"
 BUG = "\U0001f41b"
@@ -2195,6 +2676,7 @@
 BOUQUET = "\U0001f490"
 CHERRY_BLOSSOM = "\U0001f338"
 WHITE_FLOWER = "\U0001f4ae"
+LOTUS = "\U0001fab7"
 ROSETTE = "\U0001f3f5\ufe0f"
 ROSE = "\U0001f339"
 WILTED_FLOWER = "\U0001f940"
@@ -2215,6 +2697,8 @@
 MAPLE_LEAF = "\U0001f341"
 FALLEN_LEAF = "\U0001f342"
 LEAF_FLUTTERING_IN_WIND = "\U0001f343"
+EMPTY_NEST = "\U0001fab9"
+NEST_WITH_EGGS = "\U0001faba"
 GRAPES = "\U0001f347"
 MELON = "\U0001f348"
 WATERMELON = "\U0001f349"
@@ -2248,6 +2732,7 @@
 ONION = "\U0001f9c5"
 MUSHROOM = "\U0001f344"
 PEANUTS = "\U0001f95c"
+BEANS = "\U0001fad8"
 CHESTNUT = "\U0001f330"
 BREAD = "\U0001f35e"
 CROISSANT = "\U0001f950"
@@ -2333,6 +2818,7 @@
 CLINKING_BEER_MUGS = "\U0001f37b"
 CLINKING_GLASSES = "\U0001f942"
 TUMBLER_GLASS = "\U0001f943"
+POURING_LIQUID = "\U0001fad7"
 CUP_WITH_STRAW = "\U0001f964"
 BUBBLE_TEA = "\U0001f9cb"
 BEVERAGE_BOX = "\U0001f9c3"
@@ -2343,6 +2829,7 @@
 FORK_AND_KNIFE = "\U0001f374"
 SPOON = "\U0001f944"
 KITCHEN_KNIFE = "\U0001f52a"
+JAR = "\U0001fad9"
 AMPHORA = "\U0001f3fa"
 GLOBE_SHOWING_EUROPE_AFRICA = "\U0001f30d"
 GLOBE_SHOWING_AMERICAS = "\U0001f30e"
@@ -2405,6 +2892,7 @@
 BRIDGE_AT_NIGHT = "\U0001f309"
 HOT_SPRINGS = "\u2668\ufe0f"
 CAROUSEL_HORSE = "\U0001f3a0"
+PLAYGROUND_SLIDE = "\U0001f6dd"
 FERRIS_WHEEL = "\U0001f3a1"
 ROLLER_COASTER = "\U0001f3a2"
 BARBER_POLE = "\U0001f488"
@@ -2453,12 +2941,14 @@
 RAILWAY_TRACK = "\U0001f6e4\ufe0f"
 OIL_DRUM = "\U0001f6e2\ufe0f"
 FUEL_PUMP = "\u26fd"
+WHEEL = "\U0001f6de"
 POLICE_CAR_LIGHT = "\U0001f6a8"
 HORIZONTAL_TRAFFIC_LIGHT = "\U0001f6a5"
 VERTICAL_TRAFFIC_LIGHT = "\U0001f6a6"
 STOP_SIGN = "\U0001f6d1"
 CONSTRUCTION = "\U0001f6a7"
 ANCHOR = "\u2693"
+RING_BUOY = "\U0001f6df"
 SAILBOAT = "\u26f5"
 CANOE = "\U0001f6f6"
 SPEEDBOAT = "\U0001f6a4"
@@ -2613,13 +3103,14 @@
 SKIS = "\U0001f3bf"
 SLED = "\U0001f6f7"
 CURLING_STONE = "\U0001f94c"
-DIRECT_HIT = "\U0001f3af"
+BULLSEYE = "\U0001f3af"
 YO_YO = "\U0001fa80"
 KITE = "\U0001fa81"
 POOL_8_BALL = "\U0001f3b1"
 CRYSTAL_BALL = "\U0001f52e"
 MAGIC_WAND = "\U0001fa84"
 NAZAR_AMULET = "\U0001f9ff"
+HAMSA = "\U0001faac"
 VIDEO_GAME = "\U0001f3ae"
 JOYSTICK = "\U0001f579\ufe0f"
 SLOT_MACHINE = "\U0001f3b0"
@@ -2627,6 +3118,7 @@
 PUZZLE_PIECE = "\U0001f9e9"
 TEDDY_BEAR = "\U0001f9f8"
 PINATA = "\U0001fa85"
+MIRROR_BALL = "\U0001faa9"
 NESTING_DOLLS = "\U0001fa86"
 SPADE_SUIT = "\u2660\ufe0f"
 HEART_SUIT = "\u2665\ufe0f"
@@ -2722,6 +3214,7 @@
 PAGER = "\U0001f4df"
 FAX_MACHINE = "\U0001f4e0"
 BATTERY = "\U0001f50b"
+LOW_BATTERY = "\U0001faab"
 ELECTRIC_PLUG = "\U0001f50c"
 LAPTOP = "\U0001f4bb"
 DESKTOP_COMPUTER = "\U0001f5a5\ufe0f"
@@ -2833,7 +3326,7 @@
 HAMMER_AND_WRENCH = "\U0001f6e0\ufe0f"
 DAGGER = "\U0001f5e1\ufe0f"
 CROSSED_SWORDS = "\u2694\ufe0f"
-PISTOL = "\U0001f52b"
+WATER_PISTOL = "\U0001f52b"
 BOOMERANG = "\U0001fa83"
 BOW_AND_ARROW = "\U0001f3f9"
 SHIELD = "\U0001f6e1\ufe0f"
@@ -2862,7 +3355,9 @@
 DROP_OF_BLOOD = "\U0001fa78"
 PILL = "\U0001f48a"
 ADHESIVE_BANDAGE = "\U0001fa79"
+CRUTCH = "\U0001fa7c"
 STETHOSCOPE = "\U0001fa7a"
+X_RAY = "\U0001fa7b"
 DOOR = "\U0001f6aa"
 ELEVATOR = "\U0001f6d7"
 MIRROR = "\U0001fa9e"
@@ -2883,6 +3378,7 @@
 ROLL_OF_PAPER = "\U0001f9fb"
 BUCKET = "\U0001faa3"
 SOAP = "\U0001f9fc"
+BUBBLES = "\U0001fae7"
 TOOTHBRUSH = "\U0001faa5"
 SPONGE = "\U0001f9fd"
 FIRE_EXTINGUISHER = "\U0001f9ef"
@@ -2893,6 +3389,7 @@
 FUNERAL_URN = "\u26b1\ufe0f"
 MOAI = "\U0001f5ff"
 PLACARD = "\U0001faa7"
+IDENTIFICATION_CARD = "\U0001faaa"
 ATM_SIGN = "\U0001f3e7"
 LITTER_IN_BIN_SIGN = "\U0001f6ae"
 POTABLE_WATER = "\U0001f6b0"
@@ -2996,13 +3493,14 @@
 PLUS = "\u2795"
 MINUS = "\u2796"
 DIVIDE = "\u2797"
+HEAVY_EQUALS_SIGN = "\U0001f7f0"
 INFINITY = "\u267e\ufe0f"
 DOUBLE_EXCLAMATION_MARK = "\u203c\ufe0f"
 EXCLAMATION_QUESTION_MARK = "\u2049\ufe0f"
-QUESTION_MARK = "\u2753"
+RED_QUESTION_MARK = "\u2753"
 WHITE_QUESTION_MARK = "\u2754"
 WHITE_EXCLAMATION_MARK = "\u2755"
-EXCLAMATION_MARK = "\u2757"
+RED_EXCLAMATION_MARK = "\u2757"
 WAVY_DASH = "\u3030\ufe0f"
 CURRENCY_EXCHANGE = "\U0001f4b1"
 HEAVY_DOLLAR_SIGN = "\U0001f4b2"
@@ -3408,498 +3906,114 @@
 REGIONAL_INDICATOR_SYMBOL_LETTER_X = "\U0001f1fd"
 REGIONAL_INDICATOR_SYMBOL_LETTER_Y = "\U0001f1fe"
 REGIONAL_INDICATOR_SYMBOL_LETTER_Z = "\U0001f1ff"
-FACE_EXHALING = "\U0001f62e\u200d\U0001f4a8"
-FACE_WITH_SPIRAL_EYES = "\U0001f635\u200d\U0001f4ab"
-FACE_IN_CLOUDS = "\U0001f636\u200d\U0001f32b"
-HEART_ON_FIRE = "\u2764\ufe0f\u200d\U0001f525"
-MENDING_HEART = "\u2764\ufe0f\u200d\U0001fa79"
-WOMAN_BEARD = "\U0001f9d4\u200d\u2640\ufe0f"
-WOMAN_LIGHT_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fb\u200d\u2640\ufe0f"
-WOMAN_MEDIUM_LIGHT_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fc\u200d\u2640\ufe0f"
-WOMAN_MEDIUM_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fd\u200d\u2640\ufe0f"
-WOMAN_MEDIUM_DARK_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fe\u200d\u2640\ufe0f"
-WOMAN_DARK_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3ff\u200d\u2640\ufe0f"
-MAN_BEARD = "\U0001f9d4\u200d\u2642\ufe0f"
-MAN_LIGHT_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fb\u200d\u2642\ufe0f"
-MAN_MEDIUM_LIGHT_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fc\u200d\u2642\ufe0f"
-MAN_MEDIUM_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fd\u200d\u2642\ufe0f"
-MAN_MEDIUM_DARK_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3fe\u200d\u2642\ufe0f"
-MAN_DARK_SKIN_TONE_BEARD = "\U0001f9d4\U0001f3ff\u200d\u2642\ufe0f"
-COUPLE_WITH_HEART_LIGHT_SKIN_TONE = "\U0001f491\U0001f3fb"
-COUPLE_WITH_HEART_MEDIUM_LIGHT_SKIN_TONE = "\U0001f491\U0001f3fc"
-COUPLE_WITH_HEART_MEDIUM_SKIN_TONE = "\U0001f491\U0001f3fd"
-COUPLE_WITH_HEART_MEDIUM_DARK_SKIN_TONE = "\U0001f491\U0001f3fe"
-COUPLE_WITH_HEART_DARK_SKIN_TONE = "\U0001f491\U0001f3ff"
-KISS_LIGHT_SKIN_TONE = "\U0001f48f\U0001f3fb"
-KISS_MEDIUM_LIGHT_SKIN_TONE = "\U0001f48f\U0001f3fc"
-KISS_MEDIUM_SKIN_TONE = "\U0001f48f\U0001f3fd"
-KISS_MEDIUM_DARK_SKIN_TONE = "\U0001f48f\U0001f3fe"
-KISS_DARK_SKIN_TONE = "\U0001f48f\U0001f3ff"
-COUPLE_WITH_HEART_MAN_MAN_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb"
-COUPLE_WITH_HEART_MAN_MAN_LIGHT_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
-    "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc"
-COUPLE_WITH_HEART_MAN_MAN_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \
-    "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd"
-COUPLE_WITH_HEART_MAN_MAN_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
-    "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe"
-COUPLE_WITH_HEART_MAN_MAN_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \
-    "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff"
-COUPLE_WITH_HEART_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE_LIGHT_SKIN_TONE = \
-    "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb"
-COUPLE_WITH_HEART_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc"
-COUPLE_WITH_HEART_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \
-    "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd"
-COUPLE_WITH_HEART_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
-    "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe"
-COUPLE_WITH_HEART_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \
-    "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff"
-COUPLE_WITH_HEART_MAN_MAN_MEDIUM_SKIN_TONE_LIGHT_SKIN_TONE = \
-    "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb"
-COUPLE_WITH_HEART_MAN_MAN_MEDIUM_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
-    "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc"
-COUPLE_WITH_HEART_MAN_MAN_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd"
-COUPLE_WITH_HEART_MAN_MAN_MEDIUM_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
-    "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe"
-COUPLE_WITH_HEART_MAN_MAN_MEDIUM_SKIN_TONE_DARK_SKIN_TONE = \
-    "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff"
-COUPLE_WITH_HEART_MAN_MAN_MEDIUM_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \
-    "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb"
-COUPLE_WITH_HEART_MAN_MAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
-    "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc"
-COUPLE_WITH_HEART_MAN_MAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \
-    "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd"
-COUPLE_WITH_HEART_MAN_MAN_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe"
-COUPLE_WITH_HEART_MAN_MAN_MEDIUM_DARK_SKIN_TONE_DARK_SKIN_TONE = \
-    "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff"
-COUPLE_WITH_HEART_MAN_MAN_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \
-    "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb"
-COUPLE_WITH_HEART_MAN_MAN_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
-    "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc"
-COUPLE_WITH_HEART_MAN_MAN_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \
-    "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd"
-COUPLE_WITH_HEART_MAN_MAN_DARK_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
-    "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe"
-COUPLE_WITH_HEART_MAN_MAN_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff"
-COUPLE_WITH_HEART_WOMAN_MAN_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb"
-COUPLE_WITH_HEART_WOMAN_MAN_LIGHT_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
-    "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc"
-COUPLE_WITH_HEART_WOMAN_MAN_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \
-    "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd"
-COUPLE_WITH_HEART_WOMAN_MAN_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
-    "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe"
-COUPLE_WITH_HEART_WOMAN_MAN_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \
-    "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff"
-COUPLE_WITH_HEART_WOMAN_WOMAN_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fb"
-COUPLE_WITH_HEART_WOMAN_WOMAN_LIGHT_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
-    "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fc"
-COUPLE_WITH_HEART_WOMAN_WOMAN_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \
-    "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fd"
-COUPLE_WITH_HEART_WOMAN_WOMAN_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
-    "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fe"
-COUPLE_WITH_HEART_WOMAN_WOMAN_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \
-    "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3ff"
-COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE_LIGHT_SKIN_TONE = \
-    "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb"
-COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc"
-COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \
-    "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd"
-COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
-    "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe"
-COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \
-    "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff"
-COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE_LIGHT_SKIN_TONE = \
-    "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fb"
-COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE = \
-    "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fc"
-COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \
-    "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fd"
-COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
-    "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fe"
-COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \
-    "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3ff"
-COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_SKIN_TONE_LIGHT_SKIN_TONE = \
-    "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb"
-COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
-    "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc"
-COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd"
-COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
-    "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe"
-COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_SKIN_TONE_DARK_SKIN_TONE = \
-    "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff"
-COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_SKIN_TONE_LIGHT_SKIN_TONE = \
-    "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fb"
-COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
-    "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fc"
-COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fd"
-COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
-    "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fe"
-COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_SKIN_TONE_DARK_SKIN_TONE = \
-    "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3ff"
-COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \
-    "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb"
-COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
-    "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc"
-COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \
-    "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd"
-COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE = "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe"
-COUPLE_WITH_HEART_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE_DARK_SKIN_TONE = \
-    "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff"
-COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \
-    "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fb"
-COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
-    "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fc"
-COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \
-    "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fd"
-COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE = \
-    "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fe"
-COUPLE_WITH_HEART_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE_DARK_SKIN_TONE = \
-    "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3ff"
-COUPLE_WITH_HEART_WOMAN_MAN_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \
-    "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fb"
-COUPLE_WITH_HEART_WOMAN_MAN_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
-    "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fc"
-COUPLE_WITH_HEART_WOMAN_MAN_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \
-    "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fd"
-COUPLE_WITH_HEART_WOMAN_MAN_DARK_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
-    "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3fe"
-COUPLE_WITH_HEART_WOMAN_MAN_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f468\U0001f3ff"
-COUPLE_WITH_HEART_WOMAN_WOMAN_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \
-    "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fb"
-COUPLE_WITH_HEART_WOMAN_WOMAN_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
-    "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fc"
-COUPLE_WITH_HEART_WOMAN_WOMAN_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \
-    "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fd"
-COUPLE_WITH_HEART_WOMAN_WOMAN_DARK_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
-    "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3fe"
-COUPLE_WITH_HEART_WOMAN_WOMAN_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f469\U0001f3ff"
-COUPLE_WITH_HEART_PERSON_PERSON_LIGHT_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
-    "\U0001f9d1\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fc"
-COUPLE_WITH_HEART_PERSON_PERSON_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \
-    "\U0001f9d1\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fd"
-COUPLE_WITH_HEART_PERSON_PERSON_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
-    "\U0001f9d1\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fe"
-COUPLE_WITH_HEART_PERSON_PERSON_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \
-    "\U0001f9d1\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3ff"
-COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_LIGHT_SKIN_TONE_LIGHT_SKIN_TONE = \
-    "\U0001f9d1\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fb"
-COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \
-    "\U0001f9d1\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fd"
-COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
-    "\U0001f9d1\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fe"
-COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \
-    "\U0001f9d1\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3ff"
-COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_SKIN_TONE_LIGHT_SKIN_TONE = \
-    "\U0001f9d1\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fb"
-COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
-    "\U0001f9d1\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fc"
-COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
-    "\U0001f9d1\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fe"
-COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_SKIN_TONE_DARK_SKIN_TONE = \
-    "\U0001f9d1\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3ff"
-COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \
-    "\U0001f9d1\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fb"
-COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
-    "\U0001f9d1\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fc"
-COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \
-    "\U0001f9d1\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fd"
-COUPLE_WITH_HEART_PERSON_PERSON_MEDIUM_DARK_SKIN_TONE_DARK_SKIN_TONE = \
-    "\U0001f9d1\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3ff"
-COUPLE_WITH_HEART_PERSON_PERSON_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \
-    "\U0001f9d1\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fb"
-COUPLE_WITH_HEART_PERSON_PERSON_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
-    "\U0001f9d1\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fc"
-COUPLE_WITH_HEART_PERSON_PERSON_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \
-    "\U0001f9d1\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fd"
-COUPLE_WITH_HEART_PERSON_PERSON_DARK_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
-    "\U0001f9d1\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f9d1\U0001f3fe"
-KISS_MAN_MAN_LIGHT_SKIN_TONE = "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb"
-KISS_MAN_MAN_LIGHT_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
-    "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc"
-KISS_MAN_MAN_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \
-    "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd"
-KISS_MAN_MAN_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
-    "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe"
-KISS_MAN_MAN_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \
-    "\U0001f468\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff"
-KISS_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE_LIGHT_SKIN_TONE = \
-    "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb"
-KISS_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE = \
-    "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc"
-KISS_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \
-    "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd"
-KISS_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
-    "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe"
-KISS_MAN_MAN_MEDIUM_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \
-    "\U0001f468\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff"
-KISS_MAN_MAN_MEDIUM_SKIN_TONE_LIGHT_SKIN_TONE = \
-    "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb"
-KISS_MAN_MAN_MEDIUM_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
-    "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc"
-KISS_MAN_MAN_MEDIUM_SKIN_TONE = "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd"
-KISS_MAN_MAN_MEDIUM_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
-    "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe"
-KISS_MAN_MAN_MEDIUM_SKIN_TONE_DARK_SKIN_TONE = \
-    "\U0001f468\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff"
-KISS_MAN_MAN_MEDIUM_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \
-    "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb"
-KISS_MAN_MAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
-    "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc"
-KISS_MAN_MAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \
-    "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd"
-KISS_MAN_MAN_MEDIUM_DARK_SKIN_TONE = "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe"
-KISS_MAN_MAN_MEDIUM_DARK_SKIN_TONE_DARK_SKIN_TONE = \
-    "\U0001f468\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff"
-KISS_MAN_MAN_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \
-    "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb"
-KISS_MAN_MAN_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
-    "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc"
-KISS_MAN_MAN_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \
-    "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd"
-KISS_MAN_MAN_DARK_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
-    "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe"
-KISS_MAN_MAN_DARK_SKIN_TONE = "\U0001f468\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff"
-KISS_WOMAN_MAN_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb"
-KISS_WOMAN_MAN_LIGHT_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
-    "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc"
-KISS_WOMAN_MAN_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \
-    "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd"
-KISS_WOMAN_MAN_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
-    "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe"
-KISS_WOMAN_MAN_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \
-    "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff"
-KISS_WOMAN_WOMAN_LIGHT_SKIN_TONE = "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fb"
-KISS_WOMAN_WOMAN_LIGHT_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
-    "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fc"
-KISS_WOMAN_WOMAN_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \
-    "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fd"
-KISS_WOMAN_WOMAN_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
-    "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fe"
-KISS_WOMAN_WOMAN_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \
-    "\U0001f469\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3ff"
-KISS_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE_LIGHT_SKIN_TONE = \
-    "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb"
-KISS_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE = \
-    "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc"
-KISS_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \
-    "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd"
-KISS_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
-    "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe"
-KISS_WOMAN_MAN_MEDIUM_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \
-    "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff"
-KISS_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE_LIGHT_SKIN_TONE = \
-    "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fb"
-KISS_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE = \
-    "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fc"
-KISS_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \
-    "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fd"
-KISS_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
-    "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fe"
-KISS_WOMAN_WOMAN_MEDIUM_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \
-    "\U0001f469\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3ff"
-KISS_WOMAN_MAN_MEDIUM_SKIN_TONE_LIGHT_SKIN_TONE = \
-    "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb"
-KISS_WOMAN_MAN_MEDIUM_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
-    "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc"
-KISS_WOMAN_MAN_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd"
-KISS_WOMAN_MAN_MEDIUM_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
-    "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe"
-KISS_WOMAN_MAN_MEDIUM_SKIN_TONE_DARK_SKIN_TONE = \
-    "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff"
-KISS_WOMAN_WOMAN_MEDIUM_SKIN_TONE_LIGHT_SKIN_TONE = \
-    "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fb"
-KISS_WOMAN_WOMAN_MEDIUM_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
-    "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fc"
-KISS_WOMAN_WOMAN_MEDIUM_SKIN_TONE = "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fd"
-KISS_WOMAN_WOMAN_MEDIUM_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
-    "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fe"
-KISS_WOMAN_WOMAN_MEDIUM_SKIN_TONE_DARK_SKIN_TONE = \
-    "\U0001f469\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3ff"
-KISS_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \
-    "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb"
-KISS_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
-    "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc"
-KISS_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \
-    "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd"
-KISS_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE = \
-    "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe"
-KISS_WOMAN_MAN_MEDIUM_DARK_SKIN_TONE_DARK_SKIN_TONE = \
-    "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff"
-KISS_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \
-    "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fb"
-KISS_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
-    "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fc"
-KISS_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \
-    "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fd"
-KISS_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE = \
-    "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fe"
-KISS_WOMAN_WOMAN_MEDIUM_DARK_SKIN_TONE_DARK_SKIN_TONE = \
-    "\U0001f469\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3ff"
-KISS_WOMAN_MAN_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \
-    "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fb"
-KISS_WOMAN_MAN_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
-    "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fc"
-KISS_WOMAN_MAN_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \
-    "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fd"
-KISS_WOMAN_MAN_DARK_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
-    "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3fe"
-KISS_WOMAN_MAN_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f468\U0001f3ff"
-KISS_WOMAN_WOMAN_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \
-    "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fb"
-KISS_WOMAN_WOMAN_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
-    "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fc"
-KISS_WOMAN_WOMAN_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \
-    "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fd"
-KISS_WOMAN_WOMAN_DARK_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
-    "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3fe"
-KISS_WOMAN_WOMAN_DARK_SKIN_TONE = "\U0001f469\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f469\U0001f3ff"
-KISS_PERSON_PERSON_LIGHT_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
-    "\U0001f9d1\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fc"
-KISS_PERSON_PERSON_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \
-    "\U0001f9d1\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fd"
-KISS_PERSON_PERSON_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
-    "\U0001f9d1\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fe"
-KISS_PERSON_PERSON_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \
-    "\U0001f9d1\U0001f3fb\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3ff"
-KISS_PERSON_PERSON_MEDIUM_LIGHT_SKIN_TONE_LIGHT_SKIN_TONE = \
-    "\U0001f9d1\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fb"
-KISS_PERSON_PERSON_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_SKIN_TONE = \
-    "\U0001f9d1\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fd"
-KISS_PERSON_PERSON_MEDIUM_LIGHT_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
-    "\U0001f9d1\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fe"
-KISS_PERSON_PERSON_MEDIUM_LIGHT_SKIN_TONE_DARK_SKIN_TONE = \
-    "\U0001f9d1\U0001f3fc\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3ff"
-KISS_PERSON_PERSON_MEDIUM_SKIN_TONE_LIGHT_SKIN_TONE = \
-    "\U0001f9d1\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fb"
-KISS_PERSON_PERSON_MEDIUM_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
-    "\U0001f9d1\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fc"
-KISS_PERSON_PERSON_MEDIUM_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
-    "\U0001f9d1\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fe"
-KISS_PERSON_PERSON_MEDIUM_SKIN_TONE_DARK_SKIN_TONE = \
-    "\U0001f9d1\U0001f3fd\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3ff"
-KISS_PERSON_PERSON_MEDIUM_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \
-    "\U0001f9d1\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fb"
-KISS_PERSON_PERSON_MEDIUM_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
-    "\U0001f9d1\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fc"
-KISS_PERSON_PERSON_MEDIUM_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \
-    "\U0001f9d1\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fd"
-KISS_PERSON_PERSON_MEDIUM_DARK_SKIN_TONE_DARK_SKIN_TONE = \
-    "\U0001f9d1\U0001f3fe\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3ff"
-KISS_PERSON_PERSON_DARK_SKIN_TONE_LIGHT_SKIN_TONE = \
-    "\U0001f9d1\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fb"
-KISS_PERSON_PERSON_DARK_SKIN_TONE_MEDIUM_LIGHT_SKIN_TONE = \
-    "\U0001f9d1\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fc"
-KISS_PERSON_PERSON_DARK_SKIN_TONE_MEDIUM_SKIN_TONE = \
-    "\U0001f9d1\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fd"
-KISS_PERSON_PERSON_DARK_SKIN_TONE_MEDIUM_DARK_SKIN_TONE = \
-    "\U0001f9d1\U0001f3ff\u200d\u2764\ufe0f\u200d\U0001f48b\u200d\U0001f9d1\U0001f3fe"
-TAG_TILDE = "\U000e007e"
-TAG_PERCENT_SIGN = "\U000e0025"
-ZERO_WIDTH_JOINER = "\u200d"
-TAG_LATIN_SMALL_LETTER_E = "\U000e0065"
-TAG_LATIN_CAPITAL_LETTER_I = "\U000e0049"
-TAG_LATIN_SMALL_LETTER_N = "\U000e006e"
-TAG_LATIN_CAPITAL_LETTER_S = "\U000e0053"
 TAG_RIGHT_CURLY_BRACKET = "\U000e007d"
-TAG_DIGIT_FIVE = "\U000e0035"
-TAG_LATIN_CAPITAL_LETTER_V = "\U000e0056"
-TAG_LATIN_CAPITAL_LETTER_Z = "\U000e005a"
-TAG_LATIN_CAPITAL_LETTER_M = "\U000e004d"
-TAG_LATIN_CAPITAL_LETTER_W = "\U000e0057"
-TAG_LATIN_SMALL_LETTER_D = "\U000e0064"
-TAG_LATIN_CAPITAL_LETTER_F = "\U000e0046"
-TAG_LATIN_SMALL_LETTER_U = "\U000e0075"
-TAG_LATIN_SMALL_LETTER_B = "\U000e0062"
-VARIATION_SELECTOR_16 = "\ufe0f"
+DIGIT_FIVE = "5\ufe0f"
+TAG_LATIN_CAPITAL_LETTER_U = "\U000e0055"
+TAG_LATIN_CAPITAL_LETTER_Q = "\U000e0051"
+TAG_LATIN_CAPITAL_LETTER_K = "\U000e004b"
+COMBINING_ENCLOSING_KEYCAP = "\u20e3"
+TAG_LATIN_CAPITAL_LETTER_C = "\U000e0043"
+TAG_ASTERISK = "\U000e002a"
 TAG_FULL_STOP = "\U000e002e"
-DIGIT_FOUR = "4\ufe0f"
-TAG_LATIN_SMALL_LETTER_F = "\U000e0066"
+TAG_CIRCUMFLEX_ACCENT = "\U000e005e"
 DIGIT_ONE = "1\ufe0f"
-TAG_LATIN_CAPITAL_LETTER_B = "\U000e0042"
-TAG_DIGIT_SEVEN = "\U000e0037"
-TAG_DIGIT_ONE = "\U000e0031"
-TAG_COMMERCIAL_AT = "\U000e0040"
-TAG_LATIN_SMALL_LETTER_Z = "\U000e007a"
-TAG_LATIN_SMALL_LETTER_K = "\U000e006b"
-TAG_LATIN_SMALL_LETTER_V = "\U000e0076"
-TAG_LATIN_CAPITAL_LETTER_C = "\U000e0043"
-TAG_LATIN_SMALL_LETTER_X = "\U000e0078"
-TAG_LATIN_SMALL_LETTER_C = "\U000e0063"
-TAG_SOLIDUS = "\U000e002f"
 TAG_COMMA = "\U000e002c"
-TAG_LATIN_CAPITAL_LETTER_P = "\U000e0050"
-TAG_LATIN_SMALL_LETTER_M = "\U000e006d"
-TAG_LATIN_CAPITAL_LETTER_J = "\U000e004a"
-TAG_LATIN_SMALL_LETTER_P = "\U000e0070"
-TAG_LATIN_SMALL_LETTER_I = "\U000e0069"
-TAG_COLON = "\U000e003a"
-TAG_LATIN_SMALL_LETTER_A = "\U000e0061"
+DIGIT_ZERO = "0\ufe0f"
+TAG_EQUALS_SIGN = "\U000e003d"
+TAG_LATIN_CAPITAL_LETTER_O = "\U000e004f"
+TAG_COMMERCIAL_AT = "\U000e0040"
+DIGIT_EIGHT = "8\ufe0f"
 TAG_NUMBER_SIGN = "\U000e0023"
-TAG_LATIN_CAPITAL_LETTER_X = "\U000e0058"
 TAG_LATIN_CAPITAL_LETTER_T = "\U000e0054"
-NUMBER_SIGN = "#\ufe0f"
+TAG_LATIN_CAPITAL_LETTER_N = "\U000e004e"
+DIGIT_SIX = "6\ufe0f"
+TAG_PERCENT_SIGN = "\U000e0025"
+VARIATION_SELECTOR_16 = "\ufe0f"
+TAG_LATIN_CAPITAL_LETTER_W = "\U000e0057"
 TAG_DOLLAR_SIGN = "\U000e0024"
-TAG_LATIN_CAPITAL_LETTER_Y = "\U000e0059"
-TAG_LATIN_CAPITAL_LETTER_E = "\U000e0045"
-TAG_LATIN_SMALL_LETTER_O = "\U000e006f"
-TAG_DIGIT_FOUR = "\U000e0034"
-TAG_HYPHEN_MINUS = "\U000e002d"
-TAG_RIGHT_PARENTHESIS = "\U000e0029"
-TAG_CIRCUMFLEX_ACCENT = "\U000e005e"
-TAG_LATIN_CAPITAL_LETTER_Q = "\U000e0051"
+TAG_LOW_LINE = "\U000e005f"
+TAG_DIGIT_EIGHT = "\U000e0038"
+TAG_LATIN_CAPITAL_LETTER_M = "\U000e004d"
+TAG_LATIN_CAPITAL_LETTER_A = "\U000e0041"
 TAG_REVERSE_SOLIDUS = "\U000e005c"
-TAG_LATIN_CAPITAL_LETTER_R = "\U000e0052"
-TAG_QUOTATION_MARK = "\U000e0022"
+TAG_SOLIDUS = "\U000e002f"
+TAG_LATIN_CAPITAL_LETTER_H = "\U000e0048"
 TAG_DIGIT_NINE = "\U000e0039"
-CANCEL_TAG = "\U000e007f"
-TAG_ASTERISK = "\U000e002a"
+TAG_LEFT_CURLY_BRACKET = "\U000e007b"
+TAG_LATIN_CAPITAL_LETTER_E = "\U000e0045"
+TAG_LATIN_SMALL_LETTER_W = "\U000e0077"
+TAG_DIGIT_ZERO = "\U000e0030"
+TAG_LATIN_CAPITAL_LETTER_B = "\U000e0042"
+TAG_LATIN_CAPITAL_LETTER_F = "\U000e0046"
+TAG_LATIN_CAPITAL_LETTER_Y = "\U000e0059"
+TAG_TILDE = "\U000e007e"
+TAG_LATIN_SMALL_LETTER_P = "\U000e0070"
+TAG_LATIN_CAPITAL_LETTER_Z = "\U000e005a"
+TAG_GREATER_THAN_SIGN = "\U000e003e"
+TAG_LATIN_SMALL_LETTER_S = "\U000e0073"
+TAG_LATIN_SMALL_LETTER_G = "\U000e0067"
+TAG_APOSTROPHE = "\U000e0027"
+TAG_RIGHT_PARENTHESIS = "\U000e0029"
+TAG_DIGIT_THREE = "\U000e0033"
 TAG_LEFT_PARENTHESIS = "\U000e0028"
+TAG_DIGIT_SEVEN = "\U000e0037"
+TAG_LATIN_SMALL_LETTER_O = "\U000e006f"
+TAG_DIGIT_SIX = "\U000e0036"
+TAG_DIGIT_TWO = "\U000e0032"
+TAG_LATIN_SMALL_LETTER_F = "\U000e0066"
+TAG_LATIN_SMALL_LETTER_K = "\U000e006b"
+TAG_LATIN_SMALL_LETTER_Y = "\U000e0079"
+TAG_SPACE = "\U000e0020"
+TAG_LATIN_SMALL_LETTER_I = "\U000e0069"
+DIGIT_TWO = "2\ufe0f"
+TAG_DIGIT_ONE = "\U000e0031"
 TAG_RIGHT_SQUARE_BRACKET = "\U000e005d"
-DIGIT_SIX = "6\ufe0f"
-DIGIT_FIVE = "5\ufe0f"
+TAG_LATIN_SMALL_LETTER_R = "\U000e0072"
+HASH_SIGN = "#\ufe0f"
+TAG_SEMICOLON = "\U000e003b"
+TAG_LATIN_CAPITAL_LETTER_L = "\U000e004c"
+TAG_HYPHEN_MINUS = "\U000e002d"
 ASTERISK = "*\ufe0f"
+TAG_LATIN_SMALL_LETTER_A = "\U000e0061"
+TAG_EXCLAMATION_MARK = "\U000e0021"
+TAG_LATIN_CAPITAL_LETTER_V = "\U000e0056"
+TAG_LATIN_SMALL_LETTER_C = "\U000e0063"
+TAG_GRAVE_ACCENT = "\U000e0060"
+ZERO_WIDTH_JOINER = "\u200d"
 TAG_LATIN_CAPITAL_LETTER_G = "\U000e0047"
-DIGIT_ZERO = "0\ufe0f"
+DIGIT_NINE = "9\ufe0f"
 TAG_VERTICAL_LINE = "\U000e007c"
-TAG_PLUS_SIGN = "\U000e002b"
-TAG_LEFT_SQUARE_BRACKET = "\U000e005b"
-TAG_DIGIT_EIGHT = "\U000e0038"
-TAG_SPACE = "\U000e0020"
+TAG_LATIN_SMALL_LETTER_Z = "\U000e007a"
+TAG_LATIN_CAPITAL_LETTER_X = "\U000e0058"
 TAG_LATIN_SMALL_LETTER_J = "\U000e006a"
-TAG_LATIN_CAPITAL_LETTER_H = "\U000e0048"
-DIGIT_THREE = "3\ufe0f"
-TAG_LATIN_CAPITAL_LETTER_L = "\U000e004c"
-TAG_LATIN_CAPITAL_LETTER_D = "\U000e0044"
-TAG_LATIN_CAPITAL_LETTER_U = "\U000e0055"
-TAG_LESS_THAN_SIGN = "\U000e003c"
-TAG_EXCLAMATION_MARK = "\U000e0021"
-TAG_APOSTROPHE = "\U000e0027"
-TAG_GREATER_THAN_SIGN = "\U000e003e"
-TAG_LATIN_SMALL_LETTER_T = "\U000e0074"
-DIGIT_NINE = "9\ufe0f"
-TAG_LATIN_SMALL_LETTER_S = "\U000e0073"
-TAG_LATIN_SMALL_LETTER_Q = "\U000e0071"
-DIGIT_TWO = "2\ufe0f"
+TAG_LATIN_CAPITAL_LETTER_P = "\U000e0050"
 TAG_AMPERSAND = "\U000e0026"
-COMBINING_ENCLOSING_KEYCAP = "\u20e3"
-TAG_LATIN_SMALL_LETTER_H = "\U000e0068"
-TAG_LATIN_SMALL_LETTER_R = "\U000e0072"
-TAG_SEMICOLON = "\U000e003b"
-TAG_LATIN_CAPITAL_LETTER_O = "\U000e004f"
-TAG_LOW_LINE = "\U000e005f"
+TAG_LATIN_SMALL_LETTER_L = "\U000e006c"
+TAG_LATIN_SMALL_LETTER_X = "\U000e0078"
 DIGIT_SEVEN = "7\ufe0f"
-TAG_GRAVE_ACCENT = "\U000e0060"
-TAG_LATIN_SMALL_LETTER_W = "\U000e0077"
-TAG_EQUALS_SIGN = "\U000e003d"
-TAG_LATIN_SMALL_LETTER_G = "\U000e0067"
-TAG_LATIN_CAPITAL_LETTER_A = "\U000e0041"
-TAG_DIGIT_THREE = "\U000e0033"
-TAG_LEFT_CURLY_BRACKET = "\U000e007b"
+TAG_LATIN_CAPITAL_LETTER_J = "\U000e004a"
+TAG_LATIN_SMALL_LETTER_T = "\U000e0074"
 TAG_QUESTION_MARK = "\U000e003f"
-TAG_LATIN_SMALL_LETTER_L = "\U000e006c"
-TAG_DIGIT_SIX = "\U000e0036"
-TAG_LATIN_CAPITAL_LETTER_N = "\U000e004e"
-TAG_DIGIT_TWO = "\U000e0032"
-TAG_LATIN_CAPITAL_LETTER_K = "\U000e004b"
-TAG_DIGIT_ZERO = "\U000e0030"
-DIGIT_EIGHT = "8\ufe0f"
-TAG_LATIN_SMALL_LETTER_Y = "\U000e0079"
+TAG_LATIN_SMALL_LETTER_B = "\U000e0062"
+TAG_LEFT_SQUARE_BRACKET = "\U000e005b"
+TAG_LATIN_SMALL_LETTER_D = "\U000e0064"
+TAG_LATIN_SMALL_LETTER_E = "\U000e0065"
+TAG_LATIN_SMALL_LETTER_M = "\U000e006d"
+TAG_LESS_THAN_SIGN = "\U000e003c"
+TAG_DIGIT_FIVE = "\U000e0035"
+TAG_LATIN_CAPITAL_LETTER_D = "\U000e0044"
+TAG_LATIN_SMALL_LETTER_N = "\U000e006e"
+TAG_PLUS_SIGN = "\U000e002b"
+TAG_COLON = "\U000e003a"
+DIGIT_THREE = "3\ufe0f"
+TAG_LATIN_SMALL_LETTER_Q = "\U000e0071"
+TAG_LATIN_CAPITAL_LETTER_R = "\U000e0052"
+TAG_LATIN_CAPITAL_LETTER_S = "\U000e0053"
+DIGIT_FOUR = "4\ufe0f"
+TAG_LATIN_CAPITAL_LETTER_I = "\U000e0049"
+TAG_QUOTATION_MARK = "\U000e0022"
+CANCEL_TAG = "\U000e007f"
+TAG_LATIN_SMALL_LETTER_V = "\U000e0076"
+TAG_LATIN_SMALL_LETTER_H = "\U000e0068"
+TAG_LATIN_SMALL_LETTER_U = "\U000e0075"
+TAG_DIGIT_FOUR = "\U000e0034"

From aaaa97c77af397c462f1452ea2ec57ee1e84c412 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Mon, 31 Jan 2022 21:28:51 +0100
Subject: [PATCH 0718/1185] Document ApiCallError RPC error

---
 compiler/errors/source/503_SERVICE_UNAVAILABLE.tsv | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/compiler/errors/source/503_SERVICE_UNAVAILABLE.tsv b/compiler/errors/source/503_SERVICE_UNAVAILABLE.tsv
index 72b79cb565..eaa426a814 100644
--- a/compiler/errors/source/503_SERVICE_UNAVAILABLE.tsv
+++ b/compiler/errors/source/503_SERVICE_UNAVAILABLE.tsv
@@ -1,2 +1,3 @@
 id	message
-Timeout	Telegram is having internal problems. Please try again later.
\ No newline at end of file
+Timeout	Telegram is having internal problems. Please try again later.
+ApiCallError	Telegram is having internal problems. Please try again later.
\ No newline at end of file

From b9424c745558b7e771a738ba4fd3de9cb3ed2cf7 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Tue, 1 Feb 2022 11:01:02 +0100
Subject: [PATCH 0719/1185] Add ChatJoinRequest bound methods docs

---
 compiler/docs/compiler.py                |  5 +++++
 compiler/docs/template/bound-methods.rst | 14 ++++++++++++++
 2 files changed, 19 insertions(+)

diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py
index cb4de0a6b5..c1a4588603 100644
--- a/compiler/docs/compiler.py
+++ b/compiler/docs/compiler.py
@@ -544,6 +544,11 @@ def get_title_list(s: str) -> list:
         inline_query="""
         InlineQuery
             InlineQuery.answer
+        """,
+        chat_join_request="""
+        ChatJoinRequest
+            ChatJoinRequest.approve
+            ChatJoinRequest.decline
         """
     )
 
diff --git a/compiler/docs/template/bound-methods.rst b/compiler/docs/template/bound-methods.rst
index ebaa2ab899..33261d42b2 100644
--- a/compiler/docs/template/bound-methods.rst
+++ b/compiler/docs/template/bound-methods.rst
@@ -91,3 +91,17 @@ InlineQuery
     :hidden:
 
     {inline_query_toctree}
+
+ChatJoinRequest
+---------------
+
+.. hlist::
+    :columns: 2
+
+    {chat_join_request_hlist}
+
+.. toctree::
+    :hidden:
+
+    {chat_join_request_toctree}
+

From b676297ca921c8608fc44d350b2a5abf0da6b1ef Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Tue, 1 Feb 2022 11:36:46 +0100
Subject: [PATCH 0720/1185] Update API schema to Layer 138

---
 compiler/api/source/main_api.tl | 22 ++++++++++++++--------
 1 file changed, 14 insertions(+), 8 deletions(-)

diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl
index 824fab45d1..9d5d811bfb 100644
--- a/compiler/api/source/main_api.tl
+++ b/compiler/api/source/main_api.tl
@@ -168,7 +168,7 @@ messageActionGroupCallScheduled#b3a07661 call:InputGroupCall schedule_date:int =
 messageActionSetChatTheme#aa786345 emoticon:string = MessageAction;
 messageActionChatJoinedByRequest#ebbca3cb = MessageAction;
 
-dialog#2c171f72 flags:# pinned:flags.2?true unread_mark:flags.3?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int = Dialog;
+dialog#a8edd0f5 flags:# pinned:flags.2?true unread_mark:flags.3?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int unread_reactions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int = Dialog;
 dialogFolder#71bd134c flags:# pinned:flags.2?true folder:Folder peer:Peer top_message:int unread_muted_peers_count:int unread_unmuted_peers_count:int unread_muted_messages_count:int unread_unmuted_messages_count:int = Dialog;
 
 photoEmpty#2331b22d id:long = Photo;
@@ -544,7 +544,7 @@ inputStickerSetAnimatedEmoji#28703c8 = InputStickerSet;
 inputStickerSetDice#e67f520e emoticon:string = InputStickerSet;
 inputStickerSetAnimatedEmojiAnimations#cde3739 = InputStickerSet;
 
-stickerSet#d7df217a flags:# archived:flags.1?true official:flags.2?true masks:flags.3?true animated:flags.5?true installed_date:flags.0?int id:long access_hash:long title:string short_name:string thumbs:flags.4?Vector thumb_dc_id:flags.4?int thumb_version:flags.4?int count:int hash:int = StickerSet;
+stickerSet#d7df217a flags:# archived:flags.1?true official:flags.2?true masks:flags.3?true animated:flags.5?true gifs:flags.6?true installed_date:flags.0?int id:long access_hash:long title:string short_name:string thumbs:flags.4?Vector thumb_dc_id:flags.4?int thumb_version:flags.4?int count:int hash:int = StickerSet;
 
 messages.stickerSet#b60a24a6 set:StickerSet packs:Vector documents:Vector = messages.StickerSet;
 messages.stickerSetNotModified#d3f924eb = messages.StickerSet;
@@ -1285,17 +1285,20 @@ auth.loggedOut#c3a2835f flags:# future_auth_token:flags.0?bytes = auth.LoggedOut
 
 reactionCount#6fb250d1 flags:# chosen:flags.0?true reaction:string count:int = ReactionCount;
 
-messageReactions#87b6e36 flags:# min:flags.0?true can_see_list:flags.2?true results:Vector recent_reactons:flags.1?Vector = MessageReactions;
+messageReactions#4f2b9479 flags:# min:flags.0?true can_see_list:flags.2?true results:Vector recent_reactions:flags.1?Vector = MessageReactions;
 
-messageUserReaction#932844fa user_id:long reaction:string = MessageUserReaction;
-
-messages.messageReactionsList#a366923c flags:# count:int reactions:Vector users:Vector next_offset:flags.0?string = messages.MessageReactionsList;
+messages.messageReactionsList#31bd492d flags:# count:int reactions:Vector chats:Vector users:Vector next_offset:flags.0?string = messages.MessageReactionsList;
 
 availableReaction#c077ec01 flags:# inactive:flags.0?true reaction:string title:string static_icon:Document appear_animation:Document select_animation:Document activate_animation:Document effect_animation:Document around_animation:flags.1?Document center_icon:flags.1?Document = AvailableReaction;
 
 messages.availableReactionsNotModified#9f071957 = messages.AvailableReactions;
 messages.availableReactions#768e3aad hash:int reactions:Vector = messages.AvailableReactions;
 
+messages.translateNoResult#67ca4737 = messages.TranslatedText;
+messages.translateResultText#a214f7d0 text:string = messages.TranslatedText;
+
+messagePeerReaction#51b67eff flags:# big:flags.0?true unread:flags.1?true peer_id:Peer reaction:string = MessagePeerReaction;
+
 ---functions---
 
 invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
@@ -1574,12 +1577,15 @@ messages.hideChatJoinRequest#7fe7e815 flags:# approved:flags.0?true peer:InputPe
 messages.hideAllChatJoinRequests#e085f4ea flags:# approved:flags.0?true peer:InputPeer link:flags.1?string = Updates;
 messages.toggleNoForwards#b11eafa2 peer:InputPeer enabled:Bool = Updates;
 messages.saveDefaultSendAs#ccfddf96 peer:InputPeer send_as:InputPeer = Bool;
-messages.sendReaction#25690ce4 flags:# peer:InputPeer msg_id:int reaction:flags.0?string = Updates;
+messages.sendReaction#25690ce4 flags:# big:flags.1?true peer:InputPeer msg_id:int reaction:flags.0?string = Updates;
 messages.getMessagesReactions#8bba90e6 peer:InputPeer id:Vector = Updates;
 messages.getMessageReactionsList#e0ee6b77 flags:# peer:InputPeer id:int reaction:flags.0?string offset:flags.1?string limit:int = messages.MessageReactionsList;
 messages.setChatAvailableReactions#14050ea6 peer:InputPeer available_reactions:Vector = Updates;
 messages.getAvailableReactions#18dea0ac hash:int = messages.AvailableReactions;
 messages.setDefaultReaction#d960c4d4 reaction:string = Bool;
+messages.translateText#24ce6dee flags:# peer:flags.0?InputPeer msg_id:flags.0?int text:flags.1?string from_lang:flags.2?string to_lang:string = messages.TranslatedText;
+messages.getUnreadReactions#e85bae1a peer:InputPeer offset_id:int add_offset:int limit:int max_id:int min_id:int = messages.Messages;
+messages.readReactions#82e251d7 peer:InputPeer = messages.AffectedHistory;
 
 updates.getState#edd4882a = updates.State;
 updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference;
@@ -1728,4 +1734,4 @@ stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel
 stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages;
 stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats;
 
-// LAYER 137
\ No newline at end of file
+// LAYER 138
\ No newline at end of file

From 05bfaa3d876d7a8486d7598cd7dc8c674e6c267e Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Tue, 1 Feb 2022 11:38:58 +0100
Subject: [PATCH 0721/1185] Add support for video stickers Add Sticker.is_video
 attribute

---
 pyrogram/types/messages_and_media/message.py | 16 ++++++++--------
 pyrogram/types/messages_and_media/sticker.py |  6 ++++++
 2 files changed, 14 insertions(+), 8 deletions(-)

diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py
index 02bf2d3d98..1f6e432a16 100644
--- a/pyrogram/types/messages_and_media/message.py
+++ b/pyrogram/types/messages_and_media/message.py
@@ -674,6 +674,14 @@ async def _parse(
                             video_attributes = attributes.get(raw.types.DocumentAttributeVideo, None)
                             animation = types.Animation._parse(client, doc, video_attributes, file_name)
                             media_type = "animation"
+                        elif raw.types.DocumentAttributeSticker in attributes:
+                            sticker = await types.Sticker._parse(
+                                client, doc,
+                                attributes.get(raw.types.DocumentAttributeImageSize, None),
+                                attributes[raw.types.DocumentAttributeSticker],
+                                file_name
+                            )
+                            media_type = "sticker"
                         elif raw.types.DocumentAttributeVideo in attributes:
                             video_attributes = attributes[raw.types.DocumentAttributeVideo]
 
@@ -683,14 +691,6 @@ async def _parse(
                             else:
                                 video = types.Video._parse(client, doc, video_attributes, file_name, media.ttl_seconds)
                                 media_type = "video"
-                        elif raw.types.DocumentAttributeSticker in attributes:
-                            sticker = await types.Sticker._parse(
-                                client, doc,
-                                attributes.get(raw.types.DocumentAttributeImageSize, None),
-                                attributes[raw.types.DocumentAttributeSticker],
-                                file_name
-                            )
-                            media_type = "sticker"
                         else:
                             document = types.Document._parse(client, doc, file_name)
                             media_type = "document"
diff --git a/pyrogram/types/messages_and_media/sticker.py b/pyrogram/types/messages_and_media/sticker.py
index be494e3078..bd08792ff0 100644
--- a/pyrogram/types/messages_and_media/sticker.py
+++ b/pyrogram/types/messages_and_media/sticker.py
@@ -46,6 +46,9 @@ class Sticker(Object):
         is_animated (``bool``):
             True, if the sticker is animated
 
+        is_video (``bool``):
+            True, if the sticker is a video sticker
+
         file_name (``str``, *optional*):
             Sticker file name.
 
@@ -79,6 +82,7 @@ def __init__(
         width: int,
         height: int,
         is_animated: bool,
+        is_video: bool,
         file_name: str = None,
         mime_type: str = None,
         file_size: int = None,
@@ -98,6 +102,7 @@ def __init__(
         self.width = width
         self.height = height
         self.is_animated = is_animated
+        self.is_video = is_video
         self.emoji = emoji
         self.set_name = set_name
         self.thumbs = thumbs
@@ -167,6 +172,7 @@ async def _parse(
             width=image_size_attributes.w if image_size_attributes else 512,
             height=image_size_attributes.h if image_size_attributes else 512,
             is_animated=sticker.mime_type == "application/x-tgsticker",
+            is_video=sticker.mime_type == "video/webm",
             # TODO: mask_position
             set_name=set_name,
             emoji=sticker_attributes.alt or None,

From 855e69e3f881c8140781c1d5e42e3098b2134dd2 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Tue, 1 Feb 2022 12:07:30 +0100
Subject: [PATCH 0722/1185] Update Pyrogram to v1.4.0

---
 pyrogram/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index 81670ac9b2..2b551c08ae 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -16,7 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-__version__ = "1.3.7"
+__version__ = "1.4.0"
 __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)"
 __copyright__ = "Copyright (C) 2017-present Dan "
 

From b3f849df762df799b43dcde109ef6516a1a50281 Mon Sep 17 00:00:00 2001
From: Andrea Princic <48788808+Princic-1837592@users.noreply.github.com>
Date: Thu, 3 Feb 2022 15:26:17 +0100
Subject: [PATCH 0723/1185] Fix entities unparsing in other scenarios (#892)

---
 pyrogram/parser/html.py | 13 +++++++++++--
 1 file changed, 11 insertions(+), 2 deletions(-)

diff --git a/pyrogram/parser/html.py b/pyrogram/parser/html.py
index 81c761ac94..b1ce5c5d46 100644
--- a/pyrogram/parser/html.py
+++ b/pyrogram/parser/html.py
@@ -174,8 +174,17 @@ def unparse(text: str, entities: list):
 
             entities_offsets.append((start_tag, start,))
             entities_offsets.append((end_tag, end,))
-
-        for entity, offset in reversed(entities_offsets):
+            
+        entities_offsets = map(
+            lambda x: x[1],
+            sorted(
+                enumerate(entities_offsets),
+                key = lambda x: (x[1][1], x[0]),
+                reverse = True
+            )
+        )
+
+        for entity, offset in entities_offsets:
             text = text[:offset] + entity + text[offset:]
 
         return utils.remove_surrogates(text)

From 89c49111b0edc1ca7c586b19d10700f115ffaf6a Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Thu, 3 Feb 2022 15:27:15 +0100
Subject: [PATCH 0724/1185] Update Pyrogram to v1.4.1

---
 pyrogram/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index 2b551c08ae..d9b7d0e7d7 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -16,7 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-__version__ = "1.4.0"
+__version__ = "1.4.1"
 __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)"
 __copyright__ = "Copyright (C) 2017-present Dan "
 

From 462e5d11a5df5c3114be06015bb0f9369e239b84 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Thu, 10 Feb 2022 01:05:36 +0100
Subject: [PATCH 0725/1185] Improve stability in case of connection failures

---
 pyrogram/connection/connection.py        | 26 +++++++-----
 pyrogram/connection/transport/tcp/tcp.py |  2 +-
 pyrogram/session/session.py              | 53 ++++++++++--------------
 3 files changed, 39 insertions(+), 42 deletions(-)

diff --git a/pyrogram/connection/connection.py b/pyrogram/connection/connection.py
index 2173c70b94..62e68751eb 100644
--- a/pyrogram/connection/connection.py
+++ b/pyrogram/connection/connection.py
@@ -37,7 +37,7 @@ class Connection:
         4: TCPIntermediateO
     }
 
-    def __init__(self, dc_id: int, test_mode: bool, ipv6: bool, proxy: dict, media: bool = False, mode: int = 3):
+    def __init__(self, dc_id: int, test_mode: bool, ipv6: bool, proxy: dict, media: bool = False, mode: int = 1):
         self.dc_id = dc_id
         self.test_mode = test_mode
         self.ipv6 = ipv6
@@ -47,6 +47,7 @@ def __init__(self, dc_id: int, test_mode: bool, ipv6: bool, proxy: dict, media:
         self.mode = self.MODES.get(mode, TCPAbridged)
 
         self.protocol = None  # type: TCP
+        self.is_connected = asyncio.Event()
 
     async def connect(self):
         for i in range(Connection.MAX_RETRIES):
@@ -56,8 +57,8 @@ async def connect(self):
                 log.info("Connecting...")
                 await self.protocol.connect(self.address)
             except OSError as e:
-                log.warning(f"Unable to connect due to network issues: {e}")
-                self.protocol.close()
+                log.warning(f"Connection failed due to network issues: {e}")
+                await self.protocol.close()
                 await asyncio.sleep(1)
             else:
                 log.info("Connected! {} DC{}{} - IPv{} - {}".format(
@@ -69,18 +70,23 @@ async def connect(self):
                 ))
                 break
         else:
-            log.warning("Connection failed! Trying again...")
+            log.warning("Couldn't connect. Trying again...")
             raise TimeoutError
 
-    def close(self):
-        self.protocol.close()
+        self.is_connected.set()
+
+    async def close(self):
+        await self.protocol.close()
+        self.is_connected.clear()
         log.info("Disconnected")
 
+    async def reconnect(self):
+        await self.close()
+        await self.connect()
+
     async def send(self, data: bytes):
-        try:
-            await self.protocol.send(data)
-        except Exception:
-            raise OSError
+        await self.is_connected.wait()
+        await self.protocol.send(data)
 
     async def recv(self) -> Optional[bytes]:
         return await self.protocol.recv()
diff --git a/pyrogram/connection/transport/tcp/tcp.py b/pyrogram/connection/transport/tcp/tcp.py
index 0b858c0265..386ca32997 100644
--- a/pyrogram/connection/transport/tcp/tcp.py
+++ b/pyrogram/connection/transport/tcp/tcp.py
@@ -82,7 +82,7 @@ async def connect(self, address: tuple):
         self.socket.connect(address)
         self.reader, self.writer = await asyncio.open_connection(sock=self.socket)
 
-    def close(self):
+    async def close(self):
         try:
             self.writer.close()
         except AttributeError:
diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py
index 05d1fd4a3f..7ee5d6baff 100644
--- a/pyrogram/session/session.py
+++ b/pyrogram/session/session.py
@@ -83,7 +83,6 @@ def __init__(
         self.stored_msg_ids = []
 
         self.ping_task = None
-        self.ping_task_event = asyncio.Event()
 
         self.network_task = None
 
@@ -150,17 +149,23 @@ async def start(self):
     async def stop(self):
         self.is_connected.clear()
 
-        self.ping_task_event.set()
+        if self.ping_task:
+            self.ping_task.cancel()
 
-        if self.ping_task is not None:
-            await self.ping_task
+            try:
+                await self.ping_task
+            except asyncio.CancelledError:
+                pass
 
-        self.ping_task_event.clear()
+        if self.network_task:
+            self.network_task.cancel()
 
-        self.connection.close()
+            try:
+                await self.network_task
+            except asyncio.CancelledError:
+                pass
 
-        if self.network_task:
-            await self.network_task
+        await self.connection.close()
 
         for i in self.results.values():
             i.event.set()
@@ -189,7 +194,7 @@ async def handle_packet(self, packet):
                 self.stored_msg_ids
             )
         except SecurityCheckMismatch:
-            self.connection.close()
+            await self.connection.close()
             return
 
         messages = (
@@ -247,46 +252,32 @@ async def handle_packet(self, packet):
                 self.pending_acks.clear()
 
     async def ping_worker(self):
-        log.info("PingTask started")
-
         while True:
-            try:
-                await asyncio.wait_for(self.ping_task_event.wait(), self.PING_INTERVAL)
-            except asyncio.TimeoutError:
-                pass
-            else:
-                break
-
             try:
                 await self._send(
                     raw.functions.PingDelayDisconnect(
-                        ping_id=0, disconnect_delay=self.WAIT_TIMEOUT + 10
+                        ping_id=0,
+                        disconnect_delay=self.WAIT_TIMEOUT + 10
                     ), False
                 )
             except (OSError, TimeoutError, RPCError):
                 pass
 
-        log.info("PingTask stopped")
+            await asyncio.sleep(self.PING_INTERVAL)
 
     async def network_worker(self):
-        log.info("NetworkTask started")
-
         while True:
             packet = await self.connection.recv()
 
-            if packet is None or len(packet) == 4:
-                if packet:
-                    log.warning(f'Server sent "{Int.read(BytesIO(packet))}"')
-
-                if self.is_connected.is_set():
-                    self.loop.create_task(self.restart())
+            if not packet:
+                await self.connection.reconnect()
+                continue
 
-                break
+            if len(packet) == 4:
+                log.warning(f'Server sent "{Int.read(BytesIO(packet))}"')
 
             self.loop.create_task(self.handle_packet(packet))
 
-        log.info("NetworkTask stopped")
-
     async def _send(self, data: TLObject, wait_response: bool = True, timeout: float = WAIT_TIMEOUT):
         message = self.msg_factory(data)
         msg_id = message.msg_id

From 921d87304f1582d4de341ca8b1259595c0ab8ef9 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Thu, 10 Feb 2022 01:08:11 +0100
Subject: [PATCH 0726/1185] Do not consume async gens, turn them to gens
 instead

---
 pyrogram/sync.py | 23 +++++++++++++++++++----
 1 file changed, 19 insertions(+), 4 deletions(-)

diff --git a/pyrogram/sync.py b/pyrogram/sync.py
index 6db937b3a7..a3e0d2b545 100644
--- a/pyrogram/sync.py
+++ b/pyrogram/sync.py
@@ -30,8 +30,23 @@ def async_to_sync(obj, name):
     function = getattr(obj, name)
     main_loop = asyncio.get_event_loop()
 
-    async def consume_generator(coroutine):
-        return types.List([i async for i in coroutine])
+    def async_to_sync_gen(agen, loop, is_main_thread):
+        async def anext(agen):
+            try:
+                return await agen.__anext__(), False
+            except StopAsyncIteration:
+                return None, True
+
+        while True:
+            if is_main_thread:
+                item, done = loop.run_until_complete(anext(agen))
+            else:
+                item, done = asyncio.run_coroutine_threadsafe(anext(agen), loop).result()
+
+            if done:
+                break
+
+            yield item
 
     @functools.wraps(function)
     def async_to_sync_wrap(*args, **kwargs):
@@ -51,7 +66,7 @@ def async_to_sync_wrap(*args, **kwargs):
                     return loop.run_until_complete(coroutine)
 
                 if inspect.isasyncgen(coroutine):
-                    return loop.run_until_complete(consume_generator(coroutine))
+                    return async_to_sync_gen(coroutine, loop, True)
         else:
             if inspect.iscoroutine(coroutine):
                 if loop.is_running():
@@ -66,7 +81,7 @@ async def coro_wrapper():
                 if loop.is_running():
                     return coroutine
                 else:
-                    return asyncio.run_coroutine_threadsafe(consume_generator(coroutine), main_loop).result()
+                    return async_to_sync_gen(coroutine, main_loop, False)
 
     setattr(obj, name, async_to_sync_wrap)
 

From 7fbb4d8997a28d804371833d92e2dcf4bac0ea41 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Thu, 10 Feb 2022 01:09:06 +0100
Subject: [PATCH 0727/1185] Update Pyrogram to v1.4.2

---
 pyrogram/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index d9b7d0e7d7..a828ae2a6a 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -16,7 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-__version__ = "1.4.1"
+__version__ = "1.4.2"
 __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)"
 __copyright__ = "Copyright (C) 2017-present Dan "
 

From 5d155b896c8cfb10728cb9eff935973281460e36 Mon Sep 17 00:00:00 2001
From: Stark Programmer <88478059+StarkBotsIndustries@users.noreply.github.com>
Date: Thu, 10 Feb 2022 06:52:43 +0530
Subject: [PATCH 0728/1185] Add missing await keyword (#898)

---
 pyrogram/session/auth.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/session/auth.py b/pyrogram/session/auth.py
index d4083b2179..f33ffba935 100644
--- a/pyrogram/session/auth.py
+++ b/pyrogram/session/auth.py
@@ -258,4 +258,4 @@ async def create(self):
             else:
                 return auth_key
             finally:
-                self.connection.close()
+                await self.connection.close()

From 2c1d3ee2a4385f9bca18ea0876828678bb91c8e4 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Thu, 10 Feb 2022 02:23:36 +0100
Subject: [PATCH 0729/1185] Update Pyrogram to v1.4.3

---
 pyrogram/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index a828ae2a6a..d015fb215d 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -16,7 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-__version__ = "1.4.2"
+__version__ = "1.4.3"
 __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)"
 __copyright__ = "Copyright (C) 2017-present Dan "
 

From 03629d5ee9f4e90bee26b91811594615476c55eb Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Thu, 10 Feb 2022 04:25:36 +0100
Subject: [PATCH 0730/1185] Always try to reconnect within Connection

---
 pyrogram/connection/connection.py | 7 +------
 1 file changed, 1 insertion(+), 6 deletions(-)

diff --git a/pyrogram/connection/connection.py b/pyrogram/connection/connection.py
index 62e68751eb..04578b0ee6 100644
--- a/pyrogram/connection/connection.py
+++ b/pyrogram/connection/connection.py
@@ -27,8 +27,6 @@
 
 
 class Connection:
-    MAX_RETRIES = 3
-
     MODES = {
         0: TCPFull,
         1: TCPAbridged,
@@ -50,7 +48,7 @@ def __init__(self, dc_id: int, test_mode: bool, ipv6: bool, proxy: dict, media:
         self.is_connected = asyncio.Event()
 
     async def connect(self):
-        for i in range(Connection.MAX_RETRIES):
+        while True:
             self.protocol = self.mode(self.ipv6, self.proxy)
 
             try:
@@ -69,9 +67,6 @@ async def connect(self):
                     self.mode.__name__,
                 ))
                 break
-        else:
-            log.warning("Couldn't connect. Trying again...")
-            raise TimeoutError
 
         self.is_connected.set()
 

From f23422cb25e382be0c2e8dcce20a11637494f5ea Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Thu, 10 Feb 2022 04:25:54 +0100
Subject: [PATCH 0731/1185] Update Pyrogram to v1.4.4

---
 pyrogram/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index d015fb215d..49d8d34130 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -16,7 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-__version__ = "1.4.3"
+__version__ = "1.4.4"
 __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)"
 __copyright__ = "Copyright (C) 2017-present Dan "
 

From 5889c67fb51b61fcc20549840d1f1c099b3a58ac Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Thu, 10 Feb 2022 05:34:21 +0100
Subject: [PATCH 0732/1185] Initialize session on reconnection

---
 pyrogram/session/session.py | 21 +++++++++++++++++++++
 1 file changed, 21 insertions(+)

diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py
index 7ee5d6baff..89171ed4a1 100644
--- a/pyrogram/session/session.py
+++ b/pyrogram/session/session.py
@@ -271,6 +271,27 @@ async def network_worker(self):
 
             if not packet:
                 await self.connection.reconnect()
+
+                try:
+                    await self._send(
+                        raw.functions.InvokeWithLayer(
+                            layer=layer,
+                            query=raw.functions.InitConnection(
+                                api_id=self.client.api_id,
+                                app_version=self.client.app_version,
+                                device_model=self.client.device_model,
+                                system_version=self.client.system_version,
+                                system_lang_code=self.client.lang_code,
+                                lang_code=self.client.lang_code,
+                                lang_pack="",
+                                query=raw.functions.help.GetConfig(),
+                            )
+                        ),
+                        wait_response=False
+                    )
+                except (OSError, TimeoutError, RPCError):
+                    pass
+
                 continue
 
             if len(packet) == 4:

From 0d112407400040a90e72dc8325eccd832174d8ea Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Thu, 10 Feb 2022 05:34:40 +0100
Subject: [PATCH 0733/1185] Update Pyrogram to v.1.4.5

---
 pyrogram/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index 49d8d34130..3396e56a4f 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -16,7 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-__version__ = "1.4.4"
+__version__ = "1.4.5"
 __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)"
 __copyright__ = "Copyright (C) 2017-present Dan "
 

From dc6c816c80934a4f3e4953e7faca327b2585ac55 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Thu, 10 Feb 2022 06:44:42 +0100
Subject: [PATCH 0734/1185] Revert some of the last changes

---
 pyrogram/connection/connection.py        | 31 +++++-----
 pyrogram/connection/transport/tcp/tcp.py |  2 +-
 pyrogram/session/auth.py                 |  2 +-
 pyrogram/session/session.py              | 72 ++++++++++--------------
 4 files changed, 47 insertions(+), 60 deletions(-)

diff --git a/pyrogram/connection/connection.py b/pyrogram/connection/connection.py
index 04578b0ee6..2173c70b94 100644
--- a/pyrogram/connection/connection.py
+++ b/pyrogram/connection/connection.py
@@ -27,6 +27,8 @@
 
 
 class Connection:
+    MAX_RETRIES = 3
+
     MODES = {
         0: TCPFull,
         1: TCPAbridged,
@@ -35,7 +37,7 @@ class Connection:
         4: TCPIntermediateO
     }
 
-    def __init__(self, dc_id: int, test_mode: bool, ipv6: bool, proxy: dict, media: bool = False, mode: int = 1):
+    def __init__(self, dc_id: int, test_mode: bool, ipv6: bool, proxy: dict, media: bool = False, mode: int = 3):
         self.dc_id = dc_id
         self.test_mode = test_mode
         self.ipv6 = ipv6
@@ -45,18 +47,17 @@ def __init__(self, dc_id: int, test_mode: bool, ipv6: bool, proxy: dict, media:
         self.mode = self.MODES.get(mode, TCPAbridged)
 
         self.protocol = None  # type: TCP
-        self.is_connected = asyncio.Event()
 
     async def connect(self):
-        while True:
+        for i in range(Connection.MAX_RETRIES):
             self.protocol = self.mode(self.ipv6, self.proxy)
 
             try:
                 log.info("Connecting...")
                 await self.protocol.connect(self.address)
             except OSError as e:
-                log.warning(f"Connection failed due to network issues: {e}")
-                await self.protocol.close()
+                log.warning(f"Unable to connect due to network issues: {e}")
+                self.protocol.close()
                 await asyncio.sleep(1)
             else:
                 log.info("Connected! {} DC{}{} - IPv{} - {}".format(
@@ -67,21 +68,19 @@ async def connect(self):
                     self.mode.__name__,
                 ))
                 break
+        else:
+            log.warning("Connection failed! Trying again...")
+            raise TimeoutError
 
-        self.is_connected.set()
-
-    async def close(self):
-        await self.protocol.close()
-        self.is_connected.clear()
+    def close(self):
+        self.protocol.close()
         log.info("Disconnected")
 
-    async def reconnect(self):
-        await self.close()
-        await self.connect()
-
     async def send(self, data: bytes):
-        await self.is_connected.wait()
-        await self.protocol.send(data)
+        try:
+            await self.protocol.send(data)
+        except Exception:
+            raise OSError
 
     async def recv(self) -> Optional[bytes]:
         return await self.protocol.recv()
diff --git a/pyrogram/connection/transport/tcp/tcp.py b/pyrogram/connection/transport/tcp/tcp.py
index 386ca32997..0b858c0265 100644
--- a/pyrogram/connection/transport/tcp/tcp.py
+++ b/pyrogram/connection/transport/tcp/tcp.py
@@ -82,7 +82,7 @@ async def connect(self, address: tuple):
         self.socket.connect(address)
         self.reader, self.writer = await asyncio.open_connection(sock=self.socket)
 
-    async def close(self):
+    def close(self):
         try:
             self.writer.close()
         except AttributeError:
diff --git a/pyrogram/session/auth.py b/pyrogram/session/auth.py
index f33ffba935..d4083b2179 100644
--- a/pyrogram/session/auth.py
+++ b/pyrogram/session/auth.py
@@ -258,4 +258,4 @@ async def create(self):
             else:
                 return auth_key
             finally:
-                await self.connection.close()
+                self.connection.close()
diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py
index 89171ed4a1..05d1fd4a3f 100644
--- a/pyrogram/session/session.py
+++ b/pyrogram/session/session.py
@@ -83,6 +83,7 @@ def __init__(
         self.stored_msg_ids = []
 
         self.ping_task = None
+        self.ping_task_event = asyncio.Event()
 
         self.network_task = None
 
@@ -149,23 +150,17 @@ async def start(self):
     async def stop(self):
         self.is_connected.clear()
 
-        if self.ping_task:
-            self.ping_task.cancel()
+        self.ping_task_event.set()
 
-            try:
-                await self.ping_task
-            except asyncio.CancelledError:
-                pass
+        if self.ping_task is not None:
+            await self.ping_task
 
-        if self.network_task:
-            self.network_task.cancel()
+        self.ping_task_event.clear()
 
-            try:
-                await self.network_task
-            except asyncio.CancelledError:
-                pass
+        self.connection.close()
 
-        await self.connection.close()
+        if self.network_task:
+            await self.network_task
 
         for i in self.results.values():
             i.event.set()
@@ -194,7 +189,7 @@ async def handle_packet(self, packet):
                 self.stored_msg_ids
             )
         except SecurityCheckMismatch:
-            await self.connection.close()
+            self.connection.close()
             return
 
         messages = (
@@ -252,53 +247,46 @@ async def handle_packet(self, packet):
                 self.pending_acks.clear()
 
     async def ping_worker(self):
+        log.info("PingTask started")
+
         while True:
+            try:
+                await asyncio.wait_for(self.ping_task_event.wait(), self.PING_INTERVAL)
+            except asyncio.TimeoutError:
+                pass
+            else:
+                break
+
             try:
                 await self._send(
                     raw.functions.PingDelayDisconnect(
-                        ping_id=0,
-                        disconnect_delay=self.WAIT_TIMEOUT + 10
+                        ping_id=0, disconnect_delay=self.WAIT_TIMEOUT + 10
                     ), False
                 )
             except (OSError, TimeoutError, RPCError):
                 pass
 
-            await asyncio.sleep(self.PING_INTERVAL)
+        log.info("PingTask stopped")
 
     async def network_worker(self):
+        log.info("NetworkTask started")
+
         while True:
             packet = await self.connection.recv()
 
-            if not packet:
-                await self.connection.reconnect()
+            if packet is None or len(packet) == 4:
+                if packet:
+                    log.warning(f'Server sent "{Int.read(BytesIO(packet))}"')
 
-                try:
-                    await self._send(
-                        raw.functions.InvokeWithLayer(
-                            layer=layer,
-                            query=raw.functions.InitConnection(
-                                api_id=self.client.api_id,
-                                app_version=self.client.app_version,
-                                device_model=self.client.device_model,
-                                system_version=self.client.system_version,
-                                system_lang_code=self.client.lang_code,
-                                lang_code=self.client.lang_code,
-                                lang_pack="",
-                                query=raw.functions.help.GetConfig(),
-                            )
-                        ),
-                        wait_response=False
-                    )
-                except (OSError, TimeoutError, RPCError):
-                    pass
+                if self.is_connected.is_set():
+                    self.loop.create_task(self.restart())
 
-                continue
-
-            if len(packet) == 4:
-                log.warning(f'Server sent "{Int.read(BytesIO(packet))}"')
+                break
 
             self.loop.create_task(self.handle_packet(packet))
 
+        log.info("NetworkTask stopped")
+
     async def _send(self, data: TLObject, wait_response: bool = True, timeout: float = WAIT_TIMEOUT):
         message = self.msg_factory(data)
         msg_id = message.msg_id

From ed2db45a03dc9a2391fee8f40e9ff059c109c291 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Thu, 10 Feb 2022 06:45:59 +0100
Subject: [PATCH 0735/1185] Make Connection.send() raise the actual exception

---
 pyrogram/connection/connection.py | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/pyrogram/connection/connection.py b/pyrogram/connection/connection.py
index 2173c70b94..e90ff9c41b 100644
--- a/pyrogram/connection/connection.py
+++ b/pyrogram/connection/connection.py
@@ -77,10 +77,7 @@ def close(self):
         log.info("Disconnected")
 
     async def send(self, data: bytes):
-        try:
-            await self.protocol.send(data)
-        except Exception:
-            raise OSError
+        await self.protocol.send(data)
 
     async def recv(self) -> Optional[bytes]:
         return await self.protocol.recv()

From 22f2b1dd99fd163f44697bfd2ebe269772f5e960 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Thu, 10 Feb 2022 06:46:28 +0100
Subject: [PATCH 0736/1185] Update Pyrogram to v1.4.6

---
 pyrogram/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index 3396e56a4f..52d4614954 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -16,7 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-__version__ = "1.4.5"
+__version__ = "1.4.6"
 __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)"
 __copyright__ = "Copyright (C) 2017-present Dan "
 

From 84b0e15e2b06a9dac2b7f8755e1f7ac896bc9ee1 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Thu, 10 Feb 2022 20:14:09 +0100
Subject: [PATCH 0737/1185] Revert "Make Connection.send() raise the actual
 exception"

This reverts commit ed2db45a03dc9a2391fee8f40e9ff059c109c291.
---
 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 e90ff9c41b..2173c70b94 100644
--- a/pyrogram/connection/connection.py
+++ b/pyrogram/connection/connection.py
@@ -77,7 +77,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) -> Optional[bytes]:
         return await self.protocol.recv()

From 9279b67319b8d39898212d53d6b725a92e963fff Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Thu, 10 Feb 2022 20:15:48 +0100
Subject: [PATCH 0738/1185] Display a more meaningful error message

---
 pyrogram/connection/connection.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/pyrogram/connection/connection.py b/pyrogram/connection/connection.py
index 2173c70b94..618c92a510 100644
--- a/pyrogram/connection/connection.py
+++ b/pyrogram/connection/connection.py
@@ -79,8 +79,8 @@ def close(self):
     async def send(self, data: bytes):
         try:
             await self.protocol.send(data)
-        except Exception:
-            raise OSError
+        except Exception as e:
+            raise OSError(e)
 
     async def recv(self) -> Optional[bytes]:
         return await self.protocol.recv()

From 7edfda7f417c661344eac11e7a09613cc882afcf Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Thu, 10 Feb 2022 20:16:34 +0100
Subject: [PATCH 0739/1185] Update Pyrogram to v1.4.7

---
 pyrogram/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index 52d4614954..304ce76d3c 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -16,7 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-__version__ = "1.4.6"
+__version__ = "1.4.7"
 __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)"
 __copyright__ = "Copyright (C) 2017-present Dan "
 

From 325569d73f0f012c755ddeb7f0a72145384e998a Mon Sep 17 00:00:00 2001
From: Nik <31286021+nikisalli@users.noreply.github.com>
Date: Sun, 13 Feb 2022 11:32:34 +0100
Subject: [PATCH 0740/1185] Fix custom List __repr__ (#901)

---
 pyrogram/raw/core/list.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/raw/core/list.py b/pyrogram/raw/core/list.py
index 780fd92c3e..a7bbc16b2e 100644
--- a/pyrogram/raw/core/list.py
+++ b/pyrogram/raw/core/list.py
@@ -23,4 +23,4 @@
 
 class List(TList[Any], TLObject):
     def __repr__(self) -> str:
-        return f"pyrogram.api.core.List([{','.join(TLObject.__repr__(i) for i in self)}])"
+        return f"pyrogram.raw.core.List([{','.join(TLObject.__repr__(i) for i in self)}])"

From d9d7a867b5f6af4c2a4630524b96917825a7f618 Mon Sep 17 00:00:00 2001
From: Gor <47748749+ke1io@users.noreply.github.com>
Date: Sun, 13 Feb 2022 14:37:14 +0400
Subject: [PATCH 0741/1185] Add 406 CHANNEL_PRIVATE error (#899)

---
 compiler/errors/source/406_NOT_ACCEPTABLE.tsv | 1 +
 1 file changed, 1 insertion(+)

diff --git a/compiler/errors/source/406_NOT_ACCEPTABLE.tsv b/compiler/errors/source/406_NOT_ACCEPTABLE.tsv
index c1c5b7efb5..c2df2384dd 100644
--- a/compiler/errors/source/406_NOT_ACCEPTABLE.tsv
+++ b/compiler/errors/source/406_NOT_ACCEPTABLE.tsv
@@ -1,5 +1,6 @@
 id	message
 AUTH_KEY_DUPLICATED	The same authorization key (session file) was used in more than one place simultaneously. You must delete your session file and log in again with your phone number or bot token
+CHANNEL_PRIVATE	The channel/supergroup is not accessible
 FILEREF_UPGRADE_NEEDED	The file reference has expired and you must use a refreshed one by obtaining the original media message
 FRESH_CHANGE_ADMINS_FORBIDDEN	You were just elected admin, you can't add or modify other admins yet
 FRESH_CHANGE_PHONE_FORBIDDEN	You can't change your phone number because your session was logged-in recently

From 54f8ca25fb40f3ef953b6aded34ee1839d87ae81 Mon Sep 17 00:00:00 2001
From: Sam <25792361+sam01101@users.noreply.github.com>
Date: Sun, 13 Feb 2022 18:37:43 +0800
Subject: [PATCH 0742/1185] Add 400 CHANNEL_ADD_INVALID error (#894)

* Add RPC Error: CHANNEL_ADD_INVALID

* Update 400_BAD_REQUEST.tsv

Co-authored-by: Dan <14043624+delivrance@users.noreply.github.com>
---
 compiler/errors/source/400_BAD_REQUEST.tsv | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/compiler/errors/source/400_BAD_REQUEST.tsv b/compiler/errors/source/400_BAD_REQUEST.tsv
index d6754027ee..e5c74e010c 100644
--- a/compiler/errors/source/400_BAD_REQUEST.tsv
+++ b/compiler/errors/source/400_BAD_REQUEST.tsv
@@ -45,6 +45,7 @@ CALL_PROTOCOL_FLAGS_INVALID	Call protocol flags invalid
 CDN_METHOD_INVALID	The method can't be used on CDN DCs
 CHANNELS_ADMIN_PUBLIC_TOO_MUCH	You are an administrator of too many public channels
 CHANNELS_TOO_MUCH	You have joined too many channels or supergroups, leave some and try again
+CHANNEL_ADD_INVALID	Internal error.
 CHANNEL_BANNED	The channel is banned
 CHANNEL_INVALID	The channel parameter is invalid
 CHANNEL_PRIVATE	The channel/supergroup is not accessible
@@ -348,4 +349,4 @@ WEBDOCUMENT_URL_EMPTY	The web document URL is empty
 WEBDOCUMENT_URL_INVALID	The web document URL is invalid
 WEBPAGE_CURL_FAILED	Telegram server could not fetch the provided URL
 WEBPAGE_MEDIA_EMPTY	The URL doesn't contain any valid media
-YOU_BLOCKED_USER	You blocked this user
\ No newline at end of file
+YOU_BLOCKED_USER	You blocked this user

From bca7e6461e6519d1b360e68102df45fc5ceec66e Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Thu, 17 Feb 2022 11:41:14 +0100
Subject: [PATCH 0743/1185] Add reply_to_message_id and reply_to_top_message_id
 to Message

---
 pyrogram/types/messages_and_media/message.py | 32 ++++++++++++++------
 1 file changed, 23 insertions(+), 9 deletions(-)

diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py
index 1f6e432a16..93f0671237 100644
--- a/pyrogram/types/messages_and_media/message.py
+++ b/pyrogram/types/messages_and_media/message.py
@@ -95,6 +95,12 @@ class Message(Object, Update):
         forward_date (``int``, *optional*):
             For forwarded messages, date the original message was sent in Unix time.
 
+        reply_to_message_id (``int``, *optional*):
+            The id of the message which this message directly replied to.
+
+        reply_to_top_message_id (``int``, *optional*):
+            The id of the first message which started this message thread.
+
         reply_to_message (:obj:`~pyrogram.types.Message`, *optional*):
             For replies, the original message. Note that the Message object in this field will not contain
             further reply_to_message fields even if it itself is a reply.
@@ -308,6 +314,8 @@ def __init__(
         forward_from_message_id: int = None,
         forward_signature: str = None,
         forward_date: int = None,
+        reply_to_message_id: int = None,
+        reply_to_top_message_id: int = None,
         reply_to_message: "Message" = None,
         mentioned: bool = None,
         empty: bool = None,
@@ -380,6 +388,8 @@ def __init__(
         self.forward_from_message_id = forward_from_message_id
         self.forward_signature = forward_signature
         self.forward_date = forward_date
+        self.reply_to_message_id = reply_to_message_id
+        self.reply_to_top_message_id = reply_to_top_message_id
         self.reply_to_message = reply_to_message
         self.mentioned = mentioned
         self.empty = empty
@@ -792,15 +802,19 @@ async def _parse(
                 client=client
             )
 
-            if message.reply_to and replies:
-                try:
-                    parsed_message.reply_to_message = await client.get_messages(
-                        parsed_message.chat.id,
-                        reply_to_message_ids=message.id,
-                        replies=replies - 1
-                    )
-                except MessageIdsEmpty:
-                    pass
+            if message.reply_to:
+                if replies:
+                    try:
+                        parsed_message.reply_to_message = await client.get_messages(
+                            parsed_message.chat.id,
+                            reply_to_message_ids=message.id,
+                            replies=replies - 1
+                        )
+                    except MessageIdsEmpty:
+                        pass
+
+                parsed_message.reply_to_message_id = message.reply_to.reply_to_msg_id
+                parsed_message.reply_to_top_message_id = message.reply_to.reply_to_top_id
 
             return parsed_message
 

From bf233e3b5b18d98fb94438c593c0998e1baeb002 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Thu, 17 Feb 2022 11:42:42 +0100
Subject: [PATCH 0744/1185] Update Filters.reply to check for
 Message.reply_to_message_id

---
 pyrogram/filters.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/filters.py b/pyrogram/filters.py
index 46ca896f4d..2a44fd4f9a 100644
--- a/pyrogram/filters.py
+++ b/pyrogram/filters.py
@@ -218,7 +218,7 @@ async def text_filter(_, __, m: Message):
 
 # region reply_filter
 async def reply_filter(_, __, m: Message):
-    return bool(m.reply_to_message)
+    return bool(m.reply_to_message_id)
 
 
 reply = create(reply_filter)

From e0eccfa8fbc458c015d979ddf27dd2dad92b4238 Mon Sep 17 00:00:00 2001
From: blank X 
Date: Sat, 26 Feb 2022 10:13:08 +0000
Subject: [PATCH 0745/1185] Respect `file_name` if file passed is a file object
 (#912)

Fixes #911
---
 pyrogram/methods/messages/send_animation.py | 4 ++--
 pyrogram/methods/messages/send_audio.py     | 4 ++--
 pyrogram/methods/messages/send_document.py  | 4 ++--
 pyrogram/methods/messages/send_video.py     | 4 ++--
 4 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/pyrogram/methods/messages/send_animation.py b/pyrogram/methods/messages/send_animation.py
index 01856a79ef..963d0681b9 100644
--- a/pyrogram/methods/messages/send_animation.py
+++ b/pyrogram/methods/messages/send_animation.py
@@ -201,7 +201,7 @@ def progress(current, total):
                 thumb = await self.save_file(thumb)
                 file = await self.save_file(animation, progress=progress, progress_args=progress_args)
                 media = raw.types.InputMediaUploadedDocument(
-                    mime_type=self.guess_mime_type(animation.name) or "video/mp4",
+                    mime_type=self.guess_mime_type(file_name or animation.name) or "video/mp4",
                     file=file,
                     thumb=thumb,
                     attributes=[
@@ -211,7 +211,7 @@ def progress(current, total):
                             w=width,
                             h=height
                         ),
-                        raw.types.DocumentAttributeFilename(file_name=animation.name),
+                        raw.types.DocumentAttributeFilename(file_name=file_name or animation.name),
                         raw.types.DocumentAttributeAnimated()
                     ]
                 )
diff --git a/pyrogram/methods/messages/send_audio.py b/pyrogram/methods/messages/send_audio.py
index 4ce02c31a1..921b7890c7 100644
--- a/pyrogram/methods/messages/send_audio.py
+++ b/pyrogram/methods/messages/send_audio.py
@@ -197,7 +197,7 @@ def progress(current, total):
                 thumb = await self.save_file(thumb)
                 file = await self.save_file(audio, progress=progress, progress_args=progress_args)
                 media = raw.types.InputMediaUploadedDocument(
-                    mime_type=self.guess_mime_type(audio.name) or "audio/mpeg",
+                    mime_type=self.guess_mime_type(file_name or audio.name) or "audio/mpeg",
                     file=file,
                     thumb=thumb,
                     attributes=[
@@ -206,7 +206,7 @@ def progress(current, total):
                             performer=performer,
                             title=title
                         ),
-                        raw.types.DocumentAttributeFilename(file_name=audio.name)
+                        raw.types.DocumentAttributeFilename(file_name=file_name or audio.name)
                     ]
                 )
 
diff --git a/pyrogram/methods/messages/send_document.py b/pyrogram/methods/messages/send_document.py
index bc407fe2b3..c3765a1726 100644
--- a/pyrogram/methods/messages/send_document.py
+++ b/pyrogram/methods/messages/send_document.py
@@ -180,11 +180,11 @@ def progress(current, total):
                 thumb = await self.save_file(thumb)
                 file = await self.save_file(document, progress=progress, progress_args=progress_args)
                 media = raw.types.InputMediaUploadedDocument(
-                    mime_type=self.guess_mime_type(document.name) or "application/zip",
+                    mime_type=self.guess_mime_type(file_name or document.name) or "application/zip",
                     file=file,
                     thumb=thumb,
                     attributes=[
-                        raw.types.DocumentAttributeFilename(file_name=document.name)
+                        raw.types.DocumentAttributeFilename(file_name=file_name or document.name)
                     ]
                 )
 
diff --git a/pyrogram/methods/messages/send_video.py b/pyrogram/methods/messages/send_video.py
index cccc07ab98..e9448a2209 100644
--- a/pyrogram/methods/messages/send_video.py
+++ b/pyrogram/methods/messages/send_video.py
@@ -207,7 +207,7 @@ def progress(current, total):
                 thumb = await self.save_file(thumb)
                 file = await self.save_file(video, progress=progress, progress_args=progress_args)
                 media = raw.types.InputMediaUploadedDocument(
-                    mime_type=self.guess_mime_type(video.name) or "video/mp4",
+                    mime_type=self.guess_mime_type(file_name or video.name) or "video/mp4",
                     file=file,
                     ttl_seconds=ttl_seconds,
                     thumb=thumb,
@@ -218,7 +218,7 @@ def progress(current, total):
                             w=width,
                             h=height
                         ),
-                        raw.types.DocumentAttributeFilename(file_name=video.name)
+                        raw.types.DocumentAttributeFilename(file_name=file_name or video.name)
                     ]
                 )
 

From 4dc771b64ad44b982e3eb0145505ec54d94281f9 Mon Sep 17 00:00:00 2001
From: scrazzz <70033559+scrazzz@users.noreply.github.com>
Date: Sat, 26 Feb 2022 13:13:52 +0300
Subject: [PATCH 0746/1185] Fix docstring for `message.reply_photo` (#903)

---
 pyrogram/types/messages_and_media/message.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py
index 93f0671237..355a83a534 100644
--- a/pyrogram/types/messages_and_media/message.py
+++ b/pyrogram/types/messages_and_media/message.py
@@ -1904,7 +1904,7 @@ async def reply_photo(
                 If *reply_to_message_id* is passed, this parameter will be ignored.
                 Defaults to ``True`` in group chats and ``False`` in private chats.
 
-            caption (``bool``, *optional*):
+            caption (``str``, *optional*):
                 Photo caption, 0-1024 characters.
 
             parse_mode (``str``, *optional*):

From 748222131ddbdefa82a1530817f62ec955d67eab Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Mon, 28 Feb 2022 12:49:52 +0100
Subject: [PATCH 0747/1185] Update Pyrogram to v1.4.8

---
 pyrogram/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index 304ce76d3c..76aa3afbf6 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -16,7 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-__version__ = "1.4.7"
+__version__ = "1.4.8"
 __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)"
 __copyright__ = "Copyright (C) 2017-present Dan "
 

From 5ee6f3d2c715c916c02ac1e1682a2c5638ea3b84 Mon Sep 17 00:00:00 2001
From: "Mr. Developer" <77911154+MrBotDeveloper@users.noreply.github.com>
Date: Mon, 7 Mar 2022 17:43:22 +0530
Subject: [PATCH 0748/1185] Add some missing parameters to Message.reply_text

---
 pyrogram/types/messages_and_media/message.py | 12 +++++++++++-
 1 file changed, 11 insertions(+), 1 deletion(-)

diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py
index 355a83a534..df5f2dd1d1 100644
--- a/pyrogram/types/messages_and_media/message.py
+++ b/pyrogram/types/messages_and_media/message.py
@@ -863,7 +863,9 @@ async def reply_text(
         disable_web_page_preview: bool = None,
         disable_notification: bool = None,
         reply_to_message_id: int = None,
-        reply_markup=None
+        schedule_date: int = None,
+        protect_content: bool = None,
+        reply_markup = None
     ) -> "Message":
         """Bound method *reply_text* of :obj:`~pyrogram.types.Message`.
 
@@ -913,6 +915,12 @@ async def reply_text(
             reply_to_message_id (``int``, *optional*):
                 If the message is a reply, ID of the original message.
 
+            schedule_date (``int``, *optional*):
+                Date when the message will be automatically sent. Unix time.
+
+            protect_content (``bool``, *optional*):
+                Protects the contents of the sent message from forwarding and saving.
+
             reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*):
                 Additional interface options. An object for an inline keyboard, custom reply keyboard,
                 instructions to remove reply keyboard or to force a reply from the user.
@@ -937,6 +945,8 @@ async def reply_text(
             disable_web_page_preview=disable_web_page_preview,
             disable_notification=disable_notification,
             reply_to_message_id=reply_to_message_id,
+            schedule_date=schedule_date,
+            protect_content=protect_content,
             reply_markup=reply_markup
         )
 

From 8d7b62654c099433ccb8280272d78ef168e073e8 Mon Sep 17 00:00:00 2001
From: Nick <64551534+null-nick@users.noreply.github.com>
Date: Fri, 11 Mar 2022 12:14:59 +0100
Subject: [PATCH 0749/1185] Update API schema to Layer 139 (#936)

---
 compiler/api/source/main_api.tl | 22 +++++++++++++++++-----
 1 file changed, 17 insertions(+), 5 deletions(-)

diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl
index 9d5d811bfb..5bd7dfb568 100644
--- a/compiler/api/source/main_api.tl
+++ b/compiler/api/source/main_api.tl
@@ -213,6 +213,8 @@ inputReportReasonOther#c1e4a2b1 = ReportReason;
 inputReportReasonCopyright#9b89f93a = ReportReason;
 inputReportReasonGeoIrrelevant#dbd4feed = ReportReason;
 inputReportReasonFake#f5ddd6e7 = ReportReason;
+inputReportReasonIllegalDrugs#a8eb2be = ReportReason;
+inputReportReasonPersonalDetails#9ec7863d = ReportReason;
 
 userFull#cf366521 flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true id:long about:flags.1?string settings:PeerSettings profile_photo:flags.2?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int ttl_period:flags.14?int theme_emoticon:flags.15?string private_forward_name:flags.16?string = UserFull;
 
@@ -544,7 +546,7 @@ inputStickerSetAnimatedEmoji#28703c8 = InputStickerSet;
 inputStickerSetDice#e67f520e emoticon:string = InputStickerSet;
 inputStickerSetAnimatedEmojiAnimations#cde3739 = InputStickerSet;
 
-stickerSet#d7df217a flags:# archived:flags.1?true official:flags.2?true masks:flags.3?true animated:flags.5?true gifs:flags.6?true installed_date:flags.0?int id:long access_hash:long title:string short_name:string thumbs:flags.4?Vector thumb_dc_id:flags.4?int thumb_version:flags.4?int count:int hash:int = StickerSet;
+stickerSet#d7df217a flags:# archived:flags.1?true official:flags.2?true masks:flags.3?true animated:flags.5?true videos:flags.6?true installed_date:flags.0?int id:long access_hash:long title:string short_name:string thumbs:flags.4?Vector thumb_dc_id:flags.4?int thumb_version:flags.4?int count:int hash:int = StickerSet;
 
 messages.stickerSet#b60a24a6 set:StickerSet packs:Vector documents:Vector = messages.StickerSet;
 messages.stickerSetNotModified#d3f924eb = messages.StickerSet;
@@ -1204,7 +1206,7 @@ peerBlocked#e8fd8014 peer_id:Peer date:int = PeerBlocked;
 stats.messageStats#8999f295 views_graph:StatsGraph = stats.MessageStats;
 
 groupCallDiscarded#7780bcb4 id:long access_hash:long duration:int = GroupCall;
-groupCall#d597650c flags:# join_muted:flags.1?true can_change_join_muted:flags.2?true join_date_asc:flags.6?true schedule_start_subscribed:flags.8?true can_start_video:flags.9?true record_video_active:flags.11?true id:long access_hash:long participants_count:int title:flags.3?string stream_dc_id:flags.4?int record_start_date:flags.5?int schedule_date:flags.7?int unmuted_video_count:flags.10?int unmuted_video_limit:int version:int = GroupCall;
+groupCall#d597650c flags:# join_muted:flags.1?true can_change_join_muted:flags.2?true join_date_asc:flags.6?true schedule_start_subscribed:flags.8?true can_start_video:flags.9?true record_video_active:flags.11?true rtmp_stream:flags.12?true listeners_hidden:flags.13?true id:long access_hash:long participants_count:int title:flags.3?string stream_dc_id:flags.4?int record_start_date:flags.5?int schedule_date:flags.7?int unmuted_video_count:flags.10?int unmuted_video_limit:int version:int = GroupCall;
 
 inputGroupCall#d8aa840f id:long access_hash:long = InputGroupCall;
 
@@ -1299,6 +1301,12 @@ messages.translateResultText#a214f7d0 text:string = messages.TranslatedText;
 
 messagePeerReaction#51b67eff flags:# big:flags.0?true unread:flags.1?true peer_id:Peer reaction:string = MessagePeerReaction;
 
+groupCallStreamChannel#80eb48af channel:int scale:int last_timestamp_ms:long = GroupCallStreamChannel;
+
+phone.groupCallStreamChannels#d0e482b2 channels:Vector = phone.GroupCallStreamChannels;
+
+phone.groupCallStreamRtmpUrl#2dbf3432 url:string key:string = phone.GroupCallStreamRtmpUrl;
+
 ---functions---
 
 invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
@@ -1427,6 +1435,7 @@ contacts.addContact#e8f463d0 flags:# add_phone_privacy_exception:flags.0?true id
 contacts.acceptContact#f831a20f id:InputUser = Updates;
 contacts.getLocated#d348bc44 flags:# background:flags.1?true geo_point:InputGeoPoint self_expires:flags.0?int = Updates;
 contacts.blockFromReplies#29a8962c flags:# delete_message:flags.0?true delete_history:flags.1?true report_spam:flags.2?true msg_id:int = Updates;
+contacts.resolvePhone#8af94344 phone:string = contacts.ResolvedPeer;
 
 messages.getMessages#63c66506 id:Vector = messages.Messages;
 messages.getDialogs#a0f4cb4f flags:# exclude_pinned:flags.0?true folder_id:flags.1?int offset_date:int offset_id:int offset_peer:InputPeer limit:int hash:long = messages.Dialogs;
@@ -1586,6 +1595,7 @@ messages.setDefaultReaction#d960c4d4 reaction:string = Bool;
 messages.translateText#24ce6dee flags:# peer:flags.0?InputPeer msg_id:flags.0?int text:flags.1?string from_lang:flags.2?string to_lang:string = messages.TranslatedText;
 messages.getUnreadReactions#e85bae1a peer:InputPeer offset_id:int add_offset:int limit:int max_id:int min_id:int = messages.Messages;
 messages.readReactions#82e251d7 peer:InputPeer = messages.AffectedHistory;
+messages.searchSentMedia#107e31a0 q:string filter:MessagesFilter limit:int = messages.Messages;
 
 updates.getState#edd4882a = updates.State;
 updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference;
@@ -1682,7 +1692,7 @@ payments.getSavedInfo#227d824b = payments.SavedInfo;
 payments.clearSavedInfo#d83d70c1 flags:# credentials:flags.0?true info:flags.1?true = Bool;
 payments.getBankCardData#2e79d779 number:string = payments.BankCardData;
 
-stickers.createStickerSet#9021ab67 flags:# masks:flags.0?true animated:flags.1?true user_id:InputUser title:string short_name:string thumb:flags.2?InputDocument stickers:Vector software:flags.3?string = messages.StickerSet;
+stickers.createStickerSet#9021ab67 flags:# masks:flags.0?true animated:flags.1?true videos:flags.4?true user_id:InputUser title:string short_name:string thumb:flags.2?InputDocument stickers:Vector software:flags.3?string = messages.StickerSet;
 stickers.removeStickerFromSet#f7760f51 sticker:InputDocument = messages.StickerSet;
 stickers.changeStickerPosition#ffb6d4ca sticker:InputDocument position:int = messages.StickerSet;
 stickers.addStickerToSet#8653febe stickerset:InputStickerSet sticker:InputStickerSetItem = messages.StickerSet;
@@ -1699,7 +1709,7 @@ phone.discardCall#b2cbc1c0 flags:# video:flags.0?true peer:InputPhoneCall durati
 phone.setCallRating#59ead627 flags:# user_initiative:flags.0?true peer:InputPhoneCall rating:int comment:string = Updates;
 phone.saveCallDebug#277add7e peer:InputPhoneCall debug:DataJSON = Bool;
 phone.sendSignalingData#ff7a9383 peer:InputPhoneCall data:bytes = Bool;
-phone.createGroupCall#48cdc6d8 flags:# peer:InputPeer random_id:int title:flags.0?string schedule_date:flags.1?int = Updates;
+phone.createGroupCall#48cdc6d8 flags:# rtmp_stream:flags.2?true peer:InputPeer random_id:int title:flags.0?string schedule_date:flags.1?int = Updates;
 phone.joinGroupCall#b132ff7b flags:# muted:flags.0?true video_stopped:flags.2?true call:InputGroupCall join_as:InputPeer invite_hash:flags.1?string params:DataJSON = Updates;
 phone.leaveGroupCall#500377f9 call:InputGroupCall source:int = Updates;
 phone.inviteToGroupCall#7b393160 call:InputGroupCall users:Vector = Updates;
@@ -1718,6 +1728,8 @@ phone.startScheduledGroupCall#5680e342 call:InputGroupCall = Updates;
 phone.saveDefaultGroupCallJoinAs#575e1f8c peer:InputPeer join_as:InputPeer = Bool;
 phone.joinGroupCallPresentation#cbea6bc4 call:InputGroupCall params:DataJSON = Updates;
 phone.leaveGroupCallPresentation#1c50d144 call:InputGroupCall = Updates;
+phone.getGroupCallStreamChannels#1ab21940 call:InputGroupCall = phone.GroupCallStreamChannels;
+phone.getGroupCallStreamRtmpUrl#deb3abbf peer:InputPeer revoke:Bool = phone.GroupCallStreamRtmpUrl;
 
 langpack.getLangPack#f2f2330a lang_pack:string lang_code:string = LangPackDifference;
 langpack.getStrings#efea3803 lang_pack:string lang_code:string keys:Vector = Vector;
@@ -1734,4 +1746,4 @@ stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel
 stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages;
 stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats;
 
-// LAYER 138
\ No newline at end of file
+// LAYER 139
\ No newline at end of file

From d0e2235835e8ecdf557df7a876502a16be654eba Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Fri, 11 Mar 2022 12:29:05 +0100
Subject: [PATCH 0750/1185] Improve the RLE codec

---
 pyrogram/file_id.py | 30 ++++++++++++++++--------------
 1 file changed, 16 insertions(+), 14 deletions(-)

diff --git a/pyrogram/file_id.py b/pyrogram/file_id.py
index ee1ccc4a89..95f6ccbf1a 100644
--- a/pyrogram/file_id.py
+++ b/pyrogram/file_id.py
@@ -63,20 +63,20 @@ def rle_encode(s: bytes) -> bytes:
     Returns:
         ``bytes``: The encoded bytes
     """
-    r = b""
-    n = 0
+    r: bytes = b""
+    n: int = 0
 
     for b in s:
-        if b == 0:
+        if not b:
             n += 1
         else:
-            if n > 0:
+            if n:
                 r += bytes([0, n])
                 n = 0
 
             r += bytes([b])
 
-    if n > 0:
+    if n:
         r += bytes([0, n])
 
     return r
@@ -92,17 +92,19 @@ def rle_decode(s: bytes) -> bytes:
     Returns:
         ``bytes``: The encoded bytes
     """
-    r = b""
-    i = 0
+    r: bytes = b""
+    z: bool = False
 
-    while i < len(s):
-        if s[i] != 0:
-            r += bytes([s[i]])
-        else:
-            r += b"\x00" * s[i + 1]
-            i += 1
+    for b in s:
+        if not b:
+            z = True
+            continue
 
-        i += 1
+        if z:
+            r += b"\x00" * b
+            z = False
+        else:
+            r += bytes([b])
 
     return r
 

From 58eb10a676053669279b79ddc576c6ff4087daa3 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Mon, 14 Mar 2022 12:07:34 +0100
Subject: [PATCH 0751/1185] Remove unneeded try...except block

---
 pyrogram/client.py | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/pyrogram/client.py b/pyrogram/client.py
index ce76920fa9..7d3dbd3701 100644
--- a/pyrogram/client.py
+++ b/pyrogram/client.py
@@ -251,10 +251,7 @@ def __enter__(self):
         return self.start()
 
     def __exit__(self, *args):
-        try:
-            self.stop()
-        except ConnectionError:
-            pass
+        self.stop()
 
     async def __aenter__(self):
         return await self.start()

From 8ee5ea02b19228a40910ae641cef1a49aa603780 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sat, 19 Mar 2022 20:57:17 +0100
Subject: [PATCH 0752/1185] Update pin_chat_message to return Message instead
 of bool

---
 pyrogram/methods/chats/pin_chat_message.py   | 16 +++++++++++-----
 pyrogram/types/messages_and_media/message.py |  4 ++--
 2 files changed, 13 insertions(+), 7 deletions(-)

diff --git a/pyrogram/methods/chats/pin_chat_message.py b/pyrogram/methods/chats/pin_chat_message.py
index d4d6b040f8..016a19a7b2 100644
--- a/pyrogram/methods/chats/pin_chat_message.py
+++ b/pyrogram/methods/chats/pin_chat_message.py
@@ -18,7 +18,7 @@
 
 from typing import Union
 
-from pyrogram import raw
+from pyrogram import raw, types
 from pyrogram.scaffold import Scaffold
 
 
@@ -29,7 +29,7 @@ async def pin_chat_message(
         message_id: int,
         disable_notification: bool = False,
         both_sides: bool = False,
-    ) -> bool:
+    ) -> "types.Message":
         """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.
@@ -50,7 +50,7 @@ async def pin_chat_message(
                 Applicable to private chats only. Defaults to False.
 
         Returns:
-            ``bool``: True on success.
+            :obj:`~pyrogram.types.Message`: On success, the service message is returned.
 
         Example:
             .. code-block:: python
@@ -61,7 +61,7 @@ async def pin_chat_message(
                 # Pin without notification
                 app.pin_chat_message(chat_id, message_id, disable_notification=True)
         """
-        await self.send(
+        r = await self.send(
             raw.functions.messages.UpdatePinnedMessage(
                 peer=await self.resolve_peer(chat_id),
                 id=message_id,
@@ -70,4 +70,10 @@ async def pin_chat_message(
             )
         )
 
-        return True
+        users = {u.id: u for u in r.users}
+        chats = {c.id: c for c in r.chats}
+
+        for i in r.updates:
+            if isinstance(i, (raw.types.UpdateNewMessage,
+                              raw.types.UpdateNewChannelMessage)):
+                return await types.Message._parse(self, i.message, users, chats)
diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py
index df5f2dd1d1..ea54231d70 100644
--- a/pyrogram/types/messages_and_media/message.py
+++ b/pyrogram/types/messages_and_media/message.py
@@ -3423,7 +3423,7 @@ async def vote(
             options=option
         )
 
-    async def pin(self, disable_notification: bool = False, both_sides: bool = False) -> bool:
+    async def pin(self, disable_notification: bool = False, both_sides: bool = False) -> "types.Message":
         """Bound method *pin* of :obj:`~pyrogram.types.Message`.
 
         Use as a shortcut for:
@@ -3450,7 +3450,7 @@ async def pin(self, disable_notification: bool = False, both_sides: bool = False
                 Applicable to private chats only. Defaults to False.
 
         Returns:
-            True on success.
+            :obj:`~pyrogram.types.Message`: On success, the service message is returned.
 
         Raises:
             RPCError: In case of a Telegram RPC error.

From 65a53aeeb3141718d1bb5efd40b0a10db24165e2 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sat, 19 Mar 2022 21:00:36 +0100
Subject: [PATCH 0753/1185] Update Pyrogram to v1.4.9

---
 pyrogram/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index 76aa3afbf6..ebb3b16355 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -16,7 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-__version__ = "1.4.8"
+__version__ = "1.4.9"
 __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)"
 __copyright__ = "Copyright (C) 2017-present Dan "
 

From aa41ac5fd252146735f32f460c44fab27fcf01e7 Mon Sep 17 00:00:00 2001
From: Andrea Maugeri 
Date: Mon, 28 Mar 2022 11:48:15 +0200
Subject: [PATCH 0754/1185] Fix issue in set_bot_commands (#778)

---
 pyrogram/methods/bots/set_bot_commands.py     | 26 ++++---
 pyrogram/types/bots_and_keyboards/__init__.py |  3 +-
 .../types/bots_and_keyboards/bot_command.py   | 74 ++++++++++++++++++-
 3 files changed, 91 insertions(+), 12 deletions(-)

diff --git a/pyrogram/methods/bots/set_bot_commands.py b/pyrogram/methods/bots/set_bot_commands.py
index 02158c0cb6..769a430156 100644
--- a/pyrogram/methods/bots/set_bot_commands.py
+++ b/pyrogram/methods/bots/set_bot_commands.py
@@ -16,17 +16,23 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-from typing import Optional, List
+from typing import List, Optional
 
-from pyrogram import raw
-from pyrogram import types
+from pyrogram import raw, types
 from pyrogram.scaffold import Scaffold
 
 
 class SetBotCommands(Scaffold):
-    async def set_bot_commands(self, commands: Optional[List[types.BotCommand]]):
+    async def set_bot_commands(
+        self,
+        commands: Optional[List[types.BotCommand]],
+        scope: types.BotCommandScope = types.BotCommandScope(
+            types.BotCommandScope.DEFAULT
+        ),
+        lang_code: str = "",
+    ):
         """Set the bot commands list.
-        
+
         The commands passed will overwrite any command set previously.
         This method can be used by the own bot only.
 
@@ -40,20 +46,22 @@ async def set_bot_commands(self, commands: Optional[List[types.BotCommand]]):
 
         Example:
             .. code-block:: python
-                
+
                 from pyrogram.types import BotCommand
-                
+
                 # Set new commands
                 app.set_bot_commands([
                     BotCommand("start", "Start the bot"),
                     BotCommand("settings", "Bot settings")])
-                
+
                 # Remove commands
                 app.set_bot_commands(None)
         """
 
         return await self.send(
             raw.functions.bots.SetBotCommands(
-                commands=[c.write() for c in commands or []]
+                commands=[c.write() for c in commands or []],
+                scope=scope.write(),
+                lang_code=lang_code,
             )
         )
diff --git a/pyrogram/types/bots_and_keyboards/__init__.py b/pyrogram/types/bots_and_keyboards/__init__.py
index ccf84993c7..d69c70fa7f 100644
--- a/pyrogram/types/bots_and_keyboards/__init__.py
+++ b/pyrogram/types/bots_and_keyboards/__init__.py
@@ -16,7 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-from .bot_command import BotCommand
+from .bot_command import BotCommand, BotCommandScope
 from .callback_game import CallbackGame
 from .callback_query import CallbackQuery
 from .force_reply import ForceReply
@@ -40,4 +40,5 @@
     "ReplyKeyboardRemove",
     "LoginUrl",
     "BotCommand",
+    "BotCommandScope",
 ]
diff --git a/pyrogram/types/bots_and_keyboards/bot_command.py b/pyrogram/types/bots_and_keyboards/bot_command.py
index 66412f09cf..82cdc67ef8 100644
--- a/pyrogram/types/bots_and_keyboards/bot_command.py
+++ b/pyrogram/types/bots_and_keyboards/bot_command.py
@@ -17,6 +17,7 @@
 #  along with Pyrogram.  If not, see .
 
 from pyrogram import raw
+
 from ..object import Object
 
 
@@ -26,7 +27,7 @@ class BotCommand(Object):
     Parameters:
         command (``str``):
             The bot command, for example: "/start".
-            
+
         description (``str``):
             Description of the bot command.
     """
@@ -40,5 +41,74 @@ def __init__(self, command: str, description: str):
     def write(self):
         return raw.types.BotCommand(
             command=self.command,
-            description=self.description
+            description=self.description,
         )
+
+
+class BotCommandScope(Object):
+    """
+    Represents a scope where the bot commands, specified
+    using bots.setBotCommands will be valid.
+
+    Parameters:
+        scope (``str``):
+
+            - DEFAULT: The commands will be valid in all chats (default value)
+
+            - PRIVATE: The specified bot commands will only be valid in all private
+            chats with users.
+
+            - GROUP: The specified bot commands will be valid in all groups and supergroups
+
+            - GROUP_ADMINS: The specified bot commands will be valid only for chat
+            administrators, in all groups and supergroups.
+
+            - PEER: The specified bot commands will be valid only in a specific dialog
+
+            - PEER_ADMINS: The specified bot commands will be valid for all admins of the
+            specified group or supergroup.
+
+            - PEER_USER: The specified bot commands will be valid only for a specific user
+            in the specified chat
+    """
+
+    DEFAULT = "default"
+    PRIVATE = "users"
+    GROUP = "chats"
+    GROUP_ADMINS = "chat_admins"
+    PEER = "peer"
+    PEER_ADMINS = "peer_admins"
+    PEER_USER = "peer_user"
+
+    raw_scopes = {
+        DEFAULT: raw.types.BotCommandScopeDefault,
+        PRIVATE: raw.types.BotCommandScopeUsers,
+        GROUP: raw.types.BotCommandScopeChats,
+        GROUP_ADMINS: raw.types.BotCommandScopeChatAdmins,
+        PEER: lambda peer: raw.types.BotCommandScopePeer(peer),
+        PEER_ADMINS: lambda peer: raw.types.BotCommandScopePeerAdmins(peer),
+        PEER_USER: lambda peer, user_id: raw.types.BotCommandScopePeerUser(
+            peer, user_id
+        ),
+    }
+
+    def __init__(
+        self,
+        scope: str,
+        peer: raw.types.InputPeerUser = None,
+        user_id: raw.types.InputUser = None,
+    ):
+        super().__init__()
+        self.scope = scope
+        self.peer = peer
+        self.user_id = user_id
+
+    def write(self):
+
+        if self.scope in ["peer", "peer_admins"]:
+            return self.raw_scopes[self.scope](self.peer)
+
+        elif self.scope == "peer_user":
+            return self.raw_scopes[self.scopes](self.peer, self.user_id)
+
+        return self.raw_scopes[self.scope]()

From 7bfcd5ac5fc02710384b2166a924340c08495075 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Mon, 28 Mar 2022 13:23:12 +0200
Subject: [PATCH 0755/1185] Revamp bot commands and bot command scopes Closes
 #777

---
 compiler/docs/compiler.py                     | 17 +++-
 compiler/docs/template/types.rst              | 21 ++++-
 pyrogram/methods/bots/__init__.py             |  6 +-
 pyrogram/methods/bots/delete_bot_commands.py  | 61 ++++++++++++++
 pyrogram/methods/bots/get_bot_commands.py     | 62 ++++++++++++++
 pyrogram/methods/bots/set_bot_commands.py     | 37 +++++----
 pyrogram/types/bots_and_keyboards/__init__.py | 17 +++-
 .../types/bots_and_keyboards/bot_command.py   | 81 +++----------------
 .../bots_and_keyboards/bot_command_scope.py   | 73 +++++++++++++++++
 ...t_command_scope_all_chat_administrators.py | 32 ++++++++
 .../bot_command_scope_all_group_chats.py      | 32 ++++++++
 .../bot_command_scope_all_private_chats.py    | 32 ++++++++
 .../bot_command_scope_chat.py                 | 43 ++++++++++
 .../bot_command_scope_chat_administrators.py  | 43 ++++++++++
 .../bot_command_scope_chat_member.py          | 48 +++++++++++
 .../bot_command_scope_default.py              | 33 ++++++++
 16 files changed, 543 insertions(+), 95 deletions(-)
 create mode 100644 pyrogram/methods/bots/delete_bot_commands.py
 create mode 100644 pyrogram/methods/bots/get_bot_commands.py
 create mode 100644 pyrogram/types/bots_and_keyboards/bot_command_scope.py
 create mode 100644 pyrogram/types/bots_and_keyboards/bot_command_scope_all_chat_administrators.py
 create mode 100644 pyrogram/types/bots_and_keyboards/bot_command_scope_all_group_chats.py
 create mode 100644 pyrogram/types/bots_and_keyboards/bot_command_scope_all_private_chats.py
 create mode 100644 pyrogram/types/bots_and_keyboards/bot_command_scope_chat.py
 create mode 100644 pyrogram/types/bots_and_keyboards/bot_command_scope_chat_administrators.py
 create mode 100644 pyrogram/types/bots_and_keyboards/bot_command_scope_chat_member.py
 create mode 100644 pyrogram/types/bots_and_keyboards/bot_command_scope_default.py

diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py
index c1a4588603..118c0e636f 100644
--- a/compiler/docs/compiler.py
+++ b/compiler/docs/compiler.py
@@ -288,6 +288,8 @@ def get_title_list(s: str) -> list:
             set_game_score
             get_game_high_scores
             set_bot_commands
+            get_bot_commands
+            delete_bot_commands
         """,
         authorization="""
         Authorization
@@ -395,8 +397,8 @@ def get_title_list(s: str) -> list:
             VoiceChatEnded
             VoiceChatMembersInvited
         """,
-        bots_keyboard="""
-        Bots & Keyboards
+        bot_keyboards="""
+        Bot keyboards
             ReplyKeyboardMarkup
             KeyboardButton
             ReplyKeyboardRemove
@@ -407,7 +409,18 @@ def get_title_list(s: str) -> list:
             CallbackQuery
             GameHighScore
             CallbackGame
+        """,
+        bot_commands="""
+        Bot commands
             BotCommand
+            BotCommandScope
+            BotCommandScopeDefault
+            BotCommandScopeAllPrivateChats
+            BotCommandScopeAllGroupChats
+            BotCommandScopeAllChatAdministrators
+            BotCommandScopeChat
+            BotCommandScopeChatAdministrators
+            BotCommandScopeChatMember
         """,
         input_media="""
         Input Media
diff --git a/compiler/docs/template/types.rst b/compiler/docs/template/types.rst
index 2651c3559a..2011a61100 100644
--- a/compiler/docs/template/types.rst
+++ b/compiler/docs/template/types.rst
@@ -51,18 +51,31 @@ Messages & Media
 
     {messages_media}
 
-Bots & Keyboards
-----------------
+Bot keyboards
+-------------
+
+.. autosummary::
+    :nosignatures:
+
+    {bot_keyboards}
+
+.. toctree::
+    :hidden:
+
+    {bot_keyboards}
+
+Bot commands
+-------------
 
 .. autosummary::
     :nosignatures:
 
-    {bots_keyboard}
+    {bot_commands}
 
 .. toctree::
     :hidden:
 
-    {bots_keyboard}
+    {bot_commands}
 
 Input Media
 -----------
diff --git a/pyrogram/methods/bots/__init__.py b/pyrogram/methods/bots/__init__.py
index 0051c6c3fb..54854b8e3d 100644
--- a/pyrogram/methods/bots/__init__.py
+++ b/pyrogram/methods/bots/__init__.py
@@ -18,6 +18,8 @@
 
 from .answer_callback_query import AnswerCallbackQuery
 from .answer_inline_query import AnswerInlineQuery
+from .delete_bot_commands import DeleteBotCommands
+from .get_bot_commands import GetBotCommands
 from .get_game_high_scores import GetGameHighScores
 from .get_inline_bot_results import GetInlineBotResults
 from .request_callback_answer import RequestCallbackAnswer
@@ -36,6 +38,8 @@ class Bots(
     SendGame,
     SetGameScore,
     GetGameHighScores,
-    SetBotCommands
+    SetBotCommands,
+    GetBotCommands,
+    DeleteBotCommands
 ):
     pass
diff --git a/pyrogram/methods/bots/delete_bot_commands.py b/pyrogram/methods/bots/delete_bot_commands.py
new file mode 100644
index 0000000000..edb82debed
--- /dev/null
+++ b/pyrogram/methods/bots/delete_bot_commands.py
@@ -0,0 +1,61 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  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 import raw, types
+from pyrogram.scaffold import Scaffold
+
+
+class DeleteBotCommands(Scaffold):
+    async def delete_bot_commands(
+        self: "pyrogram.Client",
+        scope: "types.BotCommandScope" = types.BotCommandScopeDefault(),
+        language_code: str = "",
+    ):
+        """Delete the list of the bot's commands for the given scope and user language.
+        After deletion, higher level commands will be shown to affected users.
+
+        The commands passed will overwrite any command set previously.
+        This method can be used by the own bot only.
+
+        Parameters:
+            scope (:obj:`~pyrogram.types.BotCommandScope`, *optional*):
+                An object describing the scope of users for which the commands are relevant.
+                Defaults to :obj:`~pyrogram.types.BotCommandScopeDefault`.
+
+            language_code (``str``, *optional*):
+                A two-letter ISO 639-1 language code.
+                If empty, commands will be applied to all users from the given scope, for whose language there are no
+                dedicated commands.
+
+        Returns:
+            ``bool``: On success, True is returned.
+
+        Example:
+            .. code-block:: python
+
+                # Delete commands
+                app.delete_bot_commands()
+        """
+
+        return await self.send(
+            raw.functions.bots.ResetBotCommands(
+                scope=await scope.write(self),
+                lang_code=language_code,
+            )
+        )
diff --git a/pyrogram/methods/bots/get_bot_commands.py b/pyrogram/methods/bots/get_bot_commands.py
new file mode 100644
index 0000000000..c92bd21ea0
--- /dev/null
+++ b/pyrogram/methods/bots/get_bot_commands.py
@@ -0,0 +1,62 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  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 import raw, types
+from pyrogram.scaffold import Scaffold
+
+
+class GetBotCommands(Scaffold):
+    async def get_bot_commands(
+        self: "pyrogram.Client",
+        scope: "types.BotCommandScope" = types.BotCommandScopeDefault(),
+        language_code: str = "",
+    ):
+        """Get the current list of the bot's commands for the given scope and user language.
+        Returns Array of BotCommand on success. If commands aren't set, an empty list is returned.
+
+        The commands passed will overwrite any command set previously.
+        This method can be used by the own bot only.
+
+        Parameters:
+            scope (:obj:`~pyrogram.types.BotCommandScope`, *optional*):
+                An object describing the scope of users for which the commands are relevant.
+                Defaults to :obj:`~pyrogram.types.BotCommandScopeDefault`.
+
+            language_code (``str``, *optional*):
+                A two-letter ISO 639-1 language code.
+                If empty, commands will be applied to all users from the given scope, for whose language there are no
+                dedicated commands.
+
+        Returns:
+            List of :obj:`~pyrogram.types.BotCommand`: On success, the list of bot commands is returned.
+
+        Example:
+            .. code-block:: python
+
+                # Get commands
+                commands = app.get_bot_commands()
+                print(commands)
+        """
+
+        return await self.send(
+            raw.functions.bots.GetBotCommands(
+                scope=await scope.write(self),
+                lang_code=language_code,
+            )
+        )
diff --git a/pyrogram/methods/bots/set_bot_commands.py b/pyrogram/methods/bots/set_bot_commands.py
index 769a430156..c346151591 100644
--- a/pyrogram/methods/bots/set_bot_commands.py
+++ b/pyrogram/methods/bots/set_bot_commands.py
@@ -16,22 +16,21 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-from typing import List, Optional
+from typing import List
 
+import pyrogram
 from pyrogram import raw, types
 from pyrogram.scaffold import Scaffold
 
 
 class SetBotCommands(Scaffold):
     async def set_bot_commands(
-        self,
-        commands: Optional[List[types.BotCommand]],
-        scope: types.BotCommandScope = types.BotCommandScope(
-            types.BotCommandScope.DEFAULT
-        ),
-        lang_code: str = "",
+        self: "pyrogram.Client",
+        commands: List["types.BotCommand"],
+        scope: "types.BotCommandScope" = types.BotCommandScopeDefault(),
+        language_code: str = "",
     ):
-        """Set the bot commands list.
+        """Set the list of the bot's commands.
 
         The commands passed will overwrite any command set previously.
         This method can be used by the own bot only.
@@ -39,10 +38,19 @@ async def set_bot_commands(
         Parameters:
             commands (List of :obj:`~pyrogram.types.BotCommand`):
                 A list of bot commands.
-                Pass None to remove all commands.
+                At most 100 commands can be specified.
+
+            scope (:obj:`~pyrogram.types.BotCommandScope`, *optional*):
+                An object describing the scope of users for which the commands are relevant.
+                Defaults to :obj:`~pyrogram.types.BotCommandScopeDefault`.
+
+            language_code (``str``, *optional*):
+                A two-letter ISO 639-1 language code.
+                If empty, commands will be applied to all users from the given scope, for whose language there are no
+                dedicated commands.
 
         Returns:
-            ``bool``: True on success, False otherwise.
+            ``bool``: On success, True is returned.
 
         Example:
             .. code-block:: python
@@ -53,15 +61,12 @@ async def set_bot_commands(
                 app.set_bot_commands([
                     BotCommand("start", "Start the bot"),
                     BotCommand("settings", "Bot settings")])
-
-                # Remove commands
-                app.set_bot_commands(None)
         """
 
         return await self.send(
             raw.functions.bots.SetBotCommands(
-                commands=[c.write() for c in commands or []],
-                scope=scope.write(),
-                lang_code=lang_code,
+                commands=[c.write() for c in commands],
+                scope=await scope.write(self),
+                lang_code=language_code,
             )
         )
diff --git a/pyrogram/types/bots_and_keyboards/__init__.py b/pyrogram/types/bots_and_keyboards/__init__.py
index d69c70fa7f..37e6dfe831 100644
--- a/pyrogram/types/bots_and_keyboards/__init__.py
+++ b/pyrogram/types/bots_and_keyboards/__init__.py
@@ -16,7 +16,15 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-from .bot_command import BotCommand, BotCommandScope
+from .bot_command import BotCommand
+from .bot_command_scope import BotCommandScope
+from .bot_command_scope_all_chat_administrators import BotCommandScopeAllChatAdministrators
+from .bot_command_scope_all_group_chats import BotCommandScopeAllGroupChats
+from .bot_command_scope_all_private_chats import BotCommandScopeAllPrivateChats
+from .bot_command_scope_chat import BotCommandScopeChat
+from .bot_command_scope_chat_administrators import BotCommandScopeChatAdministrators
+from .bot_command_scope_chat_member import BotCommandScopeChatMember
+from .bot_command_scope_default import BotCommandScopeDefault
 from .callback_game import CallbackGame
 from .callback_query import CallbackQuery
 from .force_reply import ForceReply
@@ -41,4 +49,11 @@
     "LoginUrl",
     "BotCommand",
     "BotCommandScope",
+    "BotCommandScopeAllChatAdministrators",
+    "BotCommandScopeAllGroupChats",
+    "BotCommandScopeAllPrivateChats",
+    "BotCommandScopeChat",
+    "BotCommandScopeChatAdministrators",
+    "BotCommandScopeChatMember",
+    "BotCommandScopeDefault",
 ]
diff --git a/pyrogram/types/bots_and_keyboards/bot_command.py b/pyrogram/types/bots_and_keyboards/bot_command.py
index 82cdc67ef8..88f459dcc4 100644
--- a/pyrogram/types/bots_and_keyboards/bot_command.py
+++ b/pyrogram/types/bots_and_keyboards/bot_command.py
@@ -26,10 +26,11 @@ class BotCommand(Object):
 
     Parameters:
         command (``str``):
-            The bot command, for example: "/start".
+            Text of the command; 1-32 characters.
+            Can contain only lowercase English letters, digits and underscores.
 
         description (``str``):
-            Description of the bot command.
+            Description of the command; 1-256 characters.
     """
 
     def __init__(self, command: str, description: str):
@@ -38,77 +39,15 @@ def __init__(self, command: str, description: str):
         self.command = command
         self.description = description
 
-    def write(self):
+    def write(self) -> "raw.types.BotCommand":
         return raw.types.BotCommand(
             command=self.command,
             description=self.description,
         )
 
-
-class BotCommandScope(Object):
-    """
-    Represents a scope where the bot commands, specified
-    using bots.setBotCommands will be valid.
-
-    Parameters:
-        scope (``str``):
-
-            - DEFAULT: The commands will be valid in all chats (default value)
-
-            - PRIVATE: The specified bot commands will only be valid in all private
-            chats with users.
-
-            - GROUP: The specified bot commands will be valid in all groups and supergroups
-
-            - GROUP_ADMINS: The specified bot commands will be valid only for chat
-            administrators, in all groups and supergroups.
-
-            - PEER: The specified bot commands will be valid only in a specific dialog
-
-            - PEER_ADMINS: The specified bot commands will be valid for all admins of the
-            specified group or supergroup.
-
-            - PEER_USER: The specified bot commands will be valid only for a specific user
-            in the specified chat
-    """
-
-    DEFAULT = "default"
-    PRIVATE = "users"
-    GROUP = "chats"
-    GROUP_ADMINS = "chat_admins"
-    PEER = "peer"
-    PEER_ADMINS = "peer_admins"
-    PEER_USER = "peer_user"
-
-    raw_scopes = {
-        DEFAULT: raw.types.BotCommandScopeDefault,
-        PRIVATE: raw.types.BotCommandScopeUsers,
-        GROUP: raw.types.BotCommandScopeChats,
-        GROUP_ADMINS: raw.types.BotCommandScopeChatAdmins,
-        PEER: lambda peer: raw.types.BotCommandScopePeer(peer),
-        PEER_ADMINS: lambda peer: raw.types.BotCommandScopePeerAdmins(peer),
-        PEER_USER: lambda peer, user_id: raw.types.BotCommandScopePeerUser(
-            peer, user_id
-        ),
-    }
-
-    def __init__(
-        self,
-        scope: str,
-        peer: raw.types.InputPeerUser = None,
-        user_id: raw.types.InputUser = None,
-    ):
-        super().__init__()
-        self.scope = scope
-        self.peer = peer
-        self.user_id = user_id
-
-    def write(self):
-
-        if self.scope in ["peer", "peer_admins"]:
-            return self.raw_scopes[self.scope](self.peer)
-
-        elif self.scope == "peer_user":
-            return self.raw_scopes[self.scopes](self.peer, self.user_id)
-
-        return self.raw_scopes[self.scope]()
+    @staticmethod
+    def read(c: "raw.types.BotCommand") -> "BotCommand":
+        return BotCommand(
+            command=c.command,
+            description=c.description
+        )
diff --git a/pyrogram/types/bots_and_keyboards/bot_command_scope.py b/pyrogram/types/bots_and_keyboards/bot_command_scope.py
new file mode 100644
index 0000000000..2db638e9a7
--- /dev/null
+++ b/pyrogram/types/bots_and_keyboards/bot_command_scope.py
@@ -0,0 +1,73 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  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 import raw
+from ..object import Object
+
+
+class BotCommandScope(Object):
+    """Represents the scope to which bot commands are applied.
+
+    Currently, the following 7 scopes are supported:
+
+    - :obj:`~pyrogram.types.BotCommandScopeDefault`
+    - :obj:`~pyrogram.types.BotCommandScopeAllPrivateChats`
+    - :obj:`~pyrogram.types.BotCommandScopeAllGroupChats`
+    - :obj:`~pyrogram.types.BotCommandScopeAllChatAdministrators`
+    - :obj:`~pyrogram.types.BotCommandScopeChat`
+    - :obj:`~pyrogram.types.BotCommandScopeChatAdministrators`
+    - :obj:`~pyrogram.types.BotCommandScopeChatMember`
+
+    **Determining list of commands**
+
+    The following algorithm is used to determine the list of commands for a particular user viewing the bot menu.
+    The first list of commands which is set is returned:
+
+    **Commands in the chat with the bot**:
+
+    - BotCommandScopeChat + language_code
+    - BotCommandScopeChat
+    - BotCommandScopeAllPrivateChats + language_code
+    - BotCommandScopeAllPrivateChats
+    - BotCommandScopeDefault + language_code
+    - BotCommandScopeDefault
+
+    **Commands in group and supergroup chats**
+
+    - BotCommandScopeChatMember + language_code
+    - BotCommandScopeChatMember
+    - BotCommandScopeChatAdministrators + language_code (administrators only)
+    - BotCommandScopeChatAdministrators (administrators only)
+    - BotCommandScopeChat + language_code
+    - BotCommandScopeChat
+    - BotCommandScopeAllChatAdministrators + language_code (administrators only)
+    - BotCommandScopeAllChatAdministrators (administrators only)
+    - BotCommandScopeAllGroupChats + language_code
+    - BotCommandScopeAllGroupChats
+    - BotCommandScopeDefault + language_code
+    - BotCommandScopeDefault
+    """
+
+    def __init__(self, type: str):
+        super().__init__()
+
+        self.type = type
+
+    async def write(self, client: "pyrogram.Client") -> "raw.base.BotCommandScope":
+        raise NotImplementedError
diff --git a/pyrogram/types/bots_and_keyboards/bot_command_scope_all_chat_administrators.py b/pyrogram/types/bots_and_keyboards/bot_command_scope_all_chat_administrators.py
new file mode 100644
index 0000000000..0cc6339c77
--- /dev/null
+++ b/pyrogram/types/bots_and_keyboards/bot_command_scope_all_chat_administrators.py
@@ -0,0 +1,32 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  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 import raw
+from .bot_command_scope import BotCommandScope
+
+
+class BotCommandScopeAllChatAdministrators(BotCommandScope):
+    """Represents the scope of bot commands, covering all group and supergroup chat administrators.
+    """
+
+    def __init__(self):
+        super().__init__("all_chat_administrators")
+
+    async def write(self, client: "pyrogram.Client") -> "raw.base.BotCommandScope":
+        return raw.types.BotCommandScopeChatAdmins()
diff --git a/pyrogram/types/bots_and_keyboards/bot_command_scope_all_group_chats.py b/pyrogram/types/bots_and_keyboards/bot_command_scope_all_group_chats.py
new file mode 100644
index 0000000000..5f8457f61d
--- /dev/null
+++ b/pyrogram/types/bots_and_keyboards/bot_command_scope_all_group_chats.py
@@ -0,0 +1,32 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  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 import raw
+from .bot_command_scope import BotCommandScope
+
+
+class BotCommandScopeAllGroupChats(BotCommandScope):
+    """Represents the scope of bot commands, covering all group and supergroup chats.
+    """
+
+    def __init__(self):
+        super().__init__("all_group_chats")
+
+    async def write(self, client: "pyrogram.Client") -> "raw.base.BotCommandScope":
+        return raw.types.BotCommandScopeChats()
diff --git a/pyrogram/types/bots_and_keyboards/bot_command_scope_all_private_chats.py b/pyrogram/types/bots_and_keyboards/bot_command_scope_all_private_chats.py
new file mode 100644
index 0000000000..14b80f0dcd
--- /dev/null
+++ b/pyrogram/types/bots_and_keyboards/bot_command_scope_all_private_chats.py
@@ -0,0 +1,32 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  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 import raw
+from .bot_command_scope import BotCommandScope
+
+
+class BotCommandScopeAllPrivateChats(BotCommandScope):
+    """Represents the scope of bot commands, covering all private chats.
+    """
+
+    def __init__(self):
+        super().__init__("all_private_chats")
+
+    async def write(self, client: "pyrogram.Client") -> "raw.base.BotCommandScope":
+        return raw.types.BotCommandScopeUsers()
diff --git a/pyrogram/types/bots_and_keyboards/bot_command_scope_chat.py b/pyrogram/types/bots_and_keyboards/bot_command_scope_chat.py
new file mode 100644
index 0000000000..28912383b7
--- /dev/null
+++ b/pyrogram/types/bots_and_keyboards/bot_command_scope_chat.py
@@ -0,0 +1,43 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  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 Union
+
+import pyrogram
+from pyrogram import raw
+from .bot_command_scope import BotCommandScope
+
+
+class BotCommandScopeChat(BotCommandScope):
+    """Represents the scope of bot commands, covering a specific chat.
+
+    Parameters:
+        chat_id (``int`` | ``str``):
+            Unique identifier for the target chat or username of the target supergroup (in the format
+            @supergroupusername).
+    """
+
+    def __init__(self, chat_id: Union[int, str]):
+        super().__init__("chat")
+
+        self.chat_id = chat_id
+
+    async def write(self, client: "pyrogram.Client") -> "raw.base.BotCommandScope":
+        return raw.types.BotCommandScopePeer(
+            peer=await client.resolve_peer(self.chat_id)
+        )
diff --git a/pyrogram/types/bots_and_keyboards/bot_command_scope_chat_administrators.py b/pyrogram/types/bots_and_keyboards/bot_command_scope_chat_administrators.py
new file mode 100644
index 0000000000..6f42a29f2b
--- /dev/null
+++ b/pyrogram/types/bots_and_keyboards/bot_command_scope_chat_administrators.py
@@ -0,0 +1,43 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  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 Union
+
+import pyrogram
+from pyrogram import raw
+from .bot_command_scope import BotCommandScope
+
+
+class BotCommandScopeChatAdministrators(BotCommandScope):
+    """Represents the scope of bot commands, covering all administrators of a specific group or supergroup chat.
+
+    Parameters:
+        chat_id (``int`` | ``str``):
+            Unique identifier for the target chat or username of the target supergroup (in the format
+            @supergroupusername).
+    """
+
+    def __init__(self, chat_id: Union[int, str]):
+        super().__init__("chat_administrators")
+
+        self.chat_id = chat_id
+
+    async def write(self, client: "pyrogram.Client") -> "raw.base.BotCommandScope":
+        return raw.types.BotCommandScopePeerAdmins(
+            peer=await client.resolve_peer(self.chat_id)
+        )
diff --git a/pyrogram/types/bots_and_keyboards/bot_command_scope_chat_member.py b/pyrogram/types/bots_and_keyboards/bot_command_scope_chat_member.py
new file mode 100644
index 0000000000..8d91df1413
--- /dev/null
+++ b/pyrogram/types/bots_and_keyboards/bot_command_scope_chat_member.py
@@ -0,0 +1,48 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  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 Union
+
+import pyrogram
+from pyrogram import raw
+from .bot_command_scope import BotCommandScope
+
+
+class BotCommandScopeChatMember(BotCommandScope):
+    """Represents the scope of bot commands, covering a specific member of a group or supergroup chat.
+
+    Parameters:
+        chat_id (``int`` | ``str``):
+            Unique identifier for the target chat or username of the target supergroup (in the format
+            @supergroupusername).
+
+        user_id (``int`` | ``str``):
+            Unique identifier of the target user.
+    """
+
+    def __init__(self, chat_id: Union[int, str], user_id: Union[int, str]):
+        super().__init__("chat_member")
+
+        self.chat_id = chat_id
+        self.user_id = user_id
+
+    async def write(self, client: "pyrogram.Client") -> "raw.base.BotCommandScope":
+        return raw.types.BotCommandScopePeerUser(
+            peer=await client.resolve_peer(self.chat_id),
+            user_id=await client.resolve_peer(self.user_id)
+        )
diff --git a/pyrogram/types/bots_and_keyboards/bot_command_scope_default.py b/pyrogram/types/bots_and_keyboards/bot_command_scope_default.py
new file mode 100644
index 0000000000..d11ab012f8
--- /dev/null
+++ b/pyrogram/types/bots_and_keyboards/bot_command_scope_default.py
@@ -0,0 +1,33 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  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 import raw
+from .bot_command_scope import BotCommandScope
+
+
+class BotCommandScopeDefault(BotCommandScope):
+    """Represents the default scope of bot commands.
+    Default commands are used if no commands with a narrower scope are specified for the user.
+    """
+
+    def __init__(self):
+        super().__init__("default")
+
+    async def write(self, client: "pyrogram.Client") -> "raw.base.BotCommandScope":
+        return raw.types.BotCommandScopeDefault()

From 190760cf0eb3e90bc682b05fe54932295211c2cf Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Mon, 28 Mar 2022 13:25:59 +0200
Subject: [PATCH 0756/1185] Update Pyrogram to v1.4.10

---
 pyrogram/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index ebb3b16355..8c1336c30e 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -16,7 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-__version__ = "1.4.9"
+__version__ = "1.4.10"
 __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)"
 __copyright__ = "Copyright (C) 2017-present Dan "
 

From 06ee482b2331e94c6494248a59d10ab53ba15a24 Mon Sep 17 00:00:00 2001
From: andrew-ld <43882924+andrew-ld@users.noreply.github.com>
Date: Mon, 28 Mar 2022 20:10:52 +0200
Subject: [PATCH 0757/1185] Faster RLE codec implementation (#938)

* faster pyrogram lre encode implementation

* Update file_id.py

* optimized rle decode

Co-authored-by: andrew (from workstation) 
Co-authored-by: Dan <14043624+delivrance@users.noreply.github.com>
---
 pyrogram/file_id.py | 23 ++++++++++++-----------
 1 file changed, 12 insertions(+), 11 deletions(-)

diff --git a/pyrogram/file_id.py b/pyrogram/file_id.py
index 95f6ccbf1a..1a54a49d61 100644
--- a/pyrogram/file_id.py
+++ b/pyrogram/file_id.py
@@ -21,6 +21,7 @@
 import struct
 from enum import IntEnum
 from io import BytesIO
+from typing import List
 
 from pyrogram.raw.core import Bytes, String
 
@@ -63,7 +64,7 @@ def rle_encode(s: bytes) -> bytes:
     Returns:
         ``bytes``: The encoded bytes
     """
-    r: bytes = b""
+    r: List[int] = []
     n: int = 0
 
     for b in s:
@@ -71,15 +72,15 @@ def rle_encode(s: bytes) -> bytes:
             n += 1
         else:
             if n:
-                r += bytes([0, n])
+                r.extend((0, n))
                 n = 0
 
-            r += bytes([b])
+            r.append(b)
 
     if n:
-        r += bytes([0, n])
+        r.extend((0, n))
 
-    return r
+    return bytes(r)
 
 
 def rle_decode(s: bytes) -> bytes:
@@ -87,12 +88,12 @@ def rle_decode(s: bytes) -> bytes:
 
     Parameters:
         s (``bytes``):
-            Bytes to encode
+            Bytes to decode
 
     Returns:
-        ``bytes``: The encoded bytes
+        ``bytes``: The decoded bytes
     """
-    r: bytes = b""
+    r: List[int] = []
     z: bool = False
 
     for b in s:
@@ -101,12 +102,12 @@ def rle_decode(s: bytes) -> bytes:
             continue
 
         if z:
-            r += b"\x00" * b
+            r.extend((0,) * b)
             z = False
         else:
-            r += bytes([b])
+            r.append(b)
 
-    return r
+    return bytes(r)
 
 
 class FileType(IntEnum):

From e50b58980a68a589617f9dcfce0c52f1830b32c5 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Mon, 28 Mar 2022 20:13:46 +0200
Subject: [PATCH 0758/1185] Update Pyrogram to v1.4.11

---
 pyrogram/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index 8c1336c30e..654ff35355 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -16,7 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-__version__ = "1.4.10"
+__version__ = "1.4.11"
 __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)"
 __copyright__ = "Copyright (C) 2017-present Dan "
 

From 0825b977ea8936b650f6710d386b54fddd9ec673 Mon Sep 17 00:00:00 2001
From: Krishna-singhal <65902764+Krishna-Singhal@users.noreply.github.com>
Date: Tue, 29 Mar 2022 00:33:37 +0530
Subject: [PATCH 0759/1185] Add bound method Message.react (#937)

* Bound method `react` to send reaction

* Update message.py

* Update message.py

* Update compiler.py

Co-authored-by: Dan <14043624+delivrance@users.noreply.github.com>
---
 compiler/docs/compiler.py                    |  1 +
 pyrogram/types/messages_and_media/message.py | 36 ++++++++++++++++++++
 2 files changed, 37 insertions(+)

diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py
index 118c0e636f..75c37a7f55 100644
--- a/compiler/docs/compiler.py
+++ b/compiler/docs/compiler.py
@@ -518,6 +518,7 @@ def get_title_list(s: str) -> list:
             Message.reply_video_note
             Message.reply_voice
             Message.get_media_group
+            Message.react
         """,
         chat="""
         Chat
diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py
index ea54231d70..597fe60581 100644
--- a/pyrogram/types/messages_and_media/message.py
+++ b/pyrogram/types/messages_and_media/message.py
@@ -3286,6 +3286,42 @@ async def click(self, x: Union[int, str] = 0, y: int = None, quote: bool = None,
         else:
             await self.reply(button, quote=quote)
 
+    async def react(self, emoji: str = "") -> bool:
+        """Bound method *react* of :obj:`~pyrogram.types.Message`.
+
+        Use as a shortcut for:
+
+        .. code-block:: python
+
+            client.send_reaction(
+                chat_id=chat_id,
+                message_id=message.message_id,
+                emoji="🔥"
+            )
+
+        Example:
+            .. code-block:: python
+
+                message.react(emoji="🔥")
+
+        Parameters:
+            emoji (``str``, *optional*):
+                Reaction emoji.
+                Pass "" as emoji (default) to retract the reaction.
+
+        Returns:
+            ``bool``: On success, True is returned.
+
+        Raises:
+            RPCError: In case of a Telegram RPC error.
+        """
+
+        return await self._client.send_reaction(
+            chat_id=self.chat.id,
+            message_id=self.message_id,
+            emoji=emoji
+        )
+
     async def retract_vote(
         self,
     ) -> "types.Poll":

From 71f3125c6b592868a13fbc645176188a05861686 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Mon, 28 Mar 2022 21:04:25 +0200
Subject: [PATCH 0760/1185] Update Pyrogram to v1.4.12

---
 pyrogram/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index 654ff35355..009c75c269 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -16,7 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-__version__ = "1.4.11"
+__version__ = "1.4.12"
 __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)"
 __copyright__ = "Copyright (C) 2017-present Dan "
 

From 688c24bc3ec12674ad691cd68510fd0c0333725c Mon Sep 17 00:00:00 2001
From: Nick <64551534+null-nick@users.noreply.github.com>
Date: Mon, 11 Apr 2022 11:16:26 +0200
Subject: [PATCH 0761/1185] Update API schema to Layer 140 (#949)

---
 compiler/api/source/main_api.tl | 73 +++++++++++++++++++++++++++++----
 1 file changed, 64 insertions(+), 9 deletions(-)

diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl
index 5bd7dfb568..cb4bf0ac1c 100644
--- a/compiler/api/source/main_api.tl
+++ b/compiler/api/source/main_api.tl
@@ -88,7 +88,7 @@ storage.fileMp4#b3cea0e4 = storage.FileType;
 storage.fileWebp#1081464c = storage.FileType;
 
 userEmpty#d3bc4b7a id:long = User;
-user#3ff6ecb0 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true apply_min_photo:flags.25?true fake:flags.26?true id:long access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector bot_inline_placeholder:flags.19?string lang_code:flags.22?string = User;
+user#3ff6ecb0 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true apply_min_photo:flags.25?true fake:flags.26?true bot_attach_menu:flags.27?true id:long access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector bot_inline_placeholder:flags.19?string lang_code:flags.22?string = User;
 
 userProfilePhotoEmpty#4f11bae1 = UserProfilePhoto;
 userProfilePhoto#82d1f706 flags:# has_video:flags.0?true photo_id:long stripped_thumb:flags.1?bytes dc_id:int = UserProfilePhoto;
@@ -101,13 +101,13 @@ userStatusLastWeek#7bf09fc = UserStatus;
 userStatusLastMonth#77ebc742 = UserStatus;
 
 chatEmpty#29562865 id:long = Chat;
-chat#41cbf256 flags:# creator:flags.0?true kicked:flags.1?true left:flags.2?true deactivated:flags.5?true call_active:flags.23?true call_not_empty:flags.24?true noforwards:flags.25?true id:long title:string photo:ChatPhoto participants_count:int date:int version:int migrated_to:flags.6?InputChannel admin_rights:flags.14?ChatAdminRights default_banned_rights:flags.18?ChatBannedRights = Chat;
+chat#41cbf256 flags:# creator:flags.0?true left:flags.2?true deactivated:flags.5?true call_active:flags.23?true call_not_empty:flags.24?true noforwards:flags.25?true id:long title:string photo:ChatPhoto participants_count:int date:int version:int migrated_to:flags.6?InputChannel admin_rights:flags.14?ChatAdminRights default_banned_rights:flags.18?ChatBannedRights = Chat;
 chatForbidden#6592a1a7 id:long title:string = Chat;
 channel#8261ac61 flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true fake:flags.25?true gigagroup:flags.26?true noforwards:flags.27?true id:long access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int restriction_reason:flags.9?Vector admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int = Chat;
 channelForbidden#17d493d5 flags:# broadcast:flags.5?true megagroup:flags.8?true id:long access_hash:long title:string until_date:flags.16?int = Chat;
 
 chatFull#d18ee226 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:long about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer theme_emoticon:flags.16?string requests_pending:flags.17?int recent_requesters:flags.17?Vector available_reactions:flags.18?Vector = ChatFull;
-channelFull#e13c3d20 flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector default_send_as:flags.29?Peer available_reactions:flags.30?Vector = ChatFull;
+channelFull#ea68a619 flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true flags2:# can_delete_channel:flags2.0?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector default_send_as:flags.29?Peer available_reactions:flags.30?Vector = ChatFull;
 
 chatParticipant#c02d4007 user_id:long inviter_id:long date:int = ChatParticipant;
 chatParticipantCreator#e46bcee4 user_id:long = ChatParticipant;
@@ -167,6 +167,8 @@ messageActionSetMessagesTTL#aa1afbfd period:int = MessageAction;
 messageActionGroupCallScheduled#b3a07661 call:InputGroupCall schedule_date:int = MessageAction;
 messageActionSetChatTheme#aa786345 emoticon:string = MessageAction;
 messageActionChatJoinedByRequest#ebbca3cb = MessageAction;
+messageActionWebViewDataSentMe#47dd8079 text:string data:string = MessageAction;
+messageActionWebViewDataSent#b4c38cb5 text:string = MessageAction;
 
 dialog#a8edd0f5 flags:# pinned:flags.2?true unread_mark:flags.3?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int unread_reactions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int = Dialog;
 dialogFolder#71bd134c flags:# pinned:flags.2?true folder:Folder peer:Peer top_message:int unread_muted_peers_count:int unread_unmuted_peers_count:int unread_muted_messages_count:int unread_unmuted_messages_count:int = Dialog;
@@ -196,9 +198,9 @@ inputNotifyUsers#193b4417 = InputNotifyPeer;
 inputNotifyChats#4a95e84e = InputNotifyPeer;
 inputNotifyBroadcasts#b1db7c7e = InputNotifyPeer;
 
-inputPeerNotifySettings#9c3d198e flags:# show_previews:flags.0?Bool silent:flags.1?Bool mute_until:flags.2?int sound:flags.3?string = InputPeerNotifySettings;
+inputPeerNotifySettings#df1f002b flags:# show_previews:flags.0?Bool silent:flags.1?Bool mute_until:flags.2?int sound:flags.3?NotificationSound = InputPeerNotifySettings;
 
-peerNotifySettings#af509d20 flags:# show_previews:flags.0?Bool silent:flags.1?Bool mute_until:flags.2?int sound:flags.3?string = PeerNotifySettings;
+peerNotifySettings#a83b0426 flags:# show_previews:flags.0?Bool silent:flags.1?Bool mute_until:flags.2?int ios_sound:flags.3?NotificationSound android_sound:flags.4?NotificationSound other_sound:flags.5?NotificationSound = PeerNotifySettings;
 
 peerSettings#a518110d flags:# report_spam:flags.0?true add_contact:flags.1?true block_contact:flags.2?true share_contact:flags.3?true need_contacts_exception:flags.4?true report_geo:flags.5?true autoarchived:flags.7?true invite_members:flags.8?true request_chat_broadcast:flags.10?true geo_distance:flags.6?int request_chat_title:flags.9?string request_chat_date:flags.9?int = PeerSettings;
 
@@ -216,7 +218,7 @@ inputReportReasonFake#f5ddd6e7 = ReportReason;
 inputReportReasonIllegalDrugs#a8eb2be = ReportReason;
 inputReportReasonPersonalDetails#9ec7863d = ReportReason;
 
-userFull#cf366521 flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true id:long about:flags.1?string settings:PeerSettings profile_photo:flags.2?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int ttl_period:flags.14?int theme_emoticon:flags.15?string private_forward_name:flags.16?string = UserFull;
+userFull#8c72ea81 flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true id:long about:flags.1?string settings:PeerSettings profile_photo:flags.2?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int ttl_period:flags.14?int theme_emoticon:flags.15?string private_forward_name:flags.16?string bot_group_admin_rights:flags.17?ChatAdminRights bot_broadcast_admin_rights:flags.18?ChatAdminRights = UserFull;
 
 contact#145ade0b user_id:long mutual:Bool = Contact;
 
@@ -362,6 +364,10 @@ updateBotCommands#4d712f2e peer:Peer bot_id:long commands:Vector = U
 updatePendingJoinRequests#7063c3db peer:Peer requests_pending:int recent_requesters:Vector = Update;
 updateBotChatInviteRequester#11dfa986 peer:Peer date:int user_id:long about:string invite:ExportedChatInvite qts:int = Update;
 updateMessageReactions#154798c3 peer:Peer msg_id:int reactions:MessageReactions = Update;
+updateAttachMenuBots#17b7a20b = Update;
+updateWebViewResultSent#1592b79d query_id:long = Update;
+updateBotMenuButton#14b85813 bot_id:long button:BotMenuButton = Update;
+updateSavedRingtones#74d8be99 = Update;
 
 updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State;
 
@@ -553,7 +559,7 @@ messages.stickerSetNotModified#d3f924eb = messages.StickerSet;
 
 botCommand#c27ac8c7 command:string description:string = BotCommand;
 
-botInfo#1b74b335 user_id:long description:string commands:Vector = BotInfo;
+botInfo#e4169b5d user_id:long description:string commands:Vector menu_button:BotMenuButton = BotInfo;
 
 keyboardButton#a2fa4880 text:string = KeyboardButton;
 keyboardButtonUrl#258aff05 text:string url:string = KeyboardButton;
@@ -568,6 +574,8 @@ inputKeyboardButtonUrlAuth#d02e7fd4 flags:# request_write_access:flags.0?true te
 keyboardButtonRequestPoll#bbc7515d flags:# quiz:flags.0?Bool text:string = KeyboardButton;
 inputKeyboardButtonUserProfile#e988037b text:string user_id:InputUser = KeyboardButton;
 keyboardButtonUserProfile#308660c1 text:string user_id:long = KeyboardButton;
+keyboardButtonWebView#13767230 text:string url:string = KeyboardButton;
+keyboardButtonSimpleWebView#a0c0505c text:string url:string = KeyboardButton;
 
 keyboardButtonRow#77608b83 buttons:Vector = KeyboardButtonRow;
 
@@ -1307,6 +1315,38 @@ phone.groupCallStreamChannels#d0e482b2 channels:Vector =
 
 phone.groupCallStreamRtmpUrl#2dbf3432 url:string key:string = phone.GroupCallStreamRtmpUrl;
 
+attachMenuBotIconColor#4576f3f0 name:string color:int = AttachMenuBotIconColor;
+
+attachMenuBotIcon#b2a7386b flags:# name:string icon:Document colors:flags.0?Vector = AttachMenuBotIcon;
+
+attachMenuBot#e93cb772 flags:# inactive:flags.0?true bot_id:long short_name:string icons:Vector = AttachMenuBot;
+
+attachMenuBotsNotModified#f1d88a5c = AttachMenuBots;
+attachMenuBots#3c4301c0 hash:long bots:Vector users:Vector = AttachMenuBots;
+
+attachMenuBotsBot#93bf667f bot:AttachMenuBot users:Vector = AttachMenuBotsBot;
+
+webViewResultUrl#c14557c query_id:long url:string = WebViewResult;
+
+simpleWebViewResultUrl#882f76bb url:string = SimpleWebViewResult;
+
+webViewMessageSent#c94511c flags:# msg_id:flags.0?InputBotInlineMessageID = WebViewMessageSent;
+
+botMenuButtonDefault#7533a588 = BotMenuButton;
+botMenuButtonCommands#4258c205 = BotMenuButton;
+botMenuButton#c7b57ce6 text:string url:string = BotMenuButton;
+
+account.savedRingtonesNotModified#fbf6e8b1 = account.SavedRingtones;
+account.savedRingtones#c1e92cc5 hash:long ringtones:Vector = account.SavedRingtones;
+
+notificationSoundDefault#97e8bebe = NotificationSound;
+notificationSoundNone#6f0c34df = NotificationSound;
+notificationSoundLocal#830b9ae4 title:string data:string = NotificationSound;
+notificationSoundRingtone#ff6c8049 id:long = NotificationSound;
+
+account.savedRingtone#b7263f6d = account.SavedRingtone;
+account.savedRingtoneConverted#1f307eb7 document:Document = account.SavedRingtone;
+
 ---functions---
 
 invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
@@ -1410,6 +1450,9 @@ account.declinePasswordReset#4c9409f6 = Bool;
 account.getChatThemes#d638de89 hash:long = account.Themes;
 account.setAuthorizationTTL#bf899aa0 authorization_ttl_days:int = Bool;
 account.changeAuthorizationSettings#40f48462 flags:# hash:long encrypted_requests_disabled:flags.0?Bool call_requests_disabled:flags.1?Bool = Bool;
+account.getSavedRingtones#e1902288 hash:long = account.SavedRingtones;
+account.saveRingtone#3dea5b03 id:InputDocument unsave:Bool = account.SavedRingtone;
+account.uploadRingtone#831a83a2 file:InputFile file_name:string mime_type:string = Document;
 
 users.getUsers#d91a548 id:Vector = Vector;
 users.getFullUser#b60f5918 id:InputUser = users.UserFull;
@@ -1596,6 +1639,14 @@ messages.translateText#24ce6dee flags:# peer:flags.0?InputPeer msg_id:flags.0?in
 messages.getUnreadReactions#e85bae1a peer:InputPeer offset_id:int add_offset:int limit:int max_id:int min_id:int = messages.Messages;
 messages.readReactions#82e251d7 peer:InputPeer = messages.AffectedHistory;
 messages.searchSentMedia#107e31a0 q:string filter:MessagesFilter limit:int = messages.Messages;
+messages.getAttachMenuBots#16fcc2cb hash:long = AttachMenuBots;
+messages.getAttachMenuBot#77216192 bot:InputUser = AttachMenuBotsBot;
+messages.toggleBotInAttachMenu#1aee33af bot:InputUser enabled:Bool = Bool;
+messages.requestWebView#fa04dff flags:# from_bot_menu:flags.4?true silent:flags.5?true peer:InputPeer bot:InputUser url:flags.1?string start_param:flags.3?string theme_params:flags.2?DataJSON reply_to_msg_id:flags.0?int = WebViewResult;
+messages.prolongWebView#d22ad148 flags:# silent:flags.5?true peer:InputPeer bot:InputUser query_id:long reply_to_msg_id:flags.0?int = Bool;
+messages.requestSimpleWebView#6abb2f73 flags:# bot:InputUser url:string theme_params:flags.0?DataJSON = SimpleWebViewResult;
+messages.sendWebViewResultMessage#a4314f5 bot_query_id:string result:InputBotInlineResult = WebViewMessageSent;
+messages.sendWebViewData#dc0242c8 bot:InputUser random_id:long button_text:string data:string = Updates;
 
 updates.getState#edd4882a = updates.State;
 updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference;
@@ -1663,7 +1714,7 @@ channels.editBanned#96e6cd81 channel:InputChannel participant:InputPeer banned_r
 channels.getAdminLog#33ddf480 flags:# channel:InputChannel q:string events_filter:flags.0?ChannelAdminLogEventsFilter admins:flags.1?Vector max_id:long min_id:long limit:int = channels.AdminLogResults;
 channels.setStickers#ea8ca4f9 channel:InputChannel stickerset:InputStickerSet = Bool;
 channels.readMessageContents#eab5dc38 channel:InputChannel id:Vector = Bool;
-channels.deleteHistory#af369d42 channel:InputChannel max_id:int = Bool;
+channels.deleteHistory#9baa9647 flags:# for_everyone:flags.0?true channel:InputChannel max_id:int = Updates;
 channels.togglePreHistoryHidden#eabbb94c channel:InputChannel enabled:Bool = Updates;
 channels.getLeftChannels#8341ecc0 offset:int = messages.Chats;
 channels.getGroupsForDiscussion#f5dad378 = messages.Chats;
@@ -1683,6 +1734,10 @@ bots.answerWebhookJSONQuery#e6213f4d query_id:long data:DataJSON = Bool;
 bots.setBotCommands#517165a scope:BotCommandScope lang_code:string commands:Vector = Bool;
 bots.resetBotCommands#3d8de0f9 scope:BotCommandScope lang_code:string = Bool;
 bots.getBotCommands#e34c0dd6 scope:BotCommandScope lang_code:string = Vector;
+bots.setBotMenuButton#4504d54f user_id:InputUser button:BotMenuButton = Bool;
+bots.getBotMenuButton#9c60eb28 user_id:InputUser = BotMenuButton;
+bots.setBotBroadcastDefaultAdminRights#788464e1 admin_rights:ChatAdminRights = Bool;
+bots.setBotGroupDefaultAdminRights#925ec9ea admin_rights:ChatAdminRights = Bool;
 
 payments.getPaymentForm#8a333c8d flags:# peer:InputPeer msg_id:int theme_params:flags.0?DataJSON = payments.PaymentForm;
 payments.getPaymentReceipt#2478d1cc peer:InputPeer msg_id:int = payments.PaymentReceipt;
@@ -1746,4 +1801,4 @@ stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel
 stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages;
 stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats;
 
-// LAYER 139
\ No newline at end of file
+// LAYER 140

From a4fe5fbfc89fd9ab815144feda697b8f4d09bed5 Mon Sep 17 00:00:00 2001
From: dogghi <75123663+doggyhaha@users.noreply.github.com>
Date: Mon, 11 Apr 2022 11:21:28 +0200
Subject: [PATCH 0762/1185] Add missing on_chat_join_request decorator to docs
 (#947)

---
 docs/source/api/decorators.rst | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/docs/source/api/decorators.rst b/docs/source/api/decorators.rst
index fa0e7ad337..130f15ceb6 100644
--- a/docs/source/api/decorators.rst
+++ b/docs/source/api/decorators.rst
@@ -40,6 +40,7 @@ Index
     - :meth:`~Client.on_inline_query`
     - :meth:`~Client.on_chosen_inline_result`
     - :meth:`~Client.on_chat_member_updated`
+    - :meth:`~Client.on_chat_join_request`
     - :meth:`~Client.on_deleted_messages`
     - :meth:`~Client.on_user_status`
     - :meth:`~Client.on_poll`
@@ -57,8 +58,9 @@ Details
 .. autodecorator:: pyrogram.Client.on_inline_query()
 .. autodecorator:: pyrogram.Client.on_chosen_inline_result()
 .. autodecorator:: pyrogram.Client.on_chat_member_updated()
+.. autodecorator:: pyrogram.Client.on_chat_join_request()
 .. autodecorator:: pyrogram.Client.on_deleted_messages()
 .. autodecorator:: pyrogram.Client.on_user_status()
 .. autodecorator:: pyrogram.Client.on_poll()
 .. autodecorator:: pyrogram.Client.on_disconnect()
-.. autodecorator:: pyrogram.Client.on_raw_update()
\ No newline at end of file
+.. autodecorator:: pyrogram.Client.on_raw_update()

From 8b92ac8b7ff01533cd8aca11a722c9484da3b755 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Mon, 11 Apr 2022 12:46:29 +0200
Subject: [PATCH 0763/1185] Add support for multiple TL flags

---
 compiler/api/compiler.py | 42 +++++++++++++++++++++-------------------
 1 file changed, 22 insertions(+), 20 deletions(-)

diff --git a/compiler/api/compiler.py b/compiler/api/compiler.py
index 150fa49d37..b8cf8b0c60 100644
--- a/compiler/api/compiler.py
+++ b/compiler/api/compiler.py
@@ -34,9 +34,9 @@
 LAYER_RE = re.compile(r"//\sLAYER\s(\d+)")
 COMBINATOR_RE = re.compile(r"^([\w.]+)#([0-9a-f]+)\s(?:.*)=\s([\w<>.]+);$", re.MULTILINE)
 ARGS_RE = re.compile(r"[^{](\w+):([\w?!.<>#]+)")
-FLAGS_RE = re.compile(r"flags\.(\d+)\?")
-FLAGS_RE_2 = re.compile(r"flags\.(\d+)\?([\w<>.]+)")
-FLAGS_RE_3 = re.compile(r"flags:#")
+FLAGS_RE = re.compile(r"flags(\d?).(\d+)\?")
+FLAGS_RE_2 = re.compile(r"flags(\d?)\.(\d+)\?([\w<>.]+)")
+FLAGS_RE_3 = re.compile(r"flags(\d?):#")
 INT_RE = re.compile(r"int(\d+)")
 
 CORE_TYPES = ["int", "long", "int128", "int256", "double", "bytes", "string", "Bool", "true"]
@@ -131,10 +131,9 @@ def sort_args(args):
     for i in flags:
         args.remove(i)
 
-    try:
-        args.remove(("flags", "#"))
-    except ValueError:
-        pass
+    for i in args[:]:
+        if re.match(r"flags\d?", i[0]):
+            args.remove(i)
 
     return args + flags
 
@@ -401,42 +400,45 @@ def start(format: bool = False):
         for arg_name, arg_type in c.args:
             flag = FLAGS_RE_2.match(arg_type)
 
-            if arg_name == "flags" and arg_type == "#":
+            if re.match(r"flags\d?", arg_name) and arg_type == "#":
                 write_flags = []
 
                 for i in c.args:
                     flag = FLAGS_RE_2.match(i[1])
 
                     if flag:
-                        if flag.group(2) == "true" or flag.group(2).startswith("Vector"):
-                            write_flags.append(f"flags |= (1 << {flag.group(1)}) if self.{i[0]} else 0")
+                        if arg_name != f"flags{flag.group(1)}":
+                            continue
+
+                        if flag.group(3) == "true" or flag.group(3).startswith("Vector"):
+                            write_flags.append(f"{arg_name} |= (1 << {flag.group(2)}) if self.{i[0]} else 0")
                         else:
-                            write_flags.append(f"flags |= (1 << {flag.group(1)}) if self.{i[0]} is not None else 0")
+                            write_flags.append(f"{arg_name} |= (1 << {flag.group(2)}) if self.{i[0]} is not None else 0")
 
                 write_flags = "\n        ".join([
-                    "flags = 0",
+                    f"{arg_name} = 0",
                     "\n        ".join(write_flags),
-                    "b.write(Int(flags))\n        "
+                    f"b.write(Int({arg_name}))\n        "
                 ])
 
                 write_types += write_flags
-                read_types += "flags = Int.read(b)\n        "
+                read_types += f"\n        {arg_name} = Int.read(b)\n        "
 
                 continue
 
             if flag:
-                index, flag_type = flag.groups()
+                number, index, flag_type = flag.groups()
 
                 if flag_type == "true":
                     read_types += "\n        "
-                    read_types += f"{arg_name} = True if flags & (1 << {index}) else False"
+                    read_types += f"{arg_name} = True if flags{number} & (1 << {index}) else False"
                 elif flag_type in CORE_TYPES:
                     write_types += "\n        "
                     write_types += f"if self.{arg_name} is not None:\n            "
                     write_types += f"b.write({flag_type.title()}(self.{arg_name}))\n        "
 
                     read_types += "\n        "
-                    read_types += f"{arg_name} = {flag_type.title()}.read(b) if flags & (1 << {index}) else None"
+                    read_types += f"{arg_name} = {flag_type.title()}.read(b) if flags{number} & (1 << {index}) else None"
                 elif "vector" in flag_type.lower():
                     sub_type = arg_type.split("<")[1][:-1]
 
@@ -447,8 +449,8 @@ def start(format: bool = False):
                     )
 
                     read_types += "\n        "
-                    read_types += "{} = TLObject.read(b{}) if flags & (1 << {}) else []\n        ".format(
-                        arg_name, f", {sub_type.title()}" if sub_type in CORE_TYPES else "", index
+                    read_types += "{} = TLObject.read(b{}) if flags{} & (1 << {}) else []\n        ".format(
+                        arg_name, f", {sub_type.title()}" if sub_type in CORE_TYPES else "", number, index
                     )
                 else:
                     write_types += "\n        "
@@ -456,7 +458,7 @@ def start(format: bool = False):
                     write_types += f"b.write(self.{arg_name}.write())\n        "
 
                     read_types += "\n        "
-                    read_types += f"{arg_name} = TLObject.read(b) if flags & (1 << {index}) else None\n        "
+                    read_types += f"{arg_name} = TLObject.read(b) if flags{number} & (1 << {index}) else None\n        "
             else:
                 if arg_type in CORE_TYPES:
                     write_types += "\n        "

From 2017493c9e949cb2eec4e70c077e14341776bcc1 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Mon, 11 Apr 2022 12:54:19 +0200
Subject: [PATCH 0764/1185] Update Pyrogram to v1.4.13

---
 pyrogram/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index 009c75c269..dccbe3ba49 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -16,7 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-__version__ = "1.4.12"
+__version__ = "1.4.13"
 __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)"
 __copyright__ = "Copyright (C) 2017-present Dan "
 

From 849321e5ca7eb2e636aa9a497bd08ed71f0b89da Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Mon, 11 Apr 2022 14:27:20 +0200
Subject: [PATCH 0765/1185] Remove unneeded parts

---
 compiler/api/compiler.py | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/compiler/api/compiler.py b/compiler/api/compiler.py
index b8cf8b0c60..4f83cf0a6f 100644
--- a/compiler/api/compiler.py
+++ b/compiler/api/compiler.py
@@ -361,14 +361,12 @@ def start(format: bool = False):
 
         for i, arg in enumerate(sorted_args):
             arg_name, arg_type = arg
-            is_optional = FLAGS_RE.match(arg_type)
-            flag_number = is_optional.group(1) if is_optional else -1
             arg_type = arg_type.split("?")[-1]
 
             docstring_args.append(
                 "{}{}: {}".format(
                     arg_name,
-                    " (optional)".format(flag_number) if is_optional else "",
+                    " (optional)",
                     get_docstring_arg_type(arg_type, is_pyrogram_type=c.namespace == "pyrogram")
                 )
             )
@@ -413,7 +411,8 @@ def start(format: bool = False):
                         if flag.group(3) == "true" or flag.group(3).startswith("Vector"):
                             write_flags.append(f"{arg_name} |= (1 << {flag.group(2)}) if self.{i[0]} else 0")
                         else:
-                            write_flags.append(f"{arg_name} |= (1 << {flag.group(2)}) if self.{i[0]} is not None else 0")
+                            write_flags.append(
+                                f"{arg_name} |= (1 << {flag.group(2)}) if self.{i[0]} is not None else 0")
 
                 write_flags = "\n        ".join([
                     f"{arg_name} = 0",

From e8250bd576a23dbe60e2dfe4a929aff1565713ed Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Mon, 11 Apr 2022 14:29:17 +0200
Subject: [PATCH 0766/1185] Add missing backslash to the pattern

---
 compiler/api/compiler.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/compiler/api/compiler.py b/compiler/api/compiler.py
index 4f83cf0a6f..cc8be2d7b4 100644
--- a/compiler/api/compiler.py
+++ b/compiler/api/compiler.py
@@ -34,7 +34,7 @@
 LAYER_RE = re.compile(r"//\sLAYER\s(\d+)")
 COMBINATOR_RE = re.compile(r"^([\w.]+)#([0-9a-f]+)\s(?:.*)=\s([\w<>.]+);$", re.MULTILINE)
 ARGS_RE = re.compile(r"[^{](\w+):([\w?!.<>#]+)")
-FLAGS_RE = re.compile(r"flags(\d?).(\d+)\?")
+FLAGS_RE = re.compile(r"flags(\d?)\.(\d+)\?")
 FLAGS_RE_2 = re.compile(r"flags(\d?)\.(\d+)\?([\w<>.]+)")
 FLAGS_RE_3 = re.compile(r"flags(\d?):#")
 INT_RE = re.compile(r"int(\d+)")

From 82b029c3bf66ce11cde0bfafda6359964feb10f6 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Mon, 11 Apr 2022 14:34:29 +0200
Subject: [PATCH 0767/1185] Use Optional[Type] instead of Union[Type, None]

---
 compiler/api/compiler.py             | 2 +-
 compiler/api/template/combinator.txt | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/compiler/api/compiler.py b/compiler/api/compiler.py
index cc8be2d7b4..adc6cfff70 100644
--- a/compiler/api/compiler.py
+++ b/compiler/api/compiler.py
@@ -115,7 +115,7 @@ def get_type_hint(type: str) -> str:
         type = f"List[{get_type_hint(sub_type)}]"
 
     if is_core:
-        return f"Union[None, {type}] = None" if is_flag else type
+        return f"Optional[{type}] = None" if is_flag else type
     else:
         ns, name = type.split(".") if "." in type else ("", type)
         type = f'"raw.base.' + ".".join([ns, name]).strip(".") + '"'
diff --git a/compiler/api/template/combinator.txt b/compiler/api/template/combinator.txt
index e0275dd104..7c02a1a8e5 100644
--- a/compiler/api/template/combinator.txt
+++ b/compiler/api/template/combinator.txt
@@ -5,7 +5,7 @@ from io import BytesIO
 from pyrogram.raw.core.primitives import Int, Long, Int128, Int256, Bool, Bytes, String, Double, Vector
 from pyrogram.raw.core import TLObject
 from pyrogram import raw
-from typing import List, Union, Any
+from typing import List, Optional, Any
 
 {warning}
 

From d6c80ff0f97992c7a2473d3a80e2518130322770 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Mon, 11 Apr 2022 14:52:59 +0200
Subject: [PATCH 0768/1185] Fix non-flags parameters with the same name "flags"
 being treated as TL flags

---
 compiler/api/compiler.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/compiler/api/compiler.py b/compiler/api/compiler.py
index adc6cfff70..a65149948f 100644
--- a/compiler/api/compiler.py
+++ b/compiler/api/compiler.py
@@ -132,7 +132,7 @@ def sort_args(args):
         args.remove(i)
 
     for i in args[:]:
-        if re.match(r"flags\d?", i[0]):
+        if re.match(r"flags\d?", i[0]) and i[1] == "#":
             args.remove(i)
 
     return args + flags

From 16de8b7325b530349d06c862b26c6305d5065cbc Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Mon, 11 Apr 2022 14:53:31 +0200
Subject: [PATCH 0769/1185] Update Pyrogram to v1.4.14

---
 pyrogram/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index dccbe3ba49..c5e1570da3 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -16,7 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-__version__ = "1.4.13"
+__version__ = "1.4.14"
 __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)"
 __copyright__ = "Copyright (C) 2017-present Dan "
 

From 0c0a4b5a5c8f20a0df9dcea843271d3ab0009c68 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Mon, 11 Apr 2022 15:17:16 +0200
Subject: [PATCH 0770/1185] Remove unneeded parameter hide_via from
 send_inline_bot_result

---
 pyrogram/methods/bots/send_inline_bot_result.py | 9 ++-------
 1 file changed, 2 insertions(+), 7 deletions(-)

diff --git a/pyrogram/methods/bots/send_inline_bot_result.py b/pyrogram/methods/bots/send_inline_bot_result.py
index de00546c13..ead95810b5 100644
--- a/pyrogram/methods/bots/send_inline_bot_result.py
+++ b/pyrogram/methods/bots/send_inline_bot_result.py
@@ -29,8 +29,7 @@ async def send_inline_bot_result(
         query_id: int,
         result_id: str,
         disable_notification: bool = None,
-        reply_to_message_id: int = None,
-        hide_via: bool = None
+        reply_to_message_id: int = None
     ):
         """Send an inline bot result.
         Bot results can be retrieved using :meth:`~pyrogram.Client.get_inline_bot_results`
@@ -54,9 +53,6 @@ async def send_inline_bot_result(
             reply_to_message_id (``bool``, *optional*):
                 If the message is a reply, ID of the original message.
 
-            hide_via (``bool``):
-                Sends the message with *via @bot* hidden.
-
         Returns:
             :obj:`~pyrogram.types.Message`: On success, the sent inline result message is returned.
 
@@ -72,7 +68,6 @@ async def send_inline_bot_result(
                 id=result_id,
                 random_id=self.rnd_id(),
                 silent=disable_notification or None,
-                reply_to_msg_id=reply_to_message_id,
-                hide_via=hide_via or None
+                reply_to_msg_id=reply_to_message_id
             )
         )

From 874709c2585c10c073544755756f642e1976054c Mon Sep 17 00:00:00 2001
From: Tofik Denianto <77754555+tofikdn@users.noreply.github.com>
Date: Tue, 12 Apr 2022 04:52:32 +0700
Subject: [PATCH 0771/1185] Remove hide_via param from reply_inline_bot_result
 (#952)

---
 pyrogram/types/messages_and_media/message.py | 9 ++-------
 1 file changed, 2 insertions(+), 7 deletions(-)

diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py
index 597fe60581..e6aa25268a 100644
--- a/pyrogram/types/messages_and_media/message.py
+++ b/pyrogram/types/messages_and_media/message.py
@@ -1668,8 +1668,7 @@ async def reply_inline_bot_result(
         result_id: str,
         quote: bool = None,
         disable_notification: bool = None,
-        reply_to_message_id: int = None,
-        hide_via: bool = None
+        reply_to_message_id: int = None
     ) -> "Message":
         """Bound method *reply_inline_bot_result* of :obj:`~pyrogram.types.Message`.
 
@@ -1707,9 +1706,6 @@ async def reply_inline_bot_result(
             reply_to_message_id (``bool``, *optional*):
                 If the message is a reply, ID of the original message.
 
-            hide_via (``bool``):
-                Sends the message with *via @bot* hidden.
-
         Returns:
             On success, the sent Message is returned.
 
@@ -1727,8 +1723,7 @@ async def reply_inline_bot_result(
             query_id=query_id,
             result_id=result_id,
             disable_notification=disable_notification,
-            reply_to_message_id=reply_to_message_id,
-            hide_via=hide_via
+            reply_to_message_id=reply_to_message_id
         )
 
     async def reply_location(

From 69a50fb3b2f77c5f14619ddf5ce8d6762bfd38d7 Mon Sep 17 00:00:00 2001
From: Danstiv <50794055+Danstiv@users.noreply.github.com>
Date: Tue, 12 Apr 2022 19:01:40 +0700
Subject: [PATCH 0772/1185] Fix TLObject.__repr__ (#953)

* Fix __repr__ method

* Style fixes

Co-authored-by: Dan <14043624+delivrance@users.noreply.github.com>
---
 pyrogram/raw/core/tl_object.py | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/pyrogram/raw/core/tl_object.py b/pyrogram/raw/core/tl_object.py
index b094f7d035..7cb75b0eb5 100644
--- a/pyrogram/raw/core/tl_object.py
+++ b/pyrogram/raw/core/tl_object.py
@@ -53,6 +53,9 @@ def __str__(self) -> str:
         return dumps(self, indent=4, default=TLObject.default, ensure_ascii=False)
 
     def __repr__(self) -> str:
+        if not hasattr(self, "QUALNAME"):
+            return repr(self)
+
         return "pyrogram.raw.{}({})".format(
             self.QUALNAME,
             ", ".join(

From cb3d389b9d291ff2fa056fa5a737773382e13b19 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Tue, 12 Apr 2022 14:03:09 +0200
Subject: [PATCH 0773/1185] Update Pyrogram to v1.4.15

---
 pyrogram/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index c5e1570da3..eea5848293 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -16,7 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-__version__ = "1.4.14"
+__version__ = "1.4.15"
 __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)"
 __copyright__ = "Copyright (C) 2017-present Dan "
 

From acc2c72d6de913e6899fabea35950dd5401cb12f Mon Sep 17 00:00:00 2001
From: Shohih Abdul <50512936+DoellBarr@users.noreply.github.com>
Date: Fri, 15 Apr 2022 01:34:55 +0700
Subject: [PATCH 0774/1185] Improve type hinting for join_chat method (#957)

---
 pyrogram/methods/chats/join_chat.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/methods/chats/join_chat.py b/pyrogram/methods/chats/join_chat.py
index ca720d6d0b..3f1a09f998 100644
--- a/pyrogram/methods/chats/join_chat.py
+++ b/pyrogram/methods/chats/join_chat.py
@@ -27,7 +27,7 @@ class JoinChat(Scaffold):
     async def join_chat(
         self,
         chat_id: Union[int, str]
-    ):
+    ) -> "types.Chat":
         """Join a group chat or channel.
 
         Parameters:

From 7a5ab4bffa85dc25e43ee7a088341c88f3a5ae05 Mon Sep 17 00:00:00 2001
From: Marco Burro 
Date: Fri, 15 Apr 2022 11:35:21 +0200
Subject: [PATCH 0775/1185] Fix inline query results typing (#958)

---
 pyrogram/methods/bots/answer_inline_query.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/pyrogram/methods/bots/answer_inline_query.py b/pyrogram/methods/bots/answer_inline_query.py
index 9683e1bb34..24e7a30c78 100644
--- a/pyrogram/methods/bots/answer_inline_query.py
+++ b/pyrogram/methods/bots/answer_inline_query.py
@@ -16,7 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-from typing import List
+from typing import Iterable
 
 from pyrogram import raw
 from pyrogram import types
@@ -27,7 +27,7 @@ class AnswerInlineQuery(Scaffold):
     async def answer_inline_query(
         self,
         inline_query_id: str,
-        results: List["types.InlineQueryResult"],
+        results: Iterable["types.InlineQueryResult"],
         cache_time: int = 300,
         is_gallery: bool = False,
         is_personal: bool = False,

From 1c225776c921c054f271b9f9df6fe4a08e2284e2 Mon Sep 17 00:00:00 2001
From: Mahesh <44301650+Mahesh0253@users.noreply.github.com>
Date: Fri, 15 Apr 2022 17:25:03 +0530
Subject: [PATCH 0776/1185] Removed unnecessary create_task (#706)

---
 pyrogram/methods/advanced/save_file.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/methods/advanced/save_file.py b/pyrogram/methods/advanced/save_file.py
index b612b1124f..3f6ebb68af 100644
--- a/pyrogram/methods/advanced/save_file.py
+++ b/pyrogram/methods/advanced/save_file.py
@@ -103,7 +103,7 @@ async def worker(session):
                     return
 
                 try:
-                    await self.loop.create_task(session.send(data))
+                    await session.send(data)
                 except Exception as e:
                     log.error(e)
 

From abc84b829af6a78c273ab4b0e530c3794fd84f73 Mon Sep 17 00:00:00 2001
From: Stark Programmer <88478059+StarkBotsIndustries@users.noreply.github.com>
Date: Sat, 16 Apr 2022 22:03:26 +0530
Subject: [PATCH 0777/1185] Add bound method Chat.unpin_all_messages (#959)

---
 compiler/docs/compiler.py             |  1 +
 pyrogram/types/user_and_chats/chat.py | 20 ++++++++++++++++++++
 2 files changed, 21 insertions(+)

diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py
index 75c37a7f55..fdee4e8fcf 100644
--- a/compiler/docs/compiler.py
+++ b/compiler/docs/compiler.py
@@ -539,6 +539,7 @@ def get_title_list(s: str) -> list:
             Chat.leave
             Chat.mark_unread
             Chat.set_protected_content
+            Chat.unpin_all_messages
         """,
         user="""
         User
diff --git a/pyrogram/types/user_and_chats/chat.py b/pyrogram/types/user_and_chats/chat.py
index 6dc3fb8264..7ca8f2085e 100644
--- a/pyrogram/types/user_and_chats/chat.py
+++ b/pyrogram/types/user_and_chats/chat.py
@@ -1047,3 +1047,23 @@ async def set_protected_content(self, enabled: bool) -> bool:
             self.id,
             enabled=enabled
         )
+
+    async def unpin_all_messages(self) -> bool:
+        """Bound method *unpin_all_messages* of :obj:`~pyrogram.types.Chat`.
+
+        Use as a shortcut for:
+
+        .. code-block:: python
+
+            client.unpin_all_chat_messages(chat_id)
+
+        Example:
+            .. code-block:: python
+
+                chat.unpin_all_messages()
+
+        Returns:
+            ``bool``: On success, True is returned.
+        """
+
+        return await self._client.unpin_all_chat_messages(self.id)

From 42fdb03d243badad7ae72606219c874a57ca13e2 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sat, 16 Apr 2022 20:01:38 +0200
Subject: [PATCH 0778/1185] Revert "Remove unneeded parts"

This reverts commit 849321e5ca7eb2e636aa9a497bd08ed71f0b89da.
---
 compiler/api/compiler.py | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/compiler/api/compiler.py b/compiler/api/compiler.py
index a65149948f..97dbdf0a60 100644
--- a/compiler/api/compiler.py
+++ b/compiler/api/compiler.py
@@ -361,12 +361,14 @@ def start(format: bool = False):
 
         for i, arg in enumerate(sorted_args):
             arg_name, arg_type = arg
+            is_optional = FLAGS_RE.match(arg_type)
+            flag_number = is_optional.group(1) if is_optional else -1
             arg_type = arg_type.split("?")[-1]
 
             docstring_args.append(
                 "{}{}: {}".format(
                     arg_name,
-                    " (optional)",
+                    " (optional)".format(flag_number) if is_optional else "",
                     get_docstring_arg_type(arg_type, is_pyrogram_type=c.namespace == "pyrogram")
                 )
             )
@@ -411,8 +413,7 @@ def start(format: bool = False):
                         if flag.group(3) == "true" or flag.group(3).startswith("Vector"):
                             write_flags.append(f"{arg_name} |= (1 << {flag.group(2)}) if self.{i[0]} else 0")
                         else:
-                            write_flags.append(
-                                f"{arg_name} |= (1 << {flag.group(2)}) if self.{i[0]} is not None else 0")
+                            write_flags.append(f"{arg_name} |= (1 << {flag.group(2)}) if self.{i[0]} is not None else 0")
 
                 write_flags = "\n        ".join([
                     f"{arg_name} = 0",

From 1885acd59437f5d99a5021747950b1826925f7e5 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sat, 16 Apr 2022 20:02:10 +0200
Subject: [PATCH 0779/1185] Reformat code

---
 compiler/api/compiler.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/compiler/api/compiler.py b/compiler/api/compiler.py
index 97dbdf0a60..5736c68d49 100644
--- a/compiler/api/compiler.py
+++ b/compiler/api/compiler.py
@@ -413,7 +413,8 @@ def start(format: bool = False):
                         if flag.group(3) == "true" or flag.group(3).startswith("Vector"):
                             write_flags.append(f"{arg_name} |= (1 << {flag.group(2)}) if self.{i[0]} else 0")
                         else:
-                            write_flags.append(f"{arg_name} |= (1 << {flag.group(2)}) if self.{i[0]} is not None else 0")
+                            write_flags.append(
+                                f"{arg_name} |= (1 << {flag.group(2)}) if self.{i[0]} is not None else 0")
 
                 write_flags = "\n        ".join([
                     f"{arg_name} = 0",

From fde55a4a7f2fbd964ca51920db801e6614571e35 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 17 Apr 2022 17:36:58 +0200
Subject: [PATCH 0780/1185] Improve interoperability with threads

---
 pyrogram/sync.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/sync.py b/pyrogram/sync.py
index a3e0d2b545..41c23e3b80 100644
--- a/pyrogram/sync.py
+++ b/pyrogram/sync.py
@@ -58,7 +58,7 @@ def async_to_sync_wrap(*args, **kwargs):
             loop = asyncio.new_event_loop()
             asyncio.set_event_loop(loop)
 
-        if threading.current_thread() is threading.main_thread():
+        if threading.current_thread() is threading.main_thread() or not main_loop.is_running():
             if loop.is_running():
                 return coroutine
             else:

From 499822118f9647b1db419d08a5b46f4bfdd4263c Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 17 Apr 2022 17:42:14 +0200
Subject: [PATCH 0781/1185] Update Pyrogram to v1.4.16

---
 pyrogram/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index eea5848293..e102a89dab 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -16,7 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-__version__ = "1.4.15"
+__version__ = "1.4.16"
 __license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)"
 __copyright__ = "Copyright (C) 2017-present Dan "
 

From 976c2c47a2215b6923861da60843c909e51527ee Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 24 Apr 2022 11:56:06 +0200
Subject: [PATCH 0782/1185] Rename update- to set_username and
 set_chat_username

---
 compiler/docs/compiler.py                            |  4 ++--
 pyrogram/methods/chats/__init__.py                   |  4 ++--
 ...{update_chat_username.py => set_chat_username.py} | 11 ++++++-----
 pyrogram/methods/users/__init__.py                   |  4 ++--
 .../users/{update_username.py => set_username.py}    | 12 ++++++------
 5 files changed, 18 insertions(+), 17 deletions(-)
 rename pyrogram/methods/chats/{update_chat_username.py => set_chat_username.py} (86%)
 rename pyrogram/methods/users/{update_username.py => set_username.py} (83%)

diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py
index fdee4e8fcf..7ce7bc96c4 100644
--- a/compiler/docs/compiler.py
+++ b/compiler/docs/compiler.py
@@ -212,7 +212,7 @@ def get_title_list(s: str) -> list:
             get_dialogs
             iter_dialogs
             get_dialogs_count
-            update_chat_username
+            set_chat_username
             get_nearby_chats
             archive_chats
             unarchive_chats
@@ -240,7 +240,7 @@ def get_title_list(s: str) -> list:
             iter_profile_photos
             set_profile_photo
             delete_profile_photos
-            update_username
+            set_username
             update_profile
             block_user
             unblock_user
diff --git a/pyrogram/methods/chats/__init__.py b/pyrogram/methods/chats/__init__.py
index cba67600af..6eedbd32e6 100644
--- a/pyrogram/methods/chats/__init__.py
+++ b/pyrogram/methods/chats/__init__.py
@@ -50,13 +50,13 @@
 from .set_chat_photo import SetChatPhoto
 from .set_chat_protected_content import SetChatProtectedContent
 from .set_chat_title import SetChatTitle
+from .set_chat_username import SetChatUsername
 from .set_send_as_chat import SetSendAsChat
 from .set_slow_mode import SetSlowMode
 from .unarchive_chats import UnarchiveChats
 from .unban_chat_member import UnbanChatMember
 from .unpin_all_chat_messages import UnpinAllChatMessages
 from .unpin_chat_message import UnpinChatMessage
-from .update_chat_username import UpdateChatUsername
 
 
 class Chats(
@@ -79,7 +79,7 @@ class Chats(
     GetChatMembersCount,
     IterDialogs,
     IterChatMembers,
-    UpdateChatUsername,
+    SetChatUsername,
     SetChatPermissions,
     GetDialogsCount,
     ArchiveChats,
diff --git a/pyrogram/methods/chats/update_chat_username.py b/pyrogram/methods/chats/set_chat_username.py
similarity index 86%
rename from pyrogram/methods/chats/update_chat_username.py
rename to pyrogram/methods/chats/set_chat_username.py
index a5bf9958d3..8496dda326 100644
--- a/pyrogram/methods/chats/update_chat_username.py
+++ b/pyrogram/methods/chats/set_chat_username.py
@@ -22,19 +22,20 @@
 from pyrogram.scaffold import Scaffold
 
 
-class UpdateChatUsername(Scaffold):
-    async def update_chat_username(
+class SetChatUsername(Scaffold):
+    async def set_chat_username(
         self,
         chat_id: Union[int, str],
         username: Optional[str]
     ) -> bool:
-        """Update a channel or a supergroup username.
+        """Set a channel or a supergroup username.
 
-        To update your own username (for users only, not bots) you can use :meth:`~pyrogram.Client.update_username`.
+        To set your own username (for users only, not bots) you can use :meth:`~pyrogram.Client.set_username`.
 
         Parameters:
             chat_id (``int`` | ``str``)
                 Unique identifier (int) or username (str) of the target chat.
+
             username (``str`` | ``None``):
                 Username to set. Pass "" (empty string) or None to remove the username.
 
@@ -47,7 +48,7 @@ async def update_chat_username(
         Example:
             .. code-block:: python
 
-                app.update_chat_username(chat_id, "new_username")
+                app.set_chat_username(chat_id, "new_username")
         """
 
         peer = await self.resolve_peer(chat_id)
diff --git a/pyrogram/methods/users/__init__.py b/pyrogram/methods/users/__init__.py
index e8005b83ea..6cd81bc363 100644
--- a/pyrogram/methods/users/__init__.py
+++ b/pyrogram/methods/users/__init__.py
@@ -25,9 +25,9 @@
 from .get_users import GetUsers
 from .iter_profile_photos import IterProfilePhotos
 from .set_profile_photo import SetProfilePhoto
+from .set_username import SetUsername
 from .unblock_user import UnblockUser
 from .update_profile import UpdateProfile
-from .update_username import UpdateUsername
 
 
 class Users(
@@ -38,7 +38,7 @@ class Users(
     DeleteProfilePhotos,
     GetUsers,
     GetMe,
-    UpdateUsername,
+    SetUsername,
     GetProfilePhotosCount,
     IterProfilePhotos,
     UnblockUser,
diff --git a/pyrogram/methods/users/update_username.py b/pyrogram/methods/users/set_username.py
similarity index 83%
rename from pyrogram/methods/users/update_username.py
rename to pyrogram/methods/users/set_username.py
index ce61402a7f..881f2ae685 100644
--- a/pyrogram/methods/users/update_username.py
+++ b/pyrogram/methods/users/set_username.py
@@ -22,16 +22,16 @@
 from pyrogram.scaffold import Scaffold
 
 
-class UpdateUsername(Scaffold):
-    async def update_username(
+class SetUsername(Scaffold):
+    async def set_username(
         self,
         username: Optional[str]
     ) -> bool:
-        """Update your own username.
+        """Set 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
-        :meth:`~pyrogram.Client.update_chat_username`.
+        them from scratch using BotFather. To set a channel or supergroup username you can use
+        :meth:`~pyrogram.Client.set_chat_username`.
 
         Parameters:
             username (``str`` | ``None``):
@@ -43,7 +43,7 @@ async def update_username(
         Example:
             .. code-block:: python
 
-                app.update_username("new_username")
+                app.set_username("new_username")
         """
 
         return bool(

From 9661b804b6114e2164a1aac44c180fa833c44f15 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 24 Apr 2022 11:56:06 +0200
Subject: [PATCH 0783/1185] Revamp ChatMember, add ChatPrivileges and support
 for banned chats

---
 compiler/docs/compiler.py                     |   1 +
 pyrogram/methods/chats/iter_chat_members.py   |   6 +-
 pyrogram/methods/chats/promote_chat_member.py |  96 ++----
 .../methods/chats/restrict_chat_member.py     |  22 +-
 .../methods/chats/set_administrator_title.py  |  35 +-
 pyrogram/types/user_and_chats/__init__.py     |   4 +-
 pyrogram/types/user_and_chats/chat_member.py  | 323 ++++++------------
 .../types/user_and_chats/chat_permissions.py  |   2 +-
 .../types/user_and_chats/chat_privileges.py   | 112 ++++++
 pyrogram/types/user_and_chats/user.py         |   4 +-
 10 files changed, 276 insertions(+), 329 deletions(-)
 create mode 100644 pyrogram/types/user_and_chats/chat_privileges.py

diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py
index 7ce7bc96c4..53ceb91cef 100644
--- a/compiler/docs/compiler.py
+++ b/compiler/docs/compiler.py
@@ -361,6 +361,7 @@ def get_title_list(s: str) -> list:
             ChatPhoto
             ChatMember
             ChatPermissions
+            ChatPrivileges
             ChatInviteLink
             ChatAdminWithInviteLinks
             ChatEvent
diff --git a/pyrogram/methods/chats/iter_chat_members.py b/pyrogram/methods/chats/iter_chat_members.py
index e9fa5b8277..3661d5064f 100644
--- a/pyrogram/methods/chats/iter_chat_members.py
+++ b/pyrogram/methods/chats/iter_chat_members.py
@@ -112,14 +112,14 @@ async def iter_chat_members(
             offset += len(chat_members)
 
             for chat_member in chat_members:
-                user_id = chat_member.user.id
+                peer_id = chat_member.user.id if chat_member.user else chat_member.chat.id
 
-                if user_id in yielded:
+                if peer_id in yielded:
                     continue
 
                 yield chat_member
 
-                yielded.add(chat_member.user.id)
+                yielded.add(peer_id)
 
                 current += 1
 
diff --git a/pyrogram/methods/chats/promote_chat_member.py b/pyrogram/methods/chats/promote_chat_member.py
index 8ab5fe29e8..b1d43867f3 100644
--- a/pyrogram/methods/chats/promote_chat_member.py
+++ b/pyrogram/methods/chats/promote_chat_member.py
@@ -18,7 +18,7 @@
 
 from typing import Union
 
-from pyrogram import raw
+from pyrogram import raw, types
 from pyrogram.scaffold import Scaffold
 
 
@@ -27,17 +27,7 @@ async def promote_chat_member(
         self,
         chat_id: Union[int, str],
         user_id: Union[int, str],
-        is_anonymous: bool = False,
-        can_manage_chat: bool = True,
-        can_change_info: bool = False,
-        can_post_messages: bool = False,
-        can_edit_messages: bool = False,
-        can_delete_messages: bool = False,
-        can_restrict_members: bool = False,
-        can_invite_users: bool = False,
-        can_pin_messages: bool = False,
-        can_promote_members: bool = False,
-        can_manage_voice_chats: bool = False
+        privileges: "types.ChatPrivileges" = types.ChatPrivileges(),
     ) -> bool:
         """Promote or demote a user in a supergroup or a channel.
 
@@ -52,42 +42,8 @@ async def promote_chat_member(
                 Unique identifier (int) or username (str) of the target user.
                 For a contact that exists in your Telegram address book you can use his phone number (str).
 
-            is_anonymous (``bool``, *optional*):
-                Pass True, if the administrator's presence in the chat is hidden.
-
-            can_manage_chat (``bool``, *optional*):
-                Pass True, if the administrator can access the chat event log, chat statistics, message statistics
-                in channels, see channel members, see anonymous administrators in supergroups and ignore slow mode.
-                Implied by any other administrator privilege.
-
-            can_change_info (``bool``, *optional*):
-                Pass True, if the administrator can change chat title, photo and other settings.
-
-            can_post_messages (``bool``, *optional*):
-                Pass True, if the administrator can create channel posts, channels only.
-
-            can_edit_messages (``bool``, *optional*):
-                Pass True, if the administrator can edit messages of other users and can pin messages, channels only.
-
-            can_delete_messages (``bool``, *optional*):
-                Pass True, if the administrator can delete messages of other users.
-
-            can_restrict_members (``bool``, *optional*):
-                Pass True, if the administrator can restrict, ban or unban chat members.
-
-            can_invite_users (``bool``, *optional*):
-                Pass True, if the administrator can invite new users to the chat.
-
-            can_pin_messages (``bool``, *optional*):
-                Pass True, if the administrator can pin messages, supergroups only.
-
-            can_promote_members (``bool``, *optional*):
-                Pass True, if the administrator can add new administrators with a subset of his own privileges or
-                demote administrators that he has promoted, directly or indirectly (promoted by administrators that
-                were appointed by him).
-
-            can_manage_voice_chats (``bool``, *optional*):
-                Pass True, if the administration can manage voice chats (also called group calls).
+            privileges (:obj:`~pyrogram.types.ChatPrivileges`, *optional*):
+                New user privileges.
 
         Returns:
             ``bool``: True on success.
@@ -95,27 +51,41 @@ async def promote_chat_member(
         Example:
             .. code-block:: python
 
-                # Promote chat member to supergroup admin
+                # Promote chat member to admin
                 app.promote_chat_member(chat_id, user_id)
         """
+        chat_id = await self.resolve_peer(chat_id)
+        user_id = await self.resolve_peer(user_id)
+
+        raw_chat_member = (await self.send(
+            raw.functions.channels.GetParticipant(
+                channel=chat_id,
+                participant=user_id
+            )
+        )).participant
+
+        rank = None
+        if isinstance(raw_chat_member, raw.types.ChannelParticipantAdmin):
+            rank = raw_chat_member.rank
+
         await self.send(
             raw.functions.channels.EditAdmin(
-                channel=await self.resolve_peer(chat_id),
-                user_id=await self.resolve_peer(user_id),
+                channel=chat_id,
+                user_id=user_id,
                 admin_rights=raw.types.ChatAdminRights(
-                    anonymous=is_anonymous or None,
-                    change_info=can_change_info or None,
-                    post_messages=can_post_messages or None,
-                    edit_messages=can_edit_messages or None,
-                    delete_messages=can_delete_messages or None,
-                    ban_users=can_restrict_members or None,
-                    invite_users=can_invite_users or None,
-                    pin_messages=can_pin_messages or None,
-                    add_admins=can_promote_members or None,
-                    manage_call=can_manage_voice_chats or None,
-                    other=can_manage_chat or None
+                    anonymous=privileges.is_anonymous,
+                    change_info=privileges.can_change_info,
+                    post_messages=privileges.can_post_messages,
+                    edit_messages=privileges.can_edit_messages,
+                    delete_messages=privileges.can_delete_messages,
+                    ban_users=privileges.can_restrict_members,
+                    invite_users=privileges.can_invite_users,
+                    pin_messages=privileges.can_pin_messages,
+                    add_admins=privileges.can_promote_members,
+                    manage_call=privileges.can_manage_voice_chats,
+                    other=privileges.can_manage_chat
                 ),
-                rank=""
+                rank=rank or ""
             )
         )
 
diff --git a/pyrogram/methods/chats/restrict_chat_member.py b/pyrogram/methods/chats/restrict_chat_member.py
index 7c74d68536..7a759185d2 100644
--- a/pyrogram/methods/chats/restrict_chat_member.py
+++ b/pyrogram/methods/chats/restrict_chat_member.py
@@ -77,17 +77,17 @@ async def restrict_chat_member(
                 participant=await self.resolve_peer(user_id),
                 banned_rights=raw.types.ChatBannedRights(
                     until_date=until_date,
-                    send_messages=True if not permissions.can_send_messages else None,
-                    send_media=True if not permissions.can_send_media_messages else None,
-                    send_stickers=True if not permissions.can_send_other_messages else None,
-                    send_gifs=True if not permissions.can_send_other_messages else None,
-                    send_games=True if not permissions.can_send_other_messages else None,
-                    send_inline=True if not permissions.can_send_other_messages else None,
-                    embed_links=True if not permissions.can_add_web_page_previews else None,
-                    send_polls=True if not permissions.can_send_polls else None,
-                    change_info=True if not permissions.can_change_info else None,
-                    invite_users=True if not permissions.can_invite_users else None,
-                    pin_messages=True if not permissions.can_pin_messages else None,
+                    send_messages=not permissions.can_send_messages,
+                    send_media=not permissions.can_send_media_messages,
+                    send_stickers=not permissions.can_send_other_messages,
+                    send_gifs=not permissions.can_send_other_messages,
+                    send_games=not permissions.can_send_other_messages,
+                    send_inline=not permissions.can_send_other_messages,
+                    embed_links=not permissions.can_add_web_page_previews,
+                    send_polls=not permissions.can_send_polls,
+                    change_info=not permissions.can_change_info,
+                    invite_users=not permissions.can_invite_users,
+                    pin_messages=not permissions.can_pin_messages,
                 )
             )
         )
diff --git a/pyrogram/methods/chats/set_administrator_title.py b/pyrogram/methods/chats/set_administrator_title.py
index 382d1faccc..f59cf28a35 100644
--- a/pyrogram/methods/chats/set_administrator_title.py
+++ b/pyrogram/methods/chats/set_administrator_title.py
@@ -65,45 +65,12 @@ async def set_administrator_title(
         )).participant
 
         if isinstance(r, raw.types.ChannelParticipantCreator):
-            admin_rights = raw.types.ChatAdminRights(
-                change_info=True,
-                post_messages=True,
-                edit_messages=True,
-                delete_messages=True,
-                ban_users=True,
-                invite_users=True,
-                pin_messages=True,
-                add_admins=True,
-            )
+            admin_rights = raw.types.ChatAdminRights()
         elif isinstance(r, raw.types.ChannelParticipantAdmin):
             admin_rights = r.admin_rights
         else:
             raise ValueError("Custom titles can only be applied to owners or administrators of supergroups")
 
-        if not admin_rights.change_info:
-            admin_rights.change_info = None
-
-        if not admin_rights.post_messages:
-            admin_rights.post_messages = None
-
-        if not admin_rights.edit_messages:
-            admin_rights.edit_messages = None
-
-        if not admin_rights.delete_messages:
-            admin_rights.delete_messages = None
-
-        if not admin_rights.ban_users:
-            admin_rights.ban_users = None
-
-        if not admin_rights.invite_users:
-            admin_rights.invite_users = None
-
-        if not admin_rights.pin_messages:
-            admin_rights.pin_messages = None
-
-        if not admin_rights.add_admins:
-            admin_rights.add_admins = None
-
         await self.send(
             raw.functions.channels.EditAdmin(
                 channel=chat_id,
diff --git a/pyrogram/types/user_and_chats/__init__.py b/pyrogram/types/user_and_chats/__init__.py
index 4667628a0e..15ddf8c9e3 100644
--- a/pyrogram/types/user_and_chats/__init__.py
+++ b/pyrogram/types/user_and_chats/__init__.py
@@ -27,6 +27,7 @@
 from .chat_permissions import ChatPermissions
 from .chat_photo import ChatPhoto
 from .chat_preview import ChatPreview
+from .chat_privileges import ChatPrivileges
 from .dialog import Dialog
 from .invite_link_importer import InviteLinkImporter
 from .restriction import Restriction
@@ -55,5 +56,6 @@
     "VoiceChatMembersInvited",
     "ChatMemberUpdated",
     "VoiceChatScheduled",
-    "ChatJoinRequest"
+    "ChatJoinRequest",
+    "ChatPrivileges"
 ]
diff --git a/pyrogram/types/user_and_chats/chat_member.py b/pyrogram/types/user_and_chats/chat_member.py
index ff55081587..65324dbd57 100644
--- a/pyrogram/types/user_and_chats/chat_member.py
+++ b/pyrogram/types/user_and_chats/chat_member.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 typing import Union, Dict
+
 import pyrogram
-from pyrogram import raw
-from pyrogram import types
+from pyrogram import raw, types, utils
 from ..object import Object
 
 
@@ -26,14 +27,21 @@ class ChatMember(Object):
     """Contains information about one member of a chat.
 
     Parameters:
-        user (:obj:`~pyrogram.types.User`):
-            Information about the user.
-
         status (``str``):
             The member's status in the chat.
             Can be "creator", "administrator", "member", "restricted", "left" or "banned".
 
-        title (``str``, *optional*):
+        user (:obj:`~pyrogram.types.User`, *optional*):
+            Information about the user.
+
+        chat (:obj:`~pyrogram.types.Chat`, *optional*):
+            Information about the chat (useful in case of banned channel senders).
+
+        joined_date (``int``, *optional*):
+            Date when the user joined, unix time.
+            Not available for the owner.
+
+        custom_title (``str``, *optional*):
             A custom title that will be shown to all members instead of "Owner" or "Admin".
             Creator (owner) and administrators only. Can be None in case there's no custom title set.
 
@@ -41,10 +49,6 @@ class ChatMember(Object):
             Restricted and banned only.
             Date when restrictions will be lifted for this user; unix time.
 
-        joined_date (``int``, *optional*):
-            Date when the user joined, unix time.
-            Not available for creator.
-
         invited_by (:obj:`~pyrogram.types.User`, *optional*):
             Administrators and self member only. Information about the user who invited this member.
             In case the user joined by himself this will be the same as "user".
@@ -58,268 +62,159 @@ class ChatMember(Object):
         is_member (``bool``, *optional*):
             Restricted only. True, if the user is a member of the chat at the moment of the request.
 
-        is_anonymous (``bool``, *optional*):
-            True, if the user's presence in the chat is hidden.
-            Owner and administrators only.
-
         can_be_edited (``bool``, *optional*):
-            Administrators only.
-            True, if you are allowed to edit administrator privileges of the user.
-
-        can_manage_chat (``bool``, *optional*):
-            Administrators only.
-            True, if the administrator can access the chat event log, chat statistics, message statistics in channels,
-            see channel members, see anonymous administrators in supergroups and ignore slow mode.
-            Implied by any other administrator privilege.
-
-        can_post_messages (``bool``, *optional*):
-            Administrators only. Channels only.
-            True, if the administrator can post messages in the channel.
-
-        can_edit_messages (``bool``, *optional*):
-            Administrators only. Channels only.
-            True, if the administrator can edit messages of other users and can pin messages.
-
-        can_delete_messages (``bool``, *optional*):
-            Administrators only.
-            True, if the administrator can delete messages of other users.
-
-        can_restrict_members (``bool``, *optional*):
-            Administrators only.
-            True, if the administrator can restrict, ban or unban chat members.
-
-        can_promote_members (``bool``, *optional*):
-            Administrators only.
-            True, if the administrator can add new administrators with a subset of his own privileges or demote
-            administrators that he has promoted, directly or indirectly (promoted by administrators that were appointed
-            by the user).
+            True, if the you are allowed to edit administrator privileges of the user.
 
-        can_change_info (``bool``, *optional*):
-            Administrators and restricted only.
-            True, if the user is allowed to change the chat title, photo and other settings.
+        permissions (:obj:`~pyrogram.types.ChatPermissions`, *optional*):
+            Restricted only. Restricted actions that a non-administrator user is allowed to take.
 
-        can_invite_users (``bool``, *optional*):
-            Administrators and restricted only.
-            True, if the user is allowed to invite new users to the chat.
-
-        can_pin_messages (``bool``, *optional*):
-            Administrators and restricted only. Groups and supergroups only.
-            True, if the user is allowed to pin messages.
-
-        can_manage_voice_chats (``bool``, *optional*):
-            Administrators only. Groups and supergroups only.
-            True, if the administrator can manage voice chats (also called group calls).
-
-        can_send_messages (``bool``, *optional*):
-            Restricted only.
-            True, if the user is allowed to send text messages, contacts, locations and venues.
-
-        can_send_media_messages (``bool``, *optional*):
-            Restricted only.
-            True, if the user is allowed to send audios, documents, photos, videos, video notes and voice notes.
-
-        can_send_stickers (``bool``, *optional*):
-            True, if the user is allowed to send stickers, implies can_send_media_messages.
-
-        can_send_animations (``bool``, *optional*):
-            True, if the user is allowed to send animations (GIFs), implies can_send_media_messages.
-
-        can_send_games (``bool``, *optional*):
-            True, if the user is allowed to send games, implies can_send_media_messages.
-
-        can_use_inline_bots (``bool``, *optional*):
-            True, if the user is allowed to use inline bots, implies can_send_media_messages.
-
-        can_add_web_page_previews (``bool``, *optional*):
-            Restricted only.
-            True, if the user is allowed to add web page previews to their messages.
-
-        can_send_polls (``bool``, *optional*):
-            Restricted only.
-            True, if the user is allowed to send polls.
+        privileges (:obj:`~pyrogram.types.ChatPrivileges`, *optional*):
+            Administrators only. Privileged actions that an administrator is able to take.
     """
 
     def __init__(
         self,
         *,
         client: "pyrogram.Client" = None,
-        user: "types.User",
         status: str,
-        title: str = None,
+        user: "types.User" = None,
+        chat: "types.Chat" = None,
+        custom_title: str = None,
         until_date: int = None,
         joined_date: int = None,
         invited_by: "types.User" = None,
         promoted_by: "types.User" = None,
         restricted_by: "types.User" = None,
         is_member: bool = None,
-        is_anonymous: bool = None,
-
-        # Admin permissions
         can_be_edited: bool = None,
-        can_manage_chat: bool = None,
-        can_post_messages: bool = None,  # Channels only
-        can_edit_messages: bool = None,  # Channels only
-        can_delete_messages: bool = None,
-        can_restrict_members: bool = None,
-        can_promote_members: bool = None,
-        can_change_info: bool = None,
-        can_invite_users: bool = None,
-        can_pin_messages: bool = None,  # Groups and supergroups only
-        can_manage_voice_chats: bool = None,
-
-        # Restricted user permissions
-        can_send_messages: bool = None,  # Text, contacts, locations and venues
-        can_send_media_messages: bool = None,  # Audios, documents, photos, videos, video notes and voice notes
-        can_send_stickers: bool = None,
-        can_send_animations: bool = None,
-        can_send_games: bool = None,
-        can_use_inline_bots: bool = None,
-        can_add_web_page_previews: bool = None,
-        can_send_polls: bool = None
+        permissions: "types.ChatPermissions" = None,
+        privileges: "types.ChatPrivileges" = None
     ):
         super().__init__(client)
 
-        self.user = user
         self.status = status
-        self.title = title
+        self.user = user
+        self.chat = chat
+        self.custom_title = custom_title
         self.until_date = until_date
         self.joined_date = joined_date
         self.invited_by = invited_by
         self.promoted_by = promoted_by
         self.restricted_by = restricted_by
         self.is_member = is_member
-        self.is_anonymous = is_anonymous
-
         self.can_be_edited = can_be_edited
-        self.can_manage_chat = can_manage_chat
-        self.can_post_messages = can_post_messages
-        self.can_edit_messages = can_edit_messages
-        self.can_delete_messages = can_delete_messages
-        self.can_restrict_members = can_restrict_members
-        self.can_promote_members = can_promote_members
-        self.can_change_info = can_change_info
-        self.can_invite_users = can_invite_users
-        self.can_pin_messages = can_pin_messages
-        self.can_manage_voice_chats = can_manage_voice_chats
-
-        self.can_send_messages = can_send_messages
-        self.can_send_media_messages = can_send_media_messages
-        self.can_send_stickers = can_send_stickers
-        self.can_send_animations = can_send_animations
-        self.can_send_games = can_send_games
-        self.can_use_inline_bots = can_use_inline_bots
-        self.can_add_web_page_previews = can_add_web_page_previews
-        self.can_send_polls = can_send_polls
+        self.permissions = permissions
+        self.privileges = privileges
 
     @staticmethod
-    def _parse(client, member, users, chats) -> "ChatMember":
-        if not isinstance(member, (raw.types.ChannelParticipantBanned, raw.types.ChannelParticipantLeft)):
-            user = types.User._parse(client, users[member.user_id])
-        else:
-            if isinstance(member.peer, raw.types.PeerUser):
-                user = types.User._parse(client, users[member.peer.user_id])
-            else:
-                user = None
-
-        invited_by = (
-            types.User._parse(client, users[member.inviter_id])
-            if getattr(member, "inviter_id", None) else None
-        )
-
-        if isinstance(member, (raw.types.ChannelParticipant,
-                               raw.types.ChannelParticipantSelf,
-                               raw.types.ChatParticipant)):
+    def _parse(
+        client: "pyrogram.Client",
+        member: Union["raw.base.ChatParticipant", "raw.base.ChannelParticipant"],
+        users: Dict[int, "raw.base.User"],
+        chats: Dict[int, "raw.base.Chat"]
+    ) -> "ChatMember":
+        # Chat participants
+        if isinstance(member, raw.types.ChatParticipant):
             return ChatMember(
-                user=user,
                 status="member",
+                user=types.User._parse(client, users[member.user_id]),
                 joined_date=member.date,
-                invited_by=invited_by,
+                invited_by=types.User._parse(client, users[member.inviter_id]),
                 client=client
             )
-
-        if isinstance(member, raw.types.ChatParticipantCreator):
+        elif isinstance(member, raw.types.ChatParticipantAdmin):
             return ChatMember(
-                user=user,
-                status="creator",
+                status="administrator",
+                user=types.User._parse(client, users[member.user_id]),
+                joined_date=member.date,
+                invited_by=types.User._parse(client, users[member.inviter_id]),
                 client=client
             )
-
-        if isinstance(member, raw.types.ChatParticipantAdmin):
+        elif isinstance(member, raw.types.ChatParticipantCreator):
             return ChatMember(
-                user=user,
-                status="administrator",
-                joined_date=member.date,
-                invited_by=invited_by,
+                status="owner",
+                user=types.User._parse(client, users[member.user_id]),
                 client=client
             )
 
-        if isinstance(member, raw.types.ChannelParticipantCreator):
-            permissions = member.admin_rights
-
+        # Channel participants
+        if isinstance(member, raw.types.ChannelParticipant):
             return ChatMember(
-                user=user,
-                status="creator",
-                title=member.rank,
-                invited_by=invited_by,
-                can_change_info=permissions.change_info,
-                can_manage_chat=permissions.other,
-                can_post_messages=permissions.post_messages,
-                can_edit_messages=permissions.edit_messages,
-                can_delete_messages=permissions.delete_messages,
-                can_restrict_members=permissions.ban_users,
-                can_invite_users=permissions.invite_users,
-                can_pin_messages=permissions.pin_messages,
-                can_promote_members=permissions.add_admins,
-                can_manage_voice_chats=permissions.manage_call,
-                is_anonymous=permissions.anonymous,
+                status="member",
+                user=types.User._parse(client, users[member.user_id]),
+                joined_date=member.date,
                 client=client
             )
-
-        if isinstance(member, raw.types.ChannelParticipantAdmin):
-            permissions = member.admin_rights
-
+        elif isinstance(member, raw.types.ChannelParticipantAdmin):
             return ChatMember(
-                user=user,
                 status="administrator",
-                title=member.rank,
+                user=types.User._parse(client, users[member.user_id]),
                 joined_date=member.date,
-                invited_by=invited_by,
                 promoted_by=types.User._parse(client, users[member.promoted_by]),
+                invited_by=types.User._parse(client, users[member.inviter_id]),
+                custom_title=member.rank,
                 can_be_edited=member.can_edit,
-                can_manage_chat=permissions.other,
-                can_change_info=permissions.change_info,
-                can_post_messages=permissions.post_messages,
-                can_edit_messages=permissions.edit_messages,
-                can_delete_messages=permissions.delete_messages,
-                can_restrict_members=permissions.ban_users,
-                can_invite_users=permissions.invite_users,
-                can_pin_messages=permissions.pin_messages,
-                can_promote_members=permissions.add_admins,
-                can_manage_voice_chats=permissions.manage_call,
-                is_anonymous=permissions.anonymous,
+                privileges=types.ChatPrivileges._parse(member.admin_rights),
                 client=client
             )
+        elif isinstance(member, raw.types.ChannelParticipantBanned):
+            peer = member.peer
+            peer_id = utils.get_raw_peer_id(peer)
 
-        if isinstance(member, raw.types.ChannelParticipantBanned):
-            denied_permissions = member.banned_rights
+            user = (
+                types.User._parse(client, users[peer_id])
+                if isinstance(peer, raw.types.PeerUser) else None
+            )
+
+            chat = (
+                types.Chat._parse_chat(client, chats[peer_id])
+                if not isinstance(peer, raw.types.PeerUser) else None
+            )
 
             return ChatMember(
-                user=user,
                 status="banned" if member.banned_rights.view_messages else "restricted",
-                until_date=denied_permissions.until_date,
+                user=user,
+                chat=chat,
+                until_date=member.banned_rights.until_date,
                 joined_date=member.date,
                 is_member=not member.left,
                 restricted_by=types.User._parse(client, users[member.kicked_by]),
-                can_send_messages=not denied_permissions.send_messages,
-                can_send_media_messages=not denied_permissions.send_media,
-                can_send_stickers=not denied_permissions.send_stickers,
-                can_send_animations=not denied_permissions.send_gifs,
-                can_send_games=not denied_permissions.send_games,
-                can_use_inline_bots=not denied_permissions.send_inline,
-                can_add_web_page_previews=not denied_permissions.embed_links,
-                can_send_polls=not denied_permissions.send_polls,
-                can_change_info=not denied_permissions.change_info,
-                can_invite_users=not denied_permissions.invite_users,
-                can_pin_messages=not denied_permissions.pin_messages,
+                permissions=types.ChatPermissions._parse(member.banned_rights),
+                client=client
+            )
+        elif isinstance(member, raw.types.ChannelParticipantCreator):
+            return ChatMember(
+                status="owner",
+                user=types.User._parse(client, users[member.user_id]),
+                custom_title=member.rank,
+                privileges=types.ChatPrivileges._parse(member.admin_rights),
+                client=client
+            )
+        elif isinstance(member, raw.types.ChannelParticipantLeft):
+            peer = member.peer
+            peer_id = utils.get_raw_peer_id(peer)
+
+            user = (
+                types.User._parse(client, users[peer_id])
+                if isinstance(peer, raw.types.PeerUser) else None
+            )
+
+            chat = (
+                types.Chat._parse_chat(client, chats[peer_id])
+                if not isinstance(peer, raw.types.PeerUser) else None
+            )
+
+            return ChatMember(
+                status="left",
+                user=user,
+                chat=chat,
+                client=client
+            )
+        elif isinstance(member, raw.types.ChannelParticipantSelf):
+            return ChatMember(
+                status="member",
+                user=types.User._parse(client, users[member.user_id]),
+                joined_date=member.date,
+                invited_by=types.User._parse(client, users[member.inviter_id]),
                 client=client
             )
diff --git a/pyrogram/types/user_and_chats/chat_permissions.py b/pyrogram/types/user_and_chats/chat_permissions.py
index e209625af3..d920394c67 100644
--- a/pyrogram/types/user_and_chats/chat_permissions.py
+++ b/pyrogram/types/user_and_chats/chat_permissions.py
@@ -79,7 +79,7 @@ def __init__(
         self.can_pin_messages = can_pin_messages
 
     @staticmethod
-    def _parse(denied_permissions: "raw.types.ChatBannedRights") -> "ChatPermissions":
+    def _parse(denied_permissions: "raw.base.ChatBannedRights") -> "ChatPermissions":
         if isinstance(denied_permissions, raw.types.ChatBannedRights):
             return ChatPermissions(
                 can_send_messages=not denied_permissions.send_messages,
diff --git a/pyrogram/types/user_and_chats/chat_privileges.py b/pyrogram/types/user_and_chats/chat_privileges.py
new file mode 100644
index 0000000000..403bb9572b
--- /dev/null
+++ b/pyrogram/types/user_and_chats/chat_privileges.py
@@ -0,0 +1,112 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  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 pyrogram import raw
+from ..object import Object
+
+
+class ChatPrivileges(Object):
+    """Describes privileged actions an administrator is able to take in a chat.
+
+    Parameters:
+        can_manage_chat (``bool``, *optional*):
+            True, if the administrator can access the chat event log, chat statistics, message statistics in channels,
+            see channel members, see anonymous administrators in supergroups and ignore slow mode.
+            Implied by any other administrator privilege.
+
+        can_delete_messages (``bool``, *optional*):
+            True, if the administrator can delete messages of other users.
+
+        can_manage_voice_chats (``bool``, *optional*):
+            Groups and supergroups only.
+            True, if the administrator can manage voice chats (also called group calls).
+
+        can_restrict_members (``bool``, *optional*):
+            True, if the administrator can restrict, ban or unban chat members.
+
+        can_promote_members (``bool``, *optional*):
+            True, if the administrator can add new administrators with a subset of his own privileges or demote
+            administrators that he has promoted, directly or indirectly (promoted by administrators that were appointed
+            by the user).
+
+        can_change_info (``bool``, *optional*):
+            True, if the user is allowed to change the chat title, photo and other settings.
+
+        can_post_messages (``bool``, *optional*):
+            Channels only.
+            True, if the administrator can post messages in the channel.
+
+        can_edit_messages (``bool``, *optional*):
+            Channels only.
+            True, if the administrator can edit messages of other users and can pin messages.
+
+        can_invite_users (``bool``, *optional*):
+            True, if the user is allowed to invite new users to the chat.
+
+        can_pin_messages (``bool``, *optional*):
+            Groups and supergroups only.
+            True, if the user is allowed to pin messages.
+
+        is_anonymous (``bool``, *optional*):
+            True, if the user's presence in the chat is hidden.
+    """
+
+    def __init__(
+        self,
+        *,
+        can_manage_chat: bool = True,
+        can_delete_messages: bool = False,
+        can_manage_voice_chats: bool = False,  # Groups and supergroups only
+        can_restrict_members: bool = False,
+        can_promote_members: bool = False,
+        can_change_info: bool = False,
+        can_post_messages: bool = False,  # Channels only
+        can_edit_messages: bool = False,  # Channels only
+        can_invite_users: bool = False,
+        can_pin_messages: bool = False,  # Groups and supergroups only
+        is_anonymous: bool = False
+    ):
+        super().__init__(None)
+
+        self.can_manage_chat: bool = can_manage_chat
+        self.can_delete_messages: bool = can_delete_messages
+        self.can_manage_voice_chats: bool = can_manage_voice_chats
+        self.can_restrict_members: bool = can_restrict_members
+        self.can_promote_members: bool = can_promote_members
+        self.can_change_info: bool = can_change_info
+        self.can_post_messages: bool = can_post_messages
+        self.can_edit_messages: bool = can_edit_messages
+        self.can_invite_users: bool = can_invite_users
+        self.can_pin_messages: bool = can_pin_messages
+        self.is_anonymous: bool = is_anonymous
+
+    @staticmethod
+    def _parse(admin_rights: "raw.base.ChatAdminRights") -> "ChatPrivileges":
+        return ChatPrivileges(
+            can_manage_chat=admin_rights.other,
+            can_delete_messages=admin_rights.delete_messages,
+            can_manage_voice_chats=admin_rights.manage_call,
+            can_restrict_members=admin_rights.ban_users,
+            can_promote_members=admin_rights.add_admins,
+            can_change_info=admin_rights.change_info,
+            can_post_messages=admin_rights.post_messages,
+            can_edit_messages=admin_rights.edit_messages,
+            can_invite_users=admin_rights.invite_users,
+            can_pin_messages=admin_rights.pin_messages,
+            is_anonymous=admin_rights.anonymous
+        )
diff --git a/pyrogram/types/user_and_chats/user.py b/pyrogram/types/user_and_chats/user.py
index 8579ba157d..ac141033e2 100644
--- a/pyrogram/types/user_and_chats/user.py
+++ b/pyrogram/types/user_and_chats/user.py
@@ -206,8 +206,8 @@ def mention(self):
         return Link(f"tg://user?id={self.id}", self.first_name, self._client.parse_mode)
 
     @staticmethod
-    def _parse(client, user: "raw.types.User") -> Optional["User"]:
-        if user is None:
+    def _parse(client, user: "raw.base.User") -> Optional["User"]:
+        if user is None or isinstance(user, raw.types.UserEmpty):
             return None
 
         return User(

From bbad58a83f5ec9e295233c97743365c99757eec1 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 24 Apr 2022 11:56:06 +0200
Subject: [PATCH 0784/1185] Add enumerations

---
 docs/source/api/enums/ChatAction.rst          |   8 +
 docs/source/api/enums/ChatEventAction.rst     |   8 +
 docs/source/api/enums/ChatMemberStatus.rst    |   8 +
 docs/source/api/enums/ChatMembersFilter.rst   |   8 +
 docs/source/api/enums/ChatType.rst            |   8 +
 docs/source/api/enums/MessageEntityType.rst   |   8 +
 docs/source/api/enums/MessageMedia.rst        |   8 +
 docs/source/api/enums/MessageService.rst      |   8 +
 docs/source/api/enums/MessagesFilter.rst      |   8 +
 docs/source/api/enums/ParseMode.rst           |   8 +
 docs/source/api/enums/PollType.rst            |   8 +
 docs/source/api/enums/SentCodeType.rst        |   8 +
 docs/source/api/enums/UserStatus.rst          |   8 +
 docs/source/api/enums/cleanup.html            |   9 +
 docs/source/api/enums/index.rst               |  45 ++++
 docs/source/index.rst                         |   2 +
 pyrogram/__init__.py                          |   2 +-
 pyrogram/client.py                            |  48 ++---
 pyrogram/enums/__init__.py                    |  31 +++
 pyrogram/enums/auto_name.py                   |  24 +++
 pyrogram/enums/chat_action.py                 |  72 +++++++
 pyrogram/enums/chat_event_action.py           | 127 ++++++++++++
 pyrogram/enums/chat_member_status.py          |  43 ++++
 pyrogram/enums/chat_members_filter.py         |  42 ++++
 pyrogram/enums/chat_type.py                   |  40 ++++
 pyrogram/enums/message_entity_type.py         |  81 ++++++++
 pyrogram/enums/message_media.py               |  70 +++++++
 pyrogram/enums/message_service.py             |  70 +++++++
 pyrogram/enums/messages_filter.py             |  75 +++++++
 pyrogram/enums/parse_mode.py                  |  37 ++++
 pyrogram/enums/poll_type.py                   |  31 +++
 pyrogram/enums/sent_code_type.py              |  39 ++++
 pyrogram/enums/user_status.py                 |  43 ++++
 pyrogram/filters.py                           |   7 +-
 pyrogram/methods/chats/get_chat_members.py    |  14 +-
 pyrogram/methods/messages/copy_message.py     |   9 +-
 .../methods/messages/edit_inline_caption.py   |   9 +-
 pyrogram/methods/messages/edit_inline_text.py |   9 +-
 .../methods/messages/edit_message_caption.py  |   9 +-
 .../methods/messages/edit_message_text.py     |   9 +-
 pyrogram/methods/messages/search_global.py    |  51 +----
 .../methods/messages/search_global_count.py   |  30 +--
 pyrogram/methods/messages/search_messages.py  |  67 +-----
 .../methods/messages/search_messages_count.py |  33 +--
 pyrogram/methods/messages/send_animation.py   |   9 +-
 pyrogram/methods/messages/send_audio.py       |   9 +-
 .../methods/messages/send_cached_media.py     |   9 +-
 pyrogram/methods/messages/send_chat_action.py |  62 ++----
 pyrogram/methods/messages/send_document.py    |   9 +-
 pyrogram/methods/messages/send_message.py     |   9 +-
 pyrogram/methods/messages/send_photo.py       |   9 +-
 pyrogram/methods/messages/send_poll.py        |  12 +-
 pyrogram/methods/messages/send_video.py       |   9 +-
 pyrogram/methods/messages/send_voice.py       |   9 +-
 pyrogram/parser/html.py                       |   6 +-
 pyrogram/parser/markdown.py                   |  10 +-
 pyrogram/parser/parser.py                     |  28 +--
 pyrogram/scaffold.py                          |   6 +-
 pyrogram/types/authorization/sent_code.py     |  40 +---
 .../bots_and_keyboards/callback_query.py      |  16 +-
 .../bots_and_keyboards/game_high_score.py     |   2 +-
 .../inline_keyboard_button.py                 |   2 +-
 pyrogram/types/inline_mode/inline_query.py    |  19 +-
 .../inline_query_result_animation.py          |   9 +-
 .../inline_mode/inline_query_result_audio.py  |  13 +-
 .../inline_mode/inline_query_result_photo.py  |   9 +-
 .../inline_mode/inline_query_result_video.py  |   9 +-
 .../input_media/input_media_animation.py      |   8 +-
 .../types/input_media/input_media_audio.py    |   8 +-
 .../types/input_media/input_media_document.py |   8 +-
 .../types/input_media/input_media_photo.py    |   8 +-
 .../types/input_media/input_media_video.py    |   8 +-
 .../input_text_message_content.py             |   9 +-
 pyrogram/types/messages_and_media/message.py  | 166 ++++++---------
 .../messages_and_media/message_entity.py      | 102 ++-------
 pyrogram/types/messages_and_media/poll.py     |  10 +-
 pyrogram/types/object.py                      |   4 +
 pyrogram/types/user_and_chats/chat.py         |  34 ++-
 pyrogram/types/user_and_chats/chat_event.py   | 194 ++++++------------
 .../types/user_and_chats/chat_event_filter.py |   8 +-
 pyrogram/types/user_and_chats/chat_member.py  |  37 ++--
 .../user_and_chats/invite_link_importer.py    |   2 +-
 pyrogram/types/user_and_chats/user.py         |  43 ++--
 83 files changed, 1377 insertions(+), 869 deletions(-)
 create mode 100644 docs/source/api/enums/ChatAction.rst
 create mode 100644 docs/source/api/enums/ChatEventAction.rst
 create mode 100644 docs/source/api/enums/ChatMemberStatus.rst
 create mode 100644 docs/source/api/enums/ChatMembersFilter.rst
 create mode 100644 docs/source/api/enums/ChatType.rst
 create mode 100644 docs/source/api/enums/MessageEntityType.rst
 create mode 100644 docs/source/api/enums/MessageMedia.rst
 create mode 100644 docs/source/api/enums/MessageService.rst
 create mode 100644 docs/source/api/enums/MessagesFilter.rst
 create mode 100644 docs/source/api/enums/ParseMode.rst
 create mode 100644 docs/source/api/enums/PollType.rst
 create mode 100644 docs/source/api/enums/SentCodeType.rst
 create mode 100644 docs/source/api/enums/UserStatus.rst
 create mode 100644 docs/source/api/enums/cleanup.html
 create mode 100644 docs/source/api/enums/index.rst
 create mode 100644 pyrogram/enums/__init__.py
 create mode 100644 pyrogram/enums/auto_name.py
 create mode 100644 pyrogram/enums/chat_action.py
 create mode 100644 pyrogram/enums/chat_event_action.py
 create mode 100644 pyrogram/enums/chat_member_status.py
 create mode 100644 pyrogram/enums/chat_members_filter.py
 create mode 100644 pyrogram/enums/chat_type.py
 create mode 100644 pyrogram/enums/message_entity_type.py
 create mode 100644 pyrogram/enums/message_media.py
 create mode 100644 pyrogram/enums/message_service.py
 create mode 100644 pyrogram/enums/messages_filter.py
 create mode 100644 pyrogram/enums/parse_mode.py
 create mode 100644 pyrogram/enums/poll_type.py
 create mode 100644 pyrogram/enums/sent_code_type.py
 create mode 100644 pyrogram/enums/user_status.py

diff --git a/docs/source/api/enums/ChatAction.rst b/docs/source/api/enums/ChatAction.rst
new file mode 100644
index 0000000000..b66df5fd73
--- /dev/null
+++ b/docs/source/api/enums/ChatAction.rst
@@ -0,0 +1,8 @@
+ChatAction
+==========
+
+.. autoclass:: pyrogram.enums.ChatAction()
+    :members:
+
+.. raw:: html
+    :file: ./cleanup.html
\ No newline at end of file
diff --git a/docs/source/api/enums/ChatEventAction.rst b/docs/source/api/enums/ChatEventAction.rst
new file mode 100644
index 0000000000..0403e781db
--- /dev/null
+++ b/docs/source/api/enums/ChatEventAction.rst
@@ -0,0 +1,8 @@
+ChatEventAction
+===============
+
+.. autoclass:: pyrogram.enums.ChatEventAction()
+    :members:
+
+.. raw:: html
+    :file: ./cleanup.html
\ No newline at end of file
diff --git a/docs/source/api/enums/ChatMemberStatus.rst b/docs/source/api/enums/ChatMemberStatus.rst
new file mode 100644
index 0000000000..bff23eda66
--- /dev/null
+++ b/docs/source/api/enums/ChatMemberStatus.rst
@@ -0,0 +1,8 @@
+ChatMemberStatus
+================
+
+.. autoclass:: pyrogram.enums.ChatMemberStatus()
+    :members:
+
+.. raw:: html
+    :file: ./cleanup.html
\ No newline at end of file
diff --git a/docs/source/api/enums/ChatMembersFilter.rst b/docs/source/api/enums/ChatMembersFilter.rst
new file mode 100644
index 0000000000..5a970ffc6e
--- /dev/null
+++ b/docs/source/api/enums/ChatMembersFilter.rst
@@ -0,0 +1,8 @@
+ChatMembersFilter
+=================
+
+.. autoclass:: pyrogram.enums.ChatMembersFilter()
+    :members:
+
+.. raw:: html
+    :file: ./cleanup.html
\ No newline at end of file
diff --git a/docs/source/api/enums/ChatType.rst b/docs/source/api/enums/ChatType.rst
new file mode 100644
index 0000000000..dd653055fa
--- /dev/null
+++ b/docs/source/api/enums/ChatType.rst
@@ -0,0 +1,8 @@
+ChatType
+========
+
+.. autoclass:: pyrogram.enums.ChatType()
+    :members:
+
+.. raw:: html
+    :file: ./cleanup.html
\ No newline at end of file
diff --git a/docs/source/api/enums/MessageEntityType.rst b/docs/source/api/enums/MessageEntityType.rst
new file mode 100644
index 0000000000..c7a8965f12
--- /dev/null
+++ b/docs/source/api/enums/MessageEntityType.rst
@@ -0,0 +1,8 @@
+MessageEntityType
+=================
+
+.. autoclass:: pyrogram.enums.MessageEntityType()
+    :members:
+
+.. raw:: html
+    :file: ./cleanup.html
\ No newline at end of file
diff --git a/docs/source/api/enums/MessageMedia.rst b/docs/source/api/enums/MessageMedia.rst
new file mode 100644
index 0000000000..f42693f0c1
--- /dev/null
+++ b/docs/source/api/enums/MessageMedia.rst
@@ -0,0 +1,8 @@
+MessageMedia
+============
+
+.. autoclass:: pyrogram.enums.MessageMedia()
+    :members:
+
+.. raw:: html
+    :file: ./cleanup.html
\ No newline at end of file
diff --git a/docs/source/api/enums/MessageService.rst b/docs/source/api/enums/MessageService.rst
new file mode 100644
index 0000000000..7b7ee4e212
--- /dev/null
+++ b/docs/source/api/enums/MessageService.rst
@@ -0,0 +1,8 @@
+MessageService
+==============
+
+.. autoclass:: pyrogram.enums.MessageService()
+    :members:
+
+.. raw:: html
+    :file: ./cleanup.html
\ No newline at end of file
diff --git a/docs/source/api/enums/MessagesFilter.rst b/docs/source/api/enums/MessagesFilter.rst
new file mode 100644
index 0000000000..090907076a
--- /dev/null
+++ b/docs/source/api/enums/MessagesFilter.rst
@@ -0,0 +1,8 @@
+MessagesFilter
+==============
+
+.. autoclass:: pyrogram.enums.MessagesFilter()
+    :members:
+
+.. raw:: html
+    :file: ./cleanup.html
\ No newline at end of file
diff --git a/docs/source/api/enums/ParseMode.rst b/docs/source/api/enums/ParseMode.rst
new file mode 100644
index 0000000000..1bcc74da04
--- /dev/null
+++ b/docs/source/api/enums/ParseMode.rst
@@ -0,0 +1,8 @@
+ParseMode
+=========
+
+.. autoclass:: pyrogram.enums.ParseMode()
+    :members:
+
+.. raw:: html
+    :file: ./cleanup.html
\ No newline at end of file
diff --git a/docs/source/api/enums/PollType.rst b/docs/source/api/enums/PollType.rst
new file mode 100644
index 0000000000..d00f9ce8a8
--- /dev/null
+++ b/docs/source/api/enums/PollType.rst
@@ -0,0 +1,8 @@
+PollType
+========
+
+.. autoclass:: pyrogram.enums.PollType()
+    :members:
+
+.. raw:: html
+    :file: ./cleanup.html
\ No newline at end of file
diff --git a/docs/source/api/enums/SentCodeType.rst b/docs/source/api/enums/SentCodeType.rst
new file mode 100644
index 0000000000..d738b195b1
--- /dev/null
+++ b/docs/source/api/enums/SentCodeType.rst
@@ -0,0 +1,8 @@
+SentCodeType
+============
+
+.. autoclass:: pyrogram.enums.SentCodeType()
+    :members:
+
+.. raw:: html
+    :file: ./cleanup.html
\ No newline at end of file
diff --git a/docs/source/api/enums/UserStatus.rst b/docs/source/api/enums/UserStatus.rst
new file mode 100644
index 0000000000..c9a77e1bf9
--- /dev/null
+++ b/docs/source/api/enums/UserStatus.rst
@@ -0,0 +1,8 @@
+UserStatus
+==========
+
+.. autoclass:: pyrogram.enums.UserStatus()
+    :members:
+
+.. raw:: html
+    :file: ./cleanup.html
\ No newline at end of file
diff --git a/docs/source/api/enums/cleanup.html b/docs/source/api/enums/cleanup.html
new file mode 100644
index 0000000000..bb9db7801a
--- /dev/null
+++ b/docs/source/api/enums/cleanup.html
@@ -0,0 +1,9 @@
+
\ No newline at end of file
diff --git a/docs/source/api/enums/index.rst b/docs/source/api/enums/index.rst
new file mode 100644
index 0000000000..7dfa1f3c24
--- /dev/null
+++ b/docs/source/api/enums/index.rst
@@ -0,0 +1,45 @@
+Enumerations
+============
+
+This page is about Pyrogram enumerations.
+Enumerations are types that hold a group of related values to be used whenever a constant value is required.
+They will help you deal with those values in a type-safe way and also enable code completion so that you can be sure
+to apply only a valid value among the expected ones.
+
+-----
+
+.. currentmodule:: pyrogram.enums
+
+.. autosummary::
+    :nosignatures:
+
+    ChatAction
+    ChatEventAction
+    ChatMemberStatus
+    ChatMembersFilter
+    ChatType
+    MessageEntityType
+    MessageMedia
+    MessageService
+    MessagesFilter
+    ParseMode
+    PollType
+    SentCodeType
+    UserStatus
+
+.. toctree::
+    :hidden:
+
+    ChatAction
+    ChatEventAction
+    ChatMemberStatus
+    ChatMembersFilter
+    ChatType
+    MessageEntityType
+    MessageMedia
+    MessageService
+    MessagesFilter
+    ParseMode
+    PollType
+    SentCodeType
+    UserStatus
\ No newline at end of file
diff --git a/docs/source/index.rst b/docs/source/index.rst
index 8838c3db17..910d031f1f 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -92,6 +92,7 @@ API Reference
     - :doc:`Pyrogram Client `: Reference details about the Client class.
     - :doc:`Available Methods `: List of available high-level methods.
     - :doc:`Available Types `: List of available high-level types.
+    - :doc:`Enumerations `: List of available enumerations.
     - :doc:`Bound Methods `: List of convenient bound methods.
 
 Meta
@@ -130,6 +131,7 @@ Meta
     api/methods/index
     api/types/index
     api/bound-methods/index
+    api/enums/index
     api/handlers
     api/decorators
     api/errors/index
diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index e102a89dab..5e5ea69c39 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -37,7 +37,7 @@ class ContinuePropagation(StopAsyncIteration):
 
 import asyncio
 
-from . import raw, types, filters, handlers, emoji
+from . import raw, types, filters, handlers, emoji, enums
 from .client import Client
 from .sync import idle
 
diff --git a/pyrogram/client.py b/pyrogram/client.py
index 7d3dbd3701..cf3b8ec3b9 100644
--- a/pyrogram/client.py
+++ b/pyrogram/client.py
@@ -33,6 +33,7 @@
 
 import pyrogram
 from pyrogram import __version__, __license__
+from pyrogram import enums
 from pyrogram import raw
 from pyrogram import utils
 from pyrogram.crypto import aes
@@ -146,7 +147,7 @@ class Client(Methods, Scaffold):
             Your Smart Plugins settings as dict, e.g.: *dict(root="plugins")*.
             This is an alternative way to setup plugins if you don't want to use the *config.ini* file.
 
-        parse_mode (``str``, *optional*):
+        parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
             The parse mode, can be any of: *"combined"*, for the default combined mode. *"markdown"* or *"md"*
             to force Markdown-only styles. *"html"* to force HTML-only styles. *None* to disable the parser
             completely.
@@ -197,7 +198,7 @@ def __init__(
         workdir: str = Scaffold.WORKDIR,
         config_file: str = Scaffold.CONFIG_FILE,
         plugins: dict = None,
-        parse_mode: str = Scaffold.PARSE_MODES[0],
+        parse_mode: "enums.ParseMode" = enums.ParseMode.DEFAULT,
         no_updates: bool = None,
         takeout: bool = None,
         sleep_threshold: int = Session.SLEEP_THRESHOLD,
@@ -394,44 +395,21 @@ async def authorize(self) -> User:
 
         return signed_up
 
-    @property
-    def parse_mode(self):
-        return self._parse_mode
-
-    @parse_mode.setter
-    def parse_mode(self, parse_mode: Optional[str] = "combined"):
-        if isinstance(parse_mode, str):
-            parse_mode = parse_mode.lower()
-
-        if parse_mode not in self.PARSE_MODES:
-            raise ValueError('parse_mode must be one of {} or None. Not "{}"'.format(
-                ", ".join(f'"{m}"' for m in self.PARSE_MODES[:-1]),
-                parse_mode
-            ))
-
-        self._parse_mode = parse_mode
-
-    # TODO: redundant, remove in next major version
-    def set_parse_mode(self, parse_mode: Optional[str] = "combined"):
+    def set_parse_mode(self, parse_mode: Optional["enums.ParseMode"]):
         """Set the parse mode to be used globally by the client.
 
         When setting the parse mode with this method, all other methods having a *parse_mode* parameter will follow the
-        global value by default. The default value *"combined"* enables both Markdown and HTML styles to be used and
-        combined together.
+        global value by default.
 
         Parameters:
-            parse_mode (``str``):
-                The new parse mode, can be any of: *"combined"*, for the default combined mode. *"markdown"* or *"md"*
-                to force Markdown-only styles. *"html"* to force HTML-only styles. *None* to disable the parser
-                completely.
-
-        Raises:
-            ValueError: In case the provided *parse_mode* is not a valid parse mode.
+            parse_mode (:obj:`~pyrogram.enums.ParseMode`):
+                By default, texts are parsed using both Markdown and HTML styles.
+                You can combine both syntaxes together.
 
         Example:
             .. code-block:: python
 
-                from pyrogram import Client
+                from pyrogram import Client, enums
 
                 app = Client("my_account")
 
@@ -440,19 +418,19 @@ def set_parse_mode(self, parse_mode: Optional[str] = "combined"):
                     app.send_message("me", "1. **markdown** and html")
 
                     # Force Markdown-only, HTML is disabled
-                    app.set_parse_mode("markdown")
+                    app.set_parse_mode(enums.ParseMode.MARKDOWN)
                     app.send_message("me", "2. **markdown** and html")
 
                     # Force HTML-only, Markdown is disabled
-                    app.set_parse_mode("html")
+                    app.set_parse_mode(enums.ParseMode.HTML)
                     app.send_message("me", "3. **markdown** and html")
 
                     # Disable the parser completely
-                    app.set_parse_mode(None)
+                    app.set_parse_mode(enums.ParseMode.DISABLED)
                     app.send_message("me", "4. **markdown** and html")
 
                     # Bring back the default combined mode
-                    app.set_parse_mode()
+                    app.set_parse_mode(enums.ParseMode.DEFAULT)
                     app.send_message("me", "5. **markdown** and html")
         """
 
diff --git a/pyrogram/enums/__init__.py b/pyrogram/enums/__init__.py
new file mode 100644
index 0000000000..50c85f608e
--- /dev/null
+++ b/pyrogram/enums/__init__.py
@@ -0,0 +1,31 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  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 .chat_action import ChatAction
+from .chat_event_action import ChatEventAction
+from .chat_member_status import ChatMemberStatus
+from .chat_members_filter import ChatMembersFilter
+from .chat_type import ChatType
+from .message_entity_type import MessageEntityType
+from .message_media import MessageMedia
+from .message_service import MessageService
+from .messages_filter import MessagesFilter
+from .parse_mode import ParseMode
+from .poll_type import PollType
+from .sent_code_type import SentCodeType
+from .user_status import UserStatus
diff --git a/pyrogram/enums/auto_name.py b/pyrogram/enums/auto_name.py
new file mode 100644
index 0000000000..b1d10d1077
--- /dev/null
+++ b/pyrogram/enums/auto_name.py
@@ -0,0 +1,24 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  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 enum import Enum
+
+
+class AutoName(Enum):
+    def _generate_next_value_(self, *args):
+        return self.lower()
diff --git a/pyrogram/enums/chat_action.py b/pyrogram/enums/chat_action.py
new file mode 100644
index 0000000000..167937e002
--- /dev/null
+++ b/pyrogram/enums/chat_action.py
@@ -0,0 +1,72 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  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 pyrogram import raw
+from .auto_name import AutoName
+
+
+class ChatAction(AutoName):
+    """Chat action enumeration used in :obj:`~pyrogram.types.ChatEvent`."""
+
+    TYPING = raw.types.SendMessageTypingAction
+    "Typing text message"
+
+    UPLOAD_PHOTO = raw.types.SendMessageUploadPhotoAction
+    "Uploading photo"
+
+    RECORD_VIDEO = raw.types.SendMessageRecordVideoAction
+    "Recording video"
+
+    UPLOAD_VIDEO = raw.types.SendMessageUploadVideoAction
+    "Uploading video"
+
+    RECORD_AUDIO = raw.types.SendMessageRecordAudioAction
+    "Recording audio"
+
+    UPLOAD_AUDIO = raw.types.SendMessageUploadAudioAction
+    "Uploading audio"
+
+    UPLOAD_DOCUMENT = raw.types.SendMessageUploadDocumentAction
+    "Uploading document"
+
+    FIND_LOCATION = raw.types.SendMessageGeoLocationAction
+    "Finding location"
+
+    RECORD_VIDEO_NOTE = raw.types.SendMessageRecordRoundAction
+    "Recording video note"
+
+    UPLOAD_VIDEO_NOTE = raw.types.SendMessageUploadRoundAction
+    "Uploading video note"
+
+    PLAYING = raw.types.SendMessageGamePlayAction
+    "Playing game"
+
+    CHOOSE_CONTACT = raw.types.SendMessageChooseContactAction
+    "Choosing contact"
+
+    SPEAKING = raw.types.SpeakingInGroupCallAction
+    "Speaking in group call"
+
+    IMPORT_HISTORY = raw.types.SendMessageHistoryImportAction
+    "Importing history"
+
+    CHOOSE_STICKER = raw.types.SendMessageChooseStickerAction
+    "Choosing sticker"
+
+    CANCEL = raw.types.SendMessageCancelAction
+    "Cancel ongoing chat action"
diff --git a/pyrogram/enums/chat_event_action.py b/pyrogram/enums/chat_event_action.py
new file mode 100644
index 0000000000..9b73de913c
--- /dev/null
+++ b/pyrogram/enums/chat_event_action.py
@@ -0,0 +1,127 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  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 enum import auto
+
+from .auto_name import AutoName
+
+
+class ChatEventAction(AutoName):
+    """Chat event action enumeration used in :meth:`~pyrogram.Client.get_chat_event_log`."""
+
+    DESCRIPTION_CHANGED = auto()
+    "The chat description has been changed (see ``old_description`` and ``new_description``)"
+
+    HISTORY_TTL_CHANGED = auto()
+    "The history time-to-live has been changed (see ``old_history_ttl`` and ``new_history_ttl``)"
+
+    LINKED_CHAT_CHANGED = auto()
+    "The linked chat has been changed (see ``old_linked_chat`` and ``new_linked_chat``)"
+
+    # LOCATION_CHANGED = auto()
+    ""
+
+    PHOTO_CHANGED = auto()
+    "The chat photo has been changed (see ``old_photo`` and ``new_photo``)"
+
+    # STICKER_SET_CHANGED = auto()
+    ""
+
+    TITLE_CHANGED = auto()
+    "the chat title has been changed (see ``old_title`` and ``new_title``)"
+
+    USERNAME_CHANGED = auto()
+    "the chat username has been changed (see ``old_username`` and ``new_username``)"
+
+    CHAT_PERMISSIONS_CHANGED = auto()
+    "the default chat permissions has been changed (see ``old_chat_permissions`` and ``new_chat_permissions``)"
+
+    MESSAGE_DELETED = auto()
+    "a message has been deleted (see ``deleted_message``)"
+
+    # VOICE_CHAT_DISCARDED = auto()
+    ""
+
+    MESSAGE_EDITED = auto()
+    "a message has been edited (see ``old_message`` and ``new_message``)"
+
+    INVITE_LINK_EDITED = auto()
+    "An invite link has been edited (see ``old_invite_link`` and ``new_invite`` link)"
+
+    INVITE_LINK_REVOKED = auto()
+    "An invite link has been revoked (see ``revoked_invite_link``)"
+
+    INVITE_LINK_DELETED = auto()
+    "An invite link has been deleted (see ``deleted_invite_link``)"
+
+    MEMBER_INVITED = auto()
+    "a member has been invited by someone (see ``invited_member``)"
+
+    MEMBER_JOINED = auto()
+    "a member joined by themselves. (see ``user``)"
+
+    # MEMBER_JOINED_BY_LINK = auto()
+    ""
+
+    MEMBER_LEFT = auto()
+    "a member left by themselves. (see ``user``)"
+
+    # MEMBER_MUTED = auto()
+    ""
+
+    ADMINISTRATOR_PRIVILEGES_CHANGED = auto()
+    "a chat member has been promoted/demoted or their administrator privileges has changed (see ``old_administrator_privileges`` and ``new_administrator_privileges``)"
+
+    MEMBER_PERMISSIONS_CHANGED = auto()
+    "a chat member has been restricted/unrestricted or banned/unbanned, or their permissions has changed (see ``old_member_permissions`` and ``new_member_permissions``)"
+
+    # MEMBER_UNMUTED = auto()
+    ""
+
+    # MEMBER_VOLUME_CHANGED = auto()
+    ""
+
+    # VOICE_CHAT_STARTED = auto()
+    ""
+
+    POLL_STOPPED = auto()
+    "a poll has been stopped (see ``stopped_poll``)"
+
+    # VOICE_CHAT_SETTINGS_CHANGED = auto()
+    ""
+
+    INVITES_ENABLED = auto()
+    "the chat invitation has been enabled or disabled (see ``invites_enabled``)"
+
+    HISTORY_HIDDEN = auto()
+    "the chat history has been hidden or unhidden (see ``history_hidden``)"
+
+    SIGNATURES_ENABLED = auto()
+    "the message signatures have been enabled or disabled (see ``signatures_enabled``)"
+
+    SLOW_MODE_CHANGED = auto()
+    "the slow mode has been changes (see ``old_slow_mode`` and ``new_slow_mode``)"
+
+    MESSAGE_PINNED = auto()
+    "a message has been pinned (see ``pinned_message``)"
+
+    MESSAGE_UNPINNED = auto()
+    "a message has been unpinned (see ``unpinned_message``)"
+
+    UNKNOWN = auto()
+    "Unknown chat event action"
diff --git a/pyrogram/enums/chat_member_status.py b/pyrogram/enums/chat_member_status.py
new file mode 100644
index 0000000000..7fa9e8eca8
--- /dev/null
+++ b/pyrogram/enums/chat_member_status.py
@@ -0,0 +1,43 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  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 enum import auto
+
+from .auto_name import AutoName
+
+
+class ChatMemberStatus(AutoName):
+    """Chat member status enumeration used in :obj:`~pyrogram.types.ChatMember`."""
+
+    OWNER = auto()
+    "Chat owner"
+
+    ADMINISTRATOR = auto()
+    "Chat administrator"
+
+    MEMBER = auto()
+    "Chat member"
+
+    RESTRICTED = auto()
+    "Restricted chat member"
+
+    LEFT = auto()
+    "Left chat member"
+
+    BANNED = auto()
+    "Banned chat member"
diff --git a/pyrogram/enums/chat_members_filter.py b/pyrogram/enums/chat_members_filter.py
new file mode 100644
index 0000000000..089f3d4bb9
--- /dev/null
+++ b/pyrogram/enums/chat_members_filter.py
@@ -0,0 +1,42 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  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 pyrogram import raw
+from .auto_name import AutoName
+
+
+class ChatMembersFilter(AutoName):
+    """Chat members filter enumeration used in :meth:`~pyrogram.Client.get_chat_members`"""
+
+    ANY = raw.types.ChannelParticipantsSearch
+    "Any kind of member"
+
+    BANNED = raw.types.ChannelParticipantsKicked
+    "Banned members"
+
+    RESTRICTED = raw.types.ChannelParticipantsBanned
+    "Restricted members"
+
+    BOTS = raw.types.ChannelParticipantsBots
+    "Bots"
+
+    RECENT = raw.types.ChannelParticipantsRecent
+    "Recently active members"
+
+    ADMINISTRATORS = raw.types.ChannelParticipantsAdmins
+    "Administrators"
diff --git a/pyrogram/enums/chat_type.py b/pyrogram/enums/chat_type.py
new file mode 100644
index 0000000000..5750e16d7c
--- /dev/null
+++ b/pyrogram/enums/chat_type.py
@@ -0,0 +1,40 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  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 enum import auto
+
+from .auto_name import AutoName
+
+
+class ChatType(AutoName):
+    """Chat type enumeration used in :obj:`~pyrogram.types.Chat`."""
+
+    PRIVATE = auto()
+    "Chat is a private chat with a user"
+
+    BOT = auto()
+    "Chat is a private chat with a bot"
+
+    GROUP = auto()
+    "Chat is a basic group"
+
+    SUPERGROUP = auto()
+    "Chat is a supergroup"
+
+    CHANNEL = auto()
+    "Chat is a channel"
diff --git a/pyrogram/enums/message_entity_type.py b/pyrogram/enums/message_entity_type.py
new file mode 100644
index 0000000000..34655d37f9
--- /dev/null
+++ b/pyrogram/enums/message_entity_type.py
@@ -0,0 +1,81 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  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 pyrogram import raw
+from .auto_name import AutoName
+
+
+class MessageEntityType(AutoName):
+    """Message entity type enumeration used in :obj:`~pyrogram.types.MessageEntity`."""
+
+    MENTION = raw.types.MessageEntityMention
+    "``@username``"
+
+    HASHTAG = raw.types.MessageEntityHashtag
+    "``#hashtag``"
+
+    CASHTAG = raw.types.MessageEntityCashtag
+    "``$USD``"
+
+    BOT_COMMAND = raw.types.MessageEntityBotCommand
+    "``/start@pyrogrambot``"
+
+    URL = raw.types.MessageEntityUrl
+    "``https://pyrogram.org`` (see ``url``)"
+
+    EMAIL = raw.types.MessageEntityEmail
+    "``do-not-reply@pyrogram.org``"
+
+    PHONE_NUMBER = raw.types.MessageEntityPhone
+    "``+1-123-456-7890``"
+
+    BOLD = raw.types.MessageEntityBold
+    "Bold text"
+
+    ITALIC = raw.types.MessageEntityItalic
+    "Italic text"
+
+    UNDERLINE = raw.types.MessageEntityUnderline
+    "Underlined text"
+
+    STRIKETHROUGH = raw.types.MessageEntityStrike
+    "Strikethrough text"
+
+    SPOILER = raw.types.MessageEntitySpoiler
+    "Spoiler text"
+
+    CODE = raw.types.MessageEntityCode
+    "Monowidth string"
+
+    PRE = raw.types.MessageEntityPre
+    "Monowidth block (see ``language``)"
+
+    BLOCKQUOTE = raw.types.MessageEntityBlockquote
+    "Blockquote text"
+
+    TEXT_LINK = raw.types.MessageEntityTextUrl
+    "For clickable text URLs"
+
+    TEXT_MENTION = raw.types.MessageEntityMentionName
+    "for users without usernames (see ``user``)"
+
+    BANK_CARD = raw.types.MessageEntityBankCard
+    "Bank card text"
+
+    UNKNOWN = raw.types.MessageEntityUnknown
+    "Unknown message entity type"
diff --git a/pyrogram/enums/message_media.py b/pyrogram/enums/message_media.py
new file mode 100644
index 0000000000..b7dfd03bc6
--- /dev/null
+++ b/pyrogram/enums/message_media.py
@@ -0,0 +1,70 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  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 enum import auto
+
+from .auto_name import AutoName
+
+
+class MessageMedia(AutoName):
+    """Message media enumeration used in :obj:`~pyrogram.types.Message`."""
+
+    AUDIO = auto()
+    "Audio media"
+
+    DOCUMENT = auto()
+    "Document media"
+
+    PHOTO = auto()
+    "Photo media"
+
+    STICKER = auto()
+    "Sticker media"
+
+    VIDEO = auto()
+    "Video media"
+
+    ANIMATION = auto()
+    "Animation media"
+
+    VOICE = auto()
+    "Voice media"
+
+    VIDEO_NOTE = auto()
+    "Video note media"
+
+    CONTACT = auto()
+    "Contact media"
+
+    LOCATION = auto()
+    "Location media"
+
+    VENUE = auto()
+    "Venue media"
+
+    POLL = auto()
+    "Poll media"
+
+    WEB_PAGE = auto()
+    "Web page media"
+
+    DICE = auto()
+    "Dice media"
+
+    GAME = auto()
+    "Game media"
diff --git a/pyrogram/enums/message_service.py b/pyrogram/enums/message_service.py
new file mode 100644
index 0000000000..b38e471793
--- /dev/null
+++ b/pyrogram/enums/message_service.py
@@ -0,0 +1,70 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  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 enum import auto
+
+from .auto_name import AutoName
+
+
+class MessageService(AutoName):
+    """Message service enumeration used in :obj:`~pyrogram.types.Message`."""
+
+    NEW_CHAT_MEMBERS = auto()
+    "New members join"
+
+    LEFT_CHAT_MEMBERS = auto()
+    "Left chat members"
+
+    NEW_CHAT_TITLE = auto()
+    "New chat title"
+
+    NEW_CHAT_PHOTO = auto()
+    "New chat photo"
+
+    DELETE_CHAT_PHOTO = auto()
+    "Deleted chat photo"
+
+    GROUP_CHAT_CREATED = auto()
+    "Group chat created"
+
+    CHANNEL_CHAT_CREATED = auto()
+    "Channel chat created"
+
+    MIGRATE_TO_CHAT_ID = auto()
+    "Migrated to chat id"
+
+    MIGRATE_FROM_CHAT_ID = auto()
+    "Migrated from chat id"
+
+    PINNED_MESSAGE = auto()
+    "Pinned message"
+
+    GAME_HIGH_SCORE = auto()
+    "Game high score"
+
+    VOICE_CHAT_STARTED = auto()
+    "Voice chat started"
+
+    VOICE_CHAT_ENDED = auto()
+    "Voice chat ended"
+
+    VOICE_CHAT_SCHEDULED = auto()
+    "Voice chat scheduled"
+
+    VOICE_CHAT_MEMBERS_INVITED = auto()
+    "Voice chat members invited"
diff --git a/pyrogram/enums/messages_filter.py b/pyrogram/enums/messages_filter.py
new file mode 100644
index 0000000000..64d856aa98
--- /dev/null
+++ b/pyrogram/enums/messages_filter.py
@@ -0,0 +1,75 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  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 pyrogram import raw
+from .auto_name import AutoName
+
+
+class MessagesFilter(AutoName):
+    """Messages filter enumeration used in used in :meth:`~pyrogram.Client.search_messages` and used in :meth:`~pyrogram.Client.search_global`"""
+
+    ANY = raw.types.InputMessagesFilterEmpty
+    "Any kind of message"
+
+    PHOTO = raw.types.InputMessagesFilterPhotos
+    "Photo messages"
+
+    VIDEO = raw.types.InputMessagesFilterVideo
+    "Video messages"
+
+    PHOTO_VIDEO = raw.types.InputMessagesFilterPhotoVideo
+    "Photo and video messages"
+
+    DOCUMENT = raw.types.InputMessagesFilterDocument
+    "Document messages"
+
+    URL = raw.types.InputMessagesFilterUrl
+    "Messages containing URLs"
+
+    ANIMATION = raw.types.InputMessagesFilterGif
+    "Animation messages"
+
+    VOICE_NOTE = raw.types.InputMessagesFilterVoice
+    "Voice note messages"
+
+    VIDEO_NOTE = raw.types.InputMessagesFilterRoundVideo
+    "Video note messages"
+
+    AUDIO_VIDEO_NOTE = raw.types.InputMessagesFilterRoundVideo
+    "Audio and video note messages"
+
+    AUDIO = raw.types.InputMessagesFilterMusic
+    "Audio messages (music)"
+
+    CHAT_PHOTO = raw.types.InputMessagesFilterChatPhotos
+    "Chat photo messages"
+
+    PHONE_CALL = raw.types.InputMessagesFilterPhoneCalls
+    "Phone call messages"
+
+    MENTION = raw.types.InputMessagesFilterMyMentions
+    "Messages containing mentions"
+
+    LOCATION = raw.types.InputMessagesFilterGeo
+    "Location messages"
+
+    CONTACT = raw.types.InputMessagesFilterContacts
+    "Contact messages"
+
+    PINNED = raw.types.InputMessagesFilterPinned
+    "Pinned messages"
diff --git a/pyrogram/enums/parse_mode.py b/pyrogram/enums/parse_mode.py
new file mode 100644
index 0000000000..26dc165a25
--- /dev/null
+++ b/pyrogram/enums/parse_mode.py
@@ -0,0 +1,37 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  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 enum import auto
+
+from .auto_name import AutoName
+
+
+class ParseMode(AutoName):
+    """Parse mode enumeration used in various places to set a specific parse mode"""
+
+    DEFAULT = auto()
+    "Default mode. Markdown and HTML combined"
+
+    MARKDOWN = auto()
+    "Markdown only mode"
+
+    HTML = auto()
+    "HTML only mode"
+
+    DISABLED = auto()
+    "Disabled mode"
diff --git a/pyrogram/enums/poll_type.py b/pyrogram/enums/poll_type.py
new file mode 100644
index 0000000000..384592dee3
--- /dev/null
+++ b/pyrogram/enums/poll_type.py
@@ -0,0 +1,31 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  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 enum import auto
+
+from .auto_name import AutoName
+
+
+class PollType(AutoName):
+    """Poll type enumeration used in :obj:`~pyrogram.types.Poll`."""
+
+    QUIZ = auto()
+    "Quiz poll"
+
+    REGULAR = auto()
+    "Regular poll"
diff --git a/pyrogram/enums/sent_code_type.py b/pyrogram/enums/sent_code_type.py
new file mode 100644
index 0000000000..73ae724cf2
--- /dev/null
+++ b/pyrogram/enums/sent_code_type.py
@@ -0,0 +1,39 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  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 pyrogram import raw
+from .auto_name import AutoName
+
+
+class SentCodeType(AutoName):
+    """Sent code type enumeration used in :obj:`~pyrogram.types.SentCode`."""
+
+    APP = raw.types.auth.SentCodeTypeApp
+    "The code was sent through the telegram app."
+
+    CALL = raw.types.auth.SentCodeTypeCall
+    "The code will be sent via a phone call. A synthesized voice will tell the user which verification code to input."
+
+    FLASH_CALL = raw.types.auth.SentCodeTypeFlashCall
+    "The code will be sent via a flash phone call, that will be closed immediately."
+
+    MISSED_CALL = raw.types.auth.SentCodeTypeMissedCall
+    "Missed call"
+
+    SMS = raw.types.auth.SentCodeTypeSms
+    "The code was sent via SMS."
diff --git a/pyrogram/enums/user_status.py b/pyrogram/enums/user_status.py
new file mode 100644
index 0000000000..c7c25234cb
--- /dev/null
+++ b/pyrogram/enums/user_status.py
@@ -0,0 +1,43 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  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 enum import auto
+
+from .auto_name import AutoName
+
+
+class UserStatus(AutoName):
+    """User status enumeration used in :obj:`~pyrogram.types.User`."""
+
+    ONLINE = auto()
+    """User is online"""
+
+    OFFLINE = auto()
+    """User is offline"""
+
+    RECENTLY = auto()
+    """User was seen recently"""
+
+    LAST_WEEK = auto()
+    """User was seen last week"""
+
+    LAST_MONTH = auto()
+    """User was seen last month"""
+
+    LONG_AGO = auto()
+    """User was seen long ago"""
diff --git a/pyrogram/filters.py b/pyrogram/filters.py
index 2a44fd4f9a..276d70c299 100644
--- a/pyrogram/filters.py
+++ b/pyrogram/filters.py
@@ -21,6 +21,7 @@
 from typing import Callable, Union, List, Pattern
 
 import pyrogram
+from pyrogram import enums
 from pyrogram.types import Message, CallbackQuery, InlineQuery, InlineKeyboardMarkup, ReplyKeyboardMarkup, Update
 
 
@@ -438,7 +439,7 @@ async def dice_filter(_, __, m: Message):
 
 # region private_filter
 async def private_filter(_, __, m: Message):
-    return bool(m.chat and m.chat.type in {"private", "bot"})
+    return bool(m.chat and m.chat.type in {enums.ChatType.PRIVATE, enums.ChatType.BOT})
 
 
 private = create(private_filter)
@@ -449,7 +450,7 @@ async def private_filter(_, __, m: Message):
 
 # region group_filter
 async def group_filter(_, __, m: Message):
-    return bool(m.chat and m.chat.type in {"group", "supergroup"})
+    return bool(m.chat and m.chat.type in {enums.ChatType.GROUP, enums.ChatType.SUPERGROUP})
 
 
 group = create(group_filter)
@@ -460,7 +461,7 @@ async def group_filter(_, __, m: Message):
 
 # region channel_filter
 async def channel_filter(_, __, m: Message):
-    return bool(m.chat and m.chat.type == "channel")
+    return bool(m.chat and m.chat.type == enums.ChatType.CHANNEL)
 
 
 channel = create(channel_filter)
diff --git a/pyrogram/methods/chats/get_chat_members.py b/pyrogram/methods/chats/get_chat_members.py
index 07d831a964..4c8ae60229 100644
--- a/pyrogram/methods/chats/get_chat_members.py
+++ b/pyrogram/methods/chats/get_chat_members.py
@@ -19,22 +19,12 @@
 import logging
 from typing import Union, List
 
-from pyrogram import raw
-from pyrogram import types
+from pyrogram import raw, types, enums
 from pyrogram.scaffold import Scaffold
 
 log = logging.getLogger(__name__)
 
 
-class Filters:
-    ALL = "all"
-    BANNED = "banned"
-    RESTRICTED = "restricted"
-    BOTS = "bots"
-    RECENT = "recent"
-    ADMINISTRATORS = "administrators"
-
-
 class GetChatMembers(Scaffold):
     async def get_chat_members(
         self,
@@ -42,7 +32,7 @@ async def get_chat_members(
         offset: int = 0,
         limit: int = 200,
         query: str = "",
-        filter: str = Filters.RECENT
+        filter: "enums.ChatMembersFilter" = enums.ChatMembersFilter.ANY
     ) -> List["types.ChatMember"]:
         """Get a chunk of the members list of a chat.
 
diff --git a/pyrogram/methods/messages/copy_message.py b/pyrogram/methods/messages/copy_message.py
index 1295edef44..07c0c5faed 100644
--- a/pyrogram/methods/messages/copy_message.py
+++ b/pyrogram/methods/messages/copy_message.py
@@ -19,7 +19,7 @@
 import logging
 from typing import Union, List, Optional
 
-from pyrogram import types
+from pyrogram import types, enums
 from pyrogram.scaffold import Scaffold
 
 log = logging.getLogger(__name__)
@@ -32,7 +32,7 @@ async def copy_message(
         from_chat_id: Union[int, str],
         message_id: int,
         caption: str = None,
-        parse_mode: Optional[str] = object,
+        parse_mode: Optional["enums.ParseMode"] = None,
         caption_entities: List["types.MessageEntity"] = None,
         disable_notification: bool = None,
         reply_to_message_id: int = None,
@@ -69,12 +69,9 @@ async def copy_message(
                 If not specified, the original caption is kept.
                 Pass "" (empty string) to remove the caption.
 
-            parse_mode (``str``, *optional*):
+            parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
                 By default, texts are parsed using both Markdown and HTML styles.
                 You can combine both syntaxes together.
-                Pass "markdown" or "md" to enable Markdown-style parsing only.
-                Pass "html" to enable HTML-style parsing only.
-                Pass None to completely disable style parsing.
 
             caption_entities (List of :obj:`~pyrogram.types.MessageEntity`):
                 List of special entities that appear in the new caption, which can be specified instead of *parse_mode*.
diff --git a/pyrogram/methods/messages/edit_inline_caption.py b/pyrogram/methods/messages/edit_inline_caption.py
index 2f009a0586..ed99b62d70 100644
--- a/pyrogram/methods/messages/edit_inline_caption.py
+++ b/pyrogram/methods/messages/edit_inline_caption.py
@@ -18,7 +18,7 @@
 
 from typing import Optional
 
-from pyrogram import types
+from pyrogram import types, enums
 from pyrogram.scaffold import Scaffold
 
 
@@ -27,7 +27,7 @@ async def edit_inline_caption(
         self,
         inline_message_id: str,
         caption: str,
-        parse_mode: Optional[str] = object,
+        parse_mode: Optional["enums.ParseMode"] = None,
         reply_markup: "types.InlineKeyboardMarkup" = None
     ) -> bool:
         """Edit the caption of inline media messages.
@@ -39,12 +39,9 @@ async def edit_inline_caption(
             caption (``str``):
                 New caption of the media message.
 
-            parse_mode (``str``, *optional*):
+            parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
                 By default, texts are parsed using both Markdown and HTML styles.
                 You can combine both syntaxes together.
-                Pass "markdown" or "md" to enable Markdown-style parsing only.
-                Pass "html" to enable HTML-style parsing only.
-                Pass None to completely disable style parsing.
 
             reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*):
                 An InlineKeyboardMarkup object.
diff --git a/pyrogram/methods/messages/edit_inline_text.py b/pyrogram/methods/messages/edit_inline_text.py
index 996dd4f5be..af4b555574 100644
--- a/pyrogram/methods/messages/edit_inline_text.py
+++ b/pyrogram/methods/messages/edit_inline_text.py
@@ -18,7 +18,7 @@
 
 from typing import Optional
 
-from pyrogram import raw
+from pyrogram import raw, enums
 from pyrogram import types
 from pyrogram import utils
 from pyrogram.scaffold import Scaffold
@@ -30,7 +30,7 @@ async def edit_inline_text(
         self,
         inline_message_id: str,
         text: str,
-        parse_mode: Optional[str] = object,
+        parse_mode: Optional["enums.ParseMode"] = None,
         disable_web_page_preview: bool = None,
         reply_markup: "types.InlineKeyboardMarkup" = None
     ) -> bool:
@@ -43,12 +43,9 @@ async def edit_inline_text(
             text (``str``):
                 New text of the message.
 
-            parse_mode (``str``, *optional*):
+            parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
                 By default, texts are parsed using both Markdown and HTML styles.
                 You can combine both syntaxes together.
-                Pass "markdown" or "md" to enable Markdown-style parsing only.
-                Pass "html" to enable HTML-style parsing only.
-                Pass None to completely disable style parsing.
 
             disable_web_page_preview (``bool``, *optional*):
                 Disables link previews for links in this message.
diff --git a/pyrogram/methods/messages/edit_message_caption.py b/pyrogram/methods/messages/edit_message_caption.py
index 9abdfbe972..e368d4f57c 100644
--- a/pyrogram/methods/messages/edit_message_caption.py
+++ b/pyrogram/methods/messages/edit_message_caption.py
@@ -18,7 +18,7 @@
 
 from typing import Union, List, Optional
 
-from pyrogram import types
+from pyrogram import types, enums
 from pyrogram.scaffold import Scaffold
 
 
@@ -28,7 +28,7 @@ async def edit_message_caption(
         chat_id: Union[int, str],
         message_id: int,
         caption: str,
-        parse_mode: Optional[str] = object,
+        parse_mode: Optional["enums.ParseMode"] = None,
         caption_entities: List["types.MessageEntity"] = None,
         reply_markup: "types.InlineKeyboardMarkup" = None
     ) -> "types.Message":
@@ -46,12 +46,9 @@ async def edit_message_caption(
             caption (``str``):
                 New caption of the media message.
 
-            parse_mode (``str``, *optional*):
+            parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
                 By default, texts are parsed using both Markdown and HTML styles.
                 You can combine both syntaxes together.
-                Pass "markdown" or "md" to enable Markdown-style parsing only.
-                Pass "html" to enable HTML-style parsing only.
-                Pass None to completely disable style parsing.
 
             caption_entities (List of :obj:`~pyrogram.types.MessageEntity`):
                 List of special entities that appear in the caption, which can be specified instead of *parse_mode*.
diff --git a/pyrogram/methods/messages/edit_message_text.py b/pyrogram/methods/messages/edit_message_text.py
index 5d6ba9cfa9..c58429a257 100644
--- a/pyrogram/methods/messages/edit_message_text.py
+++ b/pyrogram/methods/messages/edit_message_text.py
@@ -18,7 +18,7 @@
 
 from typing import Union, List, Optional
 
-from pyrogram import raw
+from pyrogram import raw, enums
 from pyrogram import types
 from pyrogram import utils
 from pyrogram.scaffold import Scaffold
@@ -30,7 +30,7 @@ async def edit_message_text(
         chat_id: Union[int, str],
         message_id: int,
         text: str,
-        parse_mode: Optional[str] = object,
+        parse_mode: Optional["enums.ParseMode"] = None,
         entities: List["types.MessageEntity"] = None,
         disable_web_page_preview: bool = None,
         reply_markup: "types.InlineKeyboardMarkup" = None
@@ -49,12 +49,9 @@ async def edit_message_text(
             text (``str``):
                 New text of the message.
 
-            parse_mode (``str``, *optional*):
+            parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
                 By default, texts are parsed using both Markdown and HTML styles.
                 You can combine both syntaxes together.
-                Pass "markdown" or "md" to enable Markdown-style parsing only.
-                Pass "html" to enable HTML-style parsing only.
-                Pass None to completely disable style parsing.
 
             entities (List of :obj:`~pyrogram.types.MessageEntity`):
                 List of special entities that appear in message text, which can be specified instead of *parse_mode*.
diff --git a/pyrogram/methods/messages/search_global.py b/pyrogram/methods/messages/search_global.py
index c3fb7acbac..2c22b26f79 100644
--- a/pyrogram/methods/messages/search_global.py
+++ b/pyrogram/methods/messages/search_global.py
@@ -18,37 +18,17 @@
 
 from typing import AsyncGenerator, Optional
 
-from pyrogram import raw
+from pyrogram import raw, enums
 from pyrogram import types
 from pyrogram import utils
 from pyrogram.scaffold import Scaffold
 
 
-class Filters:
-    EMPTY = raw.types.InputMessagesFilterEmpty()
-    PHOTO = raw.types.InputMessagesFilterPhotos()
-    VIDEO = raw.types.InputMessagesFilterVideo()
-    PHOTO_VIDEO = raw.types.InputMessagesFilterPhotoVideo()
-    DOCUMENT = raw.types.InputMessagesFilterDocument()
-    URL = raw.types.InputMessagesFilterUrl()
-    ANIMATION = raw.types.InputMessagesFilterGif()
-    VOICE_NOTE = raw.types.InputMessagesFilterVoice()
-    AUDIO = raw.types.InputMessagesFilterMusic()
-    CHAT_PHOTO = raw.types.InputMessagesFilterChatPhotos()
-    AUDIO_VIDEO_NOTE = raw.types.InputMessagesFilterRoundVideo()
-    VIDEO_NOTE = raw.types.InputMessagesFilterRoundVideo()
-    LOCATION = raw.types.InputMessagesFilterGeo()
-    CONTACT = raw.types.InputMessagesFilterContacts()
-
-
-POSSIBLE_VALUES = list(map(lambda x: x.lower(), filter(lambda x: not x.startswith("__"), Filters.__dict__.keys())))
-
-
 class SearchGlobal(Scaffold):
     async def search_global(
         self,
         query: str = "",
-        filter: str = "empty",
+        filter: "enums.MessagesFilter" = enums.MessagesFilter.ANY,
         limit: int = 0,
     ) -> Optional[AsyncGenerator["types.Message", None]]:
         """Search messages globally from all of your chats.
@@ -65,23 +45,9 @@ async def search_global(
                 Text query string.
                 Use "@" to search for mentions.
             
-            filter (``str``, *optional*):
-                Pass a filter in order to search for specific kind of messages only:
-
-                - ``"empty"``: Search for all kind of messages (default).
-                - ``"photo"``: Search for photos.
-                - ``"video"``: Search for video.
-                - ``"photo_video"``: Search for either photo or video.
-                - ``"document"``: Search for documents (generic files).
-                - ``"url"``: Search for messages containing URLs (web links).
-                - ``"animation"``: Search for animations (GIFs).
-                - ``"voice_note"``: Search for voice notes.
-                - ``"audio"``: Search for audio files (music).
-                - ``"chat_photo"``: Search for chat photos.
-                - ``"audio_video_note"``: Search for either audio or video notes.
-                - ``"video_note"``: Search for video notes.
-                - ``"location"``: Search for location messages.
-                - ``"contact"``: Search for contact messages.
+            filter (:obj:`~pyrogram.enums.MessagesFilter`, *optional*):
+                Pass a filter in order to search for specific kind of messages only.
+                Defaults to any message (no filter).
 
             limit (``int``, *optional*):
                 Limits the number of messages to be retrieved.
@@ -101,11 +67,6 @@ async def search_global(
                 for message in app.search_global(filter="photo", limit=20):
                     print(message.photo)
         """
-        try:
-            filter = Filters.__dict__[filter.upper()]
-        except KeyError:
-            raise ValueError('Invalid filter "{}". Possible values are: {}'.format(
-                filter, ", ".join(f'"{v}"' for v in POSSIBLE_VALUES))) from None
         current = 0
         # There seems to be an hard limit of 10k, beyond which Telegram starts spitting one message at a time.
         total = abs(limit) or (1 << 31)
@@ -121,7 +82,7 @@ async def search_global(
                 await self.send(
                     raw.functions.messages.SearchGlobal(
                         q=query,
-                        filter=filter,
+                        filter=filter.value(),
                         min_date=0,
                         max_date=0,
                         offset_rate=offset_date,
diff --git a/pyrogram/methods/messages/search_global_count.py b/pyrogram/methods/messages/search_global_count.py
index 78b6965402..d8da0163e5 100644
--- a/pyrogram/methods/messages/search_global_count.py
+++ b/pyrogram/methods/messages/search_global_count.py
@@ -16,16 +16,15 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-from pyrogram import raw
+from pyrogram import raw, enums
 from pyrogram.scaffold import Scaffold
-from .search_messages import Filters, POSSIBLE_VALUES
 
 
 class SearchGlobalCount(Scaffold):
     async def search_global_count(
         self,
         query: str = "",
-        filter: str = "empty",
+        filter: "enums.MessagesFilter" = enums.MessagesFilter.ANY,
     ) -> int:
         """Get the count of messages resulting from a global search.
 
@@ -36,37 +35,16 @@ async def search_global_count(
                 Text query string.
                 Use "@" to search for mentions.
 
-            filter (``str``, *optional*):
+            filter (:obj:`~pyrogram.enums.MessagesFilter`, *optional*):
                 Pass a filter in order to search for specific kind of messages only:
 
-                - ``"empty"``: Search for all kind of messages (default).
-                - ``"photo"``: Search for photos.
-                - ``"video"``: Search for video.
-                - ``"photo_video"``: Search for either photo or video.
-                - ``"document"``: Search for documents (generic files).
-                - ``"url"``: Search for messages containing URLs (web links).
-                - ``"animation"``: Search for animations (GIFs).
-                - ``"voice_note"``: Search for voice notes.
-                - ``"audio"``: Search for audio files (music).
-                - ``"chat_photo"``: Search for chat photos.
-                - ``"audio_video_note"``: Search for either audio or video notes.
-                - ``"video_note"``: Search for video notes.
-                - ``"location"``: Search for location messages.
-                - ``"contact"``: Search for contact messages.
-
         Returns:
             ``int``: On success, the messages count is returned.
         """
-        try:
-            filter = Filters.__dict__[filter.upper()]
-        except KeyError:
-            raise ValueError('Invalid filter "{}". Possible values are: {}'.format(
-                filter, ", ".join(f'"{v}"' for v in POSSIBLE_VALUES))) from None
-
         r = await self.send(
             raw.functions.messages.SearchGlobal(
                 q=query,
-                filter=filter,
+                filter=filter.value(),
                 min_date=0,
                 max_date=0,
                 offset_rate=0,
diff --git a/pyrogram/methods/messages/search_messages.py b/pyrogram/methods/messages/search_messages.py
index 91bd6a5afc..07728193e4 100644
--- a/pyrogram/methods/messages/search_messages.py
+++ b/pyrogram/methods/messages/search_messages.py
@@ -18,56 +18,25 @@
 
 from typing import Union, List, AsyncGenerator, Optional
 
-from pyrogram import raw
-from pyrogram import types
-from pyrogram import utils
+from pyrogram import raw, types, utils, enums
 from pyrogram.scaffold import Scaffold
 
 
-class Filters:
-    EMPTY = raw.types.InputMessagesFilterEmpty()
-    PHOTO = raw.types.InputMessagesFilterPhotos()
-    VIDEO = raw.types.InputMessagesFilterVideo()
-    PHOTO_VIDEO = raw.types.InputMessagesFilterPhotoVideo()
-    DOCUMENT = raw.types.InputMessagesFilterDocument()
-    URL = raw.types.InputMessagesFilterUrl()
-    ANIMATION = raw.types.InputMessagesFilterGif()
-    VOICE_NOTE = raw.types.InputMessagesFilterVoice()
-    AUDIO = raw.types.InputMessagesFilterMusic()
-    CHAT_PHOTO = raw.types.InputMessagesFilterChatPhotos()
-    PHONE_CALL = raw.types.InputMessagesFilterPhoneCalls()
-    AUDIO_VIDEO_NOTE = raw.types.InputMessagesFilterRoundVideo()
-    VIDEO_NOTE = raw.types.InputMessagesFilterRoundVideo()
-    MENTION = raw.types.InputMessagesFilterMyMentions()
-    LOCATION = raw.types.InputMessagesFilterGeo()
-    CONTACT = raw.types.InputMessagesFilterContacts()
-    PINNED = raw.types.InputMessagesFilterPinned()
-
-
-POSSIBLE_VALUES = list(map(lambda x: x.lower(), filter(lambda x: not x.startswith("__"), Filters.__dict__.keys())))
-
-
 # noinspection PyShadowingBuiltins
 async def get_chunk(
     client: Scaffold,
     chat_id: Union[int, str],
     query: str = "",
-    filter: str = "empty",
+    filter: "enums.MessagesFilter" = enums.MessagesFilter.ANY,
     offset: int = 0,
     limit: int = 100,
     from_user: Union[int, str] = None
 ) -> List["types.Message"]:
-    try:
-        filter = Filters.__dict__[filter.upper()]
-    except KeyError:
-        raise ValueError('Invalid filter "{}". Possible values are: {}'.format(
-            filter, ", ".join(f'"{v}"' for v in POSSIBLE_VALUES))) from None
-
     r = await client.send(
         raw.functions.messages.Search(
             peer=await client.resolve_peer(chat_id),
             q=query,
-            filter=filter,
+            filter=filter.value(),
             min_date=0,
             max_date=0,
             offset_id=0,
@@ -95,7 +64,7 @@ async def search_messages(
         chat_id: Union[int, str],
         query: str = "",
         offset: int = 0,
-        filter: str = "empty",
+        filter: "enums.MessagesFilter" = enums.MessagesFilter.ANY,
         limit: int = 0,
         from_user: Union[int, str] = None
     ) -> Optional[AsyncGenerator["types.Message", None]]:
@@ -119,26 +88,9 @@ async def search_messages(
                 Sequential number of the first message to be returned.
                 Defaults to 0.
 
-            filter (``str``, *optional*):
-                Pass a filter in order to search for specific kind of messages only:
-
-                - ``"empty"``: Search for all kind of messages (default).
-                - ``"photo"``: Search for photos.
-                - ``"video"``: Search for video.
-                - ``"photo_video"``: Search for either photo or video.
-                - ``"document"``: Search for documents (generic files).
-                - ``"url"``: Search for messages containing URLs (web links).
-                - ``"animation"``: Search for animations (GIFs).
-                - ``"voice_note"``: Search for voice notes.
-                - ``"audio"``: Search for audio files (music).
-                - ``"chat_photo"``: Search for chat photos.
-                - ``"phone_call"``: Search for phone calls.
-                - ``"audio_video_note"``: Search for either audio or video notes.
-                - ``"video_note"``: Search for video notes.
-                - ``"mention"``: Search for messages containing mentions to yourself.
-                - ``"location"``: Search for location messages.
-                - ``"contact"``: Search for contact messages.
-                - ``"pinned"``: Search for pinned messages.
+            filter (:obj:`~pyrogram.enums.MessagesFilter`, *optional*):
+                Pass a filter in order to search for specific kind of messages only.
+                Defaults to any message (no filter).
 
             limit (``int``, *optional*):
                 Limits the number of messages to be retrieved.
@@ -153,18 +105,21 @@ async def search_messages(
         Example:
             .. code-block:: python
 
+                from pyrogram import enums
+
                 # Search for text messages in chat. Get the last 120 results
                 for message in app.search_messages(chat_id, query="hello", limit=120):
                     print(message.text)
 
                 # Search for pinned messages in chat
-                for message in app.search_messages(chat_id, filter="pinned"):
+                for message in app.search_messages(chat_id, filter=enums.MessagesFilter.PINNED):
                     print(message.text)
 
                 # Search for messages containing "hello" sent by yourself in chat
                 for message in app.search_messages(chat, "hello", from_user="me"):
                     print(message.text)
         """
+
         current = 0
         total = abs(limit) or (1 << 31) - 1
         limit = min(100, total)
diff --git a/pyrogram/methods/messages/search_messages_count.py b/pyrogram/methods/messages/search_messages_count.py
index 3004eb39ce..87e0a74572 100644
--- a/pyrogram/methods/messages/search_messages_count.py
+++ b/pyrogram/methods/messages/search_messages_count.py
@@ -18,9 +18,8 @@
 
 from typing import Union
 
-from pyrogram import raw
+from pyrogram import raw, enums
 from pyrogram.scaffold import Scaffold
-from .search_messages import Filters, POSSIBLE_VALUES
 
 
 class SearchMessagesCount(Scaffold):
@@ -28,7 +27,7 @@ async def search_messages_count(
         self,
         chat_id: Union[int, str],
         query: str = "",
-        filter: str = "empty",
+        filter: "enums.MessagesFilter" = enums.MessagesFilter.ANY,
         from_user: Union[int, str] = None
     ) -> int:
         """Get the count of messages resulting from a search inside a chat.
@@ -47,44 +46,20 @@ async def search_messages_count(
                 When passed while searching for media messages, the query will be applied to captions.
                 Defaults to "" (empty string).
 
-            filter (``str``, *optional*):
+            filter (:obj:`~pyrogram.enums.MessagesFilter`, *optional*):
                 Pass a filter in order to search for specific kind of messages only:
 
-                - ``"empty"``: Search for all kind of messages (default).
-                - ``"photo"``: Search for photos.
-                - ``"video"``: Search for video.
-                - ``"photo_video"``: Search for either photo or video.
-                - ``"document"``: Search for documents (generic files).
-                - ``"url"``: Search for messages containing URLs (web links).
-                - ``"animation"``: Search for animations (GIFs).
-                - ``"voice_note"``: Search for voice notes.
-                - ``"audio"``: Search for audio files (music).
-                - ``"chat_photo"``: Search for chat photos.
-                - ``"phone_call"``: Search for phone calls.
-                - ``"audio_video_note"``: Search for either audio or video notes.
-                - ``"video_note"``: Search for video notes.
-                - ``"mention"``: Search for messages containing mentions to yourself.
-                - ``"location"``: Search for location messages.
-                - ``"contact"``: Search for contact messages.
-                - ``"pinned"``: Search for pinned messages.
-
             from_user (``int`` | ``str``, *optional*):
                 Unique identifier (int) or username (str) of the target user you want to search for messages from.
 
         Returns:
             ``int``: On success, the messages count is returned.
         """
-        try:
-            filter = Filters.__dict__[filter.upper()]
-        except KeyError:
-            raise ValueError('Invalid filter "{}". Possible values are: {}'.format(
-                filter, ", ".join(f'"{v}"' for v in POSSIBLE_VALUES))) from None
-
         r = await self.send(
             raw.functions.messages.Search(
                 peer=await self.resolve_peer(chat_id),
                 q=query,
-                filter=filter,
+                filter=filter.value(),
                 min_date=0,
                 max_date=0,
                 offset_id=0,
diff --git a/pyrogram/methods/messages/send_animation.py b/pyrogram/methods/messages/send_animation.py
index 963d0681b9..3777b3eab1 100644
--- a/pyrogram/methods/messages/send_animation.py
+++ b/pyrogram/methods/messages/send_animation.py
@@ -20,7 +20,7 @@
 import re
 from typing import Union, BinaryIO, List, Optional
 
-from pyrogram import StopTransmission
+from pyrogram import StopTransmission, enums
 from pyrogram import raw
 from pyrogram import types
 from pyrogram import utils
@@ -36,7 +36,7 @@ async def send_animation(
         animation: Union[str, BinaryIO],
         caption: str = "",
         unsave: bool = False,
-        parse_mode: Optional[str] = object,
+        parse_mode: Optional["enums.ParseMode"] = None,
         caption_entities: List["types.MessageEntity"] = None,
         duration: int = 0,
         width: int = 0,
@@ -78,12 +78,9 @@ async def send_animation(
                 By default, the server will save into your own collection any new animation you send.
                 Pass True to automatically unsave the sent animation. Defaults to False.
 
-            parse_mode (``str``, *optional*):
+            parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
                 By default, texts are parsed using both Markdown and HTML styles.
                 You can combine both syntaxes together.
-                Pass "markdown" or "md" to enable Markdown-style parsing only.
-                Pass "html" to enable HTML-style parsing only.
-                Pass None to completely disable style parsing.
 
             caption_entities (List of :obj:`~pyrogram.types.MessageEntity`):
                 List of special entities that appear in the caption, which can be specified instead of *parse_mode*.
diff --git a/pyrogram/methods/messages/send_audio.py b/pyrogram/methods/messages/send_audio.py
index 921b7890c7..e00930af1d 100644
--- a/pyrogram/methods/messages/send_audio.py
+++ b/pyrogram/methods/messages/send_audio.py
@@ -20,7 +20,7 @@
 import re
 from typing import Union, BinaryIO, List, Optional
 
-from pyrogram import StopTransmission
+from pyrogram import StopTransmission, enums
 from pyrogram import raw
 from pyrogram import types
 from pyrogram import utils
@@ -35,7 +35,7 @@ async def send_audio(
         chat_id: Union[int, str],
         audio: Union[str, BinaryIO],
         caption: str = "",
-        parse_mode: Optional[str] = object,
+        parse_mode: Optional["enums.ParseMode"] = None,
         caption_entities: List["types.MessageEntity"] = None,
         duration: int = 0,
         performer: str = None,
@@ -75,12 +75,9 @@ async def send_audio(
             caption (``str``, *optional*):
                 Audio caption, 0-1024 characters.
 
-            parse_mode (``str``, *optional*):
+            parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
                 By default, texts are parsed using both Markdown and HTML styles.
                 You can combine both syntaxes together.
-                Pass "markdown" or "md" to enable Markdown-style parsing only.
-                Pass "html" to enable HTML-style parsing only.
-                Pass None to completely disable style parsing.
 
             caption_entities (List of :obj:`~pyrogram.types.MessageEntity`):
                 List of special entities that appear in the caption, which can be specified instead of *parse_mode*.
diff --git a/pyrogram/methods/messages/send_cached_media.py b/pyrogram/methods/messages/send_cached_media.py
index 84fe9b17ee..5c8951212f 100644
--- a/pyrogram/methods/messages/send_cached_media.py
+++ b/pyrogram/methods/messages/send_cached_media.py
@@ -18,7 +18,7 @@
 
 from typing import Union, List, Optional
 
-from pyrogram import raw
+from pyrogram import raw, enums
 from pyrogram import types
 from pyrogram import utils
 from pyrogram.scaffold import Scaffold
@@ -30,7 +30,7 @@ async def send_cached_media(
         chat_id: Union[int, str],
         file_id: str,
         caption: str = "",
-        parse_mode: Optional[str] = object,
+        parse_mode: Optional["enums.ParseMode"] = None,
         caption_entities: List["types.MessageEntity"] = None,
         disable_notification: bool = None,
         reply_to_message_id: int = None,
@@ -62,12 +62,9 @@ async def send_cached_media(
             caption (``str``, *optional*):
                 Media caption, 0-1024 characters.
 
-            parse_mode (``str``, *optional*):
+            parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
                 By default, texts are parsed using both Markdown and HTML styles.
                 You can combine both syntaxes together.
-                Pass "markdown" or "md" to enable Markdown-style parsing only.
-                Pass "html" to enable HTML-style parsing only.
-                Pass None to completely disable style parsing.
 
             caption_entities (List of :obj:`~pyrogram.types.MessageEntity`):
                 List of special entities that appear in the caption, which can be specified instead of *parse_mode*.
diff --git a/pyrogram/methods/messages/send_chat_action.py b/pyrogram/methods/messages/send_chat_action.py
index fc79c01553..bbff1d4421 100644
--- a/pyrogram/methods/messages/send_chat_action.py
+++ b/pyrogram/methods/messages/send_chat_action.py
@@ -16,37 +16,19 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-import json
 from typing import Union
 
+import pyrogram
 from pyrogram import raw
 from pyrogram.scaffold import Scaffold
 
 
-class ChatAction:
-    TYPING = raw.types.SendMessageTypingAction
-    UPLOAD_PHOTO = raw.types.SendMessageUploadPhotoAction
-    RECORD_VIDEO = raw.types.SendMessageRecordVideoAction
-    UPLOAD_VIDEO = raw.types.SendMessageUploadVideoAction
-    RECORD_AUDIO = raw.types.SendMessageRecordAudioAction
-    UPLOAD_AUDIO = raw.types.SendMessageUploadAudioAction
-    UPLOAD_DOCUMENT = raw.types.SendMessageUploadDocumentAction
-    FIND_LOCATION = raw.types.SendMessageGeoLocationAction
-    RECORD_VIDEO_NOTE = raw.types.SendMessageRecordRoundAction
-    UPLOAD_VIDEO_NOTE = raw.types.SendMessageUploadRoundAction
-    PLAYING = raw.types.SendMessageGamePlayAction
-    CHOOSE_CONTACT = raw.types.SendMessageChooseContactAction
-    SPEAKING = raw.types.SpeakingInGroupCallAction
-    IMPORT_HISTORY = raw.types.SendMessageHistoryImportAction
-    CHOOSE_STICKER = raw.types.SendMessageChooseStickerAction
-    CANCEL = raw.types.SendMessageCancelAction
-
-
-POSSIBLE_VALUES = list(map(lambda x: x.lower(), filter(lambda x: not x.startswith("__"), ChatAction.__dict__.keys())))
-
-
 class SendChatAction(Scaffold):
-    async def send_chat_action(self, chat_id: Union[int, str], action: str) -> bool:
+    async def send_chat_action(
+        self,
+        chat_id: Union[int, str],
+        action: "pyrogram.enums.ChatAction"
+    ) -> bool:
         """Tell the other party that something is happening on your side.
 
         Parameters:
@@ -55,14 +37,8 @@ async def send_chat_action(self, chat_id: Union[int, str], action: str) -> bool:
                 For your personal cloud (Saved Messages) you can simply use "me" or "self".
                 For a contact that exists in your Telegram address book you can use his phone number (str).
 
-            action (``str``):
-                Type of action to broadcast. Choose one, depending on what the user is about to receive: *"typing"* for
-                text messages, *"upload_photo"* for photos, *"record_video"* or *"upload_video"* for videos,
-                *"record_audio"* or *"upload_audio"* for audio files, *"upload_document"* for general files,
-                *"find_location"* for location data, *"record_video_note"* or *"upload_video_note"* for video notes,
-                *"choose_contact"* for contacts, *"playing"* for games, *"speaking"* for speaking in group calls or
-                *"import_history"* for importing history, *"choose_sticker"* for stickers or
-                *"cancel"* to cancel any chat action currently displayed.
+            action (:obj:`~pyrogram.enums.ChatAction`):
+                Type of action to broadcast.
 
         Returns:
             ``bool``: On success, True is returned.
@@ -73,29 +49,27 @@ async def send_chat_action(self, chat_id: Union[int, str], action: str) -> bool:
         Example:
             .. code-block:: python
 
+                from pyrogram import enums
+
                 # Send "typing" chat action
-                app.send_chat_action(chat_id, "typing")
+                app.send_chat_action(chat_id, enums.ChatAction.TYPING)
 
                 # Send "upload_video" chat action
-                app.send_chat_action(chat_id, "upload_video")
+                app.send_chat_action(chat_id, enums.ChatAction.UPLOAD_VIDEO)
 
                 # Send "playing" chat action
-                app.send_chat_action(chat_id, "playing")
+                app.send_chat_action(chat_id, enums.ChatAction.PLAYING)
 
                 # Cancel any current chat action
-                app.send_chat_action(chat_id, "cancel")
+                app.send_chat_action(chat_id, enums.ChatAction.CANCEL)
         """
 
-        try:
-            action = ChatAction.__dict__[action.upper()]
-        except KeyError:
-            raise ValueError("Invalid chat action '{}'. Possible values are: {}".format(
-                action, json.dumps(POSSIBLE_VALUES, indent=4))) from None
+        action_name = action.name.lower()
 
-        if "Upload" in action.__name__ or "History" in action.__name__:
-            action = action(progress=0)
+        if "upload" in action_name or "history" in action_name:
+            action = action.value(progress=0)
         else:
-            action = action()
+            action = action.value()
 
         return await self.send(
             raw.functions.messages.SetTyping(
diff --git a/pyrogram/methods/messages/send_document.py b/pyrogram/methods/messages/send_document.py
index c3765a1726..4a35f8a5e9 100644
--- a/pyrogram/methods/messages/send_document.py
+++ b/pyrogram/methods/messages/send_document.py
@@ -20,7 +20,7 @@
 import re
 from typing import Union, BinaryIO, List, Optional
 
-from pyrogram import StopTransmission
+from pyrogram import StopTransmission, enums
 from pyrogram import raw
 from pyrogram import types
 from pyrogram import utils
@@ -36,7 +36,7 @@ async def send_document(
         document: Union[str, BinaryIO],
         thumb: Union[str, BinaryIO] = None,
         caption: str = "",
-        parse_mode: Optional[str] = object,
+        parse_mode: Optional["enums.ParseMode"] = None,
         caption_entities: List["types.MessageEntity"] = None,
         file_name: str = None,
         force_document: bool = None,
@@ -77,12 +77,9 @@ async def send_document(
             caption (``str``, *optional*):
                 Document caption, 0-1024 characters.
 
-            parse_mode (``str``, *optional*):
+            parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
                 By default, texts are parsed using both Markdown and HTML styles.
                 You can combine both syntaxes together.
-                Pass "markdown" or "md" to enable Markdown-style parsing only.
-                Pass "html" to enable HTML-style parsing only.
-                Pass None to completely disable style parsing.
 
             caption_entities (List of :obj:`~pyrogram.types.MessageEntity`):
                 List of special entities that appear in the caption, which can be specified instead of *parse_mode*.
diff --git a/pyrogram/methods/messages/send_message.py b/pyrogram/methods/messages/send_message.py
index c0653a38d4..575aea33e7 100644
--- a/pyrogram/methods/messages/send_message.py
+++ b/pyrogram/methods/messages/send_message.py
@@ -18,7 +18,7 @@
 
 from typing import Union, List, Optional
 
-from pyrogram import raw, utils
+from pyrogram import raw, utils, enums
 from pyrogram import types
 from pyrogram.scaffold import Scaffold
 
@@ -28,7 +28,7 @@ async def send_message(
         self,
         chat_id: Union[int, str],
         text: str,
-        parse_mode: Optional[str] = object,
+        parse_mode: Optional["enums.ParseMode"] = None,
         entities: List["types.MessageEntity"] = None,
         disable_web_page_preview: bool = None,
         disable_notification: bool = None,
@@ -53,12 +53,9 @@ async def send_message(
             text (``str``):
                 Text of the message to be sent.
 
-            parse_mode (``str``, *optional*):
+            parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
                 By default, texts are parsed using both Markdown and HTML styles.
                 You can combine both syntaxes together.
-                Pass "markdown" or "md" to enable Markdown-style parsing only.
-                Pass "html" to enable HTML-style parsing only.
-                Pass None to completely disable style parsing.
 
             entities (List of :obj:`~pyrogram.types.MessageEntity`):
                 List of special entities that appear in message text, which can be specified instead of *parse_mode*.
diff --git a/pyrogram/methods/messages/send_photo.py b/pyrogram/methods/messages/send_photo.py
index c70be84e2f..442b45aec5 100644
--- a/pyrogram/methods/messages/send_photo.py
+++ b/pyrogram/methods/messages/send_photo.py
@@ -21,7 +21,7 @@
 from typing import Union, BinaryIO, List, Optional
 
 import pyrogram
-from pyrogram import raw
+from pyrogram import raw, enums
 from pyrogram import types
 from pyrogram import utils
 from pyrogram.errors import FilePartMissing
@@ -35,7 +35,7 @@ async def send_photo(
         chat_id: Union[int, str],
         photo: Union[str, BinaryIO],
         caption: str = "",
-        parse_mode: Optional[str] = object,
+        parse_mode: Optional["enums.ParseMode"] = None,
         caption_entities: List["types.MessageEntity"] = None,
         ttl_seconds: int = None,
         disable_notification: bool = None,
@@ -69,12 +69,9 @@ async def send_photo(
             caption (``str``, *optional*):
                 Photo caption, 0-1024 characters.
 
-            parse_mode (``str``, *optional*):
+            parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
                 By default, texts are parsed using both Markdown and HTML styles.
                 You can combine both syntaxes together.
-                Pass "markdown" or "md" to enable Markdown-style parsing only.
-                Pass "html" to enable HTML-style parsing only.
-                Pass None to completely disable style parsing.
 
             caption_entities (List of :obj:`~pyrogram.types.MessageEntity`):
                 List of special entities that appear in the caption, which can be specified instead of *parse_mode*.
diff --git a/pyrogram/methods/messages/send_poll.py b/pyrogram/methods/messages/send_poll.py
index a412bcdff0..df27c0573f 100644
--- a/pyrogram/methods/messages/send_poll.py
+++ b/pyrogram/methods/messages/send_poll.py
@@ -19,7 +19,7 @@
 from typing import Union, List
 
 from pyrogram import raw
-from pyrogram import types
+from pyrogram import types, enums
 from pyrogram.scaffold import Scaffold
 
 
@@ -31,7 +31,7 @@ async def send_poll(
         options: List[str],
         is_anonymous: bool = True,
         allows_multiple_answers: bool = None,
-        type: str = "regular",
+        type: "enums.PollType" = enums.PollType.REGULAR,
         correct_option_id: int = None,
         disable_notification: bool = None,
         reply_to_message_id: int = None,
@@ -62,9 +62,9 @@ async def send_poll(
                 True, if the poll needs to be anonymous.
                 Defaults to True.
 
-            type (``str``, *optional*):
-                Poll type, "quiz" or "regular".
-                Defaults to "regular"
+            type (:obj`~pyrogram.enums.PollType`, *optional*):
+                Poll type, :obj:`~pyrogram.enums.PollType.QUIZ` or :obj:`~pyrogram.enums.PollType.REGULAR`.
+                Defaults to :obj:`~pyrogram.enums.PollType.REGULAR`.
 
             allows_multiple_answers (``bool``, *optional*):
                 True, if the poll allows multiple answers, ignored for polls in quiz mode.
@@ -112,7 +112,7 @@ async def send_poll(
                         ],
                         multiple_choice=allows_multiple_answers or None,
                         public_voters=not is_anonymous or None,
-                        quiz=type == "quiz" or None
+                        quiz=type == enums.PollType.QUIZ or None
                     ),
                     correct_answers=None if correct_option_id is None else [bytes([correct_option_id])]
                 ),
diff --git a/pyrogram/methods/messages/send_video.py b/pyrogram/methods/messages/send_video.py
index e9448a2209..f6dc00eebe 100644
--- a/pyrogram/methods/messages/send_video.py
+++ b/pyrogram/methods/messages/send_video.py
@@ -20,7 +20,7 @@
 import re
 from typing import Union, BinaryIO, List, Optional
 
-from pyrogram import StopTransmission
+from pyrogram import StopTransmission, enums
 from pyrogram import raw
 from pyrogram import types
 from pyrogram import utils
@@ -35,7 +35,7 @@ async def send_video(
         chat_id: Union[int, str],
         video: Union[str, BinaryIO],
         caption: str = "",
-        parse_mode: Optional[str] = object,
+        parse_mode: Optional["enums.ParseMode"] = None,
         caption_entities: List["types.MessageEntity"] = None,
         ttl_seconds: int = None,
         duration: int = 0,
@@ -75,12 +75,9 @@ async def send_video(
             caption (``str``, *optional*):
                 Video caption, 0-1024 characters.
 
-            parse_mode (``str``, *optional*):
+            parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
                 By default, texts are parsed using both Markdown and HTML styles.
                 You can combine both syntaxes together.
-                Pass "markdown" or "md" to enable Markdown-style parsing only.
-                Pass "html" to enable HTML-style parsing only.
-                Pass None to completely disable style parsing.
 
             caption_entities (List of :obj:`~pyrogram.types.MessageEntity`):
                 List of special entities that appear in the caption, which can be specified instead of *parse_mode*.
diff --git a/pyrogram/methods/messages/send_voice.py b/pyrogram/methods/messages/send_voice.py
index 80eb386d2c..c7e960fd04 100644
--- a/pyrogram/methods/messages/send_voice.py
+++ b/pyrogram/methods/messages/send_voice.py
@@ -20,7 +20,7 @@
 import re
 from typing import Union, BinaryIO, List, Optional
 
-from pyrogram import StopTransmission
+from pyrogram import StopTransmission, enums
 from pyrogram import raw
 from pyrogram import types
 from pyrogram import utils
@@ -35,7 +35,7 @@ async def send_voice(
         chat_id: Union[int, str],
         voice: Union[str, BinaryIO],
         caption: str = "",
-        parse_mode: Optional[str] = object,
+        parse_mode: Optional["enums.ParseMode"] = None,
         caption_entities: List["types.MessageEntity"] = None,
         duration: int = 0,
         disable_notification: bool = None,
@@ -69,12 +69,9 @@ async def send_voice(
             caption (``str``, *optional*):
                 Voice message caption, 0-1024 characters.
 
-            parse_mode (``str``, *optional*):
+            parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
                 By default, texts are parsed using both Markdown and HTML styles.
                 You can combine both syntaxes together.
-                Pass "markdown" or "md" to enable Markdown-style parsing only.
-                Pass "html" to enable HTML-style parsing only.
-                Pass None to completely disable style parsing.
 
             caption_entities (List of :obj:`~pyrogram.types.MessageEntity`):
                 List of special entities that appear in the caption, which can be specified instead of *parse_mode*.
diff --git a/pyrogram/parser/html.py b/pyrogram/parser/html.py
index b1ce5c5d46..c5f3c1e12d 100644
--- a/pyrogram/parser/html.py
+++ b/pyrogram/parser/html.py
@@ -174,13 +174,13 @@ def unparse(text: str, entities: list):
 
             entities_offsets.append((start_tag, start,))
             entities_offsets.append((end_tag, end,))
-            
+
         entities_offsets = map(
             lambda x: x[1],
             sorted(
                 enumerate(entities_offsets),
-                key = lambda x: (x[1][1], x[0]),
-                reverse = True
+                key=lambda x: (x[1][1], x[0]),
+                reverse=True
             )
         )
 
diff --git a/pyrogram/parser/markdown.py b/pyrogram/parser/markdown.py
index 898ac3d6a9..8b24d06302 100644
--- a/pyrogram/parser/markdown.py
+++ b/pyrogram/parser/markdown.py
@@ -147,8 +147,14 @@ def unparse(text: str, entities: list):
             entities_offsets.append((start_tag, start,))
             entities_offsets.append((end_tag, end,))
 
-        # sorting by offset (desc)
-        entities_offsets.sort(key=lambda x: -x[1])
+        entities_offsets = map(
+            lambda x: x[1],
+            sorted(
+                enumerate(entities_offsets),
+                key=lambda x: (x[1][1], x[0]),
+                reverse=True
+            )
+        )
 
         for entity, offset in entities_offsets:
             text = text[:offset] + entity + text[offset:]
diff --git a/pyrogram/parser/parser.py b/pyrogram/parser/parser.py
index d294e04d51..884e17558e 100644
--- a/pyrogram/parser/parser.py
+++ b/pyrogram/parser/parser.py
@@ -16,10 +16,10 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-from collections import OrderedDict
 from typing import Optional
 
 import pyrogram
+from pyrogram import enums
 from .html import HTML
 from .markdown import Markdown
 
@@ -30,36 +30,26 @@ def __init__(self, client: Optional["pyrogram.Client"]):
         self.html = HTML(client)
         self.markdown = Markdown(client)
 
-    async def parse(self, text: str, mode: Optional[str] = object):
+    async def parse(self, text: str, mode: Optional[str] = None):
         text = str(text if text else "").strip()
 
-        if mode == object:
+        if mode is None:
             if self.client:
                 mode = self.client.parse_mode
             else:
-                mode = "combined"
-
-        if mode is None:
-            return OrderedDict([
-                ("message", text),
-                ("entities", [])
-            ])
-
-        mode = mode.lower()
+                mode = enums.ParseMode.DEFAULT
 
-        if mode == "combined":
+        if mode == enums.ParseMode.DEFAULT:
             return await self.markdown.parse(text)
 
-        if mode in ["markdown", "md"]:
+        if mode == enums.ParseMode.MARKDOWN:
             return await self.markdown.parse(text, True)
 
-        if mode == "html":
+        if mode == enums.ParseMode.HTML:
             return await self.html.parse(text)
 
-        raise ValueError('parse_mode must be one of {} or None. Not "{}"'.format(
-            ", ".join(f'"{m}"' for m in pyrogram.Client.PARSE_MODES[:-1]),
-            mode
-        ))
+        if mode == enums.ParseMode.DISABLED:
+            return {"message": text, "entities": []}
 
     @staticmethod
     def unparse(text: str, entities: list, is_html: bool):
diff --git a/pyrogram/scaffold.py b/pyrogram/scaffold.py
index d68ddf787b..d87d1522c8 100644
--- a/pyrogram/scaffold.py
+++ b/pyrogram/scaffold.py
@@ -26,7 +26,7 @@
 from pathlib import Path
 
 import pyrogram
-from pyrogram import __version__
+from pyrogram import __version__, enums
 from pyrogram.parser import Parser
 from pyrogram.session.internals import MsgId
 from .mime_types import mime_types
@@ -46,8 +46,6 @@ class Scaffold:
     WORKDIR = PARENT_DIR
     CONFIG_FILE = PARENT_DIR / "config.ini"
 
-    PARSE_MODES = ["combined", "markdown", "md", "html", None]
-
     mimetypes = MimeTypes()
     mimetypes.readfp(StringIO(mime_types))
 
@@ -90,7 +88,7 @@ def __init__(self):
         self.rnd_id = MsgId
 
         self.parser = Parser(self)
-        self.parse_mode = "combined"
+        self.parse_mode = enums.ParseMode.DEFAULT
 
         self.session = None
 
diff --git a/pyrogram/types/authorization/sent_code.py b/pyrogram/types/authorization/sent_code.py
index a471445af2..b0ac9eebc6 100644
--- a/pyrogram/types/authorization/sent_code.py
+++ b/pyrogram/types/authorization/sent_code.py
@@ -16,7 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-from pyrogram import raw
+from pyrogram import raw, enums
 from ..object import Object
 
 
@@ -24,29 +24,25 @@ class SentCode(Object):
     """Contains info on a sent confirmation code.
 
     Parameters:
-        type (``str``):
+        type (:obj:`~pyrogram.enums.SentCodeType`):
             Type of the current sent code.
-            Can be *"app"* (code sent via Telegram), *"sms"* (code sent via SMS), *"call"* (code sent via voice call) or
-            *"flash_call"* (code is in the last 5 digits of the caller's phone number).
 
         phone_code_hash (``str``):
             Confirmation code identifier useful for the next authorization steps (either
             :meth:`~pyrogram.Client.sign_in` or :meth:`~pyrogram.Client.sign_up`).
 
-        next_type (``str``):
+        next_type (:obj:`~pyrogram.enums.SentCodeType`, *optional*):
             Type of the next code to be sent with :meth:`~pyrogram.Client.resend_code`.
-            Can be *"sms"* (code will be sent via SMS), *"call"* (code will be sent via voice call) or *"flash_call"*
-            (code will be in the last 5 digits of caller's phone number).
 
-        timeout (``int``):
+        timeout (``int``, *optional*):
             Delay in seconds before calling :meth:`~pyrogram.Client.resend_code`.
     """
 
     def __init__(
         self, *,
-        type: str,
+        type: "enums.SentCodeType",
         phone_code_hash: str,
-        next_type: str = None,
+        next_type: "enums.SentCodeType" = None,
         timeout: int = None
     ):
         super().__init__()
@@ -58,29 +54,9 @@ def __init__(
 
     @staticmethod
     def _parse(sent_code: raw.types.auth.SentCode) -> "SentCode":
-        type = sent_code.type
-
-        if isinstance(type, raw.types.auth.SentCodeTypeApp):
-            type = "app"
-        elif isinstance(type, raw.types.auth.SentCodeTypeSms):
-            type = "sms"
-        elif isinstance(type, raw.types.auth.SentCodeTypeCall):
-            type = "call"
-        elif isinstance(type, raw.types.auth.SentCodeTypeFlashCall):
-            type = "flash_call"
-
-        next_type = sent_code.next_type
-
-        if isinstance(next_type, raw.types.auth.CodeTypeSms):
-            next_type = "sms"
-        elif isinstance(next_type, raw.types.auth.CodeTypeCall):
-            next_type = "call"
-        elif isinstance(next_type, raw.types.auth.CodeTypeFlashCall):
-            next_type = "flash_call"
-
         return SentCode(
-            type=type,
+            type=enums.SentCodeType(type(sent_code.type)),
             phone_code_hash=sent_code.phone_code_hash,
-            next_type=next_type,
+            next_type=enums.SentCodeType(type(sent_code.next_type)) if sent_code.next_type else None,
             timeout=sent_code.timeout
         )
diff --git a/pyrogram/types/bots_and_keyboards/callback_query.py b/pyrogram/types/bots_and_keyboards/callback_query.py
index 8cdd424a64..e5db872359 100644
--- a/pyrogram/types/bots_and_keyboards/callback_query.py
+++ b/pyrogram/types/bots_and_keyboards/callback_query.py
@@ -21,7 +21,7 @@
 from typing import Union, List, Match, Optional
 
 import pyrogram
-from pyrogram import raw
+from pyrogram import raw, enums
 from pyrogram import types
 from ..object import Object
 from ..update import Update
@@ -171,7 +171,7 @@ async def answer(self, text: str = None, show_alert: bool = None, url: str = Non
     async def edit_message_text(
         self,
         text: str,
-        parse_mode: Optional[str] = object,
+        parse_mode: Optional["enums.ParseMode"] = None,
         disable_web_page_preview: bool = None,
         reply_markup: "types.InlineKeyboardMarkup" = None
     ) -> Union["types.Message", bool]:
@@ -183,12 +183,9 @@ async def edit_message_text(
             text (``str``):
                 New text of the message.
 
-            parse_mode (``str``, *optional*):
+            parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
                 By default, texts are parsed using both Markdown and HTML styles.
                 You can combine both syntaxes together.
-                Pass "markdown" or "md" to enable Markdown-style parsing only.
-                Pass "html" to enable HTML-style parsing only.
-                Pass None to completely disable style parsing.
 
             disable_web_page_preview (``bool``, *optional*):
                 Disables link previews for links in this message.
@@ -224,7 +221,7 @@ async def edit_message_text(
     async def edit_message_caption(
         self,
         caption: str,
-        parse_mode: Optional[str] = object,
+        parse_mode: Optional["enums.ParseMode"] = None,
         reply_markup: "types.InlineKeyboardMarkup" = None
     ) -> Union["types.Message", bool]:
         """Edit the caption of media messages attached to callback queries.
@@ -235,12 +232,9 @@ async def edit_message_caption(
             caption (``str``):
                 New caption of the message.
 
-            parse_mode (``str``, *optional*):
+            parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
                 By default, texts are parsed using both Markdown and HTML styles.
                 You can combine both syntaxes together.
-                Pass "markdown" or "md" to enable Markdown-style parsing only.
-                Pass "html" to enable HTML-style parsing only.
-                Pass None to completely disable style parsing.
 
             reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*):
                 An InlineKeyboardMarkup object.
diff --git a/pyrogram/types/bots_and_keyboards/game_high_score.py b/pyrogram/types/bots_and_keyboards/game_high_score.py
index 7de78ce9e6..295b83fb23 100644
--- a/pyrogram/types/bots_and_keyboards/game_high_score.py
+++ b/pyrogram/types/bots_and_keyboards/game_high_score.py
@@ -32,7 +32,7 @@ class GameHighScore(Object):
         score (``int``):
             Score.
 
-        position (``position``, *optional*):
+        position (``int``, *optional*):
             Position in high score table for the game.
     """
 
diff --git a/pyrogram/types/bots_and_keyboards/inline_keyboard_button.py b/pyrogram/types/bots_and_keyboards/inline_keyboard_button.py
index 9779ec3960..de6a04207c 100644
--- a/pyrogram/types/bots_and_keyboards/inline_keyboard_button.py
+++ b/pyrogram/types/bots_and_keyboards/inline_keyboard_button.py
@@ -43,7 +43,7 @@ class InlineKeyboardButton(Object):
              An HTTP URL used to automatically authorize the user. Can be used as a replacement for
              the `Telegram Login Widget `_.
 
-        user_id (``id``, *optional*):
+        user_id (``int``, *optional*):
             User id, for links to the user profile.
 
         switch_inline_query (``str``, *optional*):
diff --git a/pyrogram/types/inline_mode/inline_query.py b/pyrogram/types/inline_mode/inline_query.py
index 2522b6934f..6f671059d0 100644
--- a/pyrogram/types/inline_mode/inline_query.py
+++ b/pyrogram/types/inline_mode/inline_query.py
@@ -20,7 +20,7 @@
 
 import pyrogram
 from pyrogram import raw
-from pyrogram import types
+from pyrogram import types, enums
 from ..object import Object
 from ..update import Update
 
@@ -43,11 +43,8 @@ class InlineQuery(Object, Update):
         offset (``str``):
             Offset of the results to be returned, can be controlled by the bot.
 
-        chat_type (``str``, *optional*):
+        chat_type (:obj:`~pyrogram.enums.ChatType`, *optional*):
             Type of the chat, from which the inline query was sent.
-            Can be either "sender" for a private chat with the inline query sender, "private", "group", "supergroup", or
-            "channel". The chat type should be always known for requests sent from official clients and most
-            third-party clients, unless the request was sent from a secret chat.
 
         location (:obj:`~pyrogram.types.Location`. *optional*):
             Sender location, only for bots that request user location.
@@ -65,7 +62,7 @@ def __init__(
         from_user: "types.User",
         query: str,
         offset: str,
-        chat_type: str,
+        chat_type: "enums.ChatType",
         location: "types.Location" = None,
         matches: List[Match] = None
     ):
@@ -85,15 +82,15 @@ def _parse(client, inline_query: raw.types.UpdateBotInlineQuery, users: dict) ->
         chat_type = None
 
         if isinstance(peer_type, raw.types.InlineQueryPeerTypeSameBotPM):
-            chat_type = "sender"
+            chat_type = enums.ChatType.BOT
         elif isinstance(peer_type, raw.types.InlineQueryPeerTypePM):
-            chat_type = "private"
+            chat_type = enums.ChatType.PRIVATE
         elif isinstance(peer_type, raw.types.InlineQueryPeerTypeChat):
-            chat_type = "group"
+            chat_type = enums.ChatType.GROUP
         elif isinstance(peer_type, raw.types.InlineQueryPeerTypeMegagroup):
-            chat_type = "supergroup"
+            chat_type = enums.ChatType.SUPERGROUP
         elif isinstance(peer_type, raw.types.InlineQueryPeerTypeBroadcast):
-            chat_type = "channel"
+            chat_type = enums.ChatType.CHANNEL
 
         return InlineQuery(
             id=str(inline_query.query_id),
diff --git a/pyrogram/types/inline_mode/inline_query_result_animation.py b/pyrogram/types/inline_mode/inline_query_result_animation.py
index 2281ae1cfd..4a170a1834 100644
--- a/pyrogram/types/inline_mode/inline_query_result_animation.py
+++ b/pyrogram/types/inline_mode/inline_query_result_animation.py
@@ -19,7 +19,7 @@
 from typing import Optional, List
 
 import pyrogram
-from pyrogram import raw, types, utils
+from pyrogram import raw, types, utils, enums
 from .inline_query_result import InlineQueryResult
 
 
@@ -52,12 +52,9 @@ class InlineQueryResultAnimation(InlineQueryResult):
         caption (``str``, *optional*):
             Caption of the photo to be sent, 0-1024 characters.
 
-        parse_mode (``str``, *optional*):
+        parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
             By default, texts are parsed using both Markdown and HTML styles.
             You can combine both syntaxes together.
-            Pass "markdown" or "md" to enable Markdown-style parsing only.
-            Pass "html" to enable HTML-style parsing only.
-            Pass None to completely disable style parsing.
 
         caption_entities (List of :obj:`~pyrogram.types.MessageEntity`):
             List of special entities that appear in the caption, which can be specified instead of *parse_mode*.
@@ -77,7 +74,7 @@ def __init__(
         title: str = None,
         description: str = None,
         caption: str = "",
-        parse_mode: Optional[str] = object,
+        parse_mode: Optional["enums.ParseMode"] = None,
         caption_entities: List["types.MessageEntity"] = None,
         reply_markup: "types.InlineKeyboardMarkup" = None,
         input_message_content: "types.InputMessageContent" = None
diff --git a/pyrogram/types/inline_mode/inline_query_result_audio.py b/pyrogram/types/inline_mode/inline_query_result_audio.py
index e36ecb1124..06ab5f944e 100644
--- a/pyrogram/types/inline_mode/inline_query_result_audio.py
+++ b/pyrogram/types/inline_mode/inline_query_result_audio.py
@@ -16,10 +16,10 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-from typing import Union, List
+from typing import Union, List, Optional
 
 import pyrogram
-from pyrogram import raw, types, utils
+from pyrogram import raw, types, utils, enums
 from .inline_query_result import InlineQueryResult
 
 
@@ -50,13 +50,10 @@ class InlineQueryResultAudio(InlineQueryResult):
         caption (``str``, *optional*):
             Caption of the audio to be sent, 0-1024 characters.
             
-        parse_mode (``str``, *optional*):
+        parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
             By default, texts are parsed using both Markdown and HTML styles.
             You can combine both syntaxes together.
-            Pass "markdown" or "md" to enable Markdown-style parsing only.
-            Pass "html" to enable HTML-style parsing only.
-            Pass None to completely disable style parsing.
-            
+
         caption_entities (List of :obj:`~pyrogram.types.MessageEntity`):
             List of special entities that appear in the caption, which can be specified instead of *parse_mode*.
             
@@ -75,7 +72,7 @@ def __init__(
         performer: str = "",
         audio_duration: int = 0,
         caption: str = "",
-        parse_mode: Union[str, None] = object,
+        parse_mode: Optional["enums.ParseMode"] = None,
         caption_entities: List["types.MessageEntity"] = None,
         reply_markup: "types.InlineKeyboardMarkup" = None,
         input_message_content: "types.InputMessageContent" = None
diff --git a/pyrogram/types/inline_mode/inline_query_result_photo.py b/pyrogram/types/inline_mode/inline_query_result_photo.py
index 679ee42947..110b604661 100644
--- a/pyrogram/types/inline_mode/inline_query_result_photo.py
+++ b/pyrogram/types/inline_mode/inline_query_result_photo.py
@@ -19,7 +19,7 @@
 from typing import Optional, List
 
 import pyrogram
-from pyrogram import raw, types, utils
+from pyrogram import raw, types, utils, enums
 from .inline_query_result import InlineQueryResult
 
 
@@ -52,12 +52,9 @@ class InlineQueryResultPhoto(InlineQueryResult):
         caption (``str``, *optional*):
             Caption of the photo to be sent, 0-1024 characters.
 
-        parse_mode (``str``, *optional*):
+        parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
             By default, texts are parsed using both Markdown and HTML styles.
             You can combine both syntaxes together.
-            Pass "markdown" or "md" to enable Markdown-style parsing only.
-            Pass "html" to enable HTML-style parsing only.
-            Pass None to completely disable style parsing.
 
         caption_entities (List of :obj:`~pyrogram.types.MessageEntity`):
                 List of special entities that appear in the caption, which can be specified instead of *parse_mode*.
@@ -77,7 +74,7 @@ def __init__(
         title: str = None,
         description: str = None,
         caption: str = "",
-        parse_mode: Optional[str] = object,
+        parse_mode: Optional["enums.ParseMode"] = None,
         caption_entities: List["types.MessageEntity"] = None,
         reply_markup: "types.InlineKeyboardMarkup" = None,
         input_message_content: "types.InputMessageContent" = None
diff --git a/pyrogram/types/inline_mode/inline_query_result_video.py b/pyrogram/types/inline_mode/inline_query_result_video.py
index cd5d845437..5f71111f4f 100644
--- a/pyrogram/types/inline_mode/inline_query_result_video.py
+++ b/pyrogram/types/inline_mode/inline_query_result_video.py
@@ -19,7 +19,7 @@
 from typing import Optional, List
 
 import pyrogram
-from pyrogram import raw, types, utils
+from pyrogram import raw, types, utils, enums
 from .inline_query_result import InlineQueryResult
 
 
@@ -63,12 +63,9 @@ class InlineQueryResultVideo(InlineQueryResult):
         caption (``str``, *optional*):
             Caption of the video to be sent, 0-1024 characters.
 
-        parse_mode (``str``, *optional*):
+        parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
             By default, texts are parsed using both Markdown and HTML styles.
             You can combine both syntaxes together.
-            Pass "markdown" or "md" to enable Markdown-style parsing only.
-            Pass "html" to enable HTML-style parsing only.
-            Pass None to completely disable style parsing.
 
         caption_entities (List of :obj:`~pyrogram.types.MessageEntity`):
             List of special entities that appear in the caption, which can be specified instead of *parse_mode*.
@@ -93,7 +90,7 @@ def __init__(
         video_duration: int = 0,
         description: str = None,
         caption: str = "",
-        parse_mode: Optional[str] = object,
+        parse_mode: Optional["enums.ParseMode"] = None,
         caption_entities: List["types.MessageEntity"] = None,
         reply_markup: "types.InlineKeyboardMarkup" = None,
         input_message_content: "types.InputMessageContent" = None
diff --git a/pyrogram/types/input_media/input_media_animation.py b/pyrogram/types/input_media/input_media_animation.py
index 76c146c510..04aa940edc 100644
--- a/pyrogram/types/input_media/input_media_animation.py
+++ b/pyrogram/types/input_media/input_media_animation.py
@@ -20,6 +20,7 @@
 
 from .input_media import InputMedia
 from ..messages_and_media import MessageEntity
+from ... import enums
 
 
 class InputMediaAnimation(InputMedia):
@@ -43,12 +44,9 @@ class InputMediaAnimation(InputMedia):
             Caption of the animation to be sent, 0-1024 characters.
             If not specified, the original caption is kept. Pass "" (empty string) to remove the caption.
 
-        parse_mode (``str``, *optional*):
+        parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
             By default, texts are parsed using both Markdown and HTML styles.
             You can combine both syntaxes together.
-            Pass "markdown" or "md" to enable Markdown-style parsing only.
-            Pass "html" to enable HTML-style parsing only.
-            Pass None to completely disable style parsing.
 
         caption_entities (List of :obj:`~pyrogram.types.MessageEntity`):
             List of special entities that appear in the caption, which can be specified instead of *parse_mode*.
@@ -68,7 +66,7 @@ def __init__(
         media: Union[str, BinaryIO],
         thumb: str = None,
         caption: str = "",
-        parse_mode: Optional[str] = object,
+        parse_mode: Optional["enums.ParseMode"] = None,
         caption_entities: List[MessageEntity] = None,
         width: int = 0,
         height: int = 0,
diff --git a/pyrogram/types/input_media/input_media_audio.py b/pyrogram/types/input_media/input_media_audio.py
index 4816659d5a..b4bb7575be 100644
--- a/pyrogram/types/input_media/input_media_audio.py
+++ b/pyrogram/types/input_media/input_media_audio.py
@@ -20,6 +20,7 @@
 
 from .input_media import InputMedia
 from ..messages_and_media import MessageEntity
+from ... import enums
 
 
 class InputMediaAudio(InputMedia):
@@ -45,12 +46,9 @@ class InputMediaAudio(InputMedia):
             Caption of the audio to be sent, 0-1024 characters.
             If not specified, the original caption is kept. Pass "" (empty string) to remove the caption.
 
-        parse_mode (``str``, *optional*):
+        parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
             By default, texts are parsed using both Markdown and HTML styles.
             You can combine both syntaxes together.
-            Pass "markdown" or "md" to enable Markdown-style parsing only.
-            Pass "html" to enable HTML-style parsing only.
-            Pass None to completely disable style parsing.
 
         caption_entities (List of :obj:`~pyrogram.types.MessageEntity`):
             List of special entities that appear in the caption, which can be specified instead of *parse_mode*.
@@ -70,7 +68,7 @@ def __init__(
         media: Union[str, BinaryIO],
         thumb: str = None,
         caption: str = "",
-        parse_mode: Optional[str] = object,
+        parse_mode: Optional["enums.ParseMode"] = None,
         caption_entities: List[MessageEntity] = None,
         duration: int = 0,
         performer: str = "",
diff --git a/pyrogram/types/input_media/input_media_document.py b/pyrogram/types/input_media/input_media_document.py
index 3b3a38fdf6..91dfc7d673 100644
--- a/pyrogram/types/input_media/input_media_document.py
+++ b/pyrogram/types/input_media/input_media_document.py
@@ -20,6 +20,7 @@
 
 from .input_media import InputMedia
 from ..messages_and_media import MessageEntity
+from ... import enums
 
 
 class InputMediaDocument(InputMedia):
@@ -43,12 +44,9 @@ class InputMediaDocument(InputMedia):
             Caption of the document to be sent, 0-1024 characters.
             If not specified, the original caption is kept. Pass "" (empty string) to remove the caption.
 
-        parse_mode (``str``, *optional*):
+        parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
             By default, texts are parsed using both Markdown and HTML styles.
             You can combine both syntaxes together.
-            Pass "markdown" or "md" to enable Markdown-style parsing only.
-            Pass "html" to enable HTML-style parsing only.
-            Pass None to completely disable style parsing.
 
         caption_entities (List of :obj:`~pyrogram.types.MessageEntity`):
             List of special entities that appear in the caption, which can be specified instead of *parse_mode*.
@@ -59,7 +57,7 @@ def __init__(
         media: Union[str, BinaryIO],
         thumb: str = None,
         caption: str = "",
-        parse_mode: Optional[str] = object,
+        parse_mode: Optional["enums.ParseMode"] = None,
         caption_entities: List[MessageEntity] = None
     ):
         super().__init__(media, caption, parse_mode, caption_entities)
diff --git a/pyrogram/types/input_media/input_media_photo.py b/pyrogram/types/input_media/input_media_photo.py
index f733a7b0bc..ce8b41a218 100644
--- a/pyrogram/types/input_media/input_media_photo.py
+++ b/pyrogram/types/input_media/input_media_photo.py
@@ -20,6 +20,7 @@
 
 from .input_media import InputMedia
 from ..messages_and_media import MessageEntity
+from ... import enums
 
 
 class InputMediaPhoto(InputMedia):
@@ -38,12 +39,9 @@ class InputMediaPhoto(InputMedia):
             Caption of the photo to be sent, 0-1024 characters.
             If not specified, the original caption is kept. Pass "" (empty string) to remove the caption.
 
-        parse_mode (``str``, *optional*):
+        parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
             By default, texts are parsed using both Markdown and HTML styles.
             You can combine both syntaxes together.
-            Pass "markdown" or "md" to enable Markdown-style parsing only.
-            Pass "html" to enable HTML-style parsing only.
-            Pass None to completely disable style parsing.
 
         caption_entities (List of :obj:`~pyrogram.types.MessageEntity`):
             List of special entities that appear in the caption, which can be specified instead of *parse_mode*.
@@ -53,7 +51,7 @@ def __init__(
         self,
         media: Union[str, BinaryIO],
         caption: str = "",
-        parse_mode: Optional[str] = object,
+        parse_mode: Optional["enums.ParseMode"] = None,
         caption_entities: List[MessageEntity] = None
     ):
         super().__init__(media, caption, parse_mode, caption_entities)
diff --git a/pyrogram/types/input_media/input_media_video.py b/pyrogram/types/input_media/input_media_video.py
index 48c79f411d..bad4e3ef3a 100644
--- a/pyrogram/types/input_media/input_media_video.py
+++ b/pyrogram/types/input_media/input_media_video.py
@@ -20,6 +20,7 @@
 
 from .input_media import InputMedia
 from ..messages_and_media import MessageEntity
+from ... import enums
 
 
 class InputMediaVideo(InputMedia):
@@ -44,12 +45,9 @@ class InputMediaVideo(InputMedia):
             Caption of the video to be sent, 0-1024 characters.
             If not specified, the original caption is kept. Pass "" (empty string) to remove the caption.
 
-        parse_mode (``str``, *optional*):
+        parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
             By default, texts are parsed using both Markdown and HTML styles.
             You can combine both syntaxes together.
-            Pass "markdown" or "md" to enable Markdown-style parsing only.
-            Pass "html" to enable HTML-style parsing only.
-            Pass None to completely disable style parsing.
 
         caption_entities (List of :obj:`~pyrogram.types.MessageEntity`):
             List of special entities that appear in the caption, which can be specified instead of *parse_mode*.
@@ -72,7 +70,7 @@ def __init__(
         media: Union[str, BinaryIO],
         thumb: str = None,
         caption: str = "",
-        parse_mode: Optional[str] = object,
+        parse_mode: Optional["enums.ParseMode"] = None,
         caption_entities: List[MessageEntity] = None,
         width: int = 0,
         height: int = 0,
diff --git a/pyrogram/types/input_message_content/input_text_message_content.py b/pyrogram/types/input_message_content/input_text_message_content.py
index ef54c0b23a..7c88f996ba 100644
--- a/pyrogram/types/input_message_content/input_text_message_content.py
+++ b/pyrogram/types/input_message_content/input_text_message_content.py
@@ -19,7 +19,7 @@
 from typing import Optional, List
 
 import pyrogram
-from pyrogram import raw, types, utils
+from pyrogram import raw, types, utils, enums
 from .input_message_content import InputMessageContent
 
 
@@ -30,12 +30,9 @@ class InputTextMessageContent(InputMessageContent):
         message_text (``str``):
             Text of the message to be sent, 1-4096 characters.
 
-        parse_mode (``str``, *optional*):
+        parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
             By default, texts are parsed using both Markdown and HTML styles.
             You can combine both syntaxes together.
-            Pass "markdown" or "md" to enable Markdown-style parsing only.
-            Pass "html" to enable HTML-style parsing only.
-            Pass None to completely disable style parsing.
 
         entities (List of :obj:`~pyrogram.types.MessageEntity`):
             List of special entities that appear in message text, which can be specified instead of *parse_mode*.
@@ -47,7 +44,7 @@ class InputTextMessageContent(InputMessageContent):
     def __init__(
         self,
         message_text: str,
-        parse_mode: Optional[str] = object,
+        parse_mode: Optional["enums.ParseMode"] = None,
         entities: List["types.MessageEntity"] = None,
         disable_web_page_preview: bool = None
     ):
diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py
index e6aa25268a..2c2f7e053d 100644
--- a/pyrogram/types/messages_and_media/message.py
+++ b/pyrogram/types/messages_and_media/message.py
@@ -21,7 +21,7 @@
 from typing import List, Match, Union, BinaryIO, Optional
 
 import pyrogram
-from pyrogram import raw
+from pyrogram import raw, enums
 from pyrogram import types
 from pyrogram import utils
 from pyrogram.errors import MessageIdsEmpty, PeerIdInvalid
@@ -112,17 +112,15 @@ class Message(Object, Update):
             The message is empty.
             A message can be empty in case it was deleted or you tried to retrieve a message that doesn't exist yet.
 
-        service (``str``, *optional*):
-            The message is a service message. This field will contain the name of the service message.
-            A service message has one and only one of these fields set: new_chat_members, left_chat_member,
-            new_chat_title, new_chat_photo, delete_chat_photo, group_chat_created, channel_chat_created,
-            migrate_to_chat_id, migrate_from_chat_id, pinned_message, game_high_score, voice_chat_started,
-            voice_chat_ended, voice_chat_scheduled, voice_chat_members_invited.
+        service (:obj:`~pyrogram.enums.MessageService`, *optional*):
+            The message is a service message.
+            This field will contain the enumeration type of the service message.
+            You can use ``service = getattr(message, message.service.value)`` to access the service message.
 
-        media (``str``, *optional*):
-            The message is a media message. This field will contain the name of the media message.
-            A media message has one and only one of these fields set: audio, document, photo, sticker, video, animation,
-            voice, video_note, contact, location, venue, poll, web_page, dice, game.
+        media (:obj:`~pyrogram.enums.MessageMedia`, *optional*):
+            The message is a media message.
+            This field will contain the enumeration type of the media message.
+            You can use ``media = getattr(message, message.media.value)`` to access the media message.
 
         edit_date (``int``, *optional*):
             Date the message was last edited in Unix time.
@@ -319,7 +317,7 @@ def __init__(
         reply_to_message: "Message" = None,
         mentioned: bool = None,
         empty: bool = None,
-        service: str = None,
+        service: "enums.MessageService" = None,
         scheduled: bool = None,
         from_scheduled: bool = None,
         media: str = None,
@@ -497,47 +495,47 @@ async def _parse(
 
             if isinstance(action, raw.types.MessageActionChatAddUser):
                 new_chat_members = [types.User._parse(client, users[i]) for i in action.users]
-                service_type = "new_chat_members"
+                service_type = enums.MessageService.NEW_CHAT_MEMBERS
             elif isinstance(action, raw.types.MessageActionChatJoinedByLink):
                 new_chat_members = [types.User._parse(client, users[utils.get_raw_peer_id(message.from_id)])]
-                service_type = "new_chat_members"
+                service_type = enums.MessageService.NEW_CHAT_MEMBERS
             elif isinstance(action, raw.types.MessageActionChatDeleteUser):
                 left_chat_member = types.User._parse(client, users[action.user_id])
-                service_type = "left_chat_member"
+                service_type = enums.MessageService.LEFT_CHAT_MEMBERS
             elif isinstance(action, raw.types.MessageActionChatEditTitle):
                 new_chat_title = action.title
-                service_type = "new_chat_title"
+                service_type = enums.MessageService.NEW_CHAT_TITLE
             elif isinstance(action, raw.types.MessageActionChatDeletePhoto):
                 delete_chat_photo = True
-                service_type = "delete_chat_photo"
+                service_type = enums.MessageService.DELETE_CHAT_PHOTO
             elif isinstance(action, raw.types.MessageActionChatMigrateTo):
                 migrate_to_chat_id = action.channel_id
-                service_type = "migrate_to_chat_id"
+                service_type = enums.MessageService.MIGRATE_TO_CHAT_ID
             elif isinstance(action, raw.types.MessageActionChannelMigrateFrom):
                 migrate_from_chat_id = action.chat_id
-                service_type = "migrate_from_chat_id"
+                service_type = enums.MessageService.MIGRATE_FROM_CHAT_ID
             elif isinstance(action, raw.types.MessageActionChatCreate):
                 group_chat_created = True
-                service_type = "group_chat_created"
+                service_type = enums.MessageService.GROUP_CHAT_CREATED
             elif isinstance(action, raw.types.MessageActionChannelCreate):
                 channel_chat_created = True
-                service_type = "channel_chat_created"
+                service_type = enums.MessageService.CHANNEL_CHAT_CREATED
             elif isinstance(action, raw.types.MessageActionChatEditPhoto):
                 new_chat_photo = types.Photo._parse(client, action.photo)
-                service_type = "new_chat_photo"
+                service_type = enums.MessageService.NEW_CHAT_PHOTO
             elif isinstance(action, raw.types.MessageActionGroupCallScheduled):
                 voice_chat_scheduled = types.VoiceChatScheduled._parse(action)
-                service_type = "voice_chat_scheduled"
+                service_type = enums.MessageService.VOICE_CHAT_SCHEDULED
             elif isinstance(action, raw.types.MessageActionGroupCall):
                 if action.duration:
                     voice_chat_ended = types.VoiceChatEnded._parse(action)
-                    service_type = "voice_chat_ended"
+                    service_type = enums.MessageService.VOICE_CHAT_ENDED
                 else:
                     voice_chat_started = types.VoiceChatStarted()
-                    service_type = "voice_chat_started"
+                    service_type = enums.MessageService.VOICE_CHAT_STARTED
             elif isinstance(action, raw.types.MessageActionInviteToGroupCall):
                 voice_chat_members_invited = types.VoiceChatMembersInvited._parse(client, action, users)
-                service_type = "voice_chat_members_invited"
+                service_type = enums.MessageService.VOICE_CHAT_MEMBERS_INVITED
 
             from_user = types.User._parse(client, users.get(user_id, None))
             sender_chat = types.Chat._parse(client, message, users, chats, is_chat=False) if not from_user else None
@@ -574,7 +572,7 @@ async def _parse(
                         replies=0
                     )
 
-                    parsed_message.service = "pinned_message"
+                    parsed_message.service = enums.MessageService.PINNED_MESSAGE
                 except MessageIdsEmpty:
                     pass
 
@@ -589,7 +587,7 @@ async def _parse(
                             replies=0
                         )
 
-                        parsed_message.service = "game_high_score"
+                        parsed_message.service = enums.MessageService.GAME_HIGH_SCORE
                     except MessageIdsEmpty:
                         pass
 
@@ -646,19 +644,19 @@ async def _parse(
             if media:
                 if isinstance(media, raw.types.MessageMediaPhoto):
                     photo = types.Photo._parse(client, media.photo, media.ttl_seconds)
-                    media_type = "photo"
+                    media_type = enums.MessageMedia.PHOTO
                 elif isinstance(media, raw.types.MessageMediaGeo):
                     location = types.Location._parse(client, media.geo)
-                    media_type = "location"
+                    media_type = enums.MessageMedia.LOCATION
                 elif isinstance(media, raw.types.MessageMediaContact):
                     contact = types.Contact._parse(client, media)
-                    media_type = "contact"
+                    media_type = enums.MessageMedia.CONTACT
                 elif isinstance(media, raw.types.MessageMediaVenue):
                     venue = types.Venue._parse(client, media)
-                    media_type = "venue"
+                    media_type = enums.MessageMedia.VENUE
                 elif isinstance(media, raw.types.MessageMediaGame):
                     game = types.Game._parse(client, message)
-                    media_type = "game"
+                    media_type = enums.MessageMedia.GAME
                 elif isinstance(media, raw.types.MessageMediaDocument):
                     doc = media.document
 
@@ -676,14 +674,14 @@ async def _parse(
 
                             if audio_attributes.voice:
                                 voice = types.Voice._parse(client, doc, audio_attributes)
-                                media_type = "voice"
+                                media_type = enums.MessageMedia.VOICE
                             else:
                                 audio = types.Audio._parse(client, doc, audio_attributes, file_name)
-                                media_type = "audio"
+                                media_type = enums.MessageMedia.AUDIO
                         elif raw.types.DocumentAttributeAnimated in attributes:
                             video_attributes = attributes.get(raw.types.DocumentAttributeVideo, None)
                             animation = types.Animation._parse(client, doc, video_attributes, file_name)
-                            media_type = "animation"
+                            media_type = enums.MessageMedia.ANIMATION
                         elif raw.types.DocumentAttributeSticker in attributes:
                             sticker = await types.Sticker._parse(
                                 client, doc,
@@ -691,31 +689,31 @@ async def _parse(
                                 attributes[raw.types.DocumentAttributeSticker],
                                 file_name
                             )
-                            media_type = "sticker"
+                            media_type = enums.MessageMedia.STICKER
                         elif raw.types.DocumentAttributeVideo in attributes:
                             video_attributes = attributes[raw.types.DocumentAttributeVideo]
 
                             if video_attributes.round_message:
                                 video_note = types.VideoNote._parse(client, doc, video_attributes)
-                                media_type = "video_note"
+                                media_type = enums.MessageMedia.VIDEO_NOTE
                             else:
                                 video = types.Video._parse(client, doc, video_attributes, file_name, media.ttl_seconds)
-                                media_type = "video"
+                                media_type = enums.MessageMedia.VIDEO
                         else:
                             document = types.Document._parse(client, doc, file_name)
-                            media_type = "document"
+                            media_type = enums.MessageMedia.DOCUMENT
                 elif isinstance(media, raw.types.MessageMediaWebPage):
                     if isinstance(media.webpage, raw.types.WebPage):
                         web_page = types.WebPage._parse(client, media.webpage)
-                        media_type = "web_page"
+                        media_type = enums.MessageMedia.WEB_PAGE
                     else:
                         media = None
                 elif isinstance(media, raw.types.MessageMediaPoll):
                     poll = types.Poll._parse(client, media)
-                    media_type = "poll"
+                    media_type = enums.MessageMedia.POLL
                 elif isinstance(media, raw.types.MessageMediaDice):
                     dice = types.Dice._parse(client, media)
-                    media_type = "dice"
+                    media_type = enums.MessageMedia.DICE
                 else:
                     media = None
 
@@ -820,7 +818,10 @@ async def _parse(
 
     @property
     def link(self) -> str:
-        if self.chat.type in ("group", "supergroup", "channel") and self.chat.username:
+        if (
+            self.chat.type in (enums.ChatType.GROUP, enums.ChatType.SUPERGROUP, enums.ChatType.CHANNEL)
+            and self.chat.username
+        ):
             return f"https://t.me/{self.chat.username}/{self.message_id}"
         else:
             return f"https://t.me/c/{utils.get_channel_id(self.chat.id)}/{self.message_id}"
@@ -858,7 +859,7 @@ async def reply_text(
         self,
         text: str,
         quote: bool = None,
-        parse_mode: Optional[str] = object,
+        parse_mode: Optional["enums.ParseMode"] = None,
         entities: List["types.MessageEntity"] = None,
         disable_web_page_preview: bool = None,
         disable_notification: bool = None,
@@ -895,12 +896,9 @@ async def reply_text(
                 If *reply_to_message_id* is passed, this parameter will be ignored.
                 Defaults to ``True`` in group chats and ``False`` in private chats.
 
-            parse_mode (``str``, *optional*):
+            parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
                 By default, texts are parsed using both Markdown and HTML styles.
                 You can combine both syntaxes together.
-                Pass "markdown" or "md" to enable Markdown-style parsing only.
-                Pass "html" to enable HTML-style parsing only.
-                Pass None to completely disable style parsing.
 
             entities (List of :obj:`~pyrogram.types.MessageEntity`):
                 List of special entities that appear in message text, which can be specified instead of *parse_mode*.
@@ -957,7 +955,7 @@ async def reply_animation(
         animation: Union[str, BinaryIO],
         quote: bool = None,
         caption: str = "",
-        parse_mode: Optional[str] = object,
+        parse_mode: Optional["enums.ParseMode"] = None,
         caption_entities: List["types.MessageEntity"] = None,
         duration: int = 0,
         width: int = 0,
@@ -1005,12 +1003,9 @@ async def reply_animation(
             caption (``str``, *optional*):
                 Animation caption, 0-1024 characters.
 
-            parse_mode (``str``, *optional*):
+            parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
                 By default, texts are parsed using both Markdown and HTML styles.
                 You can combine both syntaxes together.
-                Pass "markdown" or "md" to enable Markdown-style parsing only.
-                Pass "html" to enable HTML-style parsing only.
-                Pass None to completely disable style parsing.
 
             caption_entities (List of :obj:`~pyrogram.types.MessageEntity`):
                 List of special entities that appear in the caption, which can be specified instead of *parse_mode*.
@@ -1099,7 +1094,7 @@ async def reply_audio(
         audio: Union[str, BinaryIO],
         quote: bool = None,
         caption: str = "",
-        parse_mode: Optional[str] = object,
+        parse_mode: Optional["enums.ParseMode"] = None,
         caption_entities: List["types.MessageEntity"] = None,
         duration: int = 0,
         performer: str = None,
@@ -1147,12 +1142,9 @@ async def reply_audio(
             caption (``str``, *optional*):
                 Audio caption, 0-1024 characters.
 
-            parse_mode (``str``, *optional*):
+            parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
                 By default, texts are parsed using both Markdown and HTML styles.
                 You can combine both syntaxes together.
-                Pass "markdown" or "md" to enable Markdown-style parsing only.
-                Pass "html" to enable HTML-style parsing only.
-                Pass None to completely disable style parsing.
 
             caption_entities (List of :obj:`~pyrogram.types.MessageEntity`):
                 List of special entities that appear in the caption, which can be specified instead of *parse_mode*.
@@ -1241,7 +1233,7 @@ async def reply_cached_media(
         file_id: str,
         quote: bool = None,
         caption: str = "",
-        parse_mode: Optional[str] = object,
+        parse_mode: Optional["enums.ParseMode"] = None,
         caption_entities: List["types.MessageEntity"] = None,
         disable_notification: bool = None,
         reply_to_message_id: int = None,
@@ -1281,12 +1273,9 @@ async def reply_cached_media(
             caption (``bool``, *optional*):
                 Media caption, 0-1024 characters.
 
-            parse_mode (``str``, *optional*):
+            parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
                 By default, texts are parsed using both Markdown and HTML styles.
                 You can combine both syntaxes together.
-                Pass "markdown" or "md" to enable Markdown-style parsing only.
-                Pass "html" to enable HTML-style parsing only.
-                Pass None to completely disable style parsing.
 
             caption_entities (List of :obj:`~pyrogram.types.MessageEntity`):
                 List of special entities that appear in the caption, which can be specified instead of *parse_mode*.
@@ -1454,7 +1443,7 @@ async def reply_document(
         quote: bool = None,
         thumb: str = None,
         caption: str = "",
-        parse_mode: Optional[str] = object,
+        parse_mode: Optional["enums.ParseMode"] = None,
         caption_entities: List["types.MessageEntity"] = None,
         file_name: str = None,
         force_document: bool = None,
@@ -1507,12 +1496,9 @@ async def reply_document(
             caption (``str``, *optional*):
                 Document caption, 0-1024 characters.
 
-            parse_mode (``str``, *optional*):
+            parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
                 By default, texts are parsed using both Markdown and HTML styles.
                 You can combine both syntaxes together.
-                Pass "markdown" or "md" to enable Markdown-style parsing only.
-                Pass "html" to enable HTML-style parsing only.
-                Pass None to completely disable style parsing.
 
             caption_entities (List of :obj:`~pyrogram.types.MessageEntity`):
                 List of special entities that appear in the caption, which can be specified instead of *parse_mode*.
@@ -1867,7 +1853,7 @@ async def reply_photo(
         photo: Union[str, BinaryIO],
         quote: bool = None,
         caption: str = "",
-        parse_mode: Optional[str] = object,
+        parse_mode: Optional["enums.ParseMode"] = None,
         caption_entities: List["types.MessageEntity"] = None,
         ttl_seconds: int = None,
         disable_notification: bool = None,
@@ -1912,12 +1898,9 @@ async def reply_photo(
             caption (``str``, *optional*):
                 Photo caption, 0-1024 characters.
 
-            parse_mode (``str``, *optional*):
+            parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
                 By default, texts are parsed using both Markdown and HTML styles.
                 You can combine both syntaxes together.
-                Pass "markdown" or "md" to enable Markdown-style parsing only.
-                Pass "html" to enable HTML-style parsing only.
-                Pass None to completely disable style parsing.
 
             caption_entities (List of :obj:`~pyrogram.types.MessageEntity`):
                 List of special entities that appear in the caption, which can be specified instead of *parse_mode*.
@@ -2295,7 +2278,7 @@ async def reply_video(
         video: Union[str, BinaryIO],
         quote: bool = None,
         caption: str = "",
-        parse_mode: Optional[str] = object,
+        parse_mode: Optional["enums.ParseMode"] = None,
         caption_entities: List["types.MessageEntity"] = None,
         ttl_seconds: int = None,
         duration: int = 0,
@@ -2345,12 +2328,9 @@ async def reply_video(
             caption (``str``, *optional*):
                 Video caption, 0-1024 characters.
 
-            parse_mode (``str``, *optional*):
+            parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
                 By default, texts are parsed using both Markdown and HTML styles.
                 You can combine both syntaxes together.
-                Pass "markdown" or "md" to enable Markdown-style parsing only.
-                Pass "html" to enable HTML-style parsing only.
-                Pass None to completely disable style parsing.
 
             caption_entities (List of :obj:`~pyrogram.types.MessageEntity`):
                 List of special entities that appear in the caption, which can be specified instead of *parse_mode*.
@@ -2567,7 +2547,7 @@ async def reply_voice(
         voice: Union[str, BinaryIO],
         quote: bool = None,
         caption: str = "",
-        parse_mode: Optional[str] = object,
+        parse_mode: Optional["enums.ParseMode"] = None,
         caption_entities: List["types.MessageEntity"] = None,
         duration: int = 0,
         disable_notification: bool = None,
@@ -2612,12 +2592,9 @@ async def reply_voice(
             caption (``str``, *optional*):
                 Voice message caption, 0-1024 characters.
 
-            parse_mode (``str``, *optional*):
+            parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
                 By default, texts are parsed using both Markdown and HTML styles.
                 You can combine both syntaxes together.
-                Pass "markdown" or "md" to enable Markdown-style parsing only.
-                Pass "html" to enable HTML-style parsing only.
-                Pass None to completely disable style parsing.
 
             caption_entities (List of :obj:`~pyrogram.types.MessageEntity`):
                 List of special entities that appear in the caption, which can be specified instead of *parse_mode*.
@@ -2689,7 +2666,7 @@ async def reply_voice(
     async def edit_text(
         self,
         text: str,
-        parse_mode: Optional[str] = object,
+        parse_mode: Optional["enums.ParseMode"] = None,
         entities: List["types.MessageEntity"] = None,
         disable_web_page_preview: bool = None,
         reply_markup: "types.InlineKeyboardMarkup" = None
@@ -2717,12 +2694,9 @@ async def edit_text(
             text (``str``):
                 New text of the message.
 
-            parse_mode (``str``, *optional*):
+            parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
                 By default, texts are parsed using both Markdown and HTML styles.
                 You can combine both syntaxes together.
-                Pass "markdown" or "md" to enable Markdown-style parsing only.
-                Pass "html" to enable HTML-style parsing only.
-                Pass None to completely disable style parsing.
 
             entities (List of :obj:`~pyrogram.types.MessageEntity`):
                 List of special entities that appear in message text, which can be specified instead of *parse_mode*.
@@ -2754,7 +2728,7 @@ async def edit_text(
     async def edit_caption(
         self,
         caption: str,
-        parse_mode: Optional[str] = object,
+        parse_mode: Optional["enums.ParseMode"] = None,
         caption_entities: List["types.MessageEntity"] = None,
         reply_markup: "types.InlineKeyboardMarkup" = None
     ) -> "Message":
@@ -2779,12 +2753,9 @@ async def edit_caption(
             caption (``str``):
                 New caption of the message.
 
-            parse_mode (``str``, *optional*):
+            parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
                 By default, texts are parsed using both Markdown and HTML styles.
                 You can combine both syntaxes together.
-                Pass "markdown" or "md" to enable Markdown-style parsing only.
-                Pass "html" to enable HTML-style parsing only.
-                Pass None to completely disable style parsing.
 
             caption_entities (List of :obj:`~pyrogram.types.MessageEntity`):
                 List of special entities that appear in the caption, which can be specified instead of *parse_mode*.
@@ -2938,7 +2909,7 @@ async def copy(
         self,
         chat_id: Union[int, str],
         caption: str = None,
-        parse_mode: Optional[str] = object,
+        parse_mode: Optional["enums.ParseMode"] = None,
         caption_entities: List["types.MessageEntity"] = None,
         disable_notification: bool = None,
         reply_to_message_id: int = None,
@@ -2979,12 +2950,9 @@ async def copy(
                 If not specified, the original caption is kept.
                 Pass "" (empty string) to remove the caption.
 
-            parse_mode (``str``, *optional*):
+            parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
                 By default, texts are parsed using both Markdown and HTML styles.
                 You can combine both syntaxes together.
-                Pass "markdown" or "md" to enable Markdown-style parsing only.
-                Pass "html" to enable HTML-style parsing only.
-                Pass None to completely disable style parsing.
 
             caption_entities (List of :obj:`~pyrogram.types.MessageEntity`):
                 List of special entities that appear in the new caption, which can be specified instead of *parse_mode*.
diff --git a/pyrogram/types/messages_and_media/message_entity.py b/pyrogram/types/messages_and_media/message_entity.py
index 2595fc66bc..dbd6a98264 100644
--- a/pyrogram/types/messages_and_media/message_entity.py
+++ b/pyrogram/types/messages_and_media/message_entity.py
@@ -16,87 +16,22 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-from enum import Enum, auto
 from typing import Optional
 
 import pyrogram
-from pyrogram import raw
+from pyrogram import raw, enums
 from pyrogram import types
 from ..object import Object
 
 
-class AutoName(Enum):
-    def _generate_next_value_(self, *args):
-        return self.lower()
-
-
-class MessageEntityType(AutoName):
-    MENTION = auto()
-    HASHTAG = auto()
-    CASHTAG = auto()
-    BOT_COMMAND = auto()
-    URL = auto()
-    EMAIL = auto()
-    PHONE_NUMBER = auto()
-    BOLD = auto()
-    ITALIC = auto()
-    UNDERLINE = auto()
-    STRIKETHROUGH = auto()
-    SPOILER = auto()
-    CODE = auto()
-    PRE = auto()
-    TEXT_LINK = auto()
-    TEXT_MENTION = auto()
-    BLOCKQUOTE = auto()
-
-
-RAW_ENTITIES_TO_TYPE = {
-    raw.types.MessageEntityMention: MessageEntityType.MENTION,
-    raw.types.MessageEntityHashtag: MessageEntityType.HASHTAG,
-    raw.types.MessageEntityCashtag: MessageEntityType.CASHTAG,
-    raw.types.MessageEntityBotCommand: MessageEntityType.BOT_COMMAND,
-    raw.types.MessageEntityUrl: MessageEntityType.URL,
-    raw.types.MessageEntityEmail: MessageEntityType.EMAIL,
-    raw.types.MessageEntityBold: MessageEntityType.BOLD,
-    raw.types.MessageEntityItalic: MessageEntityType.ITALIC,
-    raw.types.MessageEntityCode: MessageEntityType.CODE,
-    raw.types.MessageEntityPre: MessageEntityType.PRE,
-    raw.types.MessageEntityUnderline: MessageEntityType.UNDERLINE,
-    raw.types.MessageEntityStrike: MessageEntityType.STRIKETHROUGH,
-    raw.types.MessageEntitySpoiler: MessageEntityType.SPOILER,
-    raw.types.MessageEntityBlockquote: MessageEntityType.BLOCKQUOTE,
-    raw.types.MessageEntityTextUrl: MessageEntityType.TEXT_LINK,
-    raw.types.MessageEntityMentionName: MessageEntityType.TEXT_MENTION,
-    raw.types.MessageEntityPhone: MessageEntityType.PHONE_NUMBER
-}
-
-TYPE_TO_RAW_ENTITIES = {v.value: k for k, v in RAW_ENTITIES_TO_TYPE.items()}
-
-
 class MessageEntity(Object):
     """One special entity in a text message.
+    
     For example, hashtags, usernames, URLs, etc.
 
     Parameters:
-        type (``str``):
-            Type of the entity. Can be:
-
-            - "mention": ``@username``.
-            - "hashtag": ``#hashtag``.
-            - "cashtag": ``$PYRO``.
-            - "bot_command": ``/start@pyrogrambot``.
-            - "url": ``https://pyrogram.org`` (see *url* below).
-            - "email": ``do-not-reply@pyrogram.org``.
-            - "phone_number": ``+1-123-456-7890``.
-            - "bold": **bold text**.
-            - "italic": *italic text*.
-            - "underline": underlined text.
-            - "strikethrough": strikethrough text.
-            - "spoiler": spoiler text.
-            - "code": monowidth string.
-            - "pre": monowidth block (see *language* below).
-            - "text_link": for clickable text URLs.
-            - "text_mention": for users without usernames (see *user* below).
+        type (:obj:`~pyrogram.enums.MessageEntityType`):
+            Type of the entity.
 
         offset (``int``):
             Offset in UTF-16 code units to the start of the entity.
@@ -118,7 +53,7 @@ def __init__(
         self,
         *,
         client: "pyrogram.Client" = None,
-        type: str,
+        type: "enums.MessageEntityType",
         offset: int,
         length: int,
         url: str = None,
@@ -135,14 +70,9 @@ def __init__(
         self.language = language
 
     @staticmethod
-    def _parse(client, entity, users: dict) -> Optional["MessageEntity"]:
-        type = RAW_ENTITIES_TO_TYPE.get(entity.__class__, None)
-
-        if type is None:
-            return None
-
+    def _parse(client, entity: "raw.base.MessageEntity", users: dict) -> Optional["MessageEntity"]:
         return MessageEntity(
-            type=type.value,
+            type=enums.MessageEntityType(entity.__class__),
             offset=entity.offset,
             length=entity.length,
             url=getattr(entity, "url", None),
@@ -166,15 +96,9 @@ async def write(self):
         if self.language is None:
             args.pop("language")
 
-        try:
-            entity = TYPE_TO_RAW_ENTITIES[self.type]
-
-            if entity is raw.types.MessageEntityMentionName:
-                entity = raw.types.InputMessageEntityMentionName
-        except KeyError as e:
-            raise ValueError(f"Invalid message entity type {e}")
-        else:
-            try:
-                return entity(**args)
-            except TypeError as e:
-                raise TypeError(f"{entity.QUALNAME}'s {e}")
+        entity = self.type.value
+
+        if entity is raw.types.MessageEntityMentionName:
+            entity = raw.types.InputMessageEntityMentionName
+
+        return entity(**args)
diff --git a/pyrogram/types/messages_and_media/poll.py b/pyrogram/types/messages_and_media/poll.py
index 8d46557d65..9d4c5118cb 100644
--- a/pyrogram/types/messages_and_media/poll.py
+++ b/pyrogram/types/messages_and_media/poll.py
@@ -19,7 +19,7 @@
 from typing import List, Union
 
 import pyrogram
-from pyrogram import raw
+from pyrogram import raw, enums
 from pyrogram import types
 from ..object import Object
 from ..update import Update
@@ -47,8 +47,8 @@ class Poll(Object, Update):
         is_anonymous (``bool``, *optional*):
             True, if the poll is anonymous
 
-        type (``str``, *optional*):
-            Poll type, currently can be "regular" or "quiz".
+        type (:obj:`~pyrogram.enums.PollType`, *optional*):
+            Poll type.
 
         allows_multiple_answers (``bool``, *optional*):
             True, if the poll allows multiple answers.
@@ -67,7 +67,7 @@ def __init__(
         total_voter_count: int,
         is_closed: bool,
         is_anonymous: bool = None,
-        type: str = None,
+        type: "enums.PollType" = None,
         allows_multiple_answers: bool = None,
         # correct_option_id: int,
         chosen_option: int = None
@@ -118,7 +118,7 @@ def _parse(client, media_poll: Union["raw.types.MessageMediaPoll", "raw.types.Up
             total_voter_count=media_poll.results.total_voters,
             is_closed=poll.closed,
             is_anonymous=not poll.public_voters,
-            type="quiz" if poll.quiz else "regular",
+            type=enums.PollType.QUIZ if poll.quiz else enums.PollType.REGULAR,
             allows_multiple_answers=poll.multiple_choice,
             chosen_option=chosen_option,
             client=client
diff --git a/pyrogram/types/object.py b/pyrogram/types/object.py
index 015eeca89a..a0925779e2 100644
--- a/pyrogram/types/object.py
+++ b/pyrogram/types/object.py
@@ -18,6 +18,7 @@
 
 import typing
 from datetime import datetime
+from enum import Enum
 from json import dumps
 
 import pyrogram
@@ -52,6 +53,9 @@ def default(obj: "Object"):
         if isinstance(obj, typing.Match):
             return repr(obj)
 
+        if isinstance(obj, Enum):
+            return str(obj)
+
         return {
             "_": obj.__class__.__name__,
             **{
diff --git a/pyrogram/types/user_and_chats/chat.py b/pyrogram/types/user_and_chats/chat.py
index 7ca8f2085e..2282b1e073 100644
--- a/pyrogram/types/user_and_chats/chat.py
+++ b/pyrogram/types/user_and_chats/chat.py
@@ -19,7 +19,7 @@
 from typing import Union, List, Generator, Optional
 
 import pyrogram
-from pyrogram import raw
+from pyrogram import raw, enums
 from pyrogram import types
 from pyrogram import utils
 from ..object import Object
@@ -32,8 +32,8 @@ class Chat(Object):
         id (``int``):
             Unique identifier for this chat.
 
-        type (``str``):
-            Type of chat, can be either "private", "bot", "group", "supergroup" or "channel".
+        type (:obj:`~pyrogram.enums.ChatType`):
+            Type of chat.
 
         is_verified (``bool``, *optional*):
             True, if this chat has been verified by Telegram. Supergroups, channels and bots only.
@@ -135,7 +135,7 @@ def __init__(
         *,
         client: "pyrogram.Client" = None,
         id: int,
-        type: str,
+        type: "enums.ChatType",
         is_verified: bool = None,
         is_restricted: bool = None,
         is_creator: bool = None,
@@ -200,7 +200,7 @@ def _parse_user_chat(client, user: raw.types.User) -> "Chat":
 
         return Chat(
             id=peer_id,
-            type="bot" if user.bot else "private",
+            type=enums.ChatType.BOT if user.bot else enums.ChatType.PRIVATE,
             is_verified=getattr(user, "verified", None),
             is_restricted=getattr(user, "restricted", None),
             is_scam=getattr(user, "scam", None),
@@ -221,7 +221,7 @@ def _parse_chat_chat(client, chat: raw.types.Chat) -> "Chat":
 
         return Chat(
             id=peer_id,
-            type="group",
+            type=enums.ChatType.GROUP,
             title=chat.title,
             is_creator=getattr(chat, "creator", None),
             photo=types.ChatPhoto._parse(client, getattr(chat, "photo", None), peer_id, 0),
@@ -239,7 +239,7 @@ def _parse_channel_chat(client, channel: raw.types.Channel) -> "Chat":
 
         return Chat(
             id=peer_id,
-            type="supergroup" if getattr(channel, "megagroup", None) else "channel",
+            type=enums.ChatType.SUPERGROUP if getattr(channel, "megagroup", None) else enums.ChatType.CHANNEL,
             is_verified=getattr(channel, "verified", None),
             is_restricted=getattr(channel, "restricted", None),
             is_creator=getattr(channel, "creator", None),
@@ -842,7 +842,7 @@ async def get_members(
         offset: int = 0,
         limit: int = 200,
         query: str = "",
-        filter: str = "all"
+        filter: "enums.ChatMembersFilter" = enums.ChatMembersFilter.ANY
     ) -> List["types.ChatMember"]:
         """Bound method *get_members* of :obj:`~pyrogram.types.Chat`.
 
@@ -930,17 +930,9 @@ def iter_members(
                 Query string to filter members based on their display names and usernames.
                 Only applicable to supergroups and channels. Defaults to "" (empty string) [2]_.
 
-            filter (``str``, *optional*):
+            filter (:obj:`~pyrogram.enums.ChatMembersFilter`, *optional*):
                 Filter used to select the kind of members you want to retrieve. Only applicable for supergroups
-                and channels. It can be any of the followings:
-                *"all"* - all kind of members,
-                *"banned"* - banned members only,
-                *"restricted"* - restricted members only,
-                *"bots"* - bots only,
-                *"recent"* - recent members only,
-                *"administrators"* - chat administrators only.
-                Only applicable to supergroups and channels.
-                Defaults to *"recent"*.
+                and channels.
 
         .. [1] Server limit: on supergroups, you can get up to 10,000 members for a single query and up to 200 members
             on channels.
@@ -950,16 +942,18 @@ def iter_members(
         Example:
             .. code-block:: python
 
+                from pyrogram import enums
+
                 # Get first 200 recent members
                 for member in chat.get_members():
                     print(member.user.first_name)
 
                 # Get all administrators
-                for member in chat.iter_members(filter="administrators"):
+                for member in chat.iter_members(filter=enums.ChatMembersFilter.ADMINISTRATORS):
                     print(member.user.first_name)
 
                 # Get first 3 bots
-                for member in chat.iter_members(filter="bots", limit=3):
+                for member in chat.iter_members(filter=enums.ChatMembersFilter.BOTS, limit=3):
                     print(member.user.first_name)
 
         Returns:
diff --git a/pyrogram/types/user_and_chats/chat_event.py b/pyrogram/types/user_and_chats/chat_event.py
index 374dac50f9..3abfd1f9d6 100644
--- a/pyrogram/types/user_and_chats/chat_event.py
+++ b/pyrogram/types/user_and_chats/chat_event.py
@@ -70,6 +70,8 @@ class ChatEventAction(AutoName):
 class ChatEvent(Object):
     """A chat event from the recent actions log (also known as admin log).
 
+    See ``action`` to know which kind of event this is and the relative attributes to get the event content.
+
     Parameters:
         id (``int``):
             Chat event identifier.
@@ -77,171 +79,99 @@ class ChatEvent(Object):
         date (``int``):
             Date of the event. Unix time.
 
-        action (``str``):
-            Event action. Can be:
-
-            - "description_changed": the chat description has been changed
-              (see *old_description* and *new_description* below).
-
-            - "history_ttl_changed": the history time-to-live has been changed
-              (see *old_history_ttl* and *new_history_ttl* below).
-
-            - "linked_chat_changed": the linked chat has been changed
-              (see *old_linked_chat* and *new_linked_chat* below).
-
-            - "photo_changed": the chat photo has been changed
-              (see *old_photo* and *new_photo* below).
-
-            - "title_changed": the chat title has been changed
-              (see *old_title* and *new_title* below).
-
-            - "username_changed": the chat username has been changed
-              (see *old_username* and *new_username* below).
-
-            - "chat_permissions_changed": the default chat permissions has been changed
-              (see *old_chat_permissions* and *new_chat_permissions* below).
-
-            - "message_deleted": a message has been deleted
-              (see *deleted_message* below).
-
-            - "message_edited": a message has been edited
-              (see *old_message* and *new_message* below).
-
-            - "member_invited": a member has been invited by someone
-              (see *invited_member* below).
-
-            - "member_joined": a member joined by themselves.
-              (see *user* below)
-
-            - "member_left": a member left by themselves.
-              (see *user* below).
-
-            - "admin_rights_changed": a chat member has been promoted/demoted or their administrator rights has changed
-              (see *old_admin_rights* and *new_admin_rights* below).
-
-            - "member_permissions_changed": a chat member has been restricted/unrestricted or banned/unbanned, or their
-              permissions has changed (see *old_member_permissions* and *new_member_permissions* below).
-
-            - "poll_stopped": a poll has been stopped
-              (see *stopped_poll* below).
-
-            - "invites_enabled": the chat invitation has been enabled or disabled
-              (see *invites_enabled* below).
-
-            - "history_hidden": the chat history has been hidden or unhidden
-              (see *history_hidden* below).
-
-            - "signatures_enabled": the message signatures have been enabled or disabled
-              (see *signatures_enabled* below).
-
-            - "slow_mode_changed": the slow mode has been changes
-              (see *old_slow_mode* and *new_slow_mode* below).
-
-            - "message_pinned": a message has been pinned
-              (see *pinned_message* below).
-
-            - "message_unpinned": a message has been unpinned
-              (see *unpinned_message* below).
-
-            - "invite_link_edited": an invite link has been edited
-              (see *edited_invite_link* below).
-
-            - "invite_link_revoked": an invite link has been revoked
-              (see *revoked_invite_link* below).
-
-            - "invite_link_deleted": an invite link has been deleted
-              (see *deleted_invite_link* below).
+        action (:obj:`~pyrogram.enums.ChatEventAction`):
+            Event action.
 
         user (:obj:`~pyrogram.types.User`):
             User that triggered the event.
 
         old_description, new_description (``str``, *optional*):
             Previous and new chat description.
-            For "description_changed" only.
+            For :obj:`~pyrogram.enums.ChatEventAction.DESCRIPTION_CHANGED` action only.
 
         old_history_ttl, new_history_ttl (``int``, *optional*):
             Previous and new chat history TTL.
-            For "history_ttl_changed" only.
+            For :obj:`~pyrogram.enums.ChatEventAction.HISTORY_TTL_CHANGED` action only.
 
         old_linked_chat, new_linked_chat (:obj:`~pyrogram.types.Chat`, *optional*):
             Previous and new linked chat.
-            For "linked_chat_changed" only.
+            For :obj:`~pyrogram.enums.ChatEventAction.LINKED_CHAT_CHANGED` action only.
 
         old_photo, new_photo (:obj:`~pyrogram.types.Photo`, *optional*):
             Previous and new chat photo.
-            For "photo_changed" only.
+            For :obj:`~pyrogram.enums.ChatEventAction.PHOTO_CHANGED` action only.
 
         old_title, new_title (``str``, *optional*):
             Previous and new chat title.
-            For "title_changed" only.
+            For :obj:`~pyrogram.enums.ChatEventAction.TITLE_CHANGED` action only.
 
         old_username, new_username (``str``, *optional*):
             Previous and new chat username.
-            For "username_changed" only.
+            For :obj:`~pyrogram.enums.ChatEventAction.USERNAME_CHANGED` action only.
 
         old_chat_permissions, new_chat_permissions (:obj:`~pyrogram.types.ChatPermissions`, *optional*):
             Previous and new default chat permissions.
-            For "chat_permissions_changed" only.
+            For :obj:`~pyrogram.enums.ChatEventAction.CHAT_PERMISSIONS_CHANGED` action only.
 
         deleted_message (:obj:`~pyrogram.types.Message`, *optional*):
             Deleted message.
-            For "deleted_message" only.
+            For :obj:`~pyrogram.enums.ChatEventAction.MESSAGE_DELETED` action only.
 
         old_message, new_message (:obj:`~pyrogram.types.Message`, *optional*):
             Previous and new message before it has been edited.
-            For "message_edited" only.
+            For :obj:`~pyrogram.enums.ChatEventAction.MESSAGE_EDITED` action only.
 
         invited_member (:obj:`~pyrogram.types.ChatMember`, *optional*):
             New invited chat member.
-            For "member_invited" only.
+            For :obj:`~pyrogram.enums.ChatEventAction.MEMBER_INVITED` action only.
 
-        old_admin_rights, new_admin_rights (:obj:`~pyrogram.types.ChatMember`, *optional*):
-            Previous and new administrator rights.
-            For "admin_rights_changed" only.
+        old_administrator_privileges, new_administrator_privileges (:obj:`~pyrogram.types.ChatMember`, *optional*):
+            Previous and new administrator privileges.
+            For :obj:`~pyrogram.enums.ChatEventAction.ADMINISTRATOR_PRIVILEGES_CHANGED` action only.
 
         old_member_permissions, new_member_permissions (:obj:`~pyrogram.types.ChatMember`, *optional*):
             Previous and new member permissions.
-            For "member_permissions_changed" only.
+            For :obj:`~pyrogram.enums.ChatEventAction.MEMBER_PERMISSIONS_CHANGED` action only.
 
         stopped_poll (:obj:`~pyrogram.types.Message`, *optional*):
             Message containing the stopped poll.
-            For "poll_stopped" only.
+            For :obj:`~pyrogram.enums.ChatEventAction.POLL_STOPPED` action only.
 
         invites_enabled (``bool``, *optional*):
             If chat invites were enabled (True) or disabled (False).
-            For "invites_enabled" only.
+            For :obj:`~pyrogram.enums.ChatEventAction.INVITES_ENABLED` action only.
 
         history_hidden (``bool``, *optional*):
             If chat history has been hidden (True) or unhidden (False).
-            For "history_hidden" only.
+            For :obj:`~pyrogram.enums.ChatEventAction.HISTORY_HIDDEN` action only.
 
         signatures_enabled (``bool``, *optional*):
             If message signatures were enabled (True) or disabled (False).
-            For "signatures_enabled" only.
+            For :obj:`~pyrogram.enums.ChatEventAction.SIGNATURES_ENABLED` action only.
 
         old_slow_mode, new_slow_mode (``int``, *optional*):
             Previous and new slow mode value in seconds.
-            For "slow_mode_changed" only.
+            For :obj:`~pyrogram.enums.ChatEventAction.SLOW_MODE_CHANGED` action only.
 
         pinned_message (:obj:`~pyrogram.types.Message`, *optional*):
             Pinned message.
-            For "message_pinned" only.
+            For :obj:`~pyrogram.enums.ChatEventAction.MESSAGE_PINNED` action only.
 
         unpinned_message (:obj:`~pyrogram.types.Message`, *optional*):
             Unpinned message.
-            For "unpinned_message" only.
+            For :obj:`~pyrogram.enums.ChatEventAction.MESSAGE_UNPINNED` action only.
 
         old_invite_link, new_invite_link (:obj:`~pyrogram.types.ChatInviteLink`, *optional*):
             Previous and new edited invite link.
-            For "invite_link_edited" only.
+            For :obj:`~pyrogram.enums.ChatEventAction.INVITE_LINK_EDITED` action only.
 
         revoked_invite_link (:obj:`~pyrogram.types.ChatInviteLink`, *optional*):
             Revoked invite link.
-            For "invite_link_revoked" only.
+            For :obj:`~pyrogram.enums.ChatEventAction.INVITE_LINK_REVOKED` action only.
 
         deleted_invite_link (:obj:`~pyrogram.types.ChatInviteLink`, *optional*):
             Deleted invite link.
-            For "invite_link_deleted" only.
+            For :obj:`~pyrogram.enums.ChatEventAction.INVITE_LINK_DELETED` action only.
     """
 
     def __init__(
@@ -279,8 +209,8 @@ def __init__(
 
         invited_member: "types.ChatMember" = None,
 
-        old_admin_rights: "types.ChatMember" = None,
-        new_admin_rights: "types.ChatMember" = None,
+        old_administrator_privileges: "types.ChatMember" = None,
+        new_administrator_privileges: "types.ChatMember" = None,
 
         old_member_permissions: "types.ChatMember" = None,
         new_member_permissions: "types.ChatMember" = None,
@@ -339,8 +269,8 @@ def __init__(
 
         self.invited_member = invited_member
 
-        self.old_admin_rights = old_admin_rights
-        self.new_admin_rights = new_admin_rights
+        self.old_administrator_privileges = old_administrator_privileges
+        self.new_administrator_privileges = new_administrator_privileges
 
         self.old_member_permissions = old_member_permissions
         self.new_member_permissions = new_member_permissions
@@ -405,8 +335,8 @@ async def _parse(
 
         invited_member: Optional[types.ChatMember] = None
 
-        old_admin_rights: Optional[types.ChatMember] = None
-        new_admin_rights: Optional[types.ChatMember] = None
+        old_administrator_privileges: Optional[types.ChatMember] = None
+        new_administrator_privileges: Optional[types.ChatMember] = None
 
         old_member_permissions: Optional[types.ChatMember] = None
         new_member_permissions: Optional[types.ChatMember] = None
@@ -433,102 +363,102 @@ async def _parse(
         if isinstance(action, raw.types.ChannelAdminLogEventActionChangeAbout):
             old_description = action.prev_value
             new_description = action.new_value
-            action = ChatEventAction.DESCRIPTION_CHANGED.value
+            action = ChatEventAction.DESCRIPTION_CHANGED
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionChangeHistoryTTL):
             old_history_ttl = action.prev_value
             new_history_ttl = action.new_value
-            action = ChatEventAction.HISTORY_TTL_CHANGED.value
+            action = ChatEventAction.HISTORY_TTL_CHANGED
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionChangeLinkedChat):
             old_linked_chat = types.Chat._parse_chat(client, chats[action.prev_value])
             new_linked_chat = types.Chat._parse_chat(client, chats[action.new_value])
-            action = ChatEventAction.LINKED_CHAT_CHANGED.value
+            action = ChatEventAction.LINKED_CHAT_CHANGED
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionChangePhoto):
             old_photo = types.Photo._parse(client, action.prev_photo)
             new_photo = types.Photo._parse(client, action.new_photo)
-            action = ChatEventAction.PHOTO_CHANGED.value
+            action = ChatEventAction.PHOTO_CHANGED
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionChangeTitle):
             old_title = action.prev_value
             new_title = action.new_value
-            action = ChatEventAction.TITLE_CHANGED.value
+            action = ChatEventAction.TITLE_CHANGED
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionChangeUsername):
             old_username = action.prev_value
             new_username = action.new_value
-            action = ChatEventAction.USERNAME_CHANGED.value
+            action = ChatEventAction.USERNAME_CHANGED
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionDefaultBannedRights):
             old_chat_permissions = types.ChatPermissions._parse(action.prev_banned_rights)
             new_chat_permissions = types.ChatPermissions._parse(action.new_banned_rights)
-            action = ChatEventAction.CHAT_PERMISSIONS_CHANGED.value
+            action = ChatEventAction.CHAT_PERMISSIONS_CHANGED
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionDeleteMessage):
             deleted_message = await types.Message._parse(client, action.message, users, chats)
-            action = ChatEventAction.MESSAGE_DELETED.value
+            action = ChatEventAction.MESSAGE_DELETED
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionEditMessage):
             old_message = await types.Message._parse(client, action.prev_message, users, chats)
             new_message = await types.Message._parse(client, action.new_message, users, chats)
-            action = ChatEventAction.MESSAGE_EDITED.value
+            action = ChatEventAction.MESSAGE_EDITED
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionParticipantInvite):
             invited_member = types.ChatMember._parse(client, action.participant, users, chats)
-            action = ChatEventAction.MEMBER_INVITED.value
+            action = ChatEventAction.MEMBER_INVITED
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionParticipantToggleAdmin):
-            old_admin_rights = types.ChatMember._parse(client, action.prev_participant, users, chats)
-            new_admin_rights = types.ChatMember._parse(client, action.new_participant, users, chats)
-            action = ChatEventAction.ADMIN_RIGHTS_CHANGED.value
+            old_administrator_privileges = types.ChatMember._parse(client, action.prev_participant, users, chats)
+            new_administrator_privileges = types.ChatMember._parse(client, action.new_participant, users, chats)
+            action = ChatEventAction.ADMIN_RIGHTS_CHANGED
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionParticipantToggleBan):
             old_member_permissions = types.ChatMember._parse(client, action.prev_participant, users, chats)
             new_member_permissions = types.ChatMember._parse(client, action.new_participant, users, chats)
-            action = ChatEventAction.MEMBER_PERMISSIONS_CHANGED.value
+            action = ChatEventAction.MEMBER_PERMISSIONS_CHANGED
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionStopPoll):
             stopped_poll = await types.Message._parse(client, action.message, users, chats)
-            action = ChatEventAction.POLL_STOPPED.value
+            action = ChatEventAction.POLL_STOPPED
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionParticipantJoin):
-            action = ChatEventAction.MEMBER_JOINED.value
+            action = ChatEventAction.MEMBER_JOINED
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionParticipantLeave):
-            action = ChatEventAction.MEMBER_LEFT.value
+            action = ChatEventAction.MEMBER_LEFT
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionToggleInvites):
             invites_enabled = action.new_value
-            action = ChatEventAction.INVITES_ENABLED.value
+            action = ChatEventAction.INVITES_ENABLED
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionTogglePreHistoryHidden):
             history_hidden = action.new_value
-            action = ChatEventAction.HISTORY_HIDDEN.value
+            action = ChatEventAction.HISTORY_HIDDEN
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionToggleSignatures):
             signatures_enabled = action.new_value
-            action = ChatEventAction.SIGNATURES_ENABLED.value
+            action = ChatEventAction.SIGNATURES_ENABLED
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionToggleSlowMode):
             old_slow_mode = action.prev_value
             new_slow_mode = action.new_value
-            action = ChatEventAction.SLOW_MODE_CHANGED.value
+            action = ChatEventAction.SLOW_MODE_CHANGED
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionUpdatePinned):
             message = action.message
 
             if message.pinned:
                 pinned_message = await types.Message._parse(client, message, users, chats)
-                action = ChatEventAction.MESSAGE_PINNED.value
+                action = ChatEventAction.MESSAGE_PINNED
             else:
                 unpinned_message = await types.Message._parse(client, message, users, chats)
-                action = ChatEventAction.MESSAGE_UNPINNED.value
+                action = ChatEventAction.MESSAGE_UNPINNED
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionExportedInviteEdit):
             old_invite_link = types.ChatInviteLink._parse(client, action.prev_invite, users)
             new_invite_link = types.ChatInviteLink._parse(client, action.new_invite, users)
-            action = ChatEventAction.INVITE_LINK_EDITED.value
+            action = ChatEventAction.INVITE_LINK_EDITED
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionExportedInviteRevoke):
             revoked_invite_link = types.ChatInviteLink._parse(client, action.invite, users)
@@ -536,10 +466,10 @@ async def _parse(
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionExportedInviteDelete):
             deleted_invite_link = types.ChatInviteLink._parse(client, action.invite, users)
-            action = ChatEventAction.INVITE_LINK_DELETED.value
+            action = ChatEventAction.INVITE_LINK_DELETED
 
         else:
-            action = f"{ChatEventAction.UNKNOWN.value}-{action.QUALNAME}"
+            action = f"{ChatEventAction.UNKNOWN}-{action.QUALNAME}"
 
         return ChatEvent(
             id=event.id,
@@ -574,8 +504,8 @@ async def _parse(
 
             invited_member=invited_member,
 
-            old_admin_rights=old_admin_rights,
-            new_admin_rights=new_admin_rights,
+            old_administrator_privileges=old_administrator_privileges,
+            new_administrator_privileges=new_administrator_privileges,
 
             old_member_permissions=old_member_permissions,
             new_member_permissions=new_member_permissions,
diff --git a/pyrogram/types/user_and_chats/chat_event_filter.py b/pyrogram/types/user_and_chats/chat_event_filter.py
index d88300bae5..7edc3a07eb 100644
--- a/pyrogram/types/user_and_chats/chat_event_filter.py
+++ b/pyrogram/types/user_and_chats/chat_event_filter.py
@@ -28,7 +28,7 @@ class ChatEventFilter(Object):
             True, if member restricted/unrestricted/banned/unbanned events should be returned.
             Defaults to False.
 
-        admin_rights (``bool``, *optional*):
+        new_privileges (``bool``, *optional*):
             True, if member promotion/demotion events should be returned.
             Defaults to False.
 
@@ -74,7 +74,7 @@ class ChatEventFilter(Object):
     def __init__(
         self, *,
         new_restrictions: bool = False,
-        admin_rights: bool = False,
+        new_privileges: bool = False,
         new_members: bool = False,
         chat_info: bool = False,
         chat_settings: bool = False,
@@ -88,7 +88,7 @@ def __init__(
         super().__init__()
 
         self.new_restrictions = new_restrictions
-        self.admin_rights = admin_rights
+        self.new_privileges = new_privileges
         self.new_members = new_members
         self.chat_info = chat_info
         self.chat_settings = chat_settings
@@ -123,7 +123,7 @@ def write(self) -> "raw.base.ChannelAdminLogEventsFilter":
             kick = True
             unkick = True
 
-        if self.admin_rights:
+        if self.new_privileges:
             promote = True
             demote = True
 
diff --git a/pyrogram/types/user_and_chats/chat_member.py b/pyrogram/types/user_and_chats/chat_member.py
index 65324dbd57..dc00354f90 100644
--- a/pyrogram/types/user_and_chats/chat_member.py
+++ b/pyrogram/types/user_and_chats/chat_member.py
@@ -19,7 +19,7 @@
 from typing import Union, Dict
 
 import pyrogram
-from pyrogram import raw, types, utils
+from pyrogram import raw, types, utils, enums
 from ..object import Object
 
 
@@ -27,9 +27,8 @@ class ChatMember(Object):
     """Contains information about one member of a chat.
 
     Parameters:
-        status (``str``):
+        status (:obj:`~pyrogram.enums.ChatMemberStatus`):
             The member's status in the chat.
-            Can be "creator", "administrator", "member", "restricted", "left" or "banned".
 
         user (:obj:`~pyrogram.types.User`, *optional*):
             Information about the user.
@@ -76,7 +75,7 @@ def __init__(
         self,
         *,
         client: "pyrogram.Client" = None,
-        status: str,
+        status: "enums.ChatMemberStatus",
         user: "types.User" = None,
         chat: "types.Chat" = None,
         custom_title: str = None,
@@ -108,15 +107,15 @@ def __init__(
 
     @staticmethod
     def _parse(
-        client: "pyrogram.Client",
-        member: Union["raw.base.ChatParticipant", "raw.base.ChannelParticipant"],
-        users: Dict[int, "raw.base.User"],
-        chats: Dict[int, "raw.base.Chat"]
+            client: "pyrogram.Client",
+            member: Union["raw.base.ChatParticipant", "raw.base.ChannelParticipant"],
+            users: Dict[int, "raw.base.User"],
+            chats: Dict[int, "raw.base.Chat"]
     ) -> "ChatMember":
         # Chat participants
         if isinstance(member, raw.types.ChatParticipant):
             return ChatMember(
-                status="member",
+                status=enums.ChatMemberStatus.MEMBER,
                 user=types.User._parse(client, users[member.user_id]),
                 joined_date=member.date,
                 invited_by=types.User._parse(client, users[member.inviter_id]),
@@ -124,7 +123,7 @@ def _parse(
             )
         elif isinstance(member, raw.types.ChatParticipantAdmin):
             return ChatMember(
-                status="administrator",
+                status=enums.ChatMemberStatus.ADMINISTRATOR,
                 user=types.User._parse(client, users[member.user_id]),
                 joined_date=member.date,
                 invited_by=types.User._parse(client, users[member.inviter_id]),
@@ -132,7 +131,7 @@ def _parse(
             )
         elif isinstance(member, raw.types.ChatParticipantCreator):
             return ChatMember(
-                status="owner",
+                status=enums.ChatMemberStatus.OWNER,
                 user=types.User._parse(client, users[member.user_id]),
                 client=client
             )
@@ -140,14 +139,14 @@ def _parse(
         # Channel participants
         if isinstance(member, raw.types.ChannelParticipant):
             return ChatMember(
-                status="member",
+                status=enums.ChatMemberStatus.MEMBER,
                 user=types.User._parse(client, users[member.user_id]),
                 joined_date=member.date,
                 client=client
             )
         elif isinstance(member, raw.types.ChannelParticipantAdmin):
             return ChatMember(
-                status="administrator",
+                status=enums.ChatMemberStatus.ADMINISTRATOR,
                 user=types.User._parse(client, users[member.user_id]),
                 joined_date=member.date,
                 promoted_by=types.User._parse(client, users[member.promoted_by]),
@@ -172,7 +171,11 @@ def _parse(
             )
 
             return ChatMember(
-                status="banned" if member.banned_rights.view_messages else "restricted",
+                status=(
+                    enums.ChatMemberStatus.BANNED
+                    if member.banned_rights.view_messages
+                    else enums.ChatMemberStatus.RESTRICTED
+                ),
                 user=user,
                 chat=chat,
                 until_date=member.banned_rights.until_date,
@@ -184,7 +187,7 @@ def _parse(
             )
         elif isinstance(member, raw.types.ChannelParticipantCreator):
             return ChatMember(
-                status="owner",
+                status=enums.ChatMemberStatus.OWNER,
                 user=types.User._parse(client, users[member.user_id]),
                 custom_title=member.rank,
                 privileges=types.ChatPrivileges._parse(member.admin_rights),
@@ -205,14 +208,14 @@ def _parse(
             )
 
             return ChatMember(
-                status="left",
+                status=enums.ChatMemberStatus.LEFT,
                 user=user,
                 chat=chat,
                 client=client
             )
         elif isinstance(member, raw.types.ChannelParticipantSelf):
             return ChatMember(
-                status="member",
+                status=enums.ChatMemberStatus.MEMBER,
                 user=types.User._parse(client, users[member.user_id]),
                 joined_date=member.date,
                 invited_by=types.User._parse(client, users[member.inviter_id]),
diff --git a/pyrogram/types/user_and_chats/invite_link_importer.py b/pyrogram/types/user_and_chats/invite_link_importer.py
index f933cfa2b0..db8569f558 100644
--- a/pyrogram/types/user_and_chats/invite_link_importer.py
+++ b/pyrogram/types/user_and_chats/invite_link_importer.py
@@ -39,7 +39,7 @@ def __init__(self, *, date, user):
         self.user = user
 
     @staticmethod
-    def _parse(client, invite_importers: "raw.types.ChatInviteImporters"):
+    def _parse(client, invite_importers: "raw.types.messages.ChatInviteImporters"):
         importers = types.List()
 
         d = {i.id: i for i in invite_importers.users}
diff --git a/pyrogram/types/user_and_chats/user.py b/pyrogram/types/user_and_chats/user.py
index ac141033e2..f4678a3541 100644
--- a/pyrogram/types/user_and_chats/user.py
+++ b/pyrogram/types/user_and_chats/user.py
@@ -20,6 +20,7 @@
 from typing import List, Optional
 
 import pyrogram
+from pyrogram import enums
 from pyrogram import raw
 from pyrogram import types
 from ..object import Object
@@ -28,9 +29,9 @@
 
 class Link(str):
     HTML = "{text}"
-    MD = "[{text}]({url})"
+    MARKDOWN = "[{text}]({url})"
 
-    def __init__(self, url: str, text: str, style: str):
+    def __init__(self, url: str, text: str, style: enums.ParseMode):
         super().__init__()
 
         self.url = url
@@ -38,13 +39,11 @@ def __init__(self, url: str, text: str, style: str):
         self.style = style
 
     @staticmethod
-    def format(url: str, text: str, style: str):
-        if style in ["md", "markdown"]:
-            fmt = Link.MD
-        elif style in ["combined", "html", None]:
-            fmt = Link.HTML
+    def format(url: str, text: str, style: enums.ParseMode):
+        if style == enums.ParseMode.MARKDOWN:
+            fmt = Link.MARKDOWN
         else:
-            raise ValueError(f"{style} is not a valid style/parse mode")
+            fmt = Link.HTML
 
         return fmt.format(url=url, text=html.escape(text))
 
@@ -103,16 +102,8 @@ class User(Object, Update):
         last_name (``str``, *optional*):
             User's or bot's last name.
 
-        status (``str``, *optional*):
-            User's Last Seen & Online status.
-            Can be one of the following:
-            "*online*", user is online right now.
-            "*offline*", user is currently offline.
-            "*recently*", user with hidden last seen time who was online between 1 second and 2-3 days ago.
-            "*within_week*", user with hidden last seen time who was online between 2-3 and seven days ago.
-            "*within_month*", user with hidden last seen time who was online between 6-7 days and a month ago.
-            "*long_time_ago*", blocked user or user with hidden last seen time who was online more than a month ago.
-            *None*, for bots.
+        status (:obj:`~pyrogram.enums.UserStatus`, *optional*):
+            User's last seen & online status. *None*, for bots.
 
         last_online_date (``int``, *optional*):
             Last online date of a user, unix time. Only available in case status is "*offline*".
@@ -237,17 +228,17 @@ def _parse(client, user: "raw.base.User") -> Optional["User"]:
     @staticmethod
     def _parse_status(user_status: "raw.base.UserStatus", is_bot: bool = False):
         if isinstance(user_status, raw.types.UserStatusOnline):
-            status, date = "online", user_status.expires
+            status, date = enums.UserStatus.ONLINE, user_status.expires
         elif isinstance(user_status, raw.types.UserStatusOffline):
-            status, date = "offline", user_status.was_online
+            status, date = enums.UserStatus.OFFLINE, user_status.was_online
         elif isinstance(user_status, raw.types.UserStatusRecently):
-            status, date = "recently", None
+            status, date = enums.UserStatus.RECENTLY, None
         elif isinstance(user_status, raw.types.UserStatusLastWeek):
-            status, date = "within_week", None
+            status, date = enums.UserStatus.LAST_WEEK, None
         elif isinstance(user_status, raw.types.UserStatusLastMonth):
-            status, date = "within_month", None
+            status, date = enums.UserStatus.LAST_MONTH, None
         else:
-            status, date = "long_time_ago", None
+            status, date = enums.UserStatus.LONG_AGO, None
 
         last_online_date = None
         next_offline_date = None
@@ -255,10 +246,10 @@ def _parse_status(user_status: "raw.base.UserStatus", is_bot: bool = False):
         if is_bot:
             status = None
 
-        if status == "online":
+        if status == enums.UserStatus.ONLINE:
             next_offline_date = date
 
-        if status == "offline":
+        if status == enums.UserStatus.OFFLINE:
             last_online_date = date
 
         return {

From b697826b5a72980ff41c9196d84d1a4385b01374 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 24 Apr 2022 11:56:06 +0200
Subject: [PATCH 0785/1185] Replace integer timestamps with datetime objects

---
 docs/source/conf.py                           |   6 +-
 pyrogram/methods/chats/ban_chat_member.py     |  17 +--
 pyrogram/methods/chats/get_dialogs.py         |  11 +-
 .../methods/chats/restrict_chat_member.py     |  17 +--
 .../invite_links/create_chat_invite_link.py   |  11 +-
 .../invite_links/edit_chat_invite_link.py     |  13 +-
 pyrogram/methods/messages/copy_media_group.py |   9 +-
 pyrogram/methods/messages/copy_message.py     |   7 +-
 pyrogram/methods/messages/forward_messages.py |  11 +-
 pyrogram/methods/messages/get_history.py      |   9 +-
 pyrogram/methods/messages/iter_history.py     |   7 +-
 pyrogram/methods/messages/send_animation.py   |   9 +-
 pyrogram/methods/messages/send_audio.py       |   9 +-
 .../methods/messages/send_cached_media.py     |   9 +-
 pyrogram/methods/messages/send_contact.py     |   9 +-
 pyrogram/methods/messages/send_dice.py        |   9 +-
 pyrogram/methods/messages/send_document.py    |   9 +-
 pyrogram/methods/messages/send_location.py    |   9 +-
 pyrogram/methods/messages/send_media_group.py |   9 +-
 pyrogram/methods/messages/send_message.py     |  14 ++-
 pyrogram/methods/messages/send_photo.py       |   9 +-
 pyrogram/methods/messages/send_poll.py        |   9 +-
 pyrogram/methods/messages/send_sticker.py     |   9 +-
 pyrogram/methods/messages/send_venue.py       |   9 +-
 pyrogram/methods/messages/send_video.py       |   9 +-
 pyrogram/methods/messages/send_video_note.py  |   9 +-
 pyrogram/methods/messages/send_voice.py       |   9 +-
 pyrogram/parser/parser.py                     |   2 +-
 .../types/messages_and_media/animation.py     |  11 +-
 pyrogram/types/messages_and_media/audio.py    |  11 +-
 pyrogram/types/messages_and_media/document.py |  11 +-
 pyrogram/types/messages_and_media/message.py  |  57 ++++-----
 pyrogram/types/messages_and_media/photo.py    |  11 +-
 pyrogram/types/messages_and_media/sticker.py  |  11 +-
 pyrogram/types/messages_and_media/video.py    |  11 +-
 .../types/messages_and_media/video_note.py    |  11 +-
 pyrogram/types/messages_and_media/voice.py    |  12 +-
 pyrogram/types/object.py                      |   8 +-
 pyrogram/types/user_and_chats/chat.py         |  17 +--
 pyrogram/types/user_and_chats/chat_event.py   | 114 ++++++------------
 .../types/user_and_chats/chat_invite_link.py  |  25 ++--
 .../types/user_and_chats/chat_join_request.py |   9 +-
 pyrogram/types/user_and_chats/chat_member.py  |  31 ++---
 .../user_and_chats/chat_member_updated.py     |  11 +-
 .../user_and_chats/invite_link_importer.py    |  16 ++-
 pyrogram/types/user_and_chats/user.py         |  21 ++--
 .../user_and_chats/voice_chat_scheduled.py    |  12 +-
 pyrogram/utils.py                             |  13 +-
 48 files changed, 359 insertions(+), 333 deletions(-)

diff --git a/docs/source/conf.py b/docs/source/conf.py
index 53ac9012c5..cb273c070f 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -18,7 +18,6 @@
 
 import os
 import sys
-from datetime import datetime
 
 sys.path.insert(0, os.path.abspath("../.."))
 
@@ -38,9 +37,14 @@
     "sphinx.ext.autodoc",
     "sphinx.ext.napoleon",
     "sphinx.ext.autosummary",
+    "sphinx.ext.intersphinx",
     "sphinx_copybutton"
 ]
 
+intersphinx_mapping = {
+    "python": ("https://docs.python.org/3", None)
+}
+
 master_doc = "index"
 source_suffix = ".rst"
 autodoc_member_order = "bysource"
diff --git a/pyrogram/methods/chats/ban_chat_member.py b/pyrogram/methods/chats/ban_chat_member.py
index bdf1175197..3edcb164ce 100644
--- a/pyrogram/methods/chats/ban_chat_member.py
+++ b/pyrogram/methods/chats/ban_chat_member.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 datetime import datetime
 from typing import Union
 
-from pyrogram import raw
+from pyrogram import raw, utils
 from pyrogram import types
 from pyrogram.scaffold import Scaffold
 
@@ -28,7 +29,7 @@ async def ban_chat_member(
         self,
         chat_id: Union[int, str],
         user_id: Union[int, str],
-        until_date: int = 0
+        until_date: datetime = datetime.fromtimestamp(0)
     ) -> Union["types.Message", bool]:
         """Ban 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
@@ -48,10 +49,10 @@ async def ban_chat_member(
                 Unique identifier (int) or username (str) of the target user.
                 For a contact that exists in your Telegram address book you can use his phone number (str).
 
-            until_date (``int``, *optional*):
-                Date when the user will be unbanned, unix time.
+            until_date (:py:obj:`~datetime.datetime`, *optional*):
+                Date when the user will be unbanned.
                 If user is banned for more than 366 days or less than 30 seconds from the current time they are
-                considered to be banned forever. Defaults to 0 (ban forever).
+                considered to be banned forever. Defaults to epoch (ban forever).
 
         Returns:
             :obj:`~pyrogram.types.Message` | ``bool``: On success, a service message will be returned (when applicable),
@@ -60,13 +61,13 @@ async def ban_chat_member(
         Example:
             .. code-block:: python
 
-                from time import time
+                from datetime import datetime, timedelta
 
                 # Ban chat member forever
                 app.ban_chat_member(chat_id, user_id)
 
                 # Ban chat member and automatically unban after 24h
-                app.ban_chat_member(chat_id, user_id, int(time.time() + 86400))
+                app.ban_chat_member(chat_id, user_id, datetime.now() + timedelta(days=1))
         """
         chat_peer = await self.resolve_peer(chat_id)
         user_peer = await self.resolve_peer(user_id)
@@ -77,7 +78,7 @@ async def ban_chat_member(
                     channel=chat_peer,
                     participant=user_peer,
                     banned_rights=raw.types.ChatBannedRights(
-                        until_date=until_date,
+                        until_date=utils.datetime_to_timestamp(until_date),
                         view_messages=True,
                         send_messages=True,
                         send_media=True,
diff --git a/pyrogram/methods/chats/get_dialogs.py b/pyrogram/methods/chats/get_dialogs.py
index 7a9d6a485f..9fb907caba 100644
--- a/pyrogram/methods/chats/get_dialogs.py
+++ b/pyrogram/methods/chats/get_dialogs.py
@@ -17,6 +17,7 @@
 #  along with Pyrogram.  If not, see .
 
 import logging
+from datetime import datetime
 from typing import List
 
 from pyrogram import raw
@@ -30,7 +31,7 @@
 class GetDialogs(Scaffold):
     async def get_dialogs(
         self,
-        offset_date: int = 0,
+        offset_date: datetime = datetime.fromtimestamp(0),
         limit: int = 100,
         pinned_only: bool = False
     ) -> List["types.Dialog"]:
@@ -40,9 +41,9 @@ async def get_dialogs(
         For a more convenient way of getting a user's dialogs see :meth:`~pyrogram.Client.iter_dialogs`.
 
         Parameters:
-            offset_date (``int``):
-                The offset date in Unix time taken from the top message of a :obj:`~pyrogram.types.Dialog`.
-                Defaults to 0. Valid for non-pinned dialogs only.
+            offset_date (:py:obj:`~datetime.datetime`):
+                The offset date taken from the top message of a :obj:`~pyrogram.types.Dialog`.
+                Defaults to epoch. Valid for non-pinned dialogs only.
 
             limit (``str``, *optional*):
                 Limits the number of dialogs to be retrieved.
@@ -73,7 +74,7 @@ async def get_dialogs(
         else:
             r = await self.send(
                 raw.functions.messages.GetDialogs(
-                    offset_date=offset_date,
+                    offset_date=utils.datetime_to_timestamp(offset_date),
                     offset_id=0,
                     offset_peer=raw.types.InputPeerEmpty(),
                     limit=limit,
diff --git a/pyrogram/methods/chats/restrict_chat_member.py b/pyrogram/methods/chats/restrict_chat_member.py
index 7a759185d2..22eee519c1 100644
--- a/pyrogram/methods/chats/restrict_chat_member.py
+++ b/pyrogram/methods/chats/restrict_chat_member.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 datetime import datetime
 from typing import Union
 
-from pyrogram import raw
+from pyrogram import raw, utils
 from pyrogram import types
 from pyrogram.scaffold import Scaffold
 
@@ -29,7 +30,7 @@ async def restrict_chat_member(
         chat_id: Union[int, str],
         user_id: Union[int, str],
         permissions: "types.ChatPermissions",
-        until_date: int = 0
+        until_date: datetime = datetime.fromtimestamp(0)
     ) -> "types.Chat":
         """Restrict a user in a supergroup.
 
@@ -47,10 +48,10 @@ async def restrict_chat_member(
             permissions (:obj:`~pyrogram.types.ChatPermissions`):
                 New user permissions.
 
-            until_date (``int``, *optional*):
-                Date when the user will be unbanned, unix time.
+            until_date (:py:obj:`~datetime.datetime`, *optional*):
+                Date when the user will be unbanned.
                 If user is banned for more than 366 days or less than 30 seconds from the current time they are
-                considered to be banned forever. Defaults to 0 (ban forever).
+                considered to be banned forever. Defaults to epoch (ban forever).
 
         Returns:
             :obj:`~pyrogram.types.Chat`: On success, a chat object is returned.
@@ -58,7 +59,7 @@ async def restrict_chat_member(
         Example:
             .. code-block:: python
 
-                from time import time
+                from datetime import datetime, timedelta
 
                 from pyrogram.types import ChatPermissions
 
@@ -66,7 +67,7 @@ async def restrict_chat_member(
                 app.restrict_chat_member(chat_id, user_id, ChatPermissions())
 
                 # Chat member muted for 24h
-                app.restrict_chat_member(chat_id, user_id, ChatPermissions(), int(time() + 86400))
+                app.restrict_chat_member(chat_id, user_id, ChatPermissions(), datetime.now() + timedelta(days=1))
 
                 # Chat member can only send text messages
                 app.restrict_chat_member(chat_id, user_id, ChatPermissions(can_send_messages=True))
@@ -76,7 +77,7 @@ async def restrict_chat_member(
                 channel=await self.resolve_peer(chat_id),
                 participant=await self.resolve_peer(user_id),
                 banned_rights=raw.types.ChatBannedRights(
-                    until_date=until_date,
+                    until_date=utils.datetime_to_timestamp(until_date),
                     send_messages=not permissions.can_send_messages,
                     send_media=not permissions.can_send_media_messages,
                     send_stickers=not permissions.can_send_other_messages,
diff --git a/pyrogram/methods/invite_links/create_chat_invite_link.py b/pyrogram/methods/invite_links/create_chat_invite_link.py
index 4e6aded0b3..eec25787a4 100644
--- a/pyrogram/methods/invite_links/create_chat_invite_link.py
+++ b/pyrogram/methods/invite_links/create_chat_invite_link.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 datetime import datetime
 from typing import Union
 
-from pyrogram import raw
+from pyrogram import raw, utils
 from pyrogram import types
 from pyrogram.scaffold import Scaffold
 
@@ -28,7 +29,7 @@ async def create_chat_invite_link(
         self,
         chat_id: Union[int, str],
         name: str = None,
-        expire_date: int = None,
+        expire_date: datetime = None,
         member_limit: int = None,
         creates_join_request: bool = None
     ) -> "types.ChatInviteLink":
@@ -46,8 +47,8 @@ async def create_chat_invite_link(
             name (``str``, *optional*):
                 Invite link name.
 
-            expire_date (``int``, *optional*):
-                Point in time (Unix timestamp) when the link will expire.
+            expire_date (:py:obj:`~datetime.datetime`, *optional*):
+                Point in time when the link will expire.
                 Defaults to None (no expiration date).
 
             member_limit (``int``, *optional*):
@@ -74,7 +75,7 @@ async def create_chat_invite_link(
         r = await self.send(
             raw.functions.messages.ExportChatInvite(
                 peer=await self.resolve_peer(chat_id),
-                expire_date=expire_date,
+                expire_date=utils.datetime_to_timestamp(expire_date),
                 usage_limit=member_limit,
                 title=name,
                 request_needed=creates_join_request
diff --git a/pyrogram/methods/invite_links/edit_chat_invite_link.py b/pyrogram/methods/invite_links/edit_chat_invite_link.py
index 5c9dc9ff74..9a37536084 100644
--- a/pyrogram/methods/invite_links/edit_chat_invite_link.py
+++ b/pyrogram/methods/invite_links/edit_chat_invite_link.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 datetime import datetime
 from typing import Union
 
-from pyrogram import raw
+from pyrogram import raw, utils
 from pyrogram import types
 from pyrogram.scaffold import Scaffold
 
@@ -29,7 +30,7 @@ async def edit_chat_invite_link(
         chat_id: Union[int, str],
         invite_link: str,
         name: str = None,
-        expire_date: int = None,
+        expire_date: datetime = None,
         member_limit: int = None,
         creates_join_request: bool = None
     ) -> "types.ChatInviteLink":
@@ -48,9 +49,9 @@ async def edit_chat_invite_link(
             name (``str``, *optional*):
                 Invite link name.
 
-            expire_date (``int``, *optional*):
-                Point in time (Unix timestamp) when the link will expire.
-                Defaults to None (no change), pass 0 to set no expiration date.
+            expire_date (:py:obj:`~datetime.datetime`, *optional*):
+                Point in time when the link will expire.
+                Defaults to None (no change), pass ``datetime.fromtimestamp(0)`` to set no expiration date.
 
             member_limit (``int``, *optional*):
                 Maximum number of users that can be members of the chat simultaneously after joining the chat via this
@@ -77,7 +78,7 @@ async def edit_chat_invite_link(
             raw.functions.messages.EditExportedChatInvite(
                 peer=await self.resolve_peer(chat_id),
                 link=invite_link,
-                expire_date=expire_date,
+                expire_date=utils.datetime_to_timestamp(expire_date),
                 usage_limit=member_limit,
                 title=name,
                 request_needed=creates_join_request
diff --git a/pyrogram/methods/messages/copy_media_group.py b/pyrogram/methods/messages/copy_media_group.py
index 72d8d04bf3..06aa6c202a 100644
--- a/pyrogram/methods/messages/copy_media_group.py
+++ b/pyrogram/methods/messages/copy_media_group.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 datetime import datetime
 from typing import Union, List
 
 from pyrogram import types, utils, raw
@@ -31,7 +32,7 @@ async def copy_media_group(
         captions: Union[List[str], str] = None,
         disable_notification: bool = None,
         reply_to_message_id: int = None,
-        schedule_date: int = None,
+        schedule_date: datetime = None,
     ) -> List["types.Message"]:
         """Copy a media group by providing one of the message ids.
 
@@ -65,8 +66,8 @@ async def copy_media_group(
             reply_to_message_id (``int``, *optional*):
                 If the message is a reply, ID of the original message.
 
-            schedule_date (``int``, *optional*):
-                Date when the message will be automatically sent. Unix time.
+            schedule_date (:py:obj:`~datetime.datetime`, *optional*):
+                Date when the message will be automatically sent.
 
         Returns:
             List of :obj:`~pyrogram.types.Message`: On success, a list of copied messages is returned.
@@ -114,7 +115,7 @@ async def copy_media_group(
                 multi_media=multi_media,
                 silent=disable_notification or None,
                 reply_to_msg_id=reply_to_message_id,
-                schedule_date=schedule_date
+                schedule_date=utils.datetime_to_timestamp(schedule_date)
             ),
             sleep_threshold=60
         )
diff --git a/pyrogram/methods/messages/copy_message.py b/pyrogram/methods/messages/copy_message.py
index 07c0c5faed..ee40945bab 100644
--- a/pyrogram/methods/messages/copy_message.py
+++ b/pyrogram/methods/messages/copy_message.py
@@ -17,6 +17,7 @@
 #  along with Pyrogram.  If not, see .
 
 import logging
+from datetime import datetime
 from typing import Union, List, Optional
 
 from pyrogram import types, enums
@@ -36,7 +37,7 @@ async def copy_message(
         caption_entities: List["types.MessageEntity"] = None,
         disable_notification: bool = None,
         reply_to_message_id: int = None,
-        schedule_date: int = None,
+        schedule_date: datetime = None,
         protect_content: bool = None,
         reply_markup: Union[
             "types.InlineKeyboardMarkup",
@@ -83,8 +84,8 @@ async def copy_message(
             reply_to_message_id (``int``, *optional*):
                 If the message is a reply, ID of the original message.
 
-            schedule_date (``int``, *optional*):
-                Date when the message will be automatically sent. Unix time.
+            schedule_date (:py:obj:`~datetime.datetime`, *optional*):
+                Date when the message will be automatically sent.
 
             protect_content (``bool``, *optional*):
                 Protects the contents of the sent message from forwarding and saving.
diff --git a/pyrogram/methods/messages/forward_messages.py b/pyrogram/methods/messages/forward_messages.py
index caa627d8ad..acddd6e65e 100644
--- a/pyrogram/methods/messages/forward_messages.py
+++ b/pyrogram/methods/messages/forward_messages.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 datetime import datetime
 from typing import Union, Iterable, List
 
-from pyrogram import raw
+from pyrogram import raw, utils
 from pyrogram import types
 from pyrogram.scaffold import Scaffold
 
@@ -30,7 +31,7 @@ async def forward_messages(
         from_chat_id: Union[int, str],
         message_ids: Union[int, Iterable[int]],
         disable_notification: bool = None,
-        schedule_date: int = None,
+        schedule_date: datetime = None,
         protect_content: bool = None
     ) -> Union["types.Message", List["types.Message"]]:
         """Forward messages of any kind.
@@ -54,8 +55,8 @@ async def forward_messages(
                 Sends the message silently.
                 Users will receive a notification with no sound.
 
-            schedule_date (``int``, *optional*):
-                Date when the message will be automatically sent. Unix time.
+            schedule_date (:py:obj:`~datetime.datetime`, *optional*):
+                Date when the message will be automatically sent.
 
             protect_content (``bool``, *optional*):
                 Protects the contents of the sent message from forwarding and saving.
@@ -85,7 +86,7 @@ async def forward_messages(
                 id=message_ids,
                 silent=disable_notification or None,
                 random_id=[self.rnd_id() for _ in message_ids],
-                schedule_date=schedule_date,
+                schedule_date=utils.datetime_to_timestamp(schedule_date),
                 noforwards=protect_content
             )
         )
diff --git a/pyrogram/methods/messages/get_history.py b/pyrogram/methods/messages/get_history.py
index d80a6b8b2d..a461b72007 100644
--- a/pyrogram/methods/messages/get_history.py
+++ b/pyrogram/methods/messages/get_history.py
@@ -17,6 +17,7 @@
 #  along with Pyrogram.  If not, see .
 
 import logging
+from datetime import datetime
 from typing import Union, List
 
 from pyrogram import raw
@@ -34,7 +35,7 @@ async def get_history(
         limit: int = 100,
         offset: int = 0,
         offset_id: int = 0,
-        offset_date: int = 0,
+        offset_date: datetime = datetime.fromtimestamp(0),
         reverse: bool = False
     ) -> List["types.Message"]:
         """Retrieve a chunk of the history of a chat.
@@ -59,8 +60,8 @@ async def get_history(
             offset_id (``int``, *optional*):
                 Pass a message identifier as offset to retrieve only older messages starting from that message.
 
-            offset_date (``int``, *optional*):
-                Pass a date in Unix time as offset to retrieve only older messages starting from that date.
+            offset_date (:py:obj:`~datetime.datetime`, *optional*):
+                Pass a date as offset to retrieve only older messages starting from that date.
 
             reverse (``bool``, *optional*):
                 Pass True to retrieve the messages in reversed order (from older to most recent).
@@ -89,7 +90,7 @@ async def get_history(
                 raw.functions.messages.GetHistory(
                     peer=await self.resolve_peer(chat_id),
                     offset_id=offset_id,
-                    offset_date=offset_date,
+                    offset_date=utils.datetime_to_timestamp(offset_date),
                     add_offset=offset * (-1 if reverse else 1) - (limit if reverse else 0),
                     limit=limit,
                     max_id=0,
diff --git a/pyrogram/methods/messages/iter_history.py b/pyrogram/methods/messages/iter_history.py
index 2e60dfde90..b9dd20098c 100644
--- a/pyrogram/methods/messages/iter_history.py
+++ b/pyrogram/methods/messages/iter_history.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 datetime import datetime
 from typing import Union, Optional, AsyncGenerator
 
 from pyrogram import types
@@ -29,7 +30,7 @@ async def iter_history(
         limit: int = 0,
         offset: int = 0,
         offset_id: int = 0,
-        offset_date: int = 0,
+        offset_date: datetime = datetime.fromtimestamp(0),
         reverse: bool = False
     ) -> Optional[AsyncGenerator["types.Message", None]]:
         """Iterate through a chat history sequentially.
@@ -55,8 +56,8 @@ async def iter_history(
             offset_id (``int``, *optional*):
                 Identifier of the first message to be returned.
 
-            offset_date (``int``, *optional*):
-                Pass a date in Unix time as offset to retrieve only older messages starting from that date.
+            offset_date (:py:obj:`~datetime.datetime`, *optional*):
+                Pass a date as offset to retrieve only older messages starting from that date.
 
             reverse (``bool``, *optional*):
                 Pass True to retrieve the messages in reversed order (from older to most recent).
diff --git a/pyrogram/methods/messages/send_animation.py b/pyrogram/methods/messages/send_animation.py
index 3777b3eab1..4e6a9f407b 100644
--- a/pyrogram/methods/messages/send_animation.py
+++ b/pyrogram/methods/messages/send_animation.py
@@ -18,6 +18,7 @@
 
 import os
 import re
+from datetime import datetime
 from typing import Union, BinaryIO, List, Optional
 
 from pyrogram import StopTransmission, enums
@@ -45,7 +46,7 @@ async def send_animation(
         file_name: str = None,
         disable_notification: bool = None,
         reply_to_message_id: int = None,
-        schedule_date: int = None,
+        schedule_date: datetime = None,
         protect_content: bool = None,
         reply_markup: Union[
             "types.InlineKeyboardMarkup",
@@ -111,8 +112,8 @@ async def send_animation(
             reply_to_message_id (``int``, *optional*):
                 If the message is a reply, ID of the original message.
 
-            schedule_date (``int``, *optional*):
-                Date when the message will be automatically sent. Unix time.
+            schedule_date (:py:obj:`~datetime.datetime`, *optional*):
+                Date when the message will be automatically sent.
 
             protect_content (``bool``, *optional*):
                 Protects the contents of the sent message from forwarding and saving.
@@ -222,7 +223,7 @@ def progress(current, total):
                             silent=disable_notification or None,
                             reply_to_msg_id=reply_to_message_id,
                             random_id=self.rnd_id(),
-                            schedule_date=schedule_date,
+                            schedule_date=utils.datetime_to_timestamp(schedule_date),
                             noforwards=protect_content,
                             reply_markup=await reply_markup.write(self) if reply_markup else None,
                             **await utils.parse_text_entities(self, caption, parse_mode, caption_entities)
diff --git a/pyrogram/methods/messages/send_audio.py b/pyrogram/methods/messages/send_audio.py
index e00930af1d..6cef5b47dd 100644
--- a/pyrogram/methods/messages/send_audio.py
+++ b/pyrogram/methods/messages/send_audio.py
@@ -27,6 +27,7 @@
 from pyrogram.errors import FilePartMissing
 from pyrogram.file_id import FileType
 from pyrogram.scaffold import Scaffold
+from datetime import datetime
 
 
 class SendAudio(Scaffold):
@@ -44,7 +45,7 @@ async def send_audio(
         file_name: str = None,
         disable_notification: bool = None,
         reply_to_message_id: int = None,
-        schedule_date: int = None,
+        schedule_date: datetime = None,
         protect_content: bool = None,
         reply_markup: Union[
             "types.InlineKeyboardMarkup",
@@ -108,8 +109,8 @@ async def send_audio(
             reply_to_message_id (``int``, *optional*):
                 If the message is a reply, ID of the original message.
 
-            schedule_date (``int``, *optional*):
-                Date when the message will be automatically sent. Unix time.
+            schedule_date (:py:obj:`~datetime.datetime`, *optional*):
+                Date when the message will be automatically sent.
 
             protect_content (``bool``, *optional*):
                 Protects the contents of the sent message from forwarding and saving.
@@ -216,7 +217,7 @@ def progress(current, total):
                             silent=disable_notification or None,
                             reply_to_msg_id=reply_to_message_id,
                             random_id=self.rnd_id(),
-                            schedule_date=schedule_date,
+                            schedule_date=utils.datetime_to_timestamp(schedule_date),
                             noforwards=protect_content,
                             reply_markup=await reply_markup.write(self) if reply_markup else None,
                             **await utils.parse_text_entities(self, caption, parse_mode, caption_entities)
diff --git a/pyrogram/methods/messages/send_cached_media.py b/pyrogram/methods/messages/send_cached_media.py
index 5c8951212f..60832553fb 100644
--- a/pyrogram/methods/messages/send_cached_media.py
+++ b/pyrogram/methods/messages/send_cached_media.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 datetime import datetime
 from typing import Union, List, Optional
 
 from pyrogram import raw, enums
@@ -34,7 +35,7 @@ async def send_cached_media(
         caption_entities: List["types.MessageEntity"] = None,
         disable_notification: bool = None,
         reply_to_message_id: int = None,
-        schedule_date: int = None,
+        schedule_date: datetime = None,
         protect_content: bool = None,
         reply_markup: Union[
             "types.InlineKeyboardMarkup",
@@ -76,8 +77,8 @@ async def send_cached_media(
             reply_to_message_id (``int``, *optional*):
                 If the message is a reply, ID of the original message.
 
-            schedule_date (``int``, *optional*):
-                Date when the message will be automatically sent. Unix time.
+            schedule_date (:py:obj:`~datetime.datetime`, *optional*):
+                Date when the message will be automatically sent.
             
             protect_content (``bool``, *optional*):
                 Protects the contents of the sent message from forwarding and saving.
@@ -102,7 +103,7 @@ async def send_cached_media(
                 silent=disable_notification or None,
                 reply_to_msg_id=reply_to_message_id,
                 random_id=self.rnd_id(),
-                schedule_date=schedule_date,
+                schedule_date=utils.datetime_to_timestamp(schedule_date),
                 noforwards=protect_content,
                 reply_markup=await reply_markup.write(self) if reply_markup else None,
                 **await utils.parse_text_entities(self, caption, parse_mode, caption_entities)
diff --git a/pyrogram/methods/messages/send_contact.py b/pyrogram/methods/messages/send_contact.py
index f396577797..f9c84f4c9f 100644
--- a/pyrogram/methods/messages/send_contact.py
+++ b/pyrogram/methods/messages/send_contact.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 datetime import datetime
 from typing import Union
 
 from pyrogram import raw
@@ -33,7 +34,7 @@ async def send_contact(
         vcard: str = None,
         disable_notification: bool = None,
         reply_to_message_id: int = None,
-        schedule_date: int = None,
+        schedule_date: datetime = None,
         protect_content: bool = None,
         reply_markup: Union[
             "types.InlineKeyboardMarkup",
@@ -69,8 +70,8 @@ async def send_contact(
             reply_to_message_id (``int``, *optional*):
                 If the message is a reply, ID of the original message.
 
-            schedule_date (``int``, *optional*):
-                Date when the message will be automatically sent. Unix time.
+            schedule_date (:py:obj:`~datetime.datetime`, *optional*):
+                Date when the message will be automatically sent.
 
             protect_content (``bool``, *optional*):
                 Protects the contents of the sent message from forwarding and saving.
@@ -100,7 +101,7 @@ async def send_contact(
                 silent=disable_notification or None,
                 reply_to_msg_id=reply_to_message_id,
                 random_id=self.rnd_id(),
-                schedule_date=schedule_date,
+                schedule_date=utils.datetime_to_timestamp(schedule_date),
                 noforwards=protect_content,
                 reply_markup=await reply_markup.write(self) if reply_markup else None
             )
diff --git a/pyrogram/methods/messages/send_dice.py b/pyrogram/methods/messages/send_dice.py
index dfc9d5b895..9c7f5d1a70 100644
--- a/pyrogram/methods/messages/send_dice.py
+++ b/pyrogram/methods/messages/send_dice.py
@@ -21,6 +21,7 @@
 from pyrogram import raw
 from pyrogram import types
 from pyrogram.scaffold import Scaffold
+from datetime import datetime
 
 
 class SendDice(Scaffold):
@@ -30,7 +31,7 @@ async def send_dice(
         emoji: str = "🎲",
         disable_notification: bool = None,
         reply_to_message_id: int = None,
-        schedule_date: int = None,
+        schedule_date: datetime = None,
         protect_content: bool = None,
         reply_markup: Union[
             "types.InlineKeyboardMarkup",
@@ -61,8 +62,8 @@ async def send_dice(
             reply_to_message_id (``int``, *optional*):
                 If the message is a reply, ID of the original message.
 
-            schedule_date (``int``, *optional*):
-                Date when the message will be automatically sent. Unix time.
+            schedule_date (:py:obj:`~datetime.datetime`, *optional*):
+                Date when the message will be automatically sent.
 
             protect_content (``bool``, *optional*):
                 Protects the contents of the sent message from forwarding and saving.
@@ -94,7 +95,7 @@ async def send_dice(
                 silent=disable_notification or None,
                 reply_to_msg_id=reply_to_message_id,
                 random_id=self.rnd_id(),
-                schedule_date=schedule_date,
+                schedule_date=utils.datetime_to_timestamp(schedule_date),
                 noforwards=protect_content,
                 reply_markup=await reply_markup.write(self) if reply_markup else None,
                 message=""
diff --git a/pyrogram/methods/messages/send_document.py b/pyrogram/methods/messages/send_document.py
index 4a35f8a5e9..5844d2d232 100644
--- a/pyrogram/methods/messages/send_document.py
+++ b/pyrogram/methods/messages/send_document.py
@@ -18,6 +18,7 @@
 
 import os
 import re
+from datetime import datetime
 from typing import Union, BinaryIO, List, Optional
 
 from pyrogram import StopTransmission, enums
@@ -42,7 +43,7 @@ async def send_document(
         force_document: bool = None,
         disable_notification: bool = None,
         reply_to_message_id: int = None,
-        schedule_date: int = None,
+        schedule_date: datetime = None,
         protect_content: bool = None,
         reply_markup: Union[
             "types.InlineKeyboardMarkup",
@@ -100,8 +101,8 @@ async def send_document(
             reply_to_message_id (``int``, *optional*):
                 If the message is a reply, ID of the original message.
 
-            schedule_date (``int``, *optional*):
-                Date when the message will be automatically sent. Unix time.
+            schedule_date (:py:obj:`~datetime.datetime`, *optional*):
+                Date when the message will be automatically sent.
 
             protect_content (``bool``, *optional*):
                 Protects the contents of the sent message from forwarding and saving.
@@ -194,7 +195,7 @@ def progress(current, total):
                             silent=disable_notification or None,
                             reply_to_msg_id=reply_to_message_id,
                             random_id=self.rnd_id(),
-                            schedule_date=schedule_date,
+                            schedule_date=utils.datetime_to_timestamp(schedule_date),
                             noforwards=protect_content,
                             reply_markup=await reply_markup.write(self) if reply_markup else None,
                             **await utils.parse_text_entities(self, caption, parse_mode, caption_entities)
diff --git a/pyrogram/methods/messages/send_location.py b/pyrogram/methods/messages/send_location.py
index d2b6616dae..6c33c1b4fc 100644
--- a/pyrogram/methods/messages/send_location.py
+++ b/pyrogram/methods/messages/send_location.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 datetime import datetime
 from typing import Union
 
 from pyrogram import raw
@@ -31,7 +32,7 @@ async def send_location(
         longitude: float,
         disable_notification: bool = None,
         reply_to_message_id: int = None,
-        schedule_date: int = None,
+        schedule_date: datetime = None,
         protect_content: bool = None,
         reply_markup: Union[
             "types.InlineKeyboardMarkup",
@@ -61,8 +62,8 @@ async def send_location(
             reply_to_message_id (``int``, *optional*):
                 If the message is a reply, ID of the original message
 
-            schedule_date (``int``, *optional*):
-                Date when the message will be automatically sent. Unix time.
+            schedule_date (:py:obj:`~datetime.datetime`, *optional*):
+                Date when the message will be automatically sent.
 
             protect_content (``bool``, *optional*):
                 Protects the contents of the sent message from forwarding and saving.
@@ -92,7 +93,7 @@ async def send_location(
                 silent=disable_notification or None,
                 reply_to_msg_id=reply_to_message_id,
                 random_id=self.rnd_id(),
-                schedule_date=schedule_date,
+                schedule_date=utils.datetime_to_timestamp(schedule_date),
                 noforwards=protect_content,
                 reply_markup=await reply_markup.write(self) if reply_markup else None
             )
diff --git a/pyrogram/methods/messages/send_media_group.py b/pyrogram/methods/messages/send_media_group.py
index 13d4d62a8a..04c91c3b44 100644
--- a/pyrogram/methods/messages/send_media_group.py
+++ b/pyrogram/methods/messages/send_media_group.py
@@ -19,6 +19,7 @@
 import logging
 import os
 import re
+from datetime import datetime
 from typing import Union, List
 
 from pyrogram import raw
@@ -43,7 +44,7 @@ async def send_media_group(
         ]],
         disable_notification: bool = None,
         reply_to_message_id: int = None,
-        schedule_date: int = None,
+        schedule_date: datetime = None,
         protect_content: bool = None,
     ) -> List["types.Message"]:
         """Send a group of photos or videos as an album.
@@ -64,8 +65,8 @@ async def send_media_group(
             reply_to_message_id (``int``, *optional*):
                 If the message is a reply, ID of the original message.
 
-            schedule_date (``int``, *optional*):
-                Date when the message will be automatically sent. Unix time.
+            schedule_date (:py:obj:`~datetime.datetime`, *optional*):
+                Date when the message will be automatically sent.
 
             protect_content (``bool``, *optional*):
                 Protects the contents of the sent message from forwarding and saving.
@@ -381,7 +382,7 @@ async def send_media_group(
                 multi_media=multi_media,
                 silent=disable_notification or None,
                 reply_to_msg_id=reply_to_message_id,
-                schedule_date=schedule_date,
+                schedule_date=utils.datetime_to_timestamp(schedule_date),
                 noforwards=protect_content
             ),
             sleep_threshold=60
diff --git a/pyrogram/methods/messages/send_message.py b/pyrogram/methods/messages/send_message.py
index 575aea33e7..a094ba9214 100644
--- a/pyrogram/methods/messages/send_message.py
+++ b/pyrogram/methods/messages/send_message.py
@@ -22,6 +22,8 @@
 from pyrogram import types
 from pyrogram.scaffold import Scaffold
 
+from datetime import datetime
+
 
 class SendMessage(Scaffold):
     async def send_message(
@@ -33,7 +35,7 @@ async def send_message(
         disable_web_page_preview: bool = None,
         disable_notification: bool = None,
         reply_to_message_id: int = None,
-        schedule_date: int = None,
+        schedule_date: datetime = None,
         protect_content: bool = None,
         reply_markup: Union[
             "types.InlineKeyboardMarkup",
@@ -70,8 +72,8 @@ async def send_message(
             reply_to_message_id (``int``, *optional*):
                 If the message is a reply, ID of the original message.
 
-            schedule_date (``int``, *optional*):
-                Date when the message will be automatically sent. Unix time.
+            schedule_date (:py:obj:`~datetime.datetime`, *optional*):
+                Date when the message will be automatically sent.
 
             protect_content (``bool``, *optional*):
                 Protects the contents of the sent message from forwarding and saving.
@@ -129,7 +131,7 @@ async def send_message(
                 silent=disable_notification or None,
                 reply_to_msg_id=reply_to_message_id,
                 random_id=self.rnd_id(),
-                schedule_date=schedule_date,
+                schedule_date=utils.datetime_to_timestamp(schedule_date),
                 reply_markup=await reply_markup.write(self) if reply_markup else None,
                 message=message,
                 entities=entities,
@@ -150,11 +152,11 @@ async def send_message(
                 message_id=r.id,
                 chat=types.Chat(
                     id=peer_id,
-                    type="private",
+                    type=enums.ChatType.PRIVATE,
                     client=self
                 ),
                 text=message,
-                date=r.date,
+                date=utils.timestamp_to_datetime(r.date),
                 outgoing=r.out,
                 reply_markup=reply_markup,
                 entities=[
diff --git a/pyrogram/methods/messages/send_photo.py b/pyrogram/methods/messages/send_photo.py
index 442b45aec5..bf7b482cbe 100644
--- a/pyrogram/methods/messages/send_photo.py
+++ b/pyrogram/methods/messages/send_photo.py
@@ -18,6 +18,7 @@
 
 import os
 import re
+from datetime import datetime
 from typing import Union, BinaryIO, List, Optional
 
 import pyrogram
@@ -40,7 +41,7 @@ async def send_photo(
         ttl_seconds: int = None,
         disable_notification: bool = None,
         reply_to_message_id: int = None,
-        schedule_date: int = None,
+        schedule_date: datetime = None,
         protect_content: bool = None,
         reply_markup: Union[
             "types.InlineKeyboardMarkup",
@@ -88,8 +89,8 @@ async def send_photo(
             reply_to_message_id (``int``, *optional*):
                 If the message is a reply, ID of the original message.
 
-            schedule_date (``int``, *optional*):
-                Date when the message will be automatically sent. Unix time.
+            schedule_date (:py:obj:`~datetime.datetime`, *optional*):
+                Date when the message will be automatically sent.
 
             protect_content (``bool``, *optional*):
                 Protects the contents of the sent message from forwarding and saving.
@@ -172,7 +173,7 @@ async def send_photo(
                             silent=disable_notification or None,
                             reply_to_msg_id=reply_to_message_id,
                             random_id=self.rnd_id(),
-                            schedule_date=schedule_date,
+                            schedule_date=utils.datetime_to_timestamp(schedule_date),
                             noforwards=protect_content,
                             reply_markup=await reply_markup.write(self) if reply_markup else None,
                             **await utils.parse_text_entities(self, caption, parse_mode, caption_entities)
diff --git a/pyrogram/methods/messages/send_poll.py b/pyrogram/methods/messages/send_poll.py
index df27c0573f..a02c52663c 100644
--- a/pyrogram/methods/messages/send_poll.py
+++ b/pyrogram/methods/messages/send_poll.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 datetime import datetime
 from typing import Union, List
 
 from pyrogram import raw
@@ -35,7 +36,7 @@ async def send_poll(
         correct_option_id: int = None,
         disable_notification: bool = None,
         reply_to_message_id: int = None,
-        schedule_date: int = None,
+        schedule_date: datetime = None,
         protect_content: bool = None,
         reply_markup: Union[
             "types.InlineKeyboardMarkup",
@@ -81,8 +82,8 @@ async def send_poll(
             reply_to_message_id (``int``, *optional*):
                 If the message is a reply, ID of the original message.
 
-            schedule_date (``int``, *optional*):
-                Date when the message will be automatically sent. Unix time.
+            schedule_date (:py:obj:`~datetime.datetime`, *optional*):
+                Date when the message will be automatically sent.
 
             protect_content (``bool``, *optional*):
                 Protects the contents of the sent message from forwarding and saving.
@@ -120,7 +121,7 @@ async def send_poll(
                 silent=disable_notification or None,
                 reply_to_msg_id=reply_to_message_id,
                 random_id=self.rnd_id(),
-                schedule_date=schedule_date,
+                schedule_date=utils.datetime_to_timestamp(schedule_date),
                 noforwards=protect_content,
                 reply_markup=await reply_markup.write(self) if reply_markup else None
             )
diff --git a/pyrogram/methods/messages/send_sticker.py b/pyrogram/methods/messages/send_sticker.py
index 641cc6359a..76571d9999 100644
--- a/pyrogram/methods/messages/send_sticker.py
+++ b/pyrogram/methods/messages/send_sticker.py
@@ -18,6 +18,7 @@
 
 import os
 import re
+from datetime import datetime
 from typing import Union, BinaryIO, Optional
 
 from pyrogram import StopTransmission
@@ -36,7 +37,7 @@ async def send_sticker(
         sticker: Union[str, BinaryIO],
         disable_notification: bool = None,
         reply_to_message_id: int = None,
-        schedule_date: int = None,
+        schedule_date: datetime = None,
         protect_content: bool = None,
         reply_markup: Union[
             "types.InlineKeyboardMarkup",
@@ -69,8 +70,8 @@ async def send_sticker(
             reply_to_message_id (``int``, *optional*):
                 If the message is a reply, ID of the original message.
 
-            schedule_date (``int``, *optional*):
-                Date when the message will be automatically sent. Unix time.
+            schedule_date (:py:obj:`~datetime.datetime`, *optional*):
+                Date when the message will be automatically sent.
 
             protect_content (``bool``, *optional*):
                 Protects the contents of the sent message from forwarding and saving.
@@ -153,7 +154,7 @@ async def send_sticker(
                             silent=disable_notification or None,
                             reply_to_msg_id=reply_to_message_id,
                             random_id=self.rnd_id(),
-                            schedule_date=schedule_date,
+                            schedule_date=utils.datetime_to_timestamp(schedule_date),
                             noforwards=protect_content,
                             reply_markup=await reply_markup.write(self) if reply_markup else None,
                             message=""
diff --git a/pyrogram/methods/messages/send_venue.py b/pyrogram/methods/messages/send_venue.py
index 7941485ebc..5293cf01df 100644
--- a/pyrogram/methods/messages/send_venue.py
+++ b/pyrogram/methods/messages/send_venue.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 datetime import datetime
 from typing import Union
 
 from pyrogram import raw
@@ -35,7 +36,7 @@ async def send_venue(
         foursquare_type: str = "",
         disable_notification: bool = None,
         reply_to_message_id: int = None,
-        schedule_date: int = None,
+        schedule_date: datetime = None,
         protect_content: bool = None,
         reply_markup: Union[
             "types.InlineKeyboardMarkup",
@@ -78,8 +79,8 @@ async def send_venue(
             reply_to_message_id (``int``, *optional*):
                 If the message is a reply, ID of the original message
 
-            schedule_date (``int``, *optional*):
-                Date when the message will be automatically sent. Unix time.
+            schedule_date (:py:obj:`~datetime.datetime`, *optional*):
+                Date when the message will be automatically sent.
 
             protect_content (``bool``, *optional*):
                 Protects the contents of the sent message from forwarding and saving.
@@ -116,7 +117,7 @@ async def send_venue(
                 silent=disable_notification or None,
                 reply_to_msg_id=reply_to_message_id,
                 random_id=self.rnd_id(),
-                schedule_date=schedule_date,
+                schedule_date=utils.datetime_to_timestamp(schedule_date),
                 noforwards=protect_content,
                 reply_markup=await reply_markup.write(self) if reply_markup else None
             )
diff --git a/pyrogram/methods/messages/send_video.py b/pyrogram/methods/messages/send_video.py
index f6dc00eebe..96f2a9c3bb 100644
--- a/pyrogram/methods/messages/send_video.py
+++ b/pyrogram/methods/messages/send_video.py
@@ -18,6 +18,7 @@
 
 import os
 import re
+from datetime import datetime
 from typing import Union, BinaryIO, List, Optional
 
 from pyrogram import StopTransmission, enums
@@ -46,7 +47,7 @@ async def send_video(
         supports_streaming: bool = True,
         disable_notification: bool = None,
         reply_to_message_id: int = None,
-        schedule_date: int = None,
+        schedule_date: datetime = None,
         protect_content: bool = None,
         reply_markup: Union[
             "types.InlineKeyboardMarkup",
@@ -117,8 +118,8 @@ async def send_video(
             reply_to_message_id (``int``, *optional*):
                 If the message is a reply, ID of the original message.
 
-            schedule_date (``int``, *optional*):
-                Date when the message will be automatically sent. Unix time.
+            schedule_date (:py:obj:`~datetime.datetime`, *optional*):
+                Date when the message will be automatically sent.
 
             protect_content (``bool``, *optional*):
                 Protects the contents of the sent message from forwarding and saving.
@@ -228,7 +229,7 @@ def progress(current, total):
                             silent=disable_notification or None,
                             reply_to_msg_id=reply_to_message_id,
                             random_id=self.rnd_id(),
-                            schedule_date=schedule_date,
+                            schedule_date=utils.datetime_to_timestamp(schedule_date),
                             noforwards=protect_content,
                             reply_markup=await reply_markup.write(self) if reply_markup else None,
                             **await utils.parse_text_entities(self, caption, parse_mode, caption_entities)
diff --git a/pyrogram/methods/messages/send_video_note.py b/pyrogram/methods/messages/send_video_note.py
index 1e52f3ab14..c2f0a159ab 100644
--- a/pyrogram/methods/messages/send_video_note.py
+++ b/pyrogram/methods/messages/send_video_note.py
@@ -17,6 +17,7 @@
 #  along with Pyrogram.  If not, see .
 
 import os
+from datetime import datetime
 from typing import Union, BinaryIO, Optional
 
 from pyrogram import StopTransmission
@@ -38,7 +39,7 @@ async def send_video_note(
         thumb: Union[str, BinaryIO] = None,
         disable_notification: bool = None,
         reply_to_message_id: int = None,
-        schedule_date: int = None,
+        schedule_date: datetime = None,
         protect_content: bool = None,
         reply_markup: Union[
             "types.InlineKeyboardMarkup",
@@ -83,8 +84,8 @@ async def send_video_note(
             reply_to_message_id (``int``, *optional*):
                 If the message is a reply, ID of the original message
 
-            schedule_date (``int``, *optional*):
-                Date when the message will be automatically sent. Unix time.
+            schedule_date (:py:obj:`~datetime.datetime`, *optional*):
+                Date when the message will be automatically sent.
 
             protect_content (``bool``, *optional*):
                 Protects the contents of the sent message from forwarding and saving.
@@ -177,7 +178,7 @@ async def send_video_note(
                             silent=disable_notification or None,
                             reply_to_msg_id=reply_to_message_id,
                             random_id=self.rnd_id(),
-                            schedule_date=schedule_date,
+                            schedule_date=utils.datetime_to_timestamp(schedule_date),
                             noforwards=protect_content,
                             reply_markup=await reply_markup.write(self) if reply_markup else None,
                             message=""
diff --git a/pyrogram/methods/messages/send_voice.py b/pyrogram/methods/messages/send_voice.py
index c7e960fd04..79fefcab1d 100644
--- a/pyrogram/methods/messages/send_voice.py
+++ b/pyrogram/methods/messages/send_voice.py
@@ -18,6 +18,7 @@
 
 import os
 import re
+from datetime import datetime
 from typing import Union, BinaryIO, List, Optional
 
 from pyrogram import StopTransmission, enums
@@ -40,7 +41,7 @@ async def send_voice(
         duration: int = 0,
         disable_notification: bool = None,
         reply_to_message_id: int = None,
-        schedule_date: int = None,
+        schedule_date: datetime = None,
         protect_content: bool = None,
         reply_markup: Union[
             "types.InlineKeyboardMarkup",
@@ -86,8 +87,8 @@ async def send_voice(
             reply_to_message_id (``int``, *optional*):
                 If the message is a reply, ID of the original message
 
-            schedule_date (``int``, *optional*):
-                Date when the message will be automatically sent. Unix time.
+            schedule_date (:py:obj:`~datetime.datetime`, *optional*):
+                Date when the message will be automatically sent.
 
             protect_content (``bool``, *optional*):
                 Protects the contents of the sent message from forwarding and saving.
@@ -178,7 +179,7 @@ async def send_voice(
                             silent=disable_notification or None,
                             reply_to_msg_id=reply_to_message_id,
                             random_id=self.rnd_id(),
-                            schedule_date=schedule_date,
+                            schedule_date=utils.datetime_to_timestamp(schedule_date),
                             noforwards=protect_content,
                             reply_markup=await reply_markup.write(self) if reply_markup else None,
                             **await utils.parse_text_entities(self, caption, parse_mode, caption_entities)
diff --git a/pyrogram/parser/parser.py b/pyrogram/parser/parser.py
index 884e17558e..35a15d0c68 100644
--- a/pyrogram/parser/parser.py
+++ b/pyrogram/parser/parser.py
@@ -30,7 +30,7 @@ def __init__(self, client: Optional["pyrogram.Client"]):
         self.html = HTML(client)
         self.markdown = Markdown(client)
 
-    async def parse(self, text: str, mode: Optional[str] = None):
+    async def parse(self, text: str, mode: Optional[enums.ParseMode] = None):
         text = str(text if text else "").strip()
 
         if mode is None:
diff --git a/pyrogram/types/messages_and_media/animation.py b/pyrogram/types/messages_and_media/animation.py
index ce901734e6..1e7bf4cf39 100644
--- a/pyrogram/types/messages_and_media/animation.py
+++ b/pyrogram/types/messages_and_media/animation.py
@@ -16,10 +16,11 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
+from datetime import datetime
 from typing import List
 
 import pyrogram
-from pyrogram import raw
+from pyrogram import raw, utils
 from pyrogram import types
 from pyrogram.file_id import FileId, FileType, FileUniqueId, FileUniqueType
 from ..object import Object
@@ -54,8 +55,8 @@ class Animation(Object):
         file_size (``int``, *optional*):
             File size.
 
-        date (``int``, *optional*):
-            Date the animation was sent in Unix time.
+        date (:py:obj:`~datetime.datetime`, *optional*):
+            Date the animation was sent.
 
         thumbs (List of :obj:`~pyrogram.types.Thumbnail`, *optional*):
             Animation thumbnails.
@@ -73,7 +74,7 @@ def __init__(
         file_name: str = None,
         mime_type: str = None,
         file_size: int = None,
-        date: int = None,
+        date: datetime = None,
         thumbs: List["types.Thumbnail"] = None
     ):
         super().__init__(client)
@@ -114,7 +115,7 @@ def _parse(
             mime_type=animation.mime_type,
             file_size=animation.size,
             file_name=file_name,
-            date=animation.date,
+            date=utils.timestamp_to_datetime(animation.date),
             thumbs=types.Thumbnail._parse(client, animation),
             client=client
         )
diff --git a/pyrogram/types/messages_and_media/audio.py b/pyrogram/types/messages_and_media/audio.py
index 60b37a59e7..e474282f0b 100644
--- a/pyrogram/types/messages_and_media/audio.py
+++ b/pyrogram/types/messages_and_media/audio.py
@@ -16,10 +16,11 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
+from datetime import datetime
 from typing import List
 
 import pyrogram
-from pyrogram import raw
+from pyrogram import raw, utils
 from pyrogram import types
 from pyrogram.file_id import FileId, FileType, FileUniqueId, FileUniqueType
 from ..object import Object
@@ -54,8 +55,8 @@ class Audio(Object):
         file_size (``int``, *optional*):
             File size.
 
-        date (``int``, *optional*):
-            Date the audio was originally sent, in Unix time.
+        date (:py:obj:`~datetime.datetime`, *optional*):
+            Date the audio was originally sent.
 
         thumbs (List of :obj:`~pyrogram.types.Thumbnail`, *optional*):
             Thumbnails of the music file album cover.
@@ -73,7 +74,7 @@ def __init__(
         file_name: str = None,
         mime_type: str = None,
         file_size: int = None,
-        date: int = None,
+        date: datetime = None,
         thumbs: List["types.Thumbnail"] = None
     ):
         super().__init__(client)
@@ -114,7 +115,7 @@ def _parse(
             mime_type=audio.mime_type,
             file_size=audio.size,
             file_name=file_name,
-            date=audio.date,
+            date=utils.timestamp_to_datetime(audio.date),
             thumbs=types.Thumbnail._parse(client, audio),
             client=client
         )
diff --git a/pyrogram/types/messages_and_media/document.py b/pyrogram/types/messages_and_media/document.py
index 333d38abb7..95ebe3f289 100644
--- a/pyrogram/types/messages_and_media/document.py
+++ b/pyrogram/types/messages_and_media/document.py
@@ -16,10 +16,11 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
+from datetime import datetime
 from typing import List
 
 import pyrogram
-from pyrogram import raw
+from pyrogram import raw, utils
 from pyrogram import types
 from pyrogram.file_id import FileId, FileType, FileUniqueId, FileUniqueType
 from ..object import Object
@@ -45,8 +46,8 @@ class Document(Object):
         file_size (``int``, *optional*):
             File size.
 
-        date (``int``, *optional*):
-            Date the document was sent in Unix time.
+        date (:py:obj:`~datetime.datetime`, *optional*):
+            Date the document was sent.
 
         thumbs (List of :obj:`~pyrogram.types.Thumbnail`, *optional*):
             Document thumbnails as defined by sender.
@@ -61,7 +62,7 @@ def __init__(
         file_name: str = None,
         mime_type: str = None,
         file_size: int = None,
-        date: int = None,
+        date: datetime = None,
         thumbs: List["types.Thumbnail"] = None
     ):
         super().__init__(client)
@@ -91,7 +92,7 @@ def _parse(client, document: "raw.types.Document", file_name: str) -> "Document"
             file_name=file_name,
             mime_type=document.mime_type,
             file_size=document.size,
-            date=document.date,
+            date=utils.timestamp_to_datetime(document.date),
             thumbs=types.Thumbnail._parse(client, document),
             client=client
         )
diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py
index 2c2f7e053d..86c340aea6 100644
--- a/pyrogram/types/messages_and_media/message.py
+++ b/pyrogram/types/messages_and_media/message.py
@@ -17,6 +17,7 @@
 #  along with Pyrogram.  If not, see .
 
 import logging
+from datetime import datetime
 from functools import partial
 from typing import List, Match, Union, BinaryIO, Optional
 
@@ -71,8 +72,8 @@ class Message(Object, Update):
             The supergroup itself for messages from anonymous group administrators.
             The linked channel for messages automatically forwarded to the discussion group.
 
-        date (``int``, *optional*):
-            Date the message was sent in Unix time.
+        date (:py:obj:`~datetime.datetime`, *optional*):
+            Date the message was sent.
 
         chat (:obj:`~pyrogram.types.Chat`, *optional*):
             Conversation the message belongs to.
@@ -92,8 +93,8 @@ class Message(Object, Update):
         forward_signature (``str``, *optional*):
             For messages forwarded from channels, signature of the post author if present.
 
-        forward_date (``int``, *optional*):
-            For forwarded messages, date the original message was sent in Unix time.
+        forward_date (:py:obj:`~datetime.datetime`, *optional*):
+            For forwarded messages, date the original message was sent.
 
         reply_to_message_id (``int``, *optional*):
             The id of the message which this message directly replied to.
@@ -122,8 +123,8 @@ class Message(Object, Update):
             This field will contain the enumeration type of the media message.
             You can use ``media = getattr(message, message.media.value)`` to access the media message.
 
-        edit_date (``int``, *optional*):
-            Date the message was last edited in Unix time.
+        edit_date (:py:obj:`~datetime.datetime`, *optional*):
+            Date the message was last edited.
 
         media_group_id (``str``, *optional*):
             The unique identifier of a media message group this message belongs to.
@@ -304,14 +305,14 @@ def __init__(
         message_id: int,
         from_user: "types.User" = None,
         sender_chat: "types.Chat" = None,
-        date: int = None,
+        date: datetime = None,
         chat: "types.Chat" = None,
         forward_from: "types.User" = None,
         forward_sender_name: str = None,
         forward_from_chat: "types.Chat" = None,
         forward_from_message_id: int = None,
         forward_signature: str = None,
-        forward_date: int = None,
+        forward_date: datetime = None,
         reply_to_message_id: int = None,
         reply_to_top_message_id: int = None,
         reply_to_message: "Message" = None,
@@ -321,7 +322,7 @@ def __init__(
         scheduled: bool = None,
         from_scheduled: bool = None,
         media: str = None,
-        edit_date: int = None,
+        edit_date: datetime = None,
         media_group_id: str = None,
         author_signature: str = None,
         has_protected_content: bool = None,
@@ -542,7 +543,7 @@ async def _parse(
 
             parsed_message = Message(
                 message_id=message.id,
-                date=message.date,
+                date=utils.timestamp_to_datetime(message.date),
                 chat=types.Chat._parse(client, message, users, chats, is_chat=True),
                 from_user=from_user,
                 sender_chat=sender_chat,
@@ -607,7 +608,7 @@ async def _parse(
             forward_header = message.fwd_from  # type: raw.types.MessageFwdHeader
 
             if forward_header:
-                forward_date = forward_header.date
+                forward_date = utils.timestamp_to_datetime(forward_header.date)
 
                 if forward_header.from_id:
                     raw_peer_id = utils.get_raw_peer_id(forward_header.from_id)
@@ -739,7 +740,7 @@ async def _parse(
 
             parsed_message = Message(
                 message_id=message.id,
-                date=message.date,
+                date=utils.timestamp_to_datetime(message.date),
                 chat=types.Chat._parse(client, message, users, chats, is_chat=True),
                 from_user=from_user,
                 sender_chat=sender_chat,
@@ -775,7 +776,7 @@ async def _parse(
                 scheduled=is_scheduled,
                 from_scheduled=message.from_scheduled,
                 media=media_type,
-                edit_date=message.edit_date,
+                edit_date=utils.timestamp_to_datetime(message.edit_date),
                 media_group_id=message.grouped_id,
                 photo=photo,
                 location=location,
@@ -864,7 +865,7 @@ async def reply_text(
         disable_web_page_preview: bool = None,
         disable_notification: bool = None,
         reply_to_message_id: int = None,
-        schedule_date: int = None,
+        schedule_date: datetime = None,
         protect_content: bool = None,
         reply_markup = None
     ) -> "Message":
@@ -913,8 +914,8 @@ async def reply_text(
             reply_to_message_id (``int``, *optional*):
                 If the message is a reply, ID of the original message.
 
-            schedule_date (``int``, *optional*):
-                Date when the message will be automatically sent. Unix time.
+            schedule_date (:py:obj:`~datetime.datetime`, *optional*):
+                Date when the message will be automatically sent.
 
             protect_content (``bool``, *optional*):
                 Protects the contents of the sent message from forwarding and saving.
@@ -1449,7 +1450,7 @@ async def reply_document(
         force_document: bool = None,
         disable_notification: bool = None,
         reply_to_message_id: int = None,
-        schedule_date: int = None,
+        schedule_date: datetime = None,
         reply_markup: Union[
             "types.InlineKeyboardMarkup",
             "types.ReplyKeyboardMarkup",
@@ -1519,8 +1520,8 @@ async def reply_document(
             reply_to_message_id (``int``, *optional*):
                 If the message is a reply, ID of the original message.
             
-            schedule_date (``int``, *optional*):
-                Date when the message will be automatically sent. Unix time.
+            schedule_date (:py:obj:`~datetime.datetime`, *optional*):
+                Date when the message will be automatically sent.
 
             reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*):
                 Additional interface options. An object for an inline keyboard, custom reply keyboard,
@@ -1982,7 +1983,7 @@ async def reply_poll(
         correct_option_id: int = None,
         disable_notification: bool = None,
         reply_to_message_id: int = None,
-        schedule_date: int = None,
+        schedule_date: datetime = None,
         reply_markup: Union[
             "types.InlineKeyboardMarkup",
             "types.ReplyKeyboardMarkup",
@@ -2042,8 +2043,8 @@ async def reply_poll(
             reply_to_message_id (``int``, *optional*):
                 If the message is a reply, ID of the original message.
             
-            schedule_date (``int``, *optional*):
-                Date when the message will be automatically sent. Unix time.
+            schedule_date (:py:obj:`~datetime.datetime`, *optional*):
+                Date when the message will be automatically sent.
 
             reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*):
                 Additional interface options. An object for an inline keyboard, custom reply keyboard,
@@ -2859,7 +2860,7 @@ async def forward(
         self,
         chat_id: Union[int, str],
         disable_notification: bool = None,
-        schedule_date: int = None
+        schedule_date: datetime = None
     ) -> Union["types.Message", List["types.Message"]]:
         """Bound method *forward* of :obj:`~pyrogram.types.Message`.
 
@@ -2888,8 +2889,8 @@ async def forward(
                 Sends the message silently.
                 Users will receive a notification with no sound.
 
-            schedule_date (``int``, *optional*):
-                Date when the message will be automatically sent. Unix time.
+            schedule_date (:py:obj:`~datetime.datetime`, *optional*):
+                Date when the message will be automatically sent.
 
         Returns:
             On success, the forwarded Message is returned.
@@ -2913,7 +2914,7 @@ async def copy(
         caption_entities: List["types.MessageEntity"] = None,
         disable_notification: bool = None,
         reply_to_message_id: int = None,
-        schedule_date: int = None,
+        schedule_date: datetime = None,
         protect_content: bool = None,
         reply_markup: Union[
             "types.InlineKeyboardMarkup",
@@ -2964,8 +2965,8 @@ async def copy(
             reply_to_message_id (``int``, *optional*):
                 If the message is a reply, ID of the original message.
 
-            schedule_date (``int``, *optional*):
-                Date when the message will be automatically sent. Unix time.
+            schedule_date (:py:obj:`~datetime.datetime`, *optional*):
+                Date when the message will be automatically sent.
 
             protect_content (``bool``, *optional*):
                 Protects the contents of the sent message from forwarding and saving.
diff --git a/pyrogram/types/messages_and_media/photo.py b/pyrogram/types/messages_and_media/photo.py
index fc496cd455..d6ad92263d 100644
--- a/pyrogram/types/messages_and_media/photo.py
+++ b/pyrogram/types/messages_and_media/photo.py
@@ -16,10 +16,11 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
+from datetime import datetime
 from typing import List
 
 import pyrogram
-from pyrogram import raw
+from pyrogram import raw, utils
 from pyrogram import types
 from pyrogram.file_id import FileId, FileType, FileUniqueId, FileUniqueType, ThumbnailSource
 from ..object import Object
@@ -45,8 +46,8 @@ class Photo(Object):
         file_size (``int``):
             File size.
 
-        date (``int``):
-            Date the photo was sent in Unix time.
+        date (:py:obj:`~datetime.datetime`):
+            Date the photo was sent.
 
         ttl_seconds (``int``, *optional*):
             Time-to-live seconds, for secret photos.
@@ -64,7 +65,7 @@ def __init__(
         width: int,
         height: int,
         file_size: int,
-        date: int,
+        date: datetime,
         ttl_seconds: int = None,
         thumbs: List["types.Thumbnail"] = None
     ):
@@ -122,7 +123,7 @@ def _parse(client, photo: "raw.types.Photo", ttl_seconds: int = None) -> "Photo"
                 width=main.w,
                 height=main.h,
                 file_size=main.size,
-                date=photo.date,
+                date=utils.timestamp_to_datetime(photo.date),
                 ttl_seconds=ttl_seconds,
                 thumbs=types.Thumbnail._parse(client, photo),
                 client=client
diff --git a/pyrogram/types/messages_and_media/sticker.py b/pyrogram/types/messages_and_media/sticker.py
index bd08792ff0..fa6e0be3fb 100644
--- a/pyrogram/types/messages_and_media/sticker.py
+++ b/pyrogram/types/messages_and_media/sticker.py
@@ -16,10 +16,11 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
+from datetime import datetime
 from typing import List
 
 import pyrogram
-from pyrogram import raw
+from pyrogram import raw, utils
 from pyrogram import types
 from pyrogram.errors import StickersetInvalid
 from pyrogram.file_id import FileId, FileType, FileUniqueId, FileUniqueType
@@ -58,8 +59,8 @@ class Sticker(Object):
         file_size (``int``, *optional*):
             File size.
 
-        date (``int``, *optional*):
-            Date the sticker was sent in Unix time.
+        date (:py:obj:`~datetime.datetime`, *optional*):
+            Date the sticker was sent.
 
         emoji (``str``, *optional*):
             Emoji associated with the sticker.
@@ -86,7 +87,7 @@ def __init__(
         file_name: str = None,
         mime_type: str = None,
         file_size: int = None,
-        date: int = None,
+        date: datetime = None,
         emoji: str = None,
         set_name: str = None,
         thumbs: List["types.Thumbnail"] = None
@@ -179,7 +180,7 @@ async def _parse(
             file_size=sticker.size,
             mime_type=sticker.mime_type,
             file_name=file_name,
-            date=sticker.date,
+            date=utils.timestamp_to_datetime(sticker.date),
             thumbs=types.Thumbnail._parse(client, sticker),
             client=client
         )
diff --git a/pyrogram/types/messages_and_media/video.py b/pyrogram/types/messages_and_media/video.py
index 1cdf055fc0..b50f9fca78 100644
--- a/pyrogram/types/messages_and_media/video.py
+++ b/pyrogram/types/messages_and_media/video.py
@@ -16,10 +16,11 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
+from datetime import datetime
 from typing import List
 
 import pyrogram
-from pyrogram import raw
+from pyrogram import raw, utils
 from pyrogram import types
 from pyrogram.file_id import FileId, FileType, FileUniqueId, FileUniqueType
 from ..object import Object
@@ -60,8 +61,8 @@ class Video(Object):
         ttl_seconds (``int``. *optional*):
             Time-to-live seconds, for secret photos.
 
-        date (``int``, *optional*):
-            Date the video was sent in Unix time.
+        date (:py:obj:`~datetime.datetime`, *optional*):
+            Date the video was sent.
 
         thumbs (List of :obj:`~pyrogram.types.Thumbnail`, *optional*):
             Video thumbnails.
@@ -81,7 +82,7 @@ def __init__(
         file_size: int = None,
         supports_streaming: bool = None,
         ttl_seconds: int = None,
-        date: int = None,
+        date: datetime = None,
         thumbs: List["types.Thumbnail"] = None
     ):
         super().__init__(client)
@@ -126,7 +127,7 @@ def _parse(
             mime_type=video.mime_type,
             supports_streaming=video_attributes.supports_streaming,
             file_size=video.size,
-            date=video.date,
+            date=utils.timestamp_to_datetime(video.date),
             ttl_seconds=ttl_seconds,
             thumbs=types.Thumbnail._parse(client, video),
             client=client
diff --git a/pyrogram/types/messages_and_media/video_note.py b/pyrogram/types/messages_and_media/video_note.py
index 3a9e8a616f..450d536db1 100644
--- a/pyrogram/types/messages_and_media/video_note.py
+++ b/pyrogram/types/messages_and_media/video_note.py
@@ -19,10 +19,11 @@
 from typing import List
 
 import pyrogram
-from pyrogram import raw
+from pyrogram import raw, utils
 from pyrogram import types
 from pyrogram.file_id import FileId, FileType, FileUniqueId, FileUniqueType
 from ..object import Object
+from datetime import datetime
 
 
 class VideoNote(Object):
@@ -48,8 +49,8 @@ class VideoNote(Object):
         file_size (``int``, *optional*):
             File size.
 
-        date (``int``, *optional*):
-            Date the video note was sent in Unix time.
+        date (:py:obj:`~datetime.datetime`, *optional*):
+            Date the video note was sent.
 
         thumbs (List of :obj:`~pyrogram.types.Thumbnail`, *optional*):
             Video thumbnails.
@@ -66,7 +67,7 @@ def __init__(
         thumbs: List["types.Thumbnail"] = None,
         mime_type: str = None,
         file_size: int = None,
-        date: int = None
+        date: datetime = None
     ):
         super().__init__(client)
 
@@ -101,7 +102,7 @@ def _parse(
             duration=video_attributes.duration,
             file_size=video_note.size,
             mime_type=video_note.mime_type,
-            date=video_note.date,
+            date=utils.timestamp_to_datetime(video_note.date),
             thumbs=types.Thumbnail._parse(client, video_note),
             client=client
         )
diff --git a/pyrogram/types/messages_and_media/voice.py b/pyrogram/types/messages_and_media/voice.py
index 4175b7ba1e..8d1c15f657 100644
--- a/pyrogram/types/messages_and_media/voice.py
+++ b/pyrogram/types/messages_and_media/voice.py
@@ -16,8 +16,10 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
+from datetime import datetime
+
 import pyrogram
-from pyrogram import raw
+from pyrogram import raw, utils
 from pyrogram.file_id import FileId, FileType, FileUniqueId, FileUniqueType
 from ..object import Object
 
@@ -45,8 +47,8 @@ class Voice(Object):
         file_size (``int``, *optional*):
             File size.
 
-        date (``int``, *optional*):
-            Date the voice was sent in Unix time.
+        date (:py:obj:`~datetime.datetime`, *optional*):
+            Date the voice was sent.
     """
 
     def __init__(
@@ -59,7 +61,7 @@ def __init__(
         waveform: bytes = None,
         mime_type: str = None,
         file_size: int = None,
-        date: int = None
+        date: datetime = None
     ):
         super().__init__(client)
 
@@ -89,6 +91,6 @@ def _parse(client, voice: "raw.types.Document", attributes: "raw.types.DocumentA
             mime_type=voice.mime_type,
             file_size=voice.size,
             waveform=attributes.waveform,
-            date=voice.date,
+            date=utils.timestamp_to_datetime(voice.date),
             client=client
         )
diff --git a/pyrogram/types/object.py b/pyrogram/types/object.py
index a0925779e2..a3641b1614 100644
--- a/pyrogram/types/object.py
+++ b/pyrogram/types/object.py
@@ -56,14 +56,14 @@ def default(obj: "Object"):
         if isinstance(obj, Enum):
             return str(obj)
 
+        if isinstance(obj, datetime):
+            return str(obj)
+
         return {
             "_": obj.__class__.__name__,
             **{
                 attr: (
-                    "*" * 9
-                    if attr == "phone_number" else
-                    str(datetime.fromtimestamp(getattr(obj, attr)))
-                    if attr.endswith("date") else
+                    "*" * 9 if attr == "phone_number" else
                     getattr(obj, attr)
                 )
                 for attr in filter(lambda x: not x.startswith("_"), obj.__dict__)
diff --git a/pyrogram/types/user_and_chats/chat.py b/pyrogram/types/user_and_chats/chat.py
index 2282b1e073..870391a49d 100644
--- a/pyrogram/types/user_and_chats/chat.py
+++ b/pyrogram/types/user_and_chats/chat.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 datetime import datetime
 from typing import Union, List, Generator, Optional
 
 import pyrogram
@@ -513,7 +514,7 @@ async def set_photo(self, photo: str) -> bool:
     async def ban_member(
         self,
         user_id: Union[int, str],
-        until_date: int = 0
+        until_date: datetime = datetime.fromtimestamp(0)
     ) -> Union["types.Message", bool]:
         """Bound method *ban_member* of :obj:`~pyrogram.types.Chat`.
 
@@ -541,10 +542,10 @@ async def ban_member(
                 Unique identifier (int) or username (str) of the target user.
                 For a contact that exists in your Telegram address book you can use his phone number (str).
 
-            until_date (``int``, *optional*):
-                Date when the user will be unbanned, unix time.
+            until_date (:py:obj:`~datetime.datetime`, *optional*):
+                Date when the user will be unbanned.
                 If user is banned for more than 366 days or less than 30 seconds from the current time they are
-                considered to be banned forever. Defaults to 0 (ban forever).
+                considered to be banned forever. Defaults to epoch (ban forever).
 
         Returns:
             :obj:`~pyrogram.types.Message` | ``bool``: On success, a service message will be returned (when applicable), otherwise, in
@@ -601,7 +602,7 @@ async def restrict_member(
         self,
         user_id: Union[int, str],
         permissions: "types.ChatPermissions",
-        until_date: int = 0,
+        until_date: datetime = datetime.fromtimestamp(0),
     ) -> "types.Chat":
         """Bound method *unban_member* of :obj:`~pyrogram.types.Chat`.
 
@@ -628,10 +629,10 @@ async def restrict_member(
             permissions (:obj:`~pyrogram.types.ChatPermissions`):
                 New user permissions.
 
-            until_date (``int``, *optional*):
-                Date when the user will be unbanned, unix time.
+            until_date (:py:obj:`~datetime.datetime`, *optional*):
+                Date when the user will be unbanned.
                 If user is banned for more than 366 days or less than 30 seconds from the current time they are
-                considered to be banned forever. Defaults to 0 (ban forever).
+                considered to be banned forever. Defaults to epoch (ban forever).
 
         Returns:
             :obj:`~pyrogram.types.Chat`: On success, a chat object is returned.
diff --git a/pyrogram/types/user_and_chats/chat_event.py b/pyrogram/types/user_and_chats/chat_event.py
index 3abfd1f9d6..fa539be868 100644
--- a/pyrogram/types/user_and_chats/chat_event.py
+++ b/pyrogram/types/user_and_chats/chat_event.py
@@ -16,57 +16,15 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-from enum import Enum, auto
+from datetime import datetime
 from typing import List, Optional
 
 import pyrogram
 from pyrogram import raw
-from pyrogram import types
+from pyrogram import types, utils, enums
 from ..object import Object
 
 
-class AutoName(Enum):
-    def _generate_next_value_(self, *args):
-        return self.lower()
-
-
-class ChatEventAction(AutoName):
-    DESCRIPTION_CHANGED = auto()
-    HISTORY_TTL_CHANGED = auto()
-    LINKED_CHAT_CHANGED = auto()
-    # LOCATION_CHANGED = auto()
-    PHOTO_CHANGED = auto()
-    # STICKER_SET_CHANGED = auto()
-    TITLE_CHANGED = auto()
-    USERNAME_CHANGED = auto()
-    CHAT_PERMISSIONS_CHANGED = auto()
-    MESSAGE_DELETED = auto()
-    # VOICE_CHAT_DISCARDED = auto()
-    MESSAGE_EDITED = auto()
-    INVITE_LINK_EDITED = auto()
-    INVITE_LINK_REVOKED = auto()
-    INVITE_LINK_DELETED = auto()
-    MEMBER_INVITED = auto()
-    MEMBER_JOINED = auto()
-    # MEMBER_JOINED_BY_LINK = auto()
-    MEMBER_LEFT = auto()
-    # MEMBER_MUTED = auto()
-    ADMIN_RIGHTS_CHANGED = auto()
-    MEMBER_PERMISSIONS_CHANGED = auto()
-    # MEMBER_UNMUTED = auto()
-    # MEMBER_VOLUME_CHANGED = auto()
-    # VOICE_CHAT_STARTED = auto()
-    POLL_STOPPED = auto()
-    # VOICE_CHAT_SETTINGS_CHANGED = auto()
-    INVITES_ENABLED = auto()
-    HISTORY_HIDDEN = auto()
-    SIGNATURES_ENABLED = auto()
-    SLOW_MODE_CHANGED = auto()
-    MESSAGE_PINNED = auto()
-    MESSAGE_UNPINNED = auto()
-    UNKNOWN = auto()
-
-
 class ChatEvent(Object):
     """A chat event from the recent actions log (also known as admin log).
 
@@ -76,8 +34,8 @@ class ChatEvent(Object):
         id (``int``):
             Chat event identifier.
 
-        date (``int``):
-            Date of the event. Unix time.
+        date (:py:obj:`~datetime.datetime`):
+            Date of the event.
 
         action (:obj:`~pyrogram.enums.ChatEventAction`):
             Event action.
@@ -177,7 +135,7 @@ class ChatEvent(Object):
     def __init__(
         self, *,
         id: int,
-        date: int,
+        date: datetime,
         user: "types.User",
         action: str,
 
@@ -232,7 +190,7 @@ def __init__(
         old_invite_link: "types.ChatInviteLink" = None,
         new_invite_link: "types.ChatInviteLink" = None,
         revoked_invite_link: "types.ChatInviteLink" = None,
-        deleted_invite_link: "types.ChatInviteLink" = None,
+        deleted_invite_link: "types.ChatInviteLink" = None
     ):
         super().__init__()
 
@@ -296,10 +254,10 @@ def __init__(
 
     @staticmethod
     async def _parse(
-        client: "pyrogram.Client",
-        event: "raw.base.ChannelAdminLogEvent",
-        users: List["raw.base.User"],
-        chats: List["raw.base.Chat"]
+            client: "pyrogram.Client",
+            event: "raw.base.ChannelAdminLogEvent",
+            users: List["raw.base.User"],
+            chats: List["raw.base.Chat"]
     ):
         users = {i.id: i for i in users}
         chats = {i.id: i for i in chats}
@@ -363,117 +321,117 @@ async def _parse(
         if isinstance(action, raw.types.ChannelAdminLogEventActionChangeAbout):
             old_description = action.prev_value
             new_description = action.new_value
-            action = ChatEventAction.DESCRIPTION_CHANGED
+            action = enums.ChatEventAction.DESCRIPTION_CHANGED
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionChangeHistoryTTL):
             old_history_ttl = action.prev_value
             new_history_ttl = action.new_value
-            action = ChatEventAction.HISTORY_TTL_CHANGED
+            action = enums.ChatEventAction.HISTORY_TTL_CHANGED
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionChangeLinkedChat):
             old_linked_chat = types.Chat._parse_chat(client, chats[action.prev_value])
             new_linked_chat = types.Chat._parse_chat(client, chats[action.new_value])
-            action = ChatEventAction.LINKED_CHAT_CHANGED
+            action = enums.ChatEventAction.LINKED_CHAT_CHANGED
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionChangePhoto):
             old_photo = types.Photo._parse(client, action.prev_photo)
             new_photo = types.Photo._parse(client, action.new_photo)
-            action = ChatEventAction.PHOTO_CHANGED
+            action = enums.ChatEventAction.PHOTO_CHANGED
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionChangeTitle):
             old_title = action.prev_value
             new_title = action.new_value
-            action = ChatEventAction.TITLE_CHANGED
+            action = enums.ChatEventAction.TITLE_CHANGED
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionChangeUsername):
             old_username = action.prev_value
             new_username = action.new_value
-            action = ChatEventAction.USERNAME_CHANGED
+            action = enums.ChatEventAction.USERNAME_CHANGED
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionDefaultBannedRights):
             old_chat_permissions = types.ChatPermissions._parse(action.prev_banned_rights)
             new_chat_permissions = types.ChatPermissions._parse(action.new_banned_rights)
-            action = ChatEventAction.CHAT_PERMISSIONS_CHANGED
+            action = enums.ChatEventAction.CHAT_PERMISSIONS_CHANGED
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionDeleteMessage):
             deleted_message = await types.Message._parse(client, action.message, users, chats)
-            action = ChatEventAction.MESSAGE_DELETED
+            action = enums.ChatEventAction.MESSAGE_DELETED
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionEditMessage):
             old_message = await types.Message._parse(client, action.prev_message, users, chats)
             new_message = await types.Message._parse(client, action.new_message, users, chats)
-            action = ChatEventAction.MESSAGE_EDITED
+            action = enums.ChatEventAction.MESSAGE_EDITED
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionParticipantInvite):
             invited_member = types.ChatMember._parse(client, action.participant, users, chats)
-            action = ChatEventAction.MEMBER_INVITED
+            action = enums.ChatEventAction.MEMBER_INVITED
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionParticipantToggleAdmin):
             old_administrator_privileges = types.ChatMember._parse(client, action.prev_participant, users, chats)
             new_administrator_privileges = types.ChatMember._parse(client, action.new_participant, users, chats)
-            action = ChatEventAction.ADMIN_RIGHTS_CHANGED
+            action = enums.ChatEventAction.ADMIN_RIGHTS_CHANGED
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionParticipantToggleBan):
             old_member_permissions = types.ChatMember._parse(client, action.prev_participant, users, chats)
             new_member_permissions = types.ChatMember._parse(client, action.new_participant, users, chats)
-            action = ChatEventAction.MEMBER_PERMISSIONS_CHANGED
+            action = enums.ChatEventAction.MEMBER_PERMISSIONS_CHANGED
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionStopPoll):
             stopped_poll = await types.Message._parse(client, action.message, users, chats)
-            action = ChatEventAction.POLL_STOPPED
+            action = enums.ChatEventAction.POLL_STOPPED
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionParticipantJoin):
-            action = ChatEventAction.MEMBER_JOINED
+            action = enums.ChatEventAction.MEMBER_JOINED
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionParticipantLeave):
-            action = ChatEventAction.MEMBER_LEFT
+            action = enums.ChatEventAction.MEMBER_LEFT
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionToggleInvites):
             invites_enabled = action.new_value
-            action = ChatEventAction.INVITES_ENABLED
+            action = enums.ChatEventAction.INVITES_ENABLED
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionTogglePreHistoryHidden):
             history_hidden = action.new_value
-            action = ChatEventAction.HISTORY_HIDDEN
+            action = enums.ChatEventAction.HISTORY_HIDDEN
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionToggleSignatures):
             signatures_enabled = action.new_value
-            action = ChatEventAction.SIGNATURES_ENABLED
+            action = enums.ChatEventAction.SIGNATURES_ENABLED
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionToggleSlowMode):
             old_slow_mode = action.prev_value
             new_slow_mode = action.new_value
-            action = ChatEventAction.SLOW_MODE_CHANGED
+            action = enums.ChatEventAction.SLOW_MODE_CHANGED
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionUpdatePinned):
             message = action.message
 
             if message.pinned:
                 pinned_message = await types.Message._parse(client, message, users, chats)
-                action = ChatEventAction.MESSAGE_PINNED
+                action = enums.ChatEventAction.MESSAGE_PINNED
             else:
                 unpinned_message = await types.Message._parse(client, message, users, chats)
-                action = ChatEventAction.MESSAGE_UNPINNED
+                action = enums.ChatEventAction.MESSAGE_UNPINNED
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionExportedInviteEdit):
             old_invite_link = types.ChatInviteLink._parse(client, action.prev_invite, users)
             new_invite_link = types.ChatInviteLink._parse(client, action.new_invite, users)
-            action = ChatEventAction.INVITE_LINK_EDITED
+            action = enums.ChatEventAction.INVITE_LINK_EDITED
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionExportedInviteRevoke):
             revoked_invite_link = types.ChatInviteLink._parse(client, action.invite, users)
-            action = ChatEventAction.INVITE_LINK_REVOKED
+            action = enums.ChatEventAction.INVITE_LINK_REVOKED
 
         elif isinstance(action, raw.types.ChannelAdminLogEventActionExportedInviteDelete):
             deleted_invite_link = types.ChatInviteLink._parse(client, action.invite, users)
-            action = ChatEventAction.INVITE_LINK_DELETED
+            action = enums.ChatEventAction.INVITE_LINK_DELETED
 
         else:
-            action = f"{ChatEventAction.UNKNOWN}-{action.QUALNAME}"
+            action = f"{enums.ChatEventAction.UNKNOWN}-{action.QUALNAME}"
 
         return ChatEvent(
             id=event.id,
-            date=event.date,
+            date=utils.timestamp_to_datetime(event.date),
             user=user,
             action=action,
             old_description=old_description,
diff --git a/pyrogram/types/user_and_chats/chat_invite_link.py b/pyrogram/types/user_and_chats/chat_invite_link.py
index 9dddea4835..7aaa56a8cf 100644
--- a/pyrogram/types/user_and_chats/chat_invite_link.py
+++ b/pyrogram/types/user_and_chats/chat_invite_link.py
@@ -16,10 +16,11 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
+from datetime import datetime
 from typing import Dict
 
 import pyrogram
-from pyrogram import raw
+from pyrogram import raw, utils
 from pyrogram import types
 from ..object import Object
 
@@ -32,8 +33,8 @@ class ChatInviteLink(Object):
             The invite link. If the link was created by another chat administrator, then the second part of the
             link will be replaced with "...".
 
-        date (``int``):
-            The date in Unix timestamp when the link was created.
+        date (:py:obj:`~datetime.datetime`):
+            The date when the link was created.
 
         is_primary (``bool``):
             True, if the link is primary.
@@ -50,8 +51,11 @@ class ChatInviteLink(Object):
         creates_join_request (``bool``, *optional*):
             True, if users joining the chat via the link need to be approved by chat administrators.
 
-        expire_date (``int``, *optional*):
-            Point in time (Unix timestamp) when the link will expire or has been expired.
+        start_date (:py:obj:`~datetime.datetime`, *optional*):
+            Point in time when the link has been edited.
+
+        expire_date (:py:obj:`~datetime.datetime`, *optional*):
+            Point in time when the link will expire or has been expired.
 
         member_limit (``int``, *optional*):
             Maximum number of users that can be members of the chat simultaneously after joining the chat via this
@@ -67,14 +71,14 @@ class ChatInviteLink(Object):
     def __init__(
         self, *,
         invite_link: str,
-        date: int,
+        date: datetime,
         is_primary: bool = None,
         is_revoked: bool = None,
         creator: "types.User" = None,
         name: str = None,
         creates_join_request: bool = None,
-        start_date: int = None,
-        expire_date: int = None,
+        start_date: datetime = None,
+        expire_date: datetime = None,
         member_limit: int = None,
         member_count: int = None,
         pending_join_request_count: int = None
@@ -108,13 +112,14 @@ def _parse(
 
         return ChatInviteLink(
             invite_link=invite.link,
-            date=invite.date,
+            date=utils.timestamp_to_datetime(invite.date),
             is_primary=invite.permanent,
             is_revoked=invite.revoked,
             creator=creator,
             name=invite.title,
             creates_join_request=invite.request_needed,
-            expire_date=invite.expire_date,
+            start_date=utils.timestamp_to_datetime(invite.start_date),
+            expire_date=utils.timestamp_to_datetime(invite.expire_date),
             member_limit=invite.usage_limit,
             member_count=invite.usage,
             pending_join_request_count=invite.requested
diff --git a/pyrogram/types/user_and_chats/chat_join_request.py b/pyrogram/types/user_and_chats/chat_join_request.py
index fe051de4f7..ee9da3ef2c 100644
--- a/pyrogram/types/user_and_chats/chat_join_request.py
+++ b/pyrogram/types/user_and_chats/chat_join_request.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 datetime import datetime
 from typing import Dict
 
 import pyrogram
@@ -35,8 +36,8 @@ class ChatJoinRequest(Object, Update):
         from_user (:obj:`~pyrogram.types.User`):
             User that sent the join request.
 
-        date (``int``):
-            Date the request was sent in Unix time
+        date (:py:obj:`~datetime.datetime`):
+            Date the request was sent.
 
         bio (``str``, *optional*):
             Bio of the user.
@@ -51,7 +52,7 @@ def __init__(
         client: "pyrogram.Client" = None,
         chat: "types.Chat",
         from_user: "types.User",
-        date: int,
+        date: datetime,
         bio: str = None,
         invite_link: "types.ChatInviteLink" = None
     ):
@@ -75,7 +76,7 @@ def _parse(
         return ChatJoinRequest(
             chat=types.Chat._parse_chat(client, chats[chat_id]),
             from_user=types.User._parse(client, users[update.user_id]),
-            date=update.date,
+            date=utils.timestamp_to_datetime(update.date),
             bio=update.about,
             invite_link=types.ChatInviteLink._parse(client, update.invite, users),
             client=client
diff --git a/pyrogram/types/user_and_chats/chat_member.py b/pyrogram/types/user_and_chats/chat_member.py
index dc00354f90..06f45b8d34 100644
--- a/pyrogram/types/user_and_chats/chat_member.py
+++ b/pyrogram/types/user_and_chats/chat_member.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 datetime import datetime
 from typing import Union, Dict
 
 import pyrogram
@@ -36,17 +37,17 @@ class ChatMember(Object):
         chat (:obj:`~pyrogram.types.Chat`, *optional*):
             Information about the chat (useful in case of banned channel senders).
 
-        joined_date (``int``, *optional*):
-            Date when the user joined, unix time.
+        joined_date (:py:obj:`~datetime.datetime`, *optional*):
+            Date when the user joined..
             Not available for the owner.
 
         custom_title (``str``, *optional*):
             A custom title that will be shown to all members instead of "Owner" or "Admin".
             Creator (owner) and administrators only. Can be None in case there's no custom title set.
 
-        until_date (``int``, *optional*):
+        until_date (:py:obj:`~datetime.datetime`, *optional*):
             Restricted and banned only.
-            Date when restrictions will be lifted for this user; unix time.
+            Date when restrictions will be lifted for this user.
 
         invited_by (:obj:`~pyrogram.types.User`, *optional*):
             Administrators and self member only. Information about the user who invited this member.
@@ -79,8 +80,8 @@ def __init__(
         user: "types.User" = None,
         chat: "types.Chat" = None,
         custom_title: str = None,
-        until_date: int = None,
-        joined_date: int = None,
+        until_date: datetime = None,
+        joined_date: datetime = None,
         invited_by: "types.User" = None,
         promoted_by: "types.User" = None,
         restricted_by: "types.User" = None,
@@ -109,15 +110,15 @@ def __init__(
     def _parse(
             client: "pyrogram.Client",
             member: Union["raw.base.ChatParticipant", "raw.base.ChannelParticipant"],
-            users: Dict[int, "raw.base.User"],
-            chats: Dict[int, "raw.base.Chat"]
+        users: Dict[int, "raw.base.User"],
+        chats: Dict[int, "raw.base.Chat"]
     ) -> "ChatMember":
         # Chat participants
         if isinstance(member, raw.types.ChatParticipant):
             return ChatMember(
                 status=enums.ChatMemberStatus.MEMBER,
                 user=types.User._parse(client, users[member.user_id]),
-                joined_date=member.date,
+                joined_date=utils.timestamp_to_datetime(member.date),
                 invited_by=types.User._parse(client, users[member.inviter_id]),
                 client=client
             )
@@ -125,7 +126,7 @@ def _parse(
             return ChatMember(
                 status=enums.ChatMemberStatus.ADMINISTRATOR,
                 user=types.User._parse(client, users[member.user_id]),
-                joined_date=member.date,
+                joined_date=utils.timestamp_to_datetime(member.date),
                 invited_by=types.User._parse(client, users[member.inviter_id]),
                 client=client
             )
@@ -141,14 +142,14 @@ def _parse(
             return ChatMember(
                 status=enums.ChatMemberStatus.MEMBER,
                 user=types.User._parse(client, users[member.user_id]),
-                joined_date=member.date,
+                joined_date=utils.timestamp_to_datetime(member.date),
                 client=client
             )
         elif isinstance(member, raw.types.ChannelParticipantAdmin):
             return ChatMember(
                 status=enums.ChatMemberStatus.ADMINISTRATOR,
                 user=types.User._parse(client, users[member.user_id]),
-                joined_date=member.date,
+                joined_date=utils.timestamp_to_datetime(member.date),
                 promoted_by=types.User._parse(client, users[member.promoted_by]),
                 invited_by=types.User._parse(client, users[member.inviter_id]),
                 custom_title=member.rank,
@@ -178,8 +179,8 @@ def _parse(
                 ),
                 user=user,
                 chat=chat,
-                until_date=member.banned_rights.until_date,
-                joined_date=member.date,
+                until_date=utils.timestamp_to_datetime(member.banned_rights.until_date),
+                joined_date=utils.timestamp_to_datetime(member.date),
                 is_member=not member.left,
                 restricted_by=types.User._parse(client, users[member.kicked_by]),
                 permissions=types.ChatPermissions._parse(member.banned_rights),
@@ -217,7 +218,7 @@ def _parse(
             return ChatMember(
                 status=enums.ChatMemberStatus.MEMBER,
                 user=types.User._parse(client, users[member.user_id]),
-                joined_date=member.date,
+                joined_date=utils.timestamp_to_datetime(member.date),
                 invited_by=types.User._parse(client, users[member.inviter_id]),
                 client=client
             )
diff --git a/pyrogram/types/user_and_chats/chat_member_updated.py b/pyrogram/types/user_and_chats/chat_member_updated.py
index 5fa7d84e6c..f8b6638db9 100644
--- a/pyrogram/types/user_and_chats/chat_member_updated.py
+++ b/pyrogram/types/user_and_chats/chat_member_updated.py
@@ -16,10 +16,11 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
+from datetime import datetime
 from typing import Dict, Union
 
 import pyrogram
-from pyrogram import raw
+from pyrogram import raw, utils
 from pyrogram import types
 from ..object import Object
 from ..update import Update
@@ -35,8 +36,8 @@ class ChatMemberUpdated(Object, Update):
         from_user (:obj:`~pyrogram.types.User`):
             Performer of the action, which resulted in the change.
 
-        date (``int``):
-            Date the change was done in Unix time.
+        date (:py:obj:`~datetime.datetime`):
+            Date the change was done.
 
         old_chat_member (:obj:`~pyrogram.types.ChatMember`, *optional*):
             Previous information about the chat member.
@@ -54,7 +55,7 @@ def __init__(
         client: "pyrogram.Client" = None,
         chat: "types.Chat",
         from_user: "types.User",
-        date: int,
+        date: datetime,
         old_chat_member: "types.ChatMember",
         new_chat_member: "types.ChatMember",
         invite_link: "types.ChatInviteLink" = None,
@@ -93,7 +94,7 @@ def _parse(
         return ChatMemberUpdated(
             chat=types.Chat._parse_chat(client, chats[chat_id]),
             from_user=types.User._parse(client, users[update.actor_id]),
-            date=update.date,
+            date=utils.timestamp_to_datetime(update.date),
             old_chat_member=old_chat_member,
             new_chat_member=new_chat_member,
             invite_link=invite_link,
diff --git a/pyrogram/types/user_and_chats/invite_link_importer.py b/pyrogram/types/user_and_chats/invite_link_importer.py
index db8569f558..34e5f397f4 100644
--- a/pyrogram/types/user_and_chats/invite_link_importer.py
+++ b/pyrogram/types/user_and_chats/invite_link_importer.py
@@ -16,7 +16,9 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-from pyrogram import raw
+from datetime import datetime
+
+from pyrogram import raw, utils
 from pyrogram import types
 from ..object import Object
 
@@ -25,14 +27,18 @@ class InviteLinkImporter(Object):
     """The date and user of when someone has joined with an invite link.
 
     Parameters:
-        date (``int``):
-            The unix time of when this user used the given link
+        date (:py:obj:`~datetime.datetime`):
+            The time of when this user used the given link
 
         user (:obj:`~pyrogram.types.User`):
             The user that has used the given invite link
     """
 
-    def __init__(self, *, date, user):
+    def __init__(
+        self, *,
+        date: datetime,
+        user: "types.User"
+    ):
         super().__init__(None)
 
         self.date = date
@@ -47,7 +53,7 @@ def _parse(client, invite_importers: "raw.types.messages.ChatInviteImporters"):
         for j in invite_importers.importers:
             importers.append(
                 InviteLinkImporter(
-                    date=j.date,
+                    date=utils.timestamp_to_datetime(j.date),
                     user=types.User._parse(client=None, user=d[j.user_id])
                 )
             )
diff --git a/pyrogram/types/user_and_chats/user.py b/pyrogram/types/user_and_chats/user.py
index f4678a3541..2ec09fb583 100644
--- a/pyrogram/types/user_and_chats/user.py
+++ b/pyrogram/types/user_and_chats/user.py
@@ -17,10 +17,11 @@
 #  along with Pyrogram.  If not, see .
 
 import html
+from datetime import datetime
 from typing import List, Optional
 
 import pyrogram
-from pyrogram import enums
+from pyrogram import enums, utils
 from pyrogram import raw
 from pyrogram import types
 from ..object import Object
@@ -103,13 +104,13 @@ class User(Object, Update):
             User's or bot's last name.
 
         status (:obj:`~pyrogram.enums.UserStatus`, *optional*):
-            User's last seen & online status. *None*, for bots.
+            User's last seen & online status. ``None``, for bots.
 
-        last_online_date (``int``, *optional*):
-            Last online date of a user, unix time. Only available in case status is "*offline*".
+        last_online_date (:py:obj:`~datetime.datetime`, *optional*):
+            Last online date of a user. Only available in case status is :obj:`~pyrogram.enums.UserStatus.OFFLINE`.
 
-        next_offline_date (``int``, *optional*):
-            Date when a user will automatically go offline, unix time. Only available in case status is "*online*".
+        next_offline_date (:py:obj:`~datetime.datetime`, *optional*):
+            Date when a user will automatically go offline. Only available in case status is :obj:`~pyrogram.enums.UserStatus.ONLINE`.
 
         username (``str``, *optional*):
             User's or bot's username.
@@ -158,8 +159,8 @@ def __init__(
         first_name: str = None,
         last_name: str = None,
         status: str = None,
-        last_online_date: int = None,
-        next_offline_date: int = None,
+        last_online_date: datetime = None,
+        next_offline_date: datetime = None,
         username: str = None,
         language_code: str = None,
         dc_id: int = None,
@@ -247,10 +248,10 @@ def _parse_status(user_status: "raw.base.UserStatus", is_bot: bool = False):
             status = None
 
         if status == enums.UserStatus.ONLINE:
-            next_offline_date = date
+            next_offline_date = utils.timestamp_to_datetime(date)
 
         if status == enums.UserStatus.OFFLINE:
-            last_online_date = date
+            last_online_date = utils.timestamp_to_datetime(date)
 
         return {
             "status": status,
diff --git a/pyrogram/types/user_and_chats/voice_chat_scheduled.py b/pyrogram/types/user_and_chats/voice_chat_scheduled.py
index 82d2125e7f..0bb00bc509 100644
--- a/pyrogram/types/user_and_chats/voice_chat_scheduled.py
+++ b/pyrogram/types/user_and_chats/voice_chat_scheduled.py
@@ -16,7 +16,9 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-from pyrogram import raw
+from datetime import datetime
+
+from pyrogram import raw, utils
 from ..object import Object
 
 
@@ -24,13 +26,13 @@ class VoiceChatScheduled(Object):
     """A service message about a voice chat scheduled in the chat.
 
     Parameters:
-        start_date (``int``):
-            Point in time (Unix timestamp) when the voice chat is supposed to be started by a chat administrator.
+        start_date (:py:obj:`~datetime.datetime`):
+            Point in time when the voice chat is supposed to be started by a chat administrator.
     """
 
     def __init__(
         self, *,
-        start_date: int
+        start_date: datetime
     ):
         super().__init__()
 
@@ -38,4 +40,4 @@ def __init__(
 
     @staticmethod
     def _parse(action: "raw.types.MessageActionGroupCallScheduled") -> "VoiceChatScheduled":
-        return VoiceChatScheduled(start_date=action.schedule_date)
+        return VoiceChatScheduled(start_date=utils.timestamp_to_datetime(action.schedule_date))
diff --git a/pyrogram/utils.py b/pyrogram/utils.py
index 6a0c8bac91..94a549467f 100644
--- a/pyrogram/utils.py
+++ b/pyrogram/utils.py
@@ -23,11 +23,12 @@
 import os
 import struct
 from concurrent.futures.thread import ThreadPoolExecutor
+from datetime import datetime
 from getpass import getpass
 from typing import Union, List, Dict, Optional
 
 import pyrogram
-from pyrogram import raw
+from pyrogram import raw, enums
 from pyrogram import types
 from pyrogram.file_id import FileId, FileType, PHOTO_TYPES, DOCUMENT_TYPES
 
@@ -294,7 +295,7 @@ def compute_password_check(r: raw.types.account.Password, password: str) -> raw.
 async def parse_text_entities(
     client: "pyrogram.Client",
     text: str,
-    parse_mode: str,
+    parse_mode: enums.ParseMode,
     entities: List["types.MessageEntity"]
 ) -> Dict[str, raw.base.MessageEntity]:
     if entities:
@@ -310,3 +311,11 @@ async def parse_text_entities(
         "message": text,
         "entities": entities
     }
+
+
+def timestamp_to_datetime(ts: Optional[int]) -> Optional[datetime]:
+    return datetime.fromtimestamp(ts) if ts else None
+
+
+def datetime_to_timestamp(dt: Optional[datetime]) -> Optional[int]:
+    return int(dt.timestamp()) if dt else None

From 274650cda9741fb9da6ae41098a9611e64567210 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 24 Apr 2022 11:56:06 +0200
Subject: [PATCH 0786/1185] Rename Message.message_id to Message.id

---
 pyrogram/methods/chats/iter_dialogs.py        |   2 +-
 pyrogram/methods/messages/iter_history.py     |   2 +-
 pyrogram/methods/messages/search_global.py    |   2 +-
 pyrogram/methods/messages/send_message.py     |   2 +-
 .../bots_and_keyboards/callback_query.py      |   6 +-
 pyrogram/types/messages_and_media/message.py  | 100 +++++++++---------
 pyrogram/utils.py                             |   8 +-
 7 files changed, 61 insertions(+), 61 deletions(-)

diff --git a/pyrogram/methods/chats/iter_dialogs.py b/pyrogram/methods/chats/iter_dialogs.py
index 2584d98d63..4553e3d47e 100644
--- a/pyrogram/methods/chats/iter_dialogs.py
+++ b/pyrogram/methods/chats/iter_dialogs.py
@@ -93,7 +93,7 @@ async def iter_dialogs(
 
             last = dialogs[-1]
 
-            offset_id = last.top_message.message_id
+            offset_id = last.top_message.id
             offset_date = last.top_message.date
             offset_peer = await self.resolve_peer(last.chat.id)
 
diff --git a/pyrogram/methods/messages/iter_history.py b/pyrogram/methods/messages/iter_history.py
index b9dd20098c..c5eda28871 100644
--- a/pyrogram/methods/messages/iter_history.py
+++ b/pyrogram/methods/messages/iter_history.py
@@ -89,7 +89,7 @@ async def iter_history(
             if not messages:
                 return
 
-            offset_id = messages[-1].message_id + (1 if reverse else 0)
+            offset_id = messages[-1].id + (1 if reverse else 0)
 
             for message in messages:
                 yield message
diff --git a/pyrogram/methods/messages/search_global.py b/pyrogram/methods/messages/search_global.py
index 2c22b26f79..6dff7bf0af 100644
--- a/pyrogram/methods/messages/search_global.py
+++ b/pyrogram/methods/messages/search_global.py
@@ -102,7 +102,7 @@ async def search_global(
 
             offset_date = last.date
             offset_peer = await self.resolve_peer(last.chat.id)
-            offset_id = last.message_id
+            offset_id = last.id
 
             for message in messages:
                 yield message
diff --git a/pyrogram/methods/messages/send_message.py b/pyrogram/methods/messages/send_message.py
index a094ba9214..34063625dd 100644
--- a/pyrogram/methods/messages/send_message.py
+++ b/pyrogram/methods/messages/send_message.py
@@ -149,7 +149,7 @@ async def send_message(
             )
 
             return types.Message(
-                message_id=r.id,
+                id=r.id,
                 chat=types.Chat(
                     id=peer_id,
                     type=enums.ChatType.PRIVATE,
diff --git a/pyrogram/types/bots_and_keyboards/callback_query.py b/pyrogram/types/bots_and_keyboards/callback_query.py
index e5db872359..436b6076ea 100644
--- a/pyrogram/types/bots_and_keyboards/callback_query.py
+++ b/pyrogram/types/bots_and_keyboards/callback_query.py
@@ -203,7 +203,7 @@ async def edit_message_text(
         if self.inline_message_id is None:
             return await self._client.edit_message_text(
                 chat_id=self.message.chat.id,
-                message_id=self.message.message_id,
+                message_id=self.message.id,
                 text=text,
                 parse_mode=parse_mode,
                 disable_web_page_preview=disable_web_page_preview,
@@ -274,7 +274,7 @@ async def edit_message_media(
         if self.inline_message_id is None:
             return await self._client.edit_message_media(
                 chat_id=self.message.chat.id,
-                message_id=self.message.message_id,
+                message_id=self.message.id,
                 media=media,
                 reply_markup=reply_markup
             )
@@ -307,7 +307,7 @@ async def edit_message_reply_markup(
         if self.inline_message_id is None:
             return await self._client.edit_message_reply_markup(
                 chat_id=self.message.chat.id,
-                message_id=self.message.message_id,
+                message_id=self.message.id,
                 reply_markup=reply_markup
             )
         else:
diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py
index 86c340aea6..56ffd691bd 100644
--- a/pyrogram/types/messages_and_media/message.py
+++ b/pyrogram/types/messages_and_media/message.py
@@ -60,7 +60,7 @@ class Message(Object, Update):
     """A message.
 
     Parameters:
-        message_id (``int``):
+        id (``int``):
             Unique message identifier inside this chat.
 
         from_user (:obj:`~pyrogram.types.User`, *optional*):
@@ -302,7 +302,7 @@ def __init__(
         self,
         *,
         client: "pyrogram.Client" = None,
-        message_id: int,
+        id: int,
         from_user: "types.User" = None,
         sender_chat: "types.Chat" = None,
         date: datetime = None,
@@ -376,7 +376,7 @@ def __init__(
     ):
         super().__init__(client)
 
-        self.message_id = message_id
+        self.id = id
         self.from_user = from_user
         self.sender_chat = sender_chat
         self.date = date
@@ -453,7 +453,7 @@ async def _parse(
         replies: int = 1
     ):
         if isinstance(message, raw.types.MessageEmpty):
-            return Message(message_id=message.id, empty=True, client=client)
+            return Message(id=message.id, empty=True, client=client)
 
         from_id = utils.get_raw_peer_id(message.from_id)
         peer_id = utils.get_raw_peer_id(message.peer_id)
@@ -542,7 +542,7 @@ async def _parse(
             sender_chat = types.Chat._parse(client, message, users, chats, is_chat=False) if not from_user else None
 
             parsed_message = Message(
-                message_id=message.id,
+                id=message.id,
                 date=utils.timestamp_to_datetime(message.date),
                 chat=types.Chat._parse(client, message, users, chats, is_chat=True),
                 from_user=from_user,
@@ -739,7 +739,7 @@ async def _parse(
                          for r in message.reactions.results] if message.reactions else None
 
             parsed_message = Message(
-                message_id=message.id,
+                id=message.id,
                 date=utils.timestamp_to_datetime(message.date),
                 chat=types.Chat._parse(client, message, users, chats, is_chat=True),
                 from_user=from_user,
@@ -823,9 +823,9 @@ def link(self) -> str:
             self.chat.type in (enums.ChatType.GROUP, enums.ChatType.SUPERGROUP, enums.ChatType.CHANNEL)
             and self.chat.username
         ):
-            return f"https://t.me/{self.chat.username}/{self.message_id}"
+            return f"https://t.me/{self.chat.username}/{self.id}"
         else:
-            return f"https://t.me/c/{utils.get_channel_id(self.chat.id)}/{self.message_id}"
+            return f"https://t.me/c/{utils.get_channel_id(self.chat.id)}/{self.id}"
 
     async def get_media_group(self) -> List["types.Message"]:
         """Bound method *get_media_group* of :obj:`~pyrogram.types.Message`.
@@ -836,7 +836,7 @@ async def get_media_group(self) -> List["types.Message"]:
 
             client.get_media_group(
                 chat_id=message.chat.id,
-                message_id=message.message_id
+                message_id=message.id
             )
             
         Example:
@@ -853,7 +853,7 @@ async def get_media_group(self) -> List["types.Message"]:
 
         return await self._client.get_media_group(
             chat_id=self.chat.id,
-            message_id=self.message_id
+            message_id=self.id
         )
 
     async def reply_text(
@@ -880,7 +880,7 @@ async def reply_text(
             client.send_message(
                 chat_id=message.chat.id,
                 text="hello",
-                reply_to_message_id=message.message_id
+                reply_to_message_id=message.id
             )
 
         Example:
@@ -934,7 +934,7 @@ async def reply_text(
             quote = self.chat.type != "private"
 
         if reply_to_message_id is None and quote:
-            reply_to_message_id = self.message_id
+            reply_to_message_id = self.id
 
         return await self._client.send_message(
             chat_id=self.chat.id,
@@ -1071,7 +1071,7 @@ async def reply_animation(
             quote = self.chat.type != "private"
 
         if reply_to_message_id is None and quote:
-            reply_to_message_id = self.message_id
+            reply_to_message_id = self.id
 
         return await self._client.send_animation(
             chat_id=self.chat.id,
@@ -1210,7 +1210,7 @@ async def reply_audio(
             quote = self.chat.type != "private"
 
         if reply_to_message_id is None and quote:
-            reply_to_message_id = self.message_id
+            reply_to_message_id = self.id
 
         return await self._client.send_audio(
             chat_id=self.chat.id,
@@ -1302,7 +1302,7 @@ async def reply_cached_media(
             quote = self.chat.type != "private"
 
         if reply_to_message_id is None and quote:
-            reply_to_message_id = self.message_id
+            reply_to_message_id = self.id
 
         return await self._client.send_cached_media(
             chat_id=self.chat.id,
@@ -1425,7 +1425,7 @@ async def reply_contact(
             quote = self.chat.type != "private"
 
         if reply_to_message_id is None and quote:
-            reply_to_message_id = self.message_id
+            reply_to_message_id = self.id
 
         return await self._client.send_contact(
             chat_id=self.chat.id,
@@ -1561,7 +1561,7 @@ async def reply_document(
             quote = self.chat.type != "private"
 
         if reply_to_message_id is None and quote:
-            reply_to_message_id = self.message_id
+            reply_to_message_id = self.id
 
         return await self._client.send_document(
             chat_id=self.chat.id,
@@ -1639,7 +1639,7 @@ async def reply_game(
             quote = self.chat.type != "private"
 
         if reply_to_message_id is None and quote:
-            reply_to_message_id = self.message_id
+            reply_to_message_id = self.id
 
         return await self._client.send_game(
             chat_id=self.chat.id,
@@ -1703,7 +1703,7 @@ async def reply_inline_bot_result(
             quote = self.chat.type != "private"
 
         if reply_to_message_id is None and quote:
-            reply_to_message_id = self.message_id
+            reply_to_message_id = self.id
 
         return await self._client.send_inline_bot_result(
             chat_id=self.chat.id,
@@ -1777,7 +1777,7 @@ async def reply_location(
             quote = self.chat.type != "private"
 
         if reply_to_message_id is None and quote:
-            reply_to_message_id = self.message_id
+            reply_to_message_id = self.id
 
         return await self._client.send_location(
             chat_id=self.chat.id,
@@ -1840,7 +1840,7 @@ async def reply_media_group(
             quote = self.chat.type != "private"
 
         if reply_to_message_id is None and quote:
-            reply_to_message_id = self.message_id
+            reply_to_message_id = self.id
 
         return await self._client.send_media_group(
             chat_id=self.chat.id,
@@ -1956,7 +1956,7 @@ async def reply_photo(
             quote = self.chat.type != "private"
 
         if reply_to_message_id is None and quote:
-            reply_to_message_id = self.message_id
+            reply_to_message_id = self.id
 
         return await self._client.send_photo(
             chat_id=self.chat.id,
@@ -2060,7 +2060,7 @@ async def reply_poll(
             quote = self.chat.type != "private"
 
         if reply_to_message_id is None and quote:
-            reply_to_message_id = self.message_id
+            reply_to_message_id = self.id
 
         return await self._client.send_poll(
             chat_id=self.chat.id,
@@ -2164,7 +2164,7 @@ async def reply_sticker(
             quote = self.chat.type != "private"
 
         if reply_to_message_id is None and quote:
-            reply_to_message_id = self.message_id
+            reply_to_message_id = self.id
 
         return await self._client.send_sticker(
             chat_id=self.chat.id,
@@ -2259,7 +2259,7 @@ async def reply_venue(
             quote = self.chat.type != "private"
 
         if reply_to_message_id is None and quote:
-            reply_to_message_id = self.message_id
+            reply_to_message_id = self.id
 
         return await self._client.send_venue(
             chat_id=self.chat.id,
@@ -2404,7 +2404,7 @@ async def reply_video(
             quote = self.chat.type != "private"
 
         if reply_to_message_id is None and quote:
-            reply_to_message_id = self.message_id
+            reply_to_message_id = self.id
 
         return await self._client.send_video(
             chat_id=self.chat.id,
@@ -2528,7 +2528,7 @@ async def reply_video_note(
             quote = self.chat.type != "private"
 
         if reply_to_message_id is None and quote:
-            reply_to_message_id = self.message_id
+            reply_to_message_id = self.id
 
         return await self._client.send_video_note(
             chat_id=self.chat.id,
@@ -2648,7 +2648,7 @@ async def reply_voice(
             quote = self.chat.type != "private"
 
         if reply_to_message_id is None and quote:
-            reply_to_message_id = self.message_id
+            reply_to_message_id = self.id
 
         return await self._client.send_voice(
             chat_id=self.chat.id,
@@ -2682,7 +2682,7 @@ async def edit_text(
 
             client.edit_message_text(
                 chat_id=message.chat.id,
-                message_id=message.message_id,
+                message_id=message.id,
                 text="hello"
             )
 
@@ -2716,7 +2716,7 @@ async def edit_text(
         """
         return await self._client.edit_message_text(
             chat_id=self.chat.id,
-            message_id=self.message_id,
+            message_id=self.id,
             text=text,
             parse_mode=parse_mode,
             entities=entities,
@@ -2741,7 +2741,7 @@ async def edit_caption(
 
             client.edit_message_caption(
                 chat_id=message.chat.id,
-                message_id=message.message_id,
+                message_id=message.id,
                 caption="hello"
             )
 
@@ -2772,7 +2772,7 @@ async def edit_caption(
         """
         return await self._client.edit_message_caption(
             chat_id=self.chat.id,
-            message_id=self.message_id,
+            message_id=self.id,
             caption=caption,
             parse_mode=parse_mode,
             caption_entities=caption_entities,
@@ -2792,7 +2792,7 @@ async def edit_media(
 
             client.edit_message_media(
                 chat_id=message.chat.id,
-                message_id=message.message_id,
+                message_id=message.id,
                 media=media
             )
 
@@ -2816,7 +2816,7 @@ async def edit_media(
         """
         return await self._client.edit_message_media(
             chat_id=self.chat.id,
-            message_id=self.message_id,
+            message_id=self.id,
             media=media,
             reply_markup=reply_markup
         )
@@ -2830,7 +2830,7 @@ async def edit_reply_markup(self, reply_markup: "types.InlineKeyboardMarkup" = N
 
             client.edit_message_reply_markup(
                 chat_id=message.chat.id,
-                message_id=message.message_id,
+                message_id=message.id,
                 reply_markup=inline_reply_markup
             )
 
@@ -2852,7 +2852,7 @@ async def edit_reply_markup(self, reply_markup: "types.InlineKeyboardMarkup" = N
         """
         return await self._client.edit_message_reply_markup(
             chat_id=self.chat.id,
-            message_id=self.message_id,
+            message_id=self.id,
             reply_markup=reply_markup
         )
 
@@ -2871,7 +2871,7 @@ async def forward(
             client.forward_messages(
                 chat_id=chat_id,
                 from_chat_id=message.chat.id,
-                message_ids=message.message_id
+                message_ids=message.id
             )
 
         Example:
@@ -2901,7 +2901,7 @@ async def forward(
         return await self._client.forward_messages(
             chat_id=chat_id,
             from_chat_id=self.chat.id,
-            message_ids=self.message_id,
+            message_ids=self.id,
             disable_notification=disable_notification,
             schedule_date=schedule_date
         )
@@ -2932,7 +2932,7 @@ async def copy(
             client.copy_message(
                 chat_id=chat_id,
                 from_chat_id=message.chat.id,
-                message_id=message.message_id
+                message_id=message.id
             )
 
         Example:
@@ -2985,10 +2985,10 @@ async def copy(
         """
         if self.service:
             log.warning(f"Service messages cannot be copied. "
-                        f"chat_id: {self.chat.id}, message_id: {self.message_id}")
+                        f"chat_id: {self.chat.id}, message_id: {self.id}")
         elif self.game and not await self._client.storage.is_bot():
             log.warning(f"Users cannot send messages with Game media type. "
-                        f"chat_id: {self.chat.id}, message_id: {self.message_id}")
+                        f"chat_id: {self.chat.id}, message_id: {self.id}")
         elif self.empty:
             log.warning(f"Empty messages cannot be copied. ")
         elif self.text:
@@ -3102,7 +3102,7 @@ async def delete(self, revoke: bool = True):
 
             client.delete_messages(
                 chat_id=chat_id,
-                message_ids=message.message_id
+                message_ids=message.id
             )
 
         Example:
@@ -3125,7 +3125,7 @@ async def delete(self, revoke: bool = True):
         """
         return await self._client.delete_messages(
             chat_id=self.chat.id,
-            message_ids=self.message_id,
+            message_ids=self.id,
             revoke=revoke
         )
 
@@ -3140,7 +3140,7 @@ async def click(self, x: Union[int, str] = 0, y: int = None, quote: bool = None,
 
             client.request_callback_answer(
                 chat_id=message.chat.id,
-                message_id=message.message_id,
+                message_id=message.id,
                 callback_data=message.reply_markup[i][j].callback_data
             )
 
@@ -3235,7 +3235,7 @@ async def click(self, x: Union[int, str] = 0, y: int = None, quote: bool = None,
             if button.callback_data:
                 return await self._client.request_callback_answer(
                     chat_id=self.chat.id,
-                    message_id=self.message_id,
+                    message_id=self.id,
                     callback_data=button.callback_data,
                     timeout=timeout
                 )
@@ -3314,7 +3314,7 @@ async def retract_vote(
 
         return await self._client.retract_vote(
             chat_id=self.chat.id,
-            message_id=self.message_id
+            message_id=self.id
         )
 
     async def download(
@@ -3397,7 +3397,7 @@ async def vote(
 
             client.vote_poll(
                 chat_id=message.chat.id,
-                message_id=message.message_id,
+                message_id=message.id,
                 option=1
             )
 
@@ -3419,7 +3419,7 @@ async def vote(
 
         return await self._client.vote_poll(
             chat_id=self.chat.id,
-            message_id=self.message_id,
+            message_id=self.id,
             options=option
         )
 
@@ -3457,7 +3457,7 @@ async def pin(self, disable_notification: bool = False, both_sides: bool = False
         """
         return await self._client.pin_chat_message(
             chat_id=self.chat.id,
-            message_id=self.message_id,
+            message_id=self.id,
             disable_notification=disable_notification,
             both_sides=both_sides
         )
@@ -3487,5 +3487,5 @@ async def unpin(self) -> bool:
         """
         return await self._client.unpin_chat_message(
             chat_id=self.chat.id,
-            message_id=self.message_id
+            message_id=self.id
         )
diff --git a/pyrogram/utils.py b/pyrogram/utils.py
index 94a549467f..60f6ca9d8a 100644
--- a/pyrogram/utils.py
+++ b/pyrogram/utils.py
@@ -115,10 +115,10 @@ async def parse_messages(client, messages: "raw.types.messages.Messages", replie
             )
 
             for message in parsed_messages:
-                reply_id = messages_with_replies.get(message.message_id, None)
+                reply_id = messages_with_replies.get(message.id, None)
 
                 for reply in reply_messages:
-                    if reply.message_id == reply_id:
+                    if reply.id == reply_id:
                         message.reply_to_message = reply
 
     return types.List(parsed_messages)
@@ -133,10 +133,10 @@ def parse_deleted_messages(client, update) -> List["types.Message"]:
     for message in messages:
         parsed_messages.append(
             types.Message(
-                message_id=message,
+                id=message,
                 chat=types.Chat(
                     id=get_channel_id(channel_id),
-                    type="channel",
+                    type=enums.ChatType.CHANNEL,
                     client=client
                 ) if channel_id is not None else None,
                 client=client

From 4ebf5cf7e970187aded2f981de5ab4e0c522e144 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 24 Apr 2022 11:56:06 +0200
Subject: [PATCH 0787/1185] Remove ability to access attributes via bracket
 notation

---
 pyrogram/raw/core/tl_object.py | 6 ------
 pyrogram/types/object.py       | 6 ------
 2 files changed, 12 deletions(-)

diff --git a/pyrogram/raw/core/tl_object.py b/pyrogram/raw/core/tl_object.py
index 7cb75b0eb5..ff67566ea8 100644
--- a/pyrogram/raw/core/tl_object.py
+++ b/pyrogram/raw/core/tl_object.py
@@ -78,11 +78,5 @@ def __eq__(self, other: Any) -> bool:
     def __len__(self) -> int:
         return len(self.write())
 
-    def __getitem__(self, item: Any) -> Any:
-        return getattr(self, item)
-
-    def __setitem__(self, key: Any, value: Any) -> Any:
-        setattr(self, key, value)
-
     def __call__(self, *args: Any, **kwargs: Any) -> Any:
         pass
diff --git a/pyrogram/types/object.py b/pyrogram/types/object.py
index a3641b1614..601bcd6e6f 100644
--- a/pyrogram/types/object.py
+++ b/pyrogram/types/object.py
@@ -94,12 +94,6 @@ def __eq__(self, other: "Object") -> bool:
 
         return True
 
-    def __getitem__(self, item):
-        return getattr(self, item)
-
-    def __setitem__(self, key, value):
-        setattr(self, key, value)
-
     def __getstate__(self):
         new_dict = self.__dict__.copy()
         new_dict.pop("_client", None)

From 32624ef5e6954804db88fc1723654bc27bc5a665 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 24 Apr 2022 11:56:06 +0200
Subject: [PATCH 0788/1185] Improve type hints

---
 compiler/api/template/combinator.txt          |   2 +-
 pyrogram/client.py                            |  58 ++++-
 pyrogram/filters.py                           |   2 +-
 pyrogram/handlers/callback_query_handler.py   |   6 +-
 .../handlers/chat_join_request_handler.py     |   6 +-
 .../handlers/chat_member_updated_handler.py   |   6 +-
 .../handlers/chosen_inline_result_handler.py  |   6 +-
 pyrogram/handlers/deleted_messages_handler.py |   2 +-
 pyrogram/handlers/disconnect_handler.py       |   6 +-
 pyrogram/handlers/inline_query_handler.py     |   6 +-
 pyrogram/handlers/message_handler.py          |   6 +-
 pyrogram/handlers/poll_handler.py             |   6 +-
 pyrogram/handlers/raw_update_handler.py       |   6 +-
 pyrogram/handlers/user_status_handler.py      |   6 +-
 pyrogram/methods/advanced/resolve_peer.py     |   6 +-
 pyrogram/methods/advanced/save_file.py        |  12 +-
 pyrogram/methods/advanced/send.py             |   6 +-
 .../methods/auth/accept_terms_of_service.py   |   9 +-
 pyrogram/methods/auth/check_password.py       |   9 +-
 pyrogram/methods/auth/connect.py              |   8 +-
 pyrogram/methods/auth/disconnect.py           |   8 +-
 pyrogram/methods/auth/get_password_hint.py    |   8 +-
 pyrogram/methods/auth/initialize.py           |   8 +-
 pyrogram/methods/auth/log_out.py              |   8 +-
 pyrogram/methods/auth/recover_password.py     |   9 +-
 pyrogram/methods/auth/resend_code.py          |  10 +-
 pyrogram/methods/auth/send_code.py            |   9 +-
 pyrogram/methods/auth/send_recovery_code.py   |   8 +-
 pyrogram/methods/auth/sign_in.py              |   6 +-
 pyrogram/methods/auth/sign_in_bot.py          |   9 +-
 pyrogram/methods/auth/sign_up.py              |   6 +-
 pyrogram/methods/auth/terminate.py            |   8 +-
 .../methods/bots/answer_callback_query.py     |   6 +-
 pyrogram/methods/bots/answer_inline_query.py  |   6 +-
 pyrogram/methods/bots/get_game_high_scores.py |   6 +-
 .../methods/bots/get_inline_bot_results.py    |   6 +-
 .../methods/bots/request_callback_answer.py   |   6 +-
 pyrogram/methods/bots/send_game.py            |   6 +-
 .../methods/bots/send_inline_bot_result.py    |   6 +-
 pyrogram/methods/bots/set_bot_commands.py     |   7 +-
 pyrogram/methods/bots/set_game_score.py       |   6 +-
 pyrogram/methods/chats/add_chat_members.py    |   6 +-
 pyrogram/methods/chats/archive_chats.py       |   6 +-
 pyrogram/methods/chats/ban_chat_member.py     |   6 +-
 pyrogram/methods/chats/create_channel.py      |   7 +-
 pyrogram/methods/chats/create_group.py        |   6 +-
 pyrogram/methods/chats/create_supergroup.py   |   7 +-
 pyrogram/methods/chats/delete_channel.py      |   9 +-
 pyrogram/methods/chats/delete_chat_photo.py   |   6 +-
 pyrogram/methods/chats/delete_supergroup.py   |   9 +-
 pyrogram/methods/chats/delete_user_history.py |   6 +-
 pyrogram/methods/chats/get_chat.py            |   6 +-
 pyrogram/methods/chats/get_chat_event_log.py  |   6 +-
 pyrogram/methods/chats/get_chat_member.py     |   6 +-
 pyrogram/methods/chats/get_chat_members.py    |  18 +-
 .../methods/chats/get_chat_members_count.py   |   6 +-
 .../methods/chats/get_chat_online_count.py    |   9 +-
 pyrogram/methods/chats/get_dialogs.py         |   6 +-
 pyrogram/methods/chats/get_dialogs_count.py   |   9 +-
 pyrogram/methods/chats/get_nearby_chats.py    |   6 +-
 pyrogram/methods/chats/get_send_as_chats.py   |   6 +-
 pyrogram/methods/chats/iter_chat_members.py   |   6 +-
 pyrogram/methods/chats/iter_dialogs.py        |   6 +-
 pyrogram/methods/chats/join_chat.py           |   6 +-
 pyrogram/methods/chats/leave_chat.py          |   6 +-
 pyrogram/methods/chats/mark_chat_unread.py    |   6 +-
 pyrogram/methods/chats/pin_chat_message.py    |   6 +-
 pyrogram/methods/chats/promote_chat_member.py |   6 +-
 .../methods/chats/restrict_chat_member.py     |   6 +-
 .../methods/chats/set_administrator_title.py  |   6 +-
 .../methods/chats/set_chat_description.py     |   6 +-
 .../methods/chats/set_chat_permissions.py     |   6 +-
 pyrogram/methods/chats/set_chat_photo.py      |   6 +-
 .../chats/set_chat_protected_content.py       |   6 +-
 pyrogram/methods/chats/set_chat_title.py      |   6 +-
 pyrogram/methods/chats/set_chat_username.py   |   6 +-
 pyrogram/methods/chats/set_send_as_chat.py    |   6 +-
 pyrogram/methods/chats/set_slow_mode.py       |   6 +-
 pyrogram/methods/chats/unarchive_chats.py     |   6 +-
 pyrogram/methods/chats/unban_chat_member.py   |   6 +-
 .../methods/chats/unpin_all_chat_messages.py  |   6 +-
 pyrogram/methods/chats/unpin_chat_message.py  |   6 +-
 pyrogram/methods/contacts/add_contact.py      |   6 +-
 pyrogram/methods/contacts/delete_contacts.py  |   6 +-
 pyrogram/methods/contacts/get_contacts.py     |   8 +-
 .../methods/contacts/get_contacts_count.py    |   8 +-
 pyrogram/methods/contacts/import_contacts.py  |   6 +-
 .../methods/decorators/on_callback_query.py   |   5 +-
 .../decorators/on_chat_join_request.py        |   5 +-
 .../decorators/on_chat_member_updated.py      |   5 +-
 .../decorators/on_chosen_inline_result.py     |   5 +-
 .../methods/decorators/on_deleted_messages.py |   5 +-
 pyrogram/methods/decorators/on_disconnect.py  |   5 +-
 .../methods/decorators/on_inline_query.py     |   5 +-
 pyrogram/methods/decorators/on_message.py     |   5 +-
 pyrogram/methods/decorators/on_poll.py        |   5 +-
 pyrogram/methods/decorators/on_raw_update.py  |   5 +-
 pyrogram/methods/decorators/on_user_status.py |   5 +-
 .../invite_links/approve_chat_join_request.py |   6 +-
 .../invite_links/create_chat_invite_link.py   |   6 +-
 .../invite_links/decline_chat_join_request.py |   6 +-
 .../delete_chat_admin_invite_links.py         |   6 +-
 .../invite_links/delete_chat_invite_link.py   |   6 +-
 .../invite_links/edit_chat_invite_link.py     |   6 +-
 .../invite_links/export_chat_invite_link.py   |   6 +-
 .../get_chat_admin_invite_links.py            |   6 +-
 .../get_chat_admin_invite_links_count.py      |   6 +-
 .../get_chat_admins_with_invite_links.py      |   6 +-
 .../invite_links/get_chat_invite_link.py      |   6 +-
 .../get_chat_invite_link_members.py           |   6 +-
 .../get_chat_invite_link_members_count.py     |   6 +-
 .../invite_links/revoke_chat_invite_link.py   |   6 +-
 pyrogram/methods/messages/copy_media_group.py |   6 +-
 pyrogram/methods/messages/copy_message.py     |   6 +-
 pyrogram/methods/messages/delete_messages.py  |   6 +-
 pyrogram/methods/messages/download_media.py   |  12 +-
 .../methods/messages/edit_inline_caption.py   |   6 +-
 .../methods/messages/edit_inline_media.py     |   6 +-
 .../messages/edit_inline_reply_markup.py      |   6 +-
 pyrogram/methods/messages/edit_inline_text.py |   6 +-
 .../methods/messages/edit_message_caption.py  |   6 +-
 .../methods/messages/edit_message_media.py    |   6 +-
 .../messages/edit_message_reply_markup.py     |   6 +-
 .../methods/messages/edit_message_text.py     |   6 +-
 pyrogram/methods/messages/forward_messages.py |   6 +-
 .../messages/get_discussion_message.py        |   6 +-
 pyrogram/methods/messages/get_history.py      |   6 +-
 .../methods/messages/get_history_count.py     |   6 +-
 pyrogram/methods/messages/get_media_group.py  |   6 +-
 pyrogram/methods/messages/get_messages.py     |   6 +-
 pyrogram/methods/messages/iter_history.py     |   6 +-
 pyrogram/methods/messages/read_history.py     |   6 +-
 pyrogram/methods/messages/retract_vote.py     |   6 +-
 pyrogram/methods/messages/search_global.py    |   6 +-
 .../methods/messages/search_global_count.py   |   6 +-
 pyrogram/methods/messages/search_messages.py  |   8 +-
 .../methods/messages/search_messages_count.py |   6 +-
 pyrogram/methods/messages/send_animation.py   |  12 +-
 pyrogram/methods/messages/send_audio.py       |  14 +-
 .../methods/messages/send_cached_media.py     |   6 +-
 pyrogram/methods/messages/send_chat_action.py |   5 +-
 pyrogram/methods/messages/send_contact.py     |   8 +-
 pyrogram/methods/messages/send_dice.py        |  10 +-
 pyrogram/methods/messages/send_document.py    |  12 +-
 pyrogram/methods/messages/send_location.py    |   8 +-
 pyrogram/methods/messages/send_media_group.py |   6 +-
 pyrogram/methods/messages/send_message.py     |   9 +-
 pyrogram/methods/messages/send_photo.py       |  11 +-
 pyrogram/methods/messages/send_poll.py        |   8 +-
 pyrogram/methods/messages/send_reaction.py    |   6 +-
 pyrogram/methods/messages/send_sticker.py     |  12 +-
 pyrogram/methods/messages/send_venue.py       |   8 +-
 pyrogram/methods/messages/send_video.py       |  12 +-
 pyrogram/methods/messages/send_video_note.py  |  12 +-
 pyrogram/methods/messages/send_voice.py       |  12 +-
 pyrogram/methods/messages/stop_poll.py        |   6 +-
 pyrogram/methods/messages/vote_poll.py        |   6 +-
 .../methods/password/change_cloud_password.py |   6 +-
 .../methods/password/enable_cloud_password.py |   6 +-
 .../methods/password/remove_cloud_password.py |   6 +-
 pyrogram/methods/users/block_user.py          |   6 +-
 .../methods/users/delete_profile_photos.py    |   6 +-
 pyrogram/methods/users/get_common_chats.py    |   9 +-
 pyrogram/methods/users/get_me.py              |   8 +-
 pyrogram/methods/users/get_profile_photos.py  |   6 +-
 .../methods/users/get_profile_photos_count.py |   9 +-
 pyrogram/methods/users/get_users.py           |   6 +-
 pyrogram/methods/users/iter_profile_photos.py |   6 +-
 pyrogram/methods/users/set_profile_photo.py   |   6 +-
 pyrogram/methods/users/set_username.py        |   6 +-
 pyrogram/methods/users/unblock_user.py        |   6 +-
 pyrogram/methods/users/update_profile.py      |   6 +-
 pyrogram/methods/utilities/add_handler.py     |  10 +-
 .../utilities/export_session_string.py        |   8 +-
 pyrogram/methods/utilities/remove_handler.py  |  10 +-
 pyrogram/methods/utilities/restart.py         |   9 +-
 pyrogram/methods/utilities/run.py             |   9 +-
 pyrogram/methods/utilities/start.py           |   8 +-
 pyrogram/methods/utilities/stop.py            |   9 +-
 .../methods/utilities/stop_transmission.py    |   3 +-
 pyrogram/scaffold.py                          | 201 ------------------
 .../inline_mode/inline_query_result_audio.py  |   2 +-
 pyrogram/types/messages_and_media/__init__.py |   2 +-
 pyrogram/types/messages_and_media/message.py  |  40 ++--
 .../types/messages_and_media/video_note.py    |   2 +-
 pyrogram/types/object.py                      |   7 +-
 pyrogram/types/user_and_chats/chat_event.py   |   8 +-
 pyrogram/types/user_and_chats/chat_member.py  |   4 +-
 pyrogram/utils.py                             |   2 +-
 189 files changed, 729 insertions(+), 815 deletions(-)
 delete mode 100644 pyrogram/scaffold.py

diff --git a/compiler/api/template/combinator.txt b/compiler/api/template/combinator.txt
index 7c02a1a8e5..fa7a76976b 100644
--- a/compiler/api/template/combinator.txt
+++ b/compiler/api/template/combinator.txt
@@ -27,7 +27,7 @@ class {name}(TLObject):  # type: ignore
         {read_types}
         return {name}({return_arguments})
 
-    def write(self) -> bytes:
+    def write(self, *args) -> bytes:
         b = BytesIO()
         b.write(Int(self.ID, False))
 
diff --git a/pyrogram/client.py b/pyrogram/client.py
index cf3b8ec3b9..cfef3a8d94 100644
--- a/pyrogram/client.py
+++ b/pyrogram/client.py
@@ -21,15 +21,19 @@
 import inspect
 import logging
 import os
+import platform
 import re
 import shutil
+import sys
 import tempfile
 from concurrent.futures.thread import ThreadPoolExecutor
 from configparser import ConfigParser
 from hashlib import sha256
 from importlib import import_module
+from io import StringIO
+from mimetypes import MimeTypes
 from pathlib import Path
-from typing import Union, List, Optional
+from typing import Union, List, Optional, Callable
 
 import pyrogram
 from pyrogram import __version__, __license__
@@ -51,12 +55,14 @@
 from pyrogram.utils import ainput
 from .dispatcher import Dispatcher
 from .file_id import FileId, FileType, ThumbnailSource
-from .scaffold import Scaffold
+from .mime_types import mime_types
+from .parser import Parser
+from .session.internals import MsgId
 
 log = logging.getLogger(__name__)
 
 
-class Client(Methods, Scaffold):
+class Client(Methods):
     """Pyrogram Client, the main means for interacting with Telegram.
 
     Parameters:
@@ -177,10 +183,26 @@ class Client(Methods, Scaffold):
             terminal environments.
     """
 
+    APP_VERSION = f"Pyrogram {__version__}"
+    DEVICE_MODEL = f"{platform.python_implementation()} {platform.python_version()}"
+    SYSTEM_VERSION = f"{platform.system()} {platform.release()}"
+
+    LANG_CODE = "en"
+
+    PARENT_DIR = Path(sys.argv[0]).parent
+
+    INVITE_LINK_RE = re.compile(r"^(?:https?://)?(?:www\.)?(?:t(?:elegram)?\.(?:org|me|dog)/(?:joinchat/|\+))([\w-]+)$")
+    WORKERS = min(32, (os.cpu_count() or 0) + 4)  # os.cpu_count() can be None
+    WORKDIR = PARENT_DIR
+    CONFIG_FILE = PARENT_DIR / "config.ini"
+
+    mimetypes = MimeTypes()
+    mimetypes.readfp(StringIO(mime_types))
+
     def __init__(
         self,
         session_name: Union[str, Storage],
-        api_id: Union[int, str] = None,
+        api_id: int = None,
         api_hash: str = None,
         app_version: str = None,
         device_model: str = None,
@@ -194,9 +216,9 @@ def __init__(
         phone_code: str = None,
         password: str = None,
         force_sms: bool = False,
-        workers: int = Scaffold.WORKERS,
-        workdir: str = Scaffold.WORKDIR,
-        config_file: str = Scaffold.CONFIG_FILE,
+        workers: int = WORKERS,
+        workdir: str = WORKDIR,
+        config_file: str = CONFIG_FILE,
         plugins: dict = None,
         parse_mode: "enums.ParseMode" = enums.ParseMode.DEFAULT,
         no_updates: bool = None,
@@ -207,7 +229,7 @@ def __init__(
         super().__init__()
 
         self.session_name = session_name
-        self.api_id = int(api_id) if api_id else None
+        self.api_id = api_id
         self.api_hash = api_hash
         self.app_version = app_version
         self.device_model = device_model
@@ -246,6 +268,24 @@ def __init__(
             raise ValueError("Unknown storage engine")
 
         self.dispatcher = Dispatcher(self)
+
+        self.rnd_id = MsgId
+
+        self.parser = Parser(self)
+        self.parse_mode = enums.ParseMode.DEFAULT
+
+        self.session = None
+
+        self.media_sessions = {}
+        self.media_sessions_lock = asyncio.Lock()
+
+        self.is_connected = None
+        self.is_initialized = None
+
+        self.takeout_id = None
+
+        self.disconnect_handler = None
+
         self.loop = asyncio.get_event_loop()
 
     def __enter__(self):
@@ -790,7 +830,7 @@ async def get_file(
         self,
         file_id: FileId,
         file_size: int,
-        progress: callable,
+        progress: Callable,
         progress_args: tuple = ()
     ) -> str:
         dc_id = file_id.dc_id
diff --git a/pyrogram/filters.py b/pyrogram/filters.py
index 276d70c299..2eeef95c59 100644
--- a/pyrogram/filters.py
+++ b/pyrogram/filters.py
@@ -127,7 +127,7 @@ def create(func: Callable, name: str = None, **kwargs) -> Filter:
     Custom filters give you extra control over which updates are allowed or not to be processed by your handlers.
 
     Parameters:
-        func (``callable``):
+        func (``Callable``):
             A function that accepts three positional arguments *(filter, client, update)* and returns a boolean: True if the
             update should be handled, False otherwise. 
             The *filter* argument refers to the filter itself and can be used to access keyword arguments (read below). 
diff --git a/pyrogram/handlers/callback_query_handler.py b/pyrogram/handlers/callback_query_handler.py
index b582d728c8..b924fffa26 100644
--- a/pyrogram/handlers/callback_query_handler.py
+++ b/pyrogram/handlers/callback_query_handler.py
@@ -16,6 +16,8 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
+from typing import Callable
+
 from .handler import Handler
 
 
@@ -27,7 +29,7 @@ class CallbackQueryHandler(Handler):
     :meth:`~pyrogram.Client.on_callback_query` decorator.
 
     Parameters:
-        callback (``callable``):
+        callback (``Callable``):
             Pass a function that will be called when a new CallbackQuery arrives. It takes *(client, callback_query)*
             as positional arguments (look at the section below for a detailed description).
 
@@ -43,5 +45,5 @@ class CallbackQueryHandler(Handler):
             The received callback query.
     """
 
-    def __init__(self, callback: callable, filters=None):
+    def __init__(self, callback: Callable, filters=None):
         super().__init__(callback, filters)
diff --git a/pyrogram/handlers/chat_join_request_handler.py b/pyrogram/handlers/chat_join_request_handler.py
index 03e2ac674e..54b8b86abb 100644
--- a/pyrogram/handlers/chat_join_request_handler.py
+++ b/pyrogram/handlers/chat_join_request_handler.py
@@ -16,6 +16,8 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
+from typing import Callable
+
 from .handler import Handler
 
 
@@ -27,7 +29,7 @@ class ChatJoinRequestHandler(Handler):
     :meth:`~pyrogram.Client.on_chat_join_request` decorator.
 
     Parameters:
-        callback (``callable``):
+        callback (``Callable``):
             Pass a function that will be called when a new ChatJoinRequest event arrives. It takes
             *(client, chat_join_request)* as positional arguments (look at the section below for a detailed
             description).
@@ -43,5 +45,5 @@ class ChatJoinRequestHandler(Handler):
             The received chat join request.
     """
 
-    def __init__(self, callback: callable, filters=None):
+    def __init__(self, callback: Callable, filters=None):
         super().__init__(callback, filters)
diff --git a/pyrogram/handlers/chat_member_updated_handler.py b/pyrogram/handlers/chat_member_updated_handler.py
index 8cfdd72696..a89e7e2b57 100644
--- a/pyrogram/handlers/chat_member_updated_handler.py
+++ b/pyrogram/handlers/chat_member_updated_handler.py
@@ -16,6 +16,8 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
+from typing import Callable
+
 from .handler import Handler
 
 
@@ -27,7 +29,7 @@ class ChatMemberUpdatedHandler(Handler):
     :meth:`~pyrogram.Client.on_chat_member_updated` decorator.
 
     Parameters:
-        callback (``callable``):
+        callback (``Callable``):
             Pass a function that will be called when a new ChatMemberUpdated event arrives. It takes
             *(client, chat_member_updated)* as positional arguments (look at the section below for a detailed
             description).
@@ -43,5 +45,5 @@ class ChatMemberUpdatedHandler(Handler):
             The received chat member update.
     """
 
-    def __init__(self, callback: callable, filters=None):
+    def __init__(self, callback: Callable, filters=None):
         super().__init__(callback, filters)
diff --git a/pyrogram/handlers/chosen_inline_result_handler.py b/pyrogram/handlers/chosen_inline_result_handler.py
index 81691a3577..1c04f8f44b 100644
--- a/pyrogram/handlers/chosen_inline_result_handler.py
+++ b/pyrogram/handlers/chosen_inline_result_handler.py
@@ -16,6 +16,8 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
+from typing import Callable
+
 from .handler import Handler
 
 
@@ -27,7 +29,7 @@ class ChosenInlineResultHandler(Handler):
     :meth:`~pyrogram.Client.on_chosen_inline_result` decorator.
 
     Parameters:
-        callback (``callable``):
+        callback (``Callable``):
             Pass a function that will be called when a new chosen inline result arrives.
             It takes *(client, chosen_inline_result)* as positional arguments (look at the section below for a
             detailed description).
@@ -44,5 +46,5 @@ class ChosenInlineResultHandler(Handler):
             The received chosen inline result.
     """
 
-    def __init__(self, callback: callable, filters=None):
+    def __init__(self, callback: Callable, filters=None):
         super().__init__(callback, filters)
diff --git a/pyrogram/handlers/deleted_messages_handler.py b/pyrogram/handlers/deleted_messages_handler.py
index a336c6503b..ab9f834705 100644
--- a/pyrogram/handlers/deleted_messages_handler.py
+++ b/pyrogram/handlers/deleted_messages_handler.py
@@ -32,7 +32,7 @@ class DeletedMessagesHandler(Handler):
     :meth:`~pyrogram.Client.on_deleted_messages` decorator.
 
     Parameters:
-        callback (``callable``):
+        callback (``Callable``):
             Pass a function that will be called when one or more messages have been deleted.
             It takes *(client, messages)* as positional arguments (look at the section below for a detailed description).
 
diff --git a/pyrogram/handlers/disconnect_handler.py b/pyrogram/handlers/disconnect_handler.py
index c471e8c7e6..7420afd67a 100644
--- a/pyrogram/handlers/disconnect_handler.py
+++ b/pyrogram/handlers/disconnect_handler.py
@@ -16,6 +16,8 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
+from typing import Callable
+
 from .handler import Handler
 
 
@@ -27,7 +29,7 @@ class DisconnectHandler(Handler):
     :meth:`~pyrogram.Client.on_disconnect` decorator.
 
     Parameters:
-        callback (``callable``):
+        callback (``Callable``):
             Pass a function that will be called when a disconnection occurs. It takes *(client)*
             as positional argument (look at the section below for a detailed description).
 
@@ -37,5 +39,5 @@ class DisconnectHandler(Handler):
             is established.
     """
 
-    def __init__(self, callback: callable):
+    def __init__(self, callback: Callable):
         super().__init__(callback)
diff --git a/pyrogram/handlers/inline_query_handler.py b/pyrogram/handlers/inline_query_handler.py
index 0ce58d17a8..f5ea23bc57 100644
--- a/pyrogram/handlers/inline_query_handler.py
+++ b/pyrogram/handlers/inline_query_handler.py
@@ -16,6 +16,8 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
+from typing import Callable
+
 from .handler import Handler
 
 
@@ -27,7 +29,7 @@ class InlineQueryHandler(Handler):
     :meth:`~pyrogram.Client.on_inline_query` decorator.
 
     Parameters:
-        callback (``callable``):
+        callback (``Callable``):
             Pass a function that will be called when a new InlineQuery arrives. It takes *(client, inline_query)*
             as positional arguments (look at the section below for a detailed description).
 
@@ -43,5 +45,5 @@ class InlineQueryHandler(Handler):
             The received inline query.
     """
 
-    def __init__(self, callback: callable, filters=None):
+    def __init__(self, callback: Callable, filters=None):
         super().__init__(callback, filters)
diff --git a/pyrogram/handlers/message_handler.py b/pyrogram/handlers/message_handler.py
index 144dbe9111..63a334fc0d 100644
--- a/pyrogram/handlers/message_handler.py
+++ b/pyrogram/handlers/message_handler.py
@@ -16,6 +16,8 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
+from typing import Callable
+
 from .handler import Handler
 
 
@@ -27,7 +29,7 @@ class MessageHandler(Handler):
     :meth:`~pyrogram.Client.on_message` decorator.
 
     Parameters:
-        callback (``callable``):
+        callback (``Callable``):
             Pass a function that will be called when a new Message arrives. It takes *(client, message)*
             as positional arguments (look at the section below for a detailed description).
 
@@ -43,5 +45,5 @@ class MessageHandler(Handler):
             The received message.
     """
 
-    def __init__(self, callback: callable, filters=None):
+    def __init__(self, callback: Callable, filters=None):
         super().__init__(callback, filters)
diff --git a/pyrogram/handlers/poll_handler.py b/pyrogram/handlers/poll_handler.py
index 0151f2b75c..332ca7ea28 100644
--- a/pyrogram/handlers/poll_handler.py
+++ b/pyrogram/handlers/poll_handler.py
@@ -16,6 +16,8 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
+from typing import Callable
+
 from .handler import Handler
 
 
@@ -28,7 +30,7 @@ class PollHandler(Handler):
     :meth:`~pyrogram.Client.on_poll` decorator.
 
     Parameters:
-        callback (``callable``):
+        callback (``Callable``):
             Pass a function that will be called when a new poll update arrives. It takes *(client, poll)*
             as positional arguments (look at the section below for a detailed description).
 
@@ -44,5 +46,5 @@ class PollHandler(Handler):
             The received poll.
     """
 
-    def __init__(self, callback: callable, filters=None):
+    def __init__(self, callback: Callable, filters=None):
         super().__init__(callback, filters)
diff --git a/pyrogram/handlers/raw_update_handler.py b/pyrogram/handlers/raw_update_handler.py
index e12e8477e4..d957083b57 100644
--- a/pyrogram/handlers/raw_update_handler.py
+++ b/pyrogram/handlers/raw_update_handler.py
@@ -16,6 +16,8 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
+from typing import Callable
+
 from .handler import Handler
 
 
@@ -27,7 +29,7 @@ class RawUpdateHandler(Handler):
     :meth:`~pyrogram.Client.on_raw_update` decorator.
 
     Parameters:
-        callback (``callable``):
+        callback (``Callable``):
             A function that will be called when a new update is received from the server. It takes
             *(client, update, users, chats)* as positional arguments (look at the section below for
             a detailed description).
@@ -61,5 +63,5 @@ class RawUpdateHandler(Handler):
         - :obj:`~pyrogram.raw.types.ChannelForbidden`
     """
 
-    def __init__(self, callback: callable):
+    def __init__(self, callback: Callable):
         super().__init__(callback)
diff --git a/pyrogram/handlers/user_status_handler.py b/pyrogram/handlers/user_status_handler.py
index caebac742b..f10871e874 100644
--- a/pyrogram/handlers/user_status_handler.py
+++ b/pyrogram/handlers/user_status_handler.py
@@ -16,6 +16,8 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
+from typing import Callable
+
 from .handler import Handler
 
 
@@ -26,7 +28,7 @@ class UserStatusHandler(Handler):
     For a nicer way to register this handler, have a look at the :meth:`~pyrogram.Client.on_user_status` decorator.
 
     Parameters:
-        callback (``callable``):
+        callback (``Callable``):
             Pass a function that will be called when a new user status update arrives. It takes *(client, user)*
             as positional arguments (look at the section below for a detailed description).
 
@@ -41,5 +43,5 @@ class UserStatusHandler(Handler):
             The user containing the updated status.
     """
 
-    def __init__(self, callback: callable, filters=None):
+    def __init__(self, callback: Callable, filters=None):
         super().__init__(callback, filters)
diff --git a/pyrogram/methods/advanced/resolve_peer.py b/pyrogram/methods/advanced/resolve_peer.py
index 2a39780cf2..db4def9aa2 100644
--- a/pyrogram/methods/advanced/resolve_peer.py
+++ b/pyrogram/methods/advanced/resolve_peer.py
@@ -20,17 +20,17 @@
 import re
 from typing import Union
 
+import pyrogram
 from pyrogram import raw
 from pyrogram import utils
 from pyrogram.errors import PeerIdInvalid
-from pyrogram.scaffold import Scaffold
 
 log = logging.getLogger(__name__)
 
 
-class ResolvePeer(Scaffold):
+class ResolvePeer:
     async def resolve_peer(
-        self,
+        self: "pyrogram.Client",
         peer_id: Union[int, str]
     ) -> Union[raw.base.InputPeer, raw.base.InputUser, raw.base.InputChannel]:
         """Get the InputPeer of a known peer id.
diff --git a/pyrogram/methods/advanced/save_file.py b/pyrogram/methods/advanced/save_file.py
index 3f6ebb68af..9c92651dba 100644
--- a/pyrogram/methods/advanced/save_file.py
+++ b/pyrogram/methods/advanced/save_file.py
@@ -25,23 +25,23 @@
 import os
 from hashlib import md5
 from pathlib import PurePath
-from typing import Union, BinaryIO
+from typing import Union, BinaryIO, Callable
 
+import pyrogram
 from pyrogram import StopTransmission
 from pyrogram import raw
-from pyrogram.scaffold import Scaffold
 from pyrogram.session import Session
 
 log = logging.getLogger(__name__)
 
 
-class SaveFile(Scaffold):
+class SaveFile:
     async def save_file(
-        self,
+        self: "pyrogram.Client",
         path: Union[str, BinaryIO],
         file_id: int = None,
         file_part: int = 0,
-        progress: callable = None,
+        progress: Callable = None,
         progress_args: tuple = ()
     ):
         """Upload a file onto Telegram servers, without actually sending the message to anyone.
@@ -64,7 +64,7 @@ async def save_file(
             file_part (``int``, *optional*):
                 In case a file part expired, pass the file_id and the file_part to retry uploading that specific chunk.
 
-            progress (``callable``, *optional*):
+            progress (``Callable``, *optional*):
                 Pass a callback function to view the file transmission progress.
                 The function must take *(current, total)* as positional arguments (look at Other Parameters below for a
                 detailed description) and will be called back each time a new file chunk has been successfully
diff --git a/pyrogram/methods/advanced/send.py b/pyrogram/methods/advanced/send.py
index 8ef0849c63..6e5551cd97 100644
--- a/pyrogram/methods/advanced/send.py
+++ b/pyrogram/methods/advanced/send.py
@@ -18,17 +18,17 @@
 
 import logging
 
+import pyrogram
 from pyrogram import raw
 from pyrogram.raw.core import TLObject
-from pyrogram.scaffold import Scaffold
 from pyrogram.session import Session
 
 log = logging.getLogger(__name__)
 
 
-class Send(Scaffold):
+class Send:
     async def send(
-        self,
+        self: "pyrogram.Client",
         data: TLObject,
         retries: int = Session.MAX_RETRIES,
         timeout: float = Session.WAIT_TIMEOUT,
diff --git a/pyrogram/methods/auth/accept_terms_of_service.py b/pyrogram/methods/auth/accept_terms_of_service.py
index 5641a8ac72..ea041d6c36 100644
--- a/pyrogram/methods/auth/accept_terms_of_service.py
+++ b/pyrogram/methods/auth/accept_terms_of_service.py
@@ -16,12 +16,15 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
+import pyrogram
 from pyrogram import raw
-from pyrogram.scaffold import Scaffold
 
 
-class AcceptTermsOfService(Scaffold):
-    async def accept_terms_of_service(self, terms_of_service_id: str) -> bool:
+class AcceptTermsOfService:
+    async def accept_terms_of_service(
+        self: "pyrogram.Client",
+        terms_of_service_id: str
+    ) -> bool:
         """Accept the given terms of service.
 
         Parameters:
diff --git a/pyrogram/methods/auth/check_password.py b/pyrogram/methods/auth/check_password.py
index 1cfa526ba0..1f1d142ced 100644
--- a/pyrogram/methods/auth/check_password.py
+++ b/pyrogram/methods/auth/check_password.py
@@ -18,16 +18,19 @@
 
 import logging
 
+import pyrogram
 from pyrogram import raw
 from pyrogram import types
-from pyrogram.scaffold import Scaffold
 from pyrogram.utils import compute_password_check
 
 log = logging.getLogger(__name__)
 
 
-class CheckPassword(Scaffold):
-    async def check_password(self, password: str) -> "types.User":
+class CheckPassword:
+    async def check_password(
+        self: "pyrogram.Client",
+        password: str
+    ) -> "types.User":
         """Check your Two-Step Verification password and log in.
 
         Parameters:
diff --git a/pyrogram/methods/auth/connect.py b/pyrogram/methods/auth/connect.py
index 82cf661f90..191a0e9376 100644
--- a/pyrogram/methods/auth/connect.py
+++ b/pyrogram/methods/auth/connect.py
@@ -16,12 +16,14 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-from pyrogram.scaffold import Scaffold
+import pyrogram
 from pyrogram.session import Session
 
 
-class Connect(Scaffold):
-    async def connect(self) -> bool:
+class Connect:
+    async def connect(
+        self: "pyrogram.Client",
+    ) -> bool:
         """
         Connect the client to Telegram servers.
 
diff --git a/pyrogram/methods/auth/disconnect.py b/pyrogram/methods/auth/disconnect.py
index ddc1e7e1cd..daa07b8353 100644
--- a/pyrogram/methods/auth/disconnect.py
+++ b/pyrogram/methods/auth/disconnect.py
@@ -16,11 +16,13 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-from pyrogram.scaffold import Scaffold
+import pyrogram
 
 
-class Disconnect(Scaffold):
-    async def disconnect(self):
+class Disconnect:
+    async def disconnect(
+        self: "pyrogram.Client",
+    ):
         """Disconnect the client from Telegram servers.
 
         Raises:
diff --git a/pyrogram/methods/auth/get_password_hint.py b/pyrogram/methods/auth/get_password_hint.py
index 6ba3f280b4..d900210681 100644
--- a/pyrogram/methods/auth/get_password_hint.py
+++ b/pyrogram/methods/auth/get_password_hint.py
@@ -18,14 +18,16 @@
 
 import logging
 
+import pyrogram
 from pyrogram import raw
-from pyrogram.scaffold import Scaffold
 
 log = logging.getLogger(__name__)
 
 
-class GetPasswordHint(Scaffold):
-    async def get_password_hint(self) -> str:
+class GetPasswordHint:
+    async def get_password_hint(
+        self: "pyrogram.Client",
+    ) -> str:
         """Get your Two-Step Verification password hint.
 
         Returns:
diff --git a/pyrogram/methods/auth/initialize.py b/pyrogram/methods/auth/initialize.py
index 0b7608818b..2ee15b6102 100644
--- a/pyrogram/methods/auth/initialize.py
+++ b/pyrogram/methods/auth/initialize.py
@@ -18,14 +18,16 @@
 
 import logging
 
-from pyrogram.scaffold import Scaffold
+import pyrogram
 from pyrogram.syncer import Syncer
 
 log = logging.getLogger(__name__)
 
 
-class Initialize(Scaffold):
-    async def initialize(self):
+class Initialize:
+    async def initialize(
+        self: "pyrogram.Client",
+    ):
         """Initialize the client by starting up workers.
 
         This method will start updates and download workers.
diff --git a/pyrogram/methods/auth/log_out.py b/pyrogram/methods/auth/log_out.py
index 7174545dc5..2f8ad019b7 100644
--- a/pyrogram/methods/auth/log_out.py
+++ b/pyrogram/methods/auth/log_out.py
@@ -18,14 +18,16 @@
 
 import logging
 
+import pyrogram
 from pyrogram import raw
-from pyrogram.scaffold import Scaffold
 
 log = logging.getLogger(__name__)
 
 
-class LogOut(Scaffold):
-    async def log_out(self):
+class LogOut:
+    async def log_out(
+        self: "pyrogram.Client",
+    ):
         """Log out from Telegram and delete the *\\*.session* file.
 
         When you log out, the current client is stopped and the storage session deleted.
diff --git a/pyrogram/methods/auth/recover_password.py b/pyrogram/methods/auth/recover_password.py
index db877ec8d6..600ac86a0b 100644
--- a/pyrogram/methods/auth/recover_password.py
+++ b/pyrogram/methods/auth/recover_password.py
@@ -18,15 +18,18 @@
 
 import logging
 
+import pyrogram
 from pyrogram import raw
 from pyrogram import types
-from pyrogram.scaffold import Scaffold
 
 log = logging.getLogger(__name__)
 
 
-class RecoverPassword(Scaffold):
-    async def recover_password(self, recovery_code: str) -> "types.User":
+class RecoverPassword:
+    async def recover_password(
+        self: "pyrogram.Client",
+        recovery_code: str
+    ) -> "types.User":
         """Recover your password with a recovery code and log in.
 
         Parameters:
diff --git a/pyrogram/methods/auth/resend_code.py b/pyrogram/methods/auth/resend_code.py
index 18e835f53d..d17cc395f8 100644
--- a/pyrogram/methods/auth/resend_code.py
+++ b/pyrogram/methods/auth/resend_code.py
@@ -18,15 +18,19 @@
 
 import logging
 
+import pyrogram
 from pyrogram import raw
 from pyrogram import types
-from pyrogram.scaffold import Scaffold
 
 log = logging.getLogger(__name__)
 
 
-class ResendCode(Scaffold):
-    async def resend_code(self, phone_number: str, phone_code_hash: str) -> "types.SentCode":
+class ResendCode:
+    async def resend_code(
+        self: "pyrogram.Client",
+        phone_number: str,
+        phone_code_hash: str
+    ) -> "types.SentCode":
         """Re-send the confirmation code using a different type.
 
         The type of the code to be re-sent is specified in the *next_type* attribute of the
diff --git a/pyrogram/methods/auth/send_code.py b/pyrogram/methods/auth/send_code.py
index 3f92ccebcb..3d42fa6b92 100644
--- a/pyrogram/methods/auth/send_code.py
+++ b/pyrogram/methods/auth/send_code.py
@@ -18,17 +18,20 @@
 
 import logging
 
+import pyrogram
 from pyrogram import raw
 from pyrogram import types
 from pyrogram.errors import PhoneMigrate, NetworkMigrate
-from pyrogram.scaffold import Scaffold
 from pyrogram.session import Session, Auth
 
 log = logging.getLogger(__name__)
 
 
-class SendCode(Scaffold):
-    async def send_code(self, phone_number: str) -> "types.SentCode":
+class SendCode:
+    async def send_code(
+        self: "pyrogram.Client",
+        phone_number: str
+    ) -> "types.SentCode":
         """Send the confirmation code to the given phone number.
 
         Parameters:
diff --git a/pyrogram/methods/auth/send_recovery_code.py b/pyrogram/methods/auth/send_recovery_code.py
index 078799783e..40d2b7ddb2 100644
--- a/pyrogram/methods/auth/send_recovery_code.py
+++ b/pyrogram/methods/auth/send_recovery_code.py
@@ -18,14 +18,16 @@
 
 import logging
 
+import pyrogram
 from pyrogram import raw
-from pyrogram.scaffold import Scaffold
 
 log = logging.getLogger(__name__)
 
 
-class SendRecoveryCode(Scaffold):
-    async def send_recovery_code(self) -> str:
+class SendRecoveryCode:
+    async def send_recovery_code(
+        self: "pyrogram.Client",
+    ) -> str:
         """Send a code to your email to recover your password.
 
         Returns:
diff --git a/pyrogram/methods/auth/sign_in.py b/pyrogram/methods/auth/sign_in.py
index 19c0fbc165..d8079c95c5 100644
--- a/pyrogram/methods/auth/sign_in.py
+++ b/pyrogram/methods/auth/sign_in.py
@@ -19,16 +19,16 @@
 import logging
 from typing import Union
 
+import pyrogram
 from pyrogram import raw
 from pyrogram import types
-from pyrogram.scaffold import Scaffold
 
 log = logging.getLogger(__name__)
 
 
-class SignIn(Scaffold):
+class SignIn:
     async def sign_in(
-        self,
+        self: "pyrogram.Client",
         phone_number: str,
         phone_code_hash: str,
         phone_code: str
diff --git a/pyrogram/methods/auth/sign_in_bot.py b/pyrogram/methods/auth/sign_in_bot.py
index 7cbcb1aca5..db4515a4ef 100644
--- a/pyrogram/methods/auth/sign_in_bot.py
+++ b/pyrogram/methods/auth/sign_in_bot.py
@@ -18,17 +18,20 @@
 
 import logging
 
+import pyrogram
 from pyrogram import raw
 from pyrogram import types
 from pyrogram.errors import UserMigrate
-from pyrogram.scaffold import Scaffold
 from pyrogram.session import Session, Auth
 
 log = logging.getLogger(__name__)
 
 
-class SignInBot(Scaffold):
-    async def sign_in_bot(self, bot_token: str) -> "types.User":
+class SignInBot:
+    async def sign_in_bot(
+        self: "pyrogram.Client",
+        bot_token: str
+    ) -> "types.User":
         """Authorize a bot using its bot token generated by BotFather.
 
         Parameters:
diff --git a/pyrogram/methods/auth/sign_up.py b/pyrogram/methods/auth/sign_up.py
index 4b18a73292..4e769ab1b4 100644
--- a/pyrogram/methods/auth/sign_up.py
+++ b/pyrogram/methods/auth/sign_up.py
@@ -18,16 +18,16 @@
 
 import logging
 
+import pyrogram
 from pyrogram import raw
 from pyrogram import types
-from pyrogram.scaffold import Scaffold
 
 log = logging.getLogger(__name__)
 
 
-class SignUp(Scaffold):
+class SignUp:
     async def sign_up(
-        self,
+        self: "pyrogram.Client",
         phone_number: str,
         phone_code_hash: str,
         first_name: str,
diff --git a/pyrogram/methods/auth/terminate.py b/pyrogram/methods/auth/terminate.py
index 46b6698263..d8cca6ba81 100644
--- a/pyrogram/methods/auth/terminate.py
+++ b/pyrogram/methods/auth/terminate.py
@@ -18,15 +18,17 @@
 
 import logging
 
+import pyrogram
 from pyrogram import raw
-from pyrogram.scaffold import Scaffold
 from pyrogram.syncer import Syncer
 
 log = logging.getLogger(__name__)
 
 
-class Terminate(Scaffold):
-    async def terminate(self):
+class Terminate:
+    async def terminate(
+        self: "pyrogram.Client",
+    ):
         """Terminate the client by shutting down workers.
 
         This method does the opposite of :meth:`~pyrogram.Client.initialize`.
diff --git a/pyrogram/methods/bots/answer_callback_query.py b/pyrogram/methods/bots/answer_callback_query.py
index 941389b74b..73d5ce5569 100644
--- a/pyrogram/methods/bots/answer_callback_query.py
+++ b/pyrogram/methods/bots/answer_callback_query.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 pyrogram
 from pyrogram import raw
-from pyrogram.scaffold import Scaffold
 
 
-class AnswerCallbackQuery(Scaffold):
+class AnswerCallbackQuery:
     async def answer_callback_query(
-        self,
+        self: "pyrogram.Client",
         callback_query_id: str,
         text: str = None,
         show_alert: bool = None,
diff --git a/pyrogram/methods/bots/answer_inline_query.py b/pyrogram/methods/bots/answer_inline_query.py
index 24e7a30c78..1751171d53 100644
--- a/pyrogram/methods/bots/answer_inline_query.py
+++ b/pyrogram/methods/bots/answer_inline_query.py
@@ -18,14 +18,14 @@
 
 from typing import Iterable
 
+import pyrogram
 from pyrogram import raw
 from pyrogram import types
-from pyrogram.scaffold import Scaffold
 
 
-class AnswerInlineQuery(Scaffold):
+class AnswerInlineQuery:
     async def answer_inline_query(
-        self,
+        self: "pyrogram.Client",
         inline_query_id: str,
         results: Iterable["types.InlineQueryResult"],
         cache_time: int = 300,
diff --git a/pyrogram/methods/bots/get_game_high_scores.py b/pyrogram/methods/bots/get_game_high_scores.py
index cd0b09ba3d..e4a2ed150c 100644
--- a/pyrogram/methods/bots/get_game_high_scores.py
+++ b/pyrogram/methods/bots/get_game_high_scores.py
@@ -18,14 +18,14 @@
 
 from typing import Union, List
 
+import pyrogram
 from pyrogram import raw
 from pyrogram import types
-from pyrogram.scaffold import Scaffold
 
 
-class GetGameHighScores(Scaffold):
+class GetGameHighScores:
     async def get_game_high_scores(
-        self,
+        self: "pyrogram.Client",
         user_id: Union[int, str],
         chat_id: Union[int, str],
         message_id: int = None
diff --git a/pyrogram/methods/bots/get_inline_bot_results.py b/pyrogram/methods/bots/get_inline_bot_results.py
index 800de93e4c..2c41fec7ec 100644
--- a/pyrogram/methods/bots/get_inline_bot_results.py
+++ b/pyrogram/methods/bots/get_inline_bot_results.py
@@ -18,14 +18,14 @@
 
 from typing import Union
 
+import pyrogram
 from pyrogram import raw
 from pyrogram.errors import UnknownError
-from pyrogram.scaffold import Scaffold
 
 
-class GetInlineBotResults(Scaffold):
+class GetInlineBotResults:
     async def get_inline_bot_results(
-        self,
+        self: "pyrogram.Client",
         bot: Union[int, str],
         query: str = "",
         offset: str = "",
diff --git a/pyrogram/methods/bots/request_callback_answer.py b/pyrogram/methods/bots/request_callback_answer.py
index 5db9dfe079..ff6ae0d3ef 100644
--- a/pyrogram/methods/bots/request_callback_answer.py
+++ b/pyrogram/methods/bots/request_callback_answer.py
@@ -18,13 +18,13 @@
 
 from typing import Union
 
+import pyrogram
 from pyrogram import raw
-from pyrogram.scaffold import Scaffold
 
 
-class RequestCallbackAnswer(Scaffold):
+class RequestCallbackAnswer:
     async def request_callback_answer(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         message_id: int,
         callback_data: Union[str, bytes],
diff --git a/pyrogram/methods/bots/send_game.py b/pyrogram/methods/bots/send_game.py
index 05b9094f58..7f9e856bb0 100644
--- a/pyrogram/methods/bots/send_game.py
+++ b/pyrogram/methods/bots/send_game.py
@@ -18,14 +18,14 @@
 
 from typing import Union
 
+import pyrogram
 from pyrogram import raw
 from pyrogram import types
-from pyrogram.scaffold import Scaffold
 
 
-class SendGame(Scaffold):
+class SendGame:
     async def send_game(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         game_short_name: str,
         disable_notification: bool = None,
diff --git a/pyrogram/methods/bots/send_inline_bot_result.py b/pyrogram/methods/bots/send_inline_bot_result.py
index ead95810b5..299aaaf6f7 100644
--- a/pyrogram/methods/bots/send_inline_bot_result.py
+++ b/pyrogram/methods/bots/send_inline_bot_result.py
@@ -18,13 +18,13 @@
 
 from typing import Union
 
+import pyrogram
 from pyrogram import raw
-from pyrogram.scaffold import Scaffold
 
 
-class SendInlineBotResult(Scaffold):
+class SendInlineBotResult:
     async def send_inline_bot_result(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         query_id: int,
         result_id: str,
diff --git a/pyrogram/methods/bots/set_bot_commands.py b/pyrogram/methods/bots/set_bot_commands.py
index c346151591..8c9baa2e6a 100644
--- a/pyrogram/methods/bots/set_bot_commands.py
+++ b/pyrogram/methods/bots/set_bot_commands.py
@@ -19,11 +19,11 @@
 from typing import List
 
 import pyrogram
-from pyrogram import raw, types
-from pyrogram.scaffold import Scaffold
+from pyrogram import raw
+from pyrogram import types
 
 
-class SetBotCommands(Scaffold):
+class SetBotCommands:
     async def set_bot_commands(
         self: "pyrogram.Client",
         commands: List["types.BotCommand"],
@@ -31,7 +31,6 @@ async def set_bot_commands(
         language_code: str = "",
     ):
         """Set the list of the bot's commands.
-
         The commands passed will overwrite any command set previously.
         This method can be used by the own bot only.
 
diff --git a/pyrogram/methods/bots/set_game_score.py b/pyrogram/methods/bots/set_game_score.py
index edb38dcfe1..ef644b60db 100644
--- a/pyrogram/methods/bots/set_game_score.py
+++ b/pyrogram/methods/bots/set_game_score.py
@@ -18,14 +18,14 @@
 
 from typing import Union
 
+import pyrogram
 from pyrogram import raw
 from pyrogram import types
-from pyrogram.scaffold import Scaffold
 
 
-class SetGameScore(Scaffold):
+class SetGameScore:
     async def set_game_score(
-        self,
+        self: "pyrogram.Client",
         user_id: Union[int, str],
         score: int,
         force: bool = None,
diff --git a/pyrogram/methods/chats/add_chat_members.py b/pyrogram/methods/chats/add_chat_members.py
index 70fb0f97c4..cda789aaba 100644
--- a/pyrogram/methods/chats/add_chat_members.py
+++ b/pyrogram/methods/chats/add_chat_members.py
@@ -18,13 +18,13 @@
 
 from typing import Union, List
 
+import pyrogram
 from pyrogram import raw
-from pyrogram.scaffold import Scaffold
 
 
-class AddChatMembers(Scaffold):
+class AddChatMembers:
     async def add_chat_members(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         user_ids: Union[Union[int, str], List[Union[int, str]]],
         forward_limit: int = 100
diff --git a/pyrogram/methods/chats/archive_chats.py b/pyrogram/methods/chats/archive_chats.py
index c65b762206..aa5b50d5ec 100644
--- a/pyrogram/methods/chats/archive_chats.py
+++ b/pyrogram/methods/chats/archive_chats.py
@@ -18,13 +18,13 @@
 
 from typing import Union, List
 
+import pyrogram
 from pyrogram import raw
-from pyrogram.scaffold import Scaffold
 
 
-class ArchiveChats(Scaffold):
+class ArchiveChats:
     async def archive_chats(
-        self,
+        self: "pyrogram.Client",
         chat_ids: Union[int, str, List[Union[int, str]]],
     ) -> bool:
         """Archive one or more chats.
diff --git a/pyrogram/methods/chats/ban_chat_member.py b/pyrogram/methods/chats/ban_chat_member.py
index 3edcb164ce..c01b70462e 100644
--- a/pyrogram/methods/chats/ban_chat_member.py
+++ b/pyrogram/methods/chats/ban_chat_member.py
@@ -19,14 +19,14 @@
 from datetime import datetime
 from typing import Union
 
+import pyrogram
 from pyrogram import raw, utils
 from pyrogram import types
-from pyrogram.scaffold import Scaffold
 
 
-class BanChatMember(Scaffold):
+class BanChatMember:
     async def ban_chat_member(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         user_id: Union[int, str],
         until_date: datetime = datetime.fromtimestamp(0)
diff --git a/pyrogram/methods/chats/create_channel.py b/pyrogram/methods/chats/create_channel.py
index b3b21eaea3..5920d5ff70 100644
--- a/pyrogram/methods/chats/create_channel.py
+++ b/pyrogram/methods/chats/create_channel.py
@@ -15,15 +15,14 @@
 #
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
-
+import pyrogram
 from pyrogram import raw
 from pyrogram import types
-from pyrogram.scaffold import Scaffold
 
 
-class CreateChannel(Scaffold):
+class CreateChannel:
     async def create_channel(
-        self,
+        self: "pyrogram.Client",
         title: str,
         description: str = ""
     ) -> "types.Chat":
diff --git a/pyrogram/methods/chats/create_group.py b/pyrogram/methods/chats/create_group.py
index 2117d3699a..d01a2e683d 100644
--- a/pyrogram/methods/chats/create_group.py
+++ b/pyrogram/methods/chats/create_group.py
@@ -18,14 +18,14 @@
 
 from typing import Union, List
 
+import pyrogram
 from pyrogram import raw
 from pyrogram import types
-from pyrogram.scaffold import Scaffold
 
 
-class CreateGroup(Scaffold):
+class CreateGroup:
     async def create_group(
-        self,
+        self: "pyrogram.Client",
         title: str,
         users: Union[Union[int, str], List[Union[int, str]]]
     ) -> "types.Chat":
diff --git a/pyrogram/methods/chats/create_supergroup.py b/pyrogram/methods/chats/create_supergroup.py
index 2c72e25b30..348fc72609 100644
--- a/pyrogram/methods/chats/create_supergroup.py
+++ b/pyrogram/methods/chats/create_supergroup.py
@@ -15,15 +15,14 @@
 #
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
-
+import pyrogram
 from pyrogram import raw
 from pyrogram import types
-from pyrogram.scaffold import Scaffold
 
 
-class CreateSupergroup(Scaffold):
+class CreateSupergroup:
     async def create_supergroup(
-        self,
+        self: "pyrogram.Client",
         title: str,
         description: str = ""
     ) -> "types.Chat":
diff --git a/pyrogram/methods/chats/delete_channel.py b/pyrogram/methods/chats/delete_channel.py
index 3f9baa4ff6..246c930af8 100644
--- a/pyrogram/methods/chats/delete_channel.py
+++ b/pyrogram/methods/chats/delete_channel.py
@@ -18,12 +18,15 @@
 
 from typing import Union
 
+import pyrogram
 from pyrogram import raw
-from pyrogram.scaffold import Scaffold
 
 
-class DeleteChannel(Scaffold):
-    async def delete_channel(self, chat_id: Union[int, str]) -> bool:
+class DeleteChannel:
+    async def delete_channel(
+        self: "pyrogram.Client",
+        chat_id: Union[int, str]
+    ) -> bool:
         """Delete a channel.
 
         Parameters:
diff --git a/pyrogram/methods/chats/delete_chat_photo.py b/pyrogram/methods/chats/delete_chat_photo.py
index 4311658bd5..0b4d6488c6 100644
--- a/pyrogram/methods/chats/delete_chat_photo.py
+++ b/pyrogram/methods/chats/delete_chat_photo.py
@@ -18,13 +18,13 @@
 
 from typing import Union
 
+import pyrogram
 from pyrogram import raw
-from pyrogram.scaffold import Scaffold
 
 
-class DeleteChatPhoto(Scaffold):
+class DeleteChatPhoto:
     async def delete_chat_photo(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str]
     ) -> bool:
         """Delete a chat photo.
diff --git a/pyrogram/methods/chats/delete_supergroup.py b/pyrogram/methods/chats/delete_supergroup.py
index b9d3bdf7d0..5f6e8168ea 100644
--- a/pyrogram/methods/chats/delete_supergroup.py
+++ b/pyrogram/methods/chats/delete_supergroup.py
@@ -18,12 +18,15 @@
 
 from typing import Union
 
+import pyrogram
 from pyrogram import raw
-from pyrogram.scaffold import Scaffold
 
 
-class DeleteSupergroup(Scaffold):
-    async def delete_supergroup(self, chat_id: Union[int, str]) -> bool:
+class DeleteSupergroup:
+    async def delete_supergroup(
+        self: "pyrogram.Client",
+        chat_id: Union[int, str]
+    ) -> bool:
         """Delete a supergroup.
 
         Parameters:
diff --git a/pyrogram/methods/chats/delete_user_history.py b/pyrogram/methods/chats/delete_user_history.py
index ca9d0c5941..867100bb6e 100644
--- a/pyrogram/methods/chats/delete_user_history.py
+++ b/pyrogram/methods/chats/delete_user_history.py
@@ -18,13 +18,13 @@
 
 from typing import Union
 
+import pyrogram
 from pyrogram import raw
-from pyrogram.scaffold import Scaffold
 
 
-class DeleteUserHistory(Scaffold):
+class DeleteUserHistory:
     async def delete_user_history(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         user_id: Union[int, str],
     ) -> bool:
diff --git a/pyrogram/methods/chats/get_chat.py b/pyrogram/methods/chats/get_chat.py
index d9ecb166ba..99e5756647 100644
--- a/pyrogram/methods/chats/get_chat.py
+++ b/pyrogram/methods/chats/get_chat.py
@@ -18,15 +18,15 @@
 
 from typing import Union
 
+import pyrogram
 from pyrogram import raw
 from pyrogram import types
 from pyrogram import utils
-from pyrogram.scaffold import Scaffold
 
 
-class GetChat(Scaffold):
+class GetChat:
     async def get_chat(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str]
     ) -> Union["types.Chat", "types.ChatPreview"]:
         """Get up to date information about a chat.
diff --git a/pyrogram/methods/chats/get_chat_event_log.py b/pyrogram/methods/chats/get_chat_event_log.py
index 3e392a204d..db27709094 100644
--- a/pyrogram/methods/chats/get_chat_event_log.py
+++ b/pyrogram/methods/chats/get_chat_event_log.py
@@ -18,14 +18,14 @@
 
 from typing import Union, List, AsyncGenerator, Optional
 
+import pyrogram
 from pyrogram import raw
 from pyrogram import types
-from pyrogram.scaffold import Scaffold
 
 
-class GetChatEventLog(Scaffold):
+class GetChatEventLog:
     async def get_chat_event_log(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         query: str = "",
         offset_id: int = 0,
diff --git a/pyrogram/methods/chats/get_chat_member.py b/pyrogram/methods/chats/get_chat_member.py
index 9987327c6b..9717d01953 100644
--- a/pyrogram/methods/chats/get_chat_member.py
+++ b/pyrogram/methods/chats/get_chat_member.py
@@ -18,15 +18,15 @@
 
 from typing import Union
 
+import pyrogram
 from pyrogram import raw
 from pyrogram import types
 from pyrogram.errors import UserNotParticipant
-from pyrogram.scaffold import Scaffold
 
 
-class GetChatMember(Scaffold):
+class GetChatMember:
     async def get_chat_member(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         user_id: Union[int, str]
     ) -> "types.ChatMember":
diff --git a/pyrogram/methods/chats/get_chat_members.py b/pyrogram/methods/chats/get_chat_members.py
index 4c8ae60229..7e6b8898fc 100644
--- a/pyrogram/methods/chats/get_chat_members.py
+++ b/pyrogram/methods/chats/get_chat_members.py
@@ -19,15 +19,15 @@
 import logging
 from typing import Union, List
 
+import pyrogram
 from pyrogram import raw, types, enums
-from pyrogram.scaffold import Scaffold
 
 log = logging.getLogger(__name__)
 
 
-class GetChatMembers(Scaffold):
+class GetChatMembers:
     async def get_chat_members(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         offset: int = 0,
         limit: int = 200,
@@ -105,17 +105,17 @@ async def get_chat_members(
         elif isinstance(peer, raw.types.InputPeerChannel):
             filter = filter.lower()
 
-            if filter == Filters.ALL:
+            if filter == enums.ChatMembersFilter.ANY:
                 filter = raw.types.ChannelParticipantsSearch(q=query)
-            elif filter == Filters.BANNED:
+            elif filter == enums.ChatMembersFilter.BANNED:
                 filter = raw.types.ChannelParticipantsKicked(q=query)
-            elif filter == Filters.RESTRICTED:
+            elif filter == enums.ChatMembersFilter.RESTRICTED:
                 filter = raw.types.ChannelParticipantsBanned(q=query)
-            elif filter == Filters.BOTS:
+            elif filter == enums.ChatMembersFilter.BOTS:
                 filter = raw.types.ChannelParticipantsBots()
-            elif filter == Filters.RECENT:
+            elif filter == enums.ChatMembersFilter.RECENT:
                 filter = raw.types.ChannelParticipantsRecent()
-            elif filter == Filters.ADMINISTRATORS:
+            elif filter == enums.ChatMembersFilter.ADMINISTRATORS:
                 filter = raw.types.ChannelParticipantsAdmins()
             else:
                 raise ValueError(f'Invalid filter "{filter}"')
diff --git a/pyrogram/methods/chats/get_chat_members_count.py b/pyrogram/methods/chats/get_chat_members_count.py
index aa943eea2a..a7dae4be05 100644
--- a/pyrogram/methods/chats/get_chat_members_count.py
+++ b/pyrogram/methods/chats/get_chat_members_count.py
@@ -18,13 +18,13 @@
 
 from typing import Union
 
+import pyrogram
 from pyrogram import raw
-from pyrogram.scaffold import Scaffold
 
 
-class GetChatMembersCount(Scaffold):
+class GetChatMembersCount:
     async def get_chat_members_count(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str]
     ) -> int:
         """Get the number of members in a chat.
diff --git a/pyrogram/methods/chats/get_chat_online_count.py b/pyrogram/methods/chats/get_chat_online_count.py
index a8d1321446..3f8e5d6aee 100644
--- a/pyrogram/methods/chats/get_chat_online_count.py
+++ b/pyrogram/methods/chats/get_chat_online_count.py
@@ -18,12 +18,15 @@
 
 from typing import Union
 
+import pyrogram
 from pyrogram import raw
-from pyrogram.scaffold import Scaffold
 
 
-class GetChatOnlineCount(Scaffold):
-    async def get_chat_online_count(self, chat_id: Union[int, str]) -> int:
+class GetChatOnlineCount:
+    async def get_chat_online_count(
+        self: "pyrogram.Client",
+        chat_id: Union[int, str]
+    ) -> int:
         """Get the number of members that are currently online in a chat.
 
         Parameters:
diff --git a/pyrogram/methods/chats/get_dialogs.py b/pyrogram/methods/chats/get_dialogs.py
index 9fb907caba..7276c80b64 100644
--- a/pyrogram/methods/chats/get_dialogs.py
+++ b/pyrogram/methods/chats/get_dialogs.py
@@ -20,17 +20,17 @@
 from datetime import datetime
 from typing import List
 
+import pyrogram
 from pyrogram import raw
 from pyrogram import types
 from pyrogram import utils
-from pyrogram.scaffold import Scaffold
 
 log = logging.getLogger(__name__)
 
 
-class GetDialogs(Scaffold):
+class GetDialogs:
     async def get_dialogs(
-        self,
+        self: "pyrogram.Client",
         offset_date: datetime = datetime.fromtimestamp(0),
         limit: int = 100,
         pinned_only: bool = False
diff --git a/pyrogram/methods/chats/get_dialogs_count.py b/pyrogram/methods/chats/get_dialogs_count.py
index a8598e5fcc..3f869909fc 100644
--- a/pyrogram/methods/chats/get_dialogs_count.py
+++ b/pyrogram/methods/chats/get_dialogs_count.py
@@ -16,12 +16,15 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
+import pyrogram
 from pyrogram import raw
-from pyrogram.scaffold import Scaffold
 
 
-class GetDialogsCount(Scaffold):
-    async def get_dialogs_count(self, pinned_only: bool = False) -> int:
+class GetDialogsCount:
+    async def get_dialogs_count(
+        self: "pyrogram.Client",
+        pinned_only: bool = False
+    ) -> int:
         """Get the total count of your dialogs.
 
         pinned_only (``bool``, *optional*):
diff --git a/pyrogram/methods/chats/get_nearby_chats.py b/pyrogram/methods/chats/get_nearby_chats.py
index dcceb4398b..0dff05aae2 100644
--- a/pyrogram/methods/chats/get_nearby_chats.py
+++ b/pyrogram/methods/chats/get_nearby_chats.py
@@ -18,15 +18,15 @@
 
 from typing import List
 
+import pyrogram
 from pyrogram import raw
 from pyrogram import types
 from pyrogram import utils
-from pyrogram.scaffold import Scaffold
 
 
-class GetNearbyChats(Scaffold):
+class GetNearbyChats:
     async def get_nearby_chats(
-        self,
+        self: "pyrogram.Client",
         latitude: float,
         longitude: float
     ) -> List["types.Chat"]:
diff --git a/pyrogram/methods/chats/get_send_as_chats.py b/pyrogram/methods/chats/get_send_as_chats.py
index 4d2fae4824..147da217ce 100644
--- a/pyrogram/methods/chats/get_send_as_chats.py
+++ b/pyrogram/methods/chats/get_send_as_chats.py
@@ -18,14 +18,14 @@
 
 from typing import List, Union
 
+import pyrogram
 from pyrogram import raw
 from pyrogram import types
-from pyrogram.scaffold import Scaffold
 
 
-class GetSendAsChats(Scaffold):
+class GetSendAsChats:
     async def get_send_as_chats(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str]
     ) -> List["types.Chat"]:
         """Get the list of "send_as" chats available.
diff --git a/pyrogram/methods/chats/iter_chat_members.py b/pyrogram/methods/chats/iter_chat_members.py
index 3661d5064f..f3ccf06c81 100644
--- a/pyrogram/methods/chats/iter_chat_members.py
+++ b/pyrogram/methods/chats/iter_chat_members.py
@@ -18,9 +18,9 @@
 
 from typing import Union, AsyncGenerator, Optional
 
+import pyrogram
 from pyrogram import raw
 from pyrogram import types
-from pyrogram.scaffold import Scaffold
 
 
 class Filters:
@@ -32,9 +32,9 @@ class Filters:
     ADMINISTRATORS = "administrators"
 
 
-class IterChatMembers(Scaffold):
+class IterChatMembers:
     async def iter_chat_members(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         limit: int = 0,
         query: str = "",
diff --git a/pyrogram/methods/chats/iter_dialogs.py b/pyrogram/methods/chats/iter_dialogs.py
index 4553e3d47e..009bdfa8c2 100644
--- a/pyrogram/methods/chats/iter_dialogs.py
+++ b/pyrogram/methods/chats/iter_dialogs.py
@@ -18,13 +18,13 @@
 
 from typing import AsyncGenerator, Optional
 
+import pyrogram
 from pyrogram import types, raw, utils
-from pyrogram.scaffold import Scaffold
 
 
-class IterDialogs(Scaffold):
+class IterDialogs:
     async def iter_dialogs(
-        self,
+        self: "pyrogram.Client",
         limit: int = 0
     ) -> Optional[AsyncGenerator["types.Dialog", None]]:
         """Iterate through a user's dialogs sequentially.
diff --git a/pyrogram/methods/chats/join_chat.py b/pyrogram/methods/chats/join_chat.py
index 3f1a09f998..c1a850af92 100644
--- a/pyrogram/methods/chats/join_chat.py
+++ b/pyrogram/methods/chats/join_chat.py
@@ -18,14 +18,14 @@
 
 from typing import Union
 
+import pyrogram
 from pyrogram import raw
 from pyrogram import types
-from pyrogram.scaffold import Scaffold
 
 
-class JoinChat(Scaffold):
+class JoinChat:
     async def join_chat(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str]
     ) -> "types.Chat":
         """Join a group chat or channel.
diff --git a/pyrogram/methods/chats/leave_chat.py b/pyrogram/methods/chats/leave_chat.py
index 3271a1f952..56369e1ce7 100644
--- a/pyrogram/methods/chats/leave_chat.py
+++ b/pyrogram/methods/chats/leave_chat.py
@@ -18,13 +18,13 @@
 
 from typing import Union
 
+import pyrogram
 from pyrogram import raw
-from pyrogram.scaffold import Scaffold
 
 
-class LeaveChat(Scaffold):
+class LeaveChat:
     async def leave_chat(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         delete: bool = False
     ):
diff --git a/pyrogram/methods/chats/mark_chat_unread.py b/pyrogram/methods/chats/mark_chat_unread.py
index 4a48c5d6fa..32251e2a7d 100644
--- a/pyrogram/methods/chats/mark_chat_unread.py
+++ b/pyrogram/methods/chats/mark_chat_unread.py
@@ -18,13 +18,13 @@
 
 from typing import Union
 
+import pyrogram
 from pyrogram.raw import functions
-from pyrogram.scaffold import Scaffold
 
 
-class MarkChatUnread(Scaffold):
+class MarkChatUnread:
     async def mark_chat_unread(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
     ) -> bool:
         """Mark a chat as unread.
diff --git a/pyrogram/methods/chats/pin_chat_message.py b/pyrogram/methods/chats/pin_chat_message.py
index 016a19a7b2..9e34e1aa6c 100644
--- a/pyrogram/methods/chats/pin_chat_message.py
+++ b/pyrogram/methods/chats/pin_chat_message.py
@@ -18,13 +18,13 @@
 
 from typing import Union
 
+import pyrogram
 from pyrogram import raw, types
-from pyrogram.scaffold import Scaffold
 
 
-class PinChatMessage(Scaffold):
+class PinChatMessage:
     async def pin_chat_message(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         message_id: int,
         disable_notification: bool = False,
diff --git a/pyrogram/methods/chats/promote_chat_member.py b/pyrogram/methods/chats/promote_chat_member.py
index b1d43867f3..63a844bb2a 100644
--- a/pyrogram/methods/chats/promote_chat_member.py
+++ b/pyrogram/methods/chats/promote_chat_member.py
@@ -18,13 +18,13 @@
 
 from typing import Union
 
+import pyrogram
 from pyrogram import raw, types
-from pyrogram.scaffold import Scaffold
 
 
-class PromoteChatMember(Scaffold):
+class PromoteChatMember:
     async def promote_chat_member(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         user_id: Union[int, str],
         privileges: "types.ChatPrivileges" = types.ChatPrivileges(),
diff --git a/pyrogram/methods/chats/restrict_chat_member.py b/pyrogram/methods/chats/restrict_chat_member.py
index 22eee519c1..a8ab21730d 100644
--- a/pyrogram/methods/chats/restrict_chat_member.py
+++ b/pyrogram/methods/chats/restrict_chat_member.py
@@ -19,14 +19,14 @@
 from datetime import datetime
 from typing import Union
 
+import pyrogram
 from pyrogram import raw, utils
 from pyrogram import types
-from pyrogram.scaffold import Scaffold
 
 
-class RestrictChatMember(Scaffold):
+class RestrictChatMember:
     async def restrict_chat_member(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         user_id: Union[int, str],
         permissions: "types.ChatPermissions",
diff --git a/pyrogram/methods/chats/set_administrator_title.py b/pyrogram/methods/chats/set_administrator_title.py
index f59cf28a35..ed59b342f5 100644
--- a/pyrogram/methods/chats/set_administrator_title.py
+++ b/pyrogram/methods/chats/set_administrator_title.py
@@ -18,13 +18,13 @@
 
 from typing import Union
 
+import pyrogram
 from pyrogram import raw
-from pyrogram.scaffold import Scaffold
 
 
-class SetAdministratorTitle(Scaffold):
+class SetAdministratorTitle:
     async def set_administrator_title(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         user_id: Union[int, str],
         title: str,
diff --git a/pyrogram/methods/chats/set_chat_description.py b/pyrogram/methods/chats/set_chat_description.py
index 6d2575f207..440b405013 100644
--- a/pyrogram/methods/chats/set_chat_description.py
+++ b/pyrogram/methods/chats/set_chat_description.py
@@ -18,13 +18,13 @@
 
 from typing import Union
 
+import pyrogram
 from pyrogram import raw
-from pyrogram.scaffold import Scaffold
 
 
-class SetChatDescription(Scaffold):
+class SetChatDescription:
     async def set_chat_description(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         description: str
     ) -> bool:
diff --git a/pyrogram/methods/chats/set_chat_permissions.py b/pyrogram/methods/chats/set_chat_permissions.py
index 2ff2b67831..dfbd5d917d 100644
--- a/pyrogram/methods/chats/set_chat_permissions.py
+++ b/pyrogram/methods/chats/set_chat_permissions.py
@@ -18,14 +18,14 @@
 
 from typing import Union
 
+import pyrogram
 from pyrogram import raw
 from pyrogram import types
-from pyrogram.scaffold import Scaffold
 
 
-class SetChatPermissions(Scaffold):
+class SetChatPermissions:
     async def set_chat_permissions(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         permissions: "types.ChatPermissions",
     ) -> "types.Chat":
diff --git a/pyrogram/methods/chats/set_chat_photo.py b/pyrogram/methods/chats/set_chat_photo.py
index a1fee7f62f..1097b2377b 100644
--- a/pyrogram/methods/chats/set_chat_photo.py
+++ b/pyrogram/methods/chats/set_chat_photo.py
@@ -19,15 +19,15 @@
 import os
 from typing import Union, BinaryIO
 
+import pyrogram
 from pyrogram import raw
 from pyrogram import utils
 from pyrogram.file_id import FileType
-from pyrogram.scaffold import Scaffold
 
 
-class SetChatPhoto(Scaffold):
+class SetChatPhoto:
     async def set_chat_photo(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         *,
         photo: Union[str, BinaryIO] = None,
diff --git a/pyrogram/methods/chats/set_chat_protected_content.py b/pyrogram/methods/chats/set_chat_protected_content.py
index d63e381d98..1372481c99 100644
--- a/pyrogram/methods/chats/set_chat_protected_content.py
+++ b/pyrogram/methods/chats/set_chat_protected_content.py
@@ -18,13 +18,13 @@
 
 from typing import Union
 
+import pyrogram
 from pyrogram.raw import functions
-from pyrogram.scaffold import Scaffold
 
 
-class SetChatProtectedContent(Scaffold):
+class SetChatProtectedContent:
     async def set_chat_protected_content(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         enabled: bool
     ) -> bool:
diff --git a/pyrogram/methods/chats/set_chat_title.py b/pyrogram/methods/chats/set_chat_title.py
index 62649f3850..ac30b078fe 100644
--- a/pyrogram/methods/chats/set_chat_title.py
+++ b/pyrogram/methods/chats/set_chat_title.py
@@ -18,13 +18,13 @@
 
 from typing import Union
 
+import pyrogram
 from pyrogram import raw
-from pyrogram.scaffold import Scaffold
 
 
-class SetChatTitle(Scaffold):
+class SetChatTitle:
     async def set_chat_title(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         title: str
     ) -> bool:
diff --git a/pyrogram/methods/chats/set_chat_username.py b/pyrogram/methods/chats/set_chat_username.py
index 8496dda326..c63207aa33 100644
--- a/pyrogram/methods/chats/set_chat_username.py
+++ b/pyrogram/methods/chats/set_chat_username.py
@@ -18,13 +18,13 @@
 
 from typing import Union, Optional
 
+import pyrogram
 from pyrogram import raw
-from pyrogram.scaffold import Scaffold
 
 
-class SetChatUsername(Scaffold):
+class SetChatUsername:
     async def set_chat_username(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         username: Optional[str]
     ) -> bool:
diff --git a/pyrogram/methods/chats/set_send_as_chat.py b/pyrogram/methods/chats/set_send_as_chat.py
index 3fc686bbcf..dabb4066f8 100644
--- a/pyrogram/methods/chats/set_send_as_chat.py
+++ b/pyrogram/methods/chats/set_send_as_chat.py
@@ -18,13 +18,13 @@
 
 from typing import Union
 
+import pyrogram
 from pyrogram import raw
-from pyrogram.scaffold import Scaffold
 
 
-class SetSendAsChat(Scaffold):
+class SetSendAsChat:
     async def set_send_as_chat(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         send_as_chat_id: Union[int, str]
     ) -> bool:
diff --git a/pyrogram/methods/chats/set_slow_mode.py b/pyrogram/methods/chats/set_slow_mode.py
index 7e6739bacb..3bc00cb21a 100644
--- a/pyrogram/methods/chats/set_slow_mode.py
+++ b/pyrogram/methods/chats/set_slow_mode.py
@@ -18,13 +18,13 @@
 
 from typing import Union, Optional
 
+import pyrogram
 from pyrogram import raw
-from pyrogram.scaffold import Scaffold
 
 
-class SetSlowMode(Scaffold):
+class SetSlowMode:
     async def set_slow_mode(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         seconds: Optional[int]
     ) -> bool:
diff --git a/pyrogram/methods/chats/unarchive_chats.py b/pyrogram/methods/chats/unarchive_chats.py
index 32867798ce..a6b77d1845 100644
--- a/pyrogram/methods/chats/unarchive_chats.py
+++ b/pyrogram/methods/chats/unarchive_chats.py
@@ -18,13 +18,13 @@
 
 from typing import Union, List
 
+import pyrogram
 from pyrogram import raw
-from pyrogram.scaffold import Scaffold
 
 
-class UnarchiveChats(Scaffold):
+class UnarchiveChats:
     async def unarchive_chats(
-        self,
+        self: "pyrogram.Client",
         chat_ids: Union[int, str, List[Union[int, str]]],
     ) -> bool:
         """Unarchive one or more chats.
diff --git a/pyrogram/methods/chats/unban_chat_member.py b/pyrogram/methods/chats/unban_chat_member.py
index c7be7b58f6..9176fe3361 100644
--- a/pyrogram/methods/chats/unban_chat_member.py
+++ b/pyrogram/methods/chats/unban_chat_member.py
@@ -18,13 +18,13 @@
 
 from typing import Union
 
+import pyrogram
 from pyrogram import raw
-from pyrogram.scaffold import Scaffold
 
 
-class UnbanChatMember(Scaffold):
+class UnbanChatMember:
     async def unban_chat_member(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         user_id: Union[int, str]
     ) -> bool:
diff --git a/pyrogram/methods/chats/unpin_all_chat_messages.py b/pyrogram/methods/chats/unpin_all_chat_messages.py
index 3028120ae2..bd75c40d6c 100644
--- a/pyrogram/methods/chats/unpin_all_chat_messages.py
+++ b/pyrogram/methods/chats/unpin_all_chat_messages.py
@@ -18,13 +18,13 @@
 
 from typing import Union
 
+import pyrogram
 from pyrogram import raw
-from pyrogram.scaffold import Scaffold
 
 
-class UnpinAllChatMessages(Scaffold):
+class UnpinAllChatMessages:
     async def unpin_all_chat_messages(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
     ) -> bool:
         """Use this method to clear the list of pinned messages in a chat.
diff --git a/pyrogram/methods/chats/unpin_chat_message.py b/pyrogram/methods/chats/unpin_chat_message.py
index 83654452cc..cf3f9e9d5e 100644
--- a/pyrogram/methods/chats/unpin_chat_message.py
+++ b/pyrogram/methods/chats/unpin_chat_message.py
@@ -18,13 +18,13 @@
 
 from typing import Union
 
+import pyrogram
 from pyrogram import raw
-from pyrogram.scaffold import Scaffold
 
 
-class UnpinChatMessage(Scaffold):
+class UnpinChatMessage:
     async def unpin_chat_message(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         message_id: int = 0
     ) -> bool:
diff --git a/pyrogram/methods/contacts/add_contact.py b/pyrogram/methods/contacts/add_contact.py
index cceb273d37..72bdd88acf 100644
--- a/pyrogram/methods/contacts/add_contact.py
+++ b/pyrogram/methods/contacts/add_contact.py
@@ -18,14 +18,14 @@
 
 from typing import Union
 
+import pyrogram
 from pyrogram import raw
 from pyrogram import types
-from pyrogram.scaffold import Scaffold
 
 
-class AddContact(Scaffold):
+class AddContact:
     async def add_contact(
-        self,
+        self: "pyrogram.Client",
         user_id: Union[int, str],
         first_name: str,
         last_name: str = "",
diff --git a/pyrogram/methods/contacts/delete_contacts.py b/pyrogram/methods/contacts/delete_contacts.py
index 273407475a..e6ef32583b 100644
--- a/pyrogram/methods/contacts/delete_contacts.py
+++ b/pyrogram/methods/contacts/delete_contacts.py
@@ -18,13 +18,13 @@
 
 from typing import List, Union
 
+import pyrogram
 from pyrogram import raw, types
-from pyrogram.scaffold import Scaffold
 
 
-class DeleteContacts(Scaffold):
+class DeleteContacts:
     async def delete_contacts(
-        self,
+        self: "pyrogram.Client",
         user_ids: Union[int, str, List[Union[int, str]]]
     ) -> Union["types.User", List["types.User"], None]:
         """Delete contacts from your Telegram address book.
diff --git a/pyrogram/methods/contacts/get_contacts.py b/pyrogram/methods/contacts/get_contacts.py
index 98ed11502d..0b41c3d6cb 100644
--- a/pyrogram/methods/contacts/get_contacts.py
+++ b/pyrogram/methods/contacts/get_contacts.py
@@ -19,15 +19,17 @@
 import logging
 from typing import List
 
+import pyrogram
 from pyrogram import raw
 from pyrogram import types
-from pyrogram.scaffold import Scaffold
 
 log = logging.getLogger(__name__)
 
 
-class GetContacts(Scaffold):
-    async def get_contacts(self) -> List["types.User"]:
+class GetContacts:
+    async def get_contacts(
+        self: "pyrogram.Client"
+    ) -> List["types.User"]:
         """Get contacts from your Telegram address book.
 
         Returns:
diff --git a/pyrogram/methods/contacts/get_contacts_count.py b/pyrogram/methods/contacts/get_contacts_count.py
index b29fed7ac1..b7e5d371c8 100644
--- a/pyrogram/methods/contacts/get_contacts_count.py
+++ b/pyrogram/methods/contacts/get_contacts_count.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 pyrogram
 from pyrogram import raw
-from pyrogram.scaffold import Scaffold
 
 
-class GetContactsCount(Scaffold):
-    async def get_contacts_count(self) -> int:
+class GetContactsCount:
+    async def get_contacts_count(
+        self: "pyrogram.Client"
+    ) -> int:
         """Get the total count of contacts from your Telegram address book.
 
         Returns:
diff --git a/pyrogram/methods/contacts/import_contacts.py b/pyrogram/methods/contacts/import_contacts.py
index b7df855439..7a6e31420f 100644
--- a/pyrogram/methods/contacts/import_contacts.py
+++ b/pyrogram/methods/contacts/import_contacts.py
@@ -18,14 +18,14 @@
 
 from typing import List
 
+import pyrogram
 from pyrogram import raw
 from pyrogram import types
-from pyrogram.scaffold import Scaffold
 
 
-class ImportContacts(Scaffold):
+class ImportContacts:
     async def import_contacts(
-        self,
+        self: "pyrogram.Client",
         contacts: List["types.InputPhoneContact"]
     ):
         """Import contacts to your Telegram address book.
diff --git a/pyrogram/methods/decorators/on_callback_query.py b/pyrogram/methods/decorators/on_callback_query.py
index fb0b716f60..07e15a3e78 100644
--- a/pyrogram/methods/decorators/on_callback_query.py
+++ b/pyrogram/methods/decorators/on_callback_query.py
@@ -20,15 +20,14 @@
 
 import pyrogram
 from pyrogram.filters import Filter
-from pyrogram.scaffold import Scaffold
 
 
-class OnCallbackQuery(Scaffold):
+class OnCallbackQuery:
     def on_callback_query(
         self=None,
         filters=None,
         group: int = 0
-    ) -> callable:
+    ) -> Callable:
         """Decorator for handling callback queries.
 
         This does the same thing as :meth:`~pyrogram.Client.add_handler` using the
diff --git a/pyrogram/methods/decorators/on_chat_join_request.py b/pyrogram/methods/decorators/on_chat_join_request.py
index 0629901814..57fb709cb5 100644
--- a/pyrogram/methods/decorators/on_chat_join_request.py
+++ b/pyrogram/methods/decorators/on_chat_join_request.py
@@ -20,15 +20,14 @@
 
 import pyrogram
 from pyrogram.filters import Filter
-from pyrogram.scaffold import Scaffold
 
 
-class OnChatJoinRequest(Scaffold):
+class OnChatJoinRequest:
     def on_chat_join_request(
         self=None,
         filters=None,
         group: int = 0
-    ) -> callable:
+    ) -> Callable:
         """Decorator for handling chat join requests.
 
         This does the same thing as :meth:`~pyrogram.Client.add_handler` using the
diff --git a/pyrogram/methods/decorators/on_chat_member_updated.py b/pyrogram/methods/decorators/on_chat_member_updated.py
index 9c10debfdd..c2f0e888a8 100644
--- a/pyrogram/methods/decorators/on_chat_member_updated.py
+++ b/pyrogram/methods/decorators/on_chat_member_updated.py
@@ -20,15 +20,14 @@
 
 import pyrogram
 from pyrogram.filters import Filter
-from pyrogram.scaffold import Scaffold
 
 
-class OnChatMemberUpdated(Scaffold):
+class OnChatMemberUpdated:
     def on_chat_member_updated(
         self=None,
         filters=None,
         group: int = 0
-    ) -> callable:
+    ) -> Callable:
         """Decorator for handling event changes on chat members.
 
         This does the same thing as :meth:`~pyrogram.Client.add_handler` using the
diff --git a/pyrogram/methods/decorators/on_chosen_inline_result.py b/pyrogram/methods/decorators/on_chosen_inline_result.py
index a2775c9bea..090f6c0425 100644
--- a/pyrogram/methods/decorators/on_chosen_inline_result.py
+++ b/pyrogram/methods/decorators/on_chosen_inline_result.py
@@ -20,15 +20,14 @@
 
 import pyrogram
 from pyrogram.filters import Filter
-from pyrogram.scaffold import Scaffold
 
 
-class OnChosenInlineResult(Scaffold):
+class OnChosenInlineResult:
     def on_chosen_inline_result(
         self=None,
         filters=None,
         group: int = 0
-    ) -> callable:
+    ) -> Callable:
         """Decorator for handling chosen inline results.
 
         This does the same thing as :meth:`~pyrogram.Client.add_handler` using the
diff --git a/pyrogram/methods/decorators/on_deleted_messages.py b/pyrogram/methods/decorators/on_deleted_messages.py
index 3bf88b088a..9565c11329 100644
--- a/pyrogram/methods/decorators/on_deleted_messages.py
+++ b/pyrogram/methods/decorators/on_deleted_messages.py
@@ -20,15 +20,14 @@
 
 import pyrogram
 from pyrogram.filters import Filter
-from pyrogram.scaffold import Scaffold
 
 
-class OnDeletedMessages(Scaffold):
+class OnDeletedMessages:
     def on_deleted_messages(
         self=None,
         filters=None,
         group: int = 0
-    ) -> callable:
+    ) -> Callable:
         """Decorator for handling deleted messages.
 
         This does the same thing as :meth:`~pyrogram.Client.add_handler` using the
diff --git a/pyrogram/methods/decorators/on_disconnect.py b/pyrogram/methods/decorators/on_disconnect.py
index 5e4f75014b..ae54800f86 100644
--- a/pyrogram/methods/decorators/on_disconnect.py
+++ b/pyrogram/methods/decorators/on_disconnect.py
@@ -19,11 +19,10 @@
 from typing import Callable
 
 import pyrogram
-from pyrogram.scaffold import Scaffold
 
 
-class OnDisconnect(Scaffold):
-    def on_disconnect(self=None) -> callable:
+class OnDisconnect:
+    def on_disconnect(self=None) -> Callable:
         """Decorator for handling disconnections.
 
         This does the same thing as :meth:`~pyrogram.Client.add_handler` using the
diff --git a/pyrogram/methods/decorators/on_inline_query.py b/pyrogram/methods/decorators/on_inline_query.py
index 4910670e33..6b53a464de 100644
--- a/pyrogram/methods/decorators/on_inline_query.py
+++ b/pyrogram/methods/decorators/on_inline_query.py
@@ -20,15 +20,14 @@
 
 import pyrogram
 from pyrogram.filters import Filter
-from pyrogram.scaffold import Scaffold
 
 
-class OnInlineQuery(Scaffold):
+class OnInlineQuery:
     def on_inline_query(
         self=None,
         filters=None,
         group: int = 0
-    ) -> callable:
+    ) -> Callable:
         """Decorator for handling inline queries.
 
         This does the same thing as :meth:`~pyrogram.Client.add_handler` using the
diff --git a/pyrogram/methods/decorators/on_message.py b/pyrogram/methods/decorators/on_message.py
index 634ca2e335..e9a3dfdd85 100644
--- a/pyrogram/methods/decorators/on_message.py
+++ b/pyrogram/methods/decorators/on_message.py
@@ -20,15 +20,14 @@
 
 import pyrogram
 from pyrogram.filters import Filter
-from pyrogram.scaffold import Scaffold
 
 
-class OnMessage(Scaffold):
+class OnMessage:
     def on_message(
         self=None,
         filters=None,
         group: int = 0
-    ) -> callable:
+    ) -> Callable:
         """Decorator for handling messages.
 
         This does the same thing as :meth:`~pyrogram.Client.add_handler` using the
diff --git a/pyrogram/methods/decorators/on_poll.py b/pyrogram/methods/decorators/on_poll.py
index 2694040372..6990c456b6 100644
--- a/pyrogram/methods/decorators/on_poll.py
+++ b/pyrogram/methods/decorators/on_poll.py
@@ -20,15 +20,14 @@
 
 import pyrogram
 from pyrogram.filters import Filter
-from pyrogram.scaffold import Scaffold
 
 
-class OnPoll(Scaffold):
+class OnPoll:
     def on_poll(
         self=None,
         filters=None,
         group: int = 0
-    ) -> callable:
+    ) -> Callable:
         """Decorator for handling poll updates.
 
         This does the same thing as :meth:`~pyrogram.Client.add_handler` using the
diff --git a/pyrogram/methods/decorators/on_raw_update.py b/pyrogram/methods/decorators/on_raw_update.py
index f61b156b90..dffc50b924 100644
--- a/pyrogram/methods/decorators/on_raw_update.py
+++ b/pyrogram/methods/decorators/on_raw_update.py
@@ -19,14 +19,13 @@
 from typing import Callable
 
 import pyrogram
-from pyrogram.scaffold import Scaffold
 
 
-class OnRawUpdate(Scaffold):
+class OnRawUpdate:
     def on_raw_update(
         self=None,
         group: int = 0
-    ) -> callable:
+    ) -> Callable:
         """Decorator for handling raw updates.
 
         This does the same thing as :meth:`~pyrogram.Client.add_handler` using the
diff --git a/pyrogram/methods/decorators/on_user_status.py b/pyrogram/methods/decorators/on_user_status.py
index 38760c2f4e..a4328c37f1 100644
--- a/pyrogram/methods/decorators/on_user_status.py
+++ b/pyrogram/methods/decorators/on_user_status.py
@@ -20,15 +20,14 @@
 
 import pyrogram
 from pyrogram.filters import Filter
-from pyrogram.scaffold import Scaffold
 
 
-class OnUserStatus(Scaffold):
+class OnUserStatus:
     def on_user_status(
         self=None,
         filters=None,
         group: int = 0
-    ) -> callable:
+    ) -> Callable:
         """Decorator for handling user status updates.
         This does the same thing as :meth:`~pyrogram.Client.add_handler` using the
         :obj:`~pyrogram.handlers.UserStatusHandler`.
diff --git a/pyrogram/methods/invite_links/approve_chat_join_request.py b/pyrogram/methods/invite_links/approve_chat_join_request.py
index 0bb2f604ca..a18a1c146a 100644
--- a/pyrogram/methods/invite_links/approve_chat_join_request.py
+++ b/pyrogram/methods/invite_links/approve_chat_join_request.py
@@ -18,13 +18,13 @@
 
 from typing import Union
 
+import pyrogram
 from pyrogram import raw
-from pyrogram.scaffold import Scaffold
 
 
-class ApproveChatJoinRequest(Scaffold):
+class ApproveChatJoinRequest:
     async def approve_chat_join_request(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         user_id: int,
     ) -> bool:
diff --git a/pyrogram/methods/invite_links/create_chat_invite_link.py b/pyrogram/methods/invite_links/create_chat_invite_link.py
index eec25787a4..15ca034162 100644
--- a/pyrogram/methods/invite_links/create_chat_invite_link.py
+++ b/pyrogram/methods/invite_links/create_chat_invite_link.py
@@ -19,14 +19,14 @@
 from datetime import datetime
 from typing import Union
 
+import pyrogram
 from pyrogram import raw, utils
 from pyrogram import types
-from pyrogram.scaffold import Scaffold
 
 
-class CreateChatInviteLink(Scaffold):
+class CreateChatInviteLink:
     async def create_chat_invite_link(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         name: str = None,
         expire_date: datetime = None,
diff --git a/pyrogram/methods/invite_links/decline_chat_join_request.py b/pyrogram/methods/invite_links/decline_chat_join_request.py
index 3fc01e2690..d7c3d2f2e0 100644
--- a/pyrogram/methods/invite_links/decline_chat_join_request.py
+++ b/pyrogram/methods/invite_links/decline_chat_join_request.py
@@ -18,13 +18,13 @@
 
 from typing import Union
 
+import pyrogram
 from pyrogram import raw
-from pyrogram.scaffold import Scaffold
 
 
-class DeclineChatJoinRequest(Scaffold):
+class DeclineChatJoinRequest:
     async def decline_chat_join_request(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         user_id: int,
     ) -> bool:
diff --git a/pyrogram/methods/invite_links/delete_chat_admin_invite_links.py b/pyrogram/methods/invite_links/delete_chat_admin_invite_links.py
index fda2aadbcb..c051717830 100644
--- a/pyrogram/methods/invite_links/delete_chat_admin_invite_links.py
+++ b/pyrogram/methods/invite_links/delete_chat_admin_invite_links.py
@@ -18,13 +18,13 @@
 
 from typing import Union
 
+import pyrogram
 from pyrogram import raw
-from pyrogram.scaffold import Scaffold
 
 
-class DeleteChatAdminInviteLinks(Scaffold):
+class DeleteChatAdminInviteLinks:
     async def delete_chat_admin_invite_links(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         admin_id: Union[int, str],
     ) -> bool:
diff --git a/pyrogram/methods/invite_links/delete_chat_invite_link.py b/pyrogram/methods/invite_links/delete_chat_invite_link.py
index eeab4e3537..19f4b49b3c 100644
--- a/pyrogram/methods/invite_links/delete_chat_invite_link.py
+++ b/pyrogram/methods/invite_links/delete_chat_invite_link.py
@@ -18,13 +18,13 @@
 
 from typing import Union
 
+import pyrogram
 from pyrogram import raw
-from pyrogram.scaffold import Scaffold
 
 
-class DeleteChatInviteLink(Scaffold):
+class DeleteChatInviteLink:
     async def delete_chat_invite_link(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         invite_link: str,
     ) -> bool:
diff --git a/pyrogram/methods/invite_links/edit_chat_invite_link.py b/pyrogram/methods/invite_links/edit_chat_invite_link.py
index 9a37536084..4a6755d6ea 100644
--- a/pyrogram/methods/invite_links/edit_chat_invite_link.py
+++ b/pyrogram/methods/invite_links/edit_chat_invite_link.py
@@ -19,14 +19,14 @@
 from datetime import datetime
 from typing import Union
 
+import pyrogram
 from pyrogram import raw, utils
 from pyrogram import types
-from pyrogram.scaffold import Scaffold
 
 
-class EditChatInviteLink(Scaffold):
+class EditChatInviteLink:
     async def edit_chat_invite_link(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         invite_link: str,
         name: str = None,
diff --git a/pyrogram/methods/invite_links/export_chat_invite_link.py b/pyrogram/methods/invite_links/export_chat_invite_link.py
index 061ccdf349..9734470fea 100644
--- a/pyrogram/methods/invite_links/export_chat_invite_link.py
+++ b/pyrogram/methods/invite_links/export_chat_invite_link.py
@@ -18,14 +18,14 @@
 
 from typing import Union
 
+import pyrogram
 from pyrogram import raw
 from pyrogram import types
-from pyrogram.scaffold import Scaffold
 
 
-class ExportChatInviteLink(Scaffold):
+class ExportChatInviteLink:
     async def export_chat_invite_link(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
     ) -> "types.ChatInviteLink":
         """Generate a new primary invite link for a chat; any previously generated primary link is revoked.
diff --git a/pyrogram/methods/invite_links/get_chat_admin_invite_links.py b/pyrogram/methods/invite_links/get_chat_admin_invite_links.py
index 8687795d70..0c2660828a 100644
--- a/pyrogram/methods/invite_links/get_chat_admin_invite_links.py
+++ b/pyrogram/methods/invite_links/get_chat_admin_invite_links.py
@@ -18,14 +18,14 @@
 
 from typing import Union, Optional, AsyncGenerator
 
+import pyrogram
 from pyrogram import raw
 from pyrogram import types
-from pyrogram.scaffold import Scaffold
 
 
-class GetChatAdminInviteLinks(Scaffold):
+class GetChatAdminInviteLinks:
     async def get_chat_admin_invite_links(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         admin_id: Union[int, str],
         revoked: bool = False,
diff --git a/pyrogram/methods/invite_links/get_chat_admin_invite_links_count.py b/pyrogram/methods/invite_links/get_chat_admin_invite_links_count.py
index 789f551a2f..419c76a776 100644
--- a/pyrogram/methods/invite_links/get_chat_admin_invite_links_count.py
+++ b/pyrogram/methods/invite_links/get_chat_admin_invite_links_count.py
@@ -18,13 +18,13 @@
 
 from typing import Union
 
+import pyrogram
 from pyrogram import raw
-from pyrogram.scaffold import Scaffold
 
 
-class GetChatAdminInviteLinksCount(Scaffold):
+class GetChatAdminInviteLinksCount:
     async def get_chat_admin_invite_links_count(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         admin_id: Union[int, str],
         revoked: bool = False,
diff --git a/pyrogram/methods/invite_links/get_chat_admins_with_invite_links.py b/pyrogram/methods/invite_links/get_chat_admins_with_invite_links.py
index 8b048a58b5..0f41925a10 100644
--- a/pyrogram/methods/invite_links/get_chat_admins_with_invite_links.py
+++ b/pyrogram/methods/invite_links/get_chat_admins_with_invite_links.py
@@ -18,13 +18,13 @@
 
 from typing import Union
 
+import pyrogram
 from pyrogram import raw, types
-from pyrogram.scaffold import Scaffold
 
 
-class GetChatAdminsWithInviteLinks(Scaffold):
+class GetChatAdminsWithInviteLinks:
     async def get_chat_admins_with_invite_links(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
     ):
         """Get the list of the administrators that have exported invite links in a chat.
diff --git a/pyrogram/methods/invite_links/get_chat_invite_link.py b/pyrogram/methods/invite_links/get_chat_invite_link.py
index a5361bab6f..0fe0da8e9e 100644
--- a/pyrogram/methods/invite_links/get_chat_invite_link.py
+++ b/pyrogram/methods/invite_links/get_chat_invite_link.py
@@ -18,14 +18,14 @@
 
 from typing import Union
 
+import pyrogram
 from pyrogram import raw
 from pyrogram import types
-from pyrogram.scaffold import Scaffold
 
 
-class GetChatInviteLink(Scaffold):
+class GetChatInviteLink:
     async def get_chat_invite_link(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         invite_link: str,
     ) -> "types.ChatInviteLink":
diff --git a/pyrogram/methods/invite_links/get_chat_invite_link_members.py b/pyrogram/methods/invite_links/get_chat_invite_link_members.py
index 5d6e920891..8269a3465b 100644
--- a/pyrogram/methods/invite_links/get_chat_invite_link_members.py
+++ b/pyrogram/methods/invite_links/get_chat_invite_link_members.py
@@ -18,14 +18,14 @@
 
 from typing import Union, Optional, AsyncGenerator
 
+import pyrogram
 from pyrogram import raw
 from pyrogram import types
-from pyrogram.scaffold import Scaffold
 
 
-class GetChatInviteLinkMembers(Scaffold):
+class GetChatInviteLinkMembers:
     async def get_chat_invite_link_members(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         invite_link: str,
         limit: int = 0
diff --git a/pyrogram/methods/invite_links/get_chat_invite_link_members_count.py b/pyrogram/methods/invite_links/get_chat_invite_link_members_count.py
index a4f5bbb710..c37258fe3c 100644
--- a/pyrogram/methods/invite_links/get_chat_invite_link_members_count.py
+++ b/pyrogram/methods/invite_links/get_chat_invite_link_members_count.py
@@ -18,13 +18,13 @@
 
 from typing import Union
 
+import pyrogram
 from pyrogram import raw
-from pyrogram.scaffold import Scaffold
 
 
-class GetChatInviteLinkMembersCount(Scaffold):
+class GetChatInviteLinkMembersCount:
     async def get_chat_invite_link_members_count(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         invite_link: str
     ) -> int:
diff --git a/pyrogram/methods/invite_links/revoke_chat_invite_link.py b/pyrogram/methods/invite_links/revoke_chat_invite_link.py
index cb3c39cdc4..64af8e5f73 100644
--- a/pyrogram/methods/invite_links/revoke_chat_invite_link.py
+++ b/pyrogram/methods/invite_links/revoke_chat_invite_link.py
@@ -18,14 +18,14 @@
 
 from typing import Union
 
+import pyrogram
 from pyrogram import raw
 from pyrogram import types
-from pyrogram.scaffold import Scaffold
 
 
-class RevokeChatInviteLink(Scaffold):
+class RevokeChatInviteLink:
     async def revoke_chat_invite_link(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         invite_link: str,
     ) -> "types.ChatInviteLink":
diff --git a/pyrogram/methods/messages/copy_media_group.py b/pyrogram/methods/messages/copy_media_group.py
index 06aa6c202a..367cf47c57 100644
--- a/pyrogram/methods/messages/copy_media_group.py
+++ b/pyrogram/methods/messages/copy_media_group.py
@@ -19,13 +19,13 @@
 from datetime import datetime
 from typing import Union, List
 
+import pyrogram
 from pyrogram import types, utils, raw
-from pyrogram.scaffold import Scaffold
 
 
-class CopyMediaGroup(Scaffold):
+class CopyMediaGroup:
     async def copy_media_group(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         from_chat_id: Union[int, str],
         message_id: int,
diff --git a/pyrogram/methods/messages/copy_message.py b/pyrogram/methods/messages/copy_message.py
index ee40945bab..94c11d4b50 100644
--- a/pyrogram/methods/messages/copy_message.py
+++ b/pyrogram/methods/messages/copy_message.py
@@ -20,15 +20,15 @@
 from datetime import datetime
 from typing import Union, List, Optional
 
+import pyrogram
 from pyrogram import types, enums
-from pyrogram.scaffold import Scaffold
 
 log = logging.getLogger(__name__)
 
 
-class CopyMessage(Scaffold):
+class CopyMessage:
     async def copy_message(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         from_chat_id: Union[int, str],
         message_id: int,
diff --git a/pyrogram/methods/messages/delete_messages.py b/pyrogram/methods/messages/delete_messages.py
index b9807dc53d..30438944fb 100644
--- a/pyrogram/methods/messages/delete_messages.py
+++ b/pyrogram/methods/messages/delete_messages.py
@@ -18,13 +18,13 @@
 
 from typing import Union, Iterable
 
+import pyrogram
 from pyrogram import raw
-from pyrogram.scaffold import Scaffold
 
 
-class DeleteMessages(Scaffold):
+class DeleteMessages:
     async def delete_messages(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         message_ids: Union[int, Iterable[int]],
         revoke: bool = True
diff --git a/pyrogram/methods/messages/download_media.py b/pyrogram/methods/messages/download_media.py
index c1472e9558..d75ba281a0 100644
--- a/pyrogram/methods/messages/download_media.py
+++ b/pyrogram/methods/messages/download_media.py
@@ -20,22 +20,22 @@
 import os
 import time
 from datetime import datetime
-from typing import Union, Optional
+from typing import Union, Optional, Callable
 
+import pyrogram
 from pyrogram import types
 from pyrogram.file_id import FileId, FileType, PHOTO_TYPES
-from pyrogram.scaffold import Scaffold
 
 DEFAULT_DOWNLOAD_DIR = "downloads/"
 
 
-class DownloadMedia(Scaffold):
+class DownloadMedia:
     async def download_media(
-        self,
+        self: "pyrogram.Client",
         message: Union["types.Message", str],
         file_name: str = DEFAULT_DOWNLOAD_DIR,
         block: bool = True,
-        progress: callable = None,
+        progress: Callable = None,
         progress_args: tuple = ()
     ) -> Optional[str]:
         """Download the media from a message.
@@ -55,7 +55,7 @@ async def download_media(
                 Blocks the code execution until the file has been downloaded.
                 Defaults to True.
 
-            progress (``callable``, *optional*):
+            progress (``Callable``, *optional*):
                 Pass a callback function to view the file transmission progress.
                 The function must take *(current, total)* as positional arguments (look at Other Parameters below for a
                 detailed description) and will be called back each time a new file chunk has been successfully
diff --git a/pyrogram/methods/messages/edit_inline_caption.py b/pyrogram/methods/messages/edit_inline_caption.py
index ed99b62d70..3068098ed9 100644
--- a/pyrogram/methods/messages/edit_inline_caption.py
+++ b/pyrogram/methods/messages/edit_inline_caption.py
@@ -18,13 +18,13 @@
 
 from typing import Optional
 
+import pyrogram
 from pyrogram import types, enums
-from pyrogram.scaffold import Scaffold
 
 
-class EditInlineCaption(Scaffold):
+class EditInlineCaption:
     async def edit_inline_caption(
-        self,
+        self: "pyrogram.Client",
         inline_message_id: str,
         caption: str,
         parse_mode: Optional["enums.ParseMode"] = None,
diff --git a/pyrogram/methods/messages/edit_inline_media.py b/pyrogram/methods/messages/edit_inline_media.py
index 6b8c834ad4..0613bd7368 100644
--- a/pyrogram/methods/messages/edit_inline_media.py
+++ b/pyrogram/methods/messages/edit_inline_media.py
@@ -19,17 +19,17 @@
 import os
 import re
 
+import pyrogram
 from pyrogram import raw
 from pyrogram import types
 from pyrogram import utils
 from pyrogram.file_id import FileType
-from pyrogram.scaffold import Scaffold
 from .inline_session import get_session
 
 
-class EditInlineMedia(Scaffold):
+class EditInlineMedia:
     async def edit_inline_media(
-        self,
+        self: "pyrogram.Client",
         inline_message_id: str,
         media: "types.InputMedia",
         reply_markup: "types.InlineKeyboardMarkup" = None
diff --git a/pyrogram/methods/messages/edit_inline_reply_markup.py b/pyrogram/methods/messages/edit_inline_reply_markup.py
index 614da61718..b3760c6751 100644
--- a/pyrogram/methods/messages/edit_inline_reply_markup.py
+++ b/pyrogram/methods/messages/edit_inline_reply_markup.py
@@ -16,16 +16,16 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
+import pyrogram
 from pyrogram import raw
 from pyrogram import types
 from pyrogram import utils
-from pyrogram.scaffold import Scaffold
 from .inline_session import get_session
 
 
-class EditInlineReplyMarkup(Scaffold):
+class EditInlineReplyMarkup:
     async def edit_inline_reply_markup(
-        self,
+        self: "pyrogram.Client",
         inline_message_id: str,
         reply_markup: "types.InlineKeyboardMarkup" = None
     ) -> bool:
diff --git a/pyrogram/methods/messages/edit_inline_text.py b/pyrogram/methods/messages/edit_inline_text.py
index af4b555574..ae4cab3661 100644
--- a/pyrogram/methods/messages/edit_inline_text.py
+++ b/pyrogram/methods/messages/edit_inline_text.py
@@ -18,16 +18,16 @@
 
 from typing import Optional
 
+import pyrogram
 from pyrogram import raw, enums
 from pyrogram import types
 from pyrogram import utils
-from pyrogram.scaffold import Scaffold
 from .inline_session import get_session
 
 
-class EditInlineText(Scaffold):
+class EditInlineText:
     async def edit_inline_text(
-        self,
+        self: "pyrogram.Client",
         inline_message_id: str,
         text: str,
         parse_mode: Optional["enums.ParseMode"] = None,
diff --git a/pyrogram/methods/messages/edit_message_caption.py b/pyrogram/methods/messages/edit_message_caption.py
index e368d4f57c..fc92b02f1f 100644
--- a/pyrogram/methods/messages/edit_message_caption.py
+++ b/pyrogram/methods/messages/edit_message_caption.py
@@ -18,13 +18,13 @@
 
 from typing import Union, List, Optional
 
+import pyrogram
 from pyrogram import types, enums
-from pyrogram.scaffold import Scaffold
 
 
-class EditMessageCaption(Scaffold):
+class EditMessageCaption:
     async def edit_message_caption(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         message_id: int,
         caption: str,
diff --git a/pyrogram/methods/messages/edit_message_media.py b/pyrogram/methods/messages/edit_message_media.py
index 4e443e71c0..1593bfb6f8 100644
--- a/pyrogram/methods/messages/edit_message_media.py
+++ b/pyrogram/methods/messages/edit_message_media.py
@@ -20,16 +20,16 @@
 import re
 from typing import Union
 
+import pyrogram
 from pyrogram import raw
 from pyrogram import types
 from pyrogram import utils
 from pyrogram.file_id import FileType
-from pyrogram.scaffold import Scaffold
 
 
-class EditMessageMedia(Scaffold):
+class EditMessageMedia:
     async def edit_message_media(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         message_id: int,
         media: "types.InputMedia",
diff --git a/pyrogram/methods/messages/edit_message_reply_markup.py b/pyrogram/methods/messages/edit_message_reply_markup.py
index 2bbe1bc9b0..91b6fcd3e2 100644
--- a/pyrogram/methods/messages/edit_message_reply_markup.py
+++ b/pyrogram/methods/messages/edit_message_reply_markup.py
@@ -18,14 +18,14 @@
 
 from typing import Union
 
+import pyrogram
 from pyrogram import raw
 from pyrogram import types
-from pyrogram.scaffold import Scaffold
 
 
-class EditMessageReplyMarkup(Scaffold):
+class EditMessageReplyMarkup:
     async def edit_message_reply_markup(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         message_id: int,
         reply_markup: "types.InlineKeyboardMarkup" = None,
diff --git a/pyrogram/methods/messages/edit_message_text.py b/pyrogram/methods/messages/edit_message_text.py
index c58429a257..0f409cfe0a 100644
--- a/pyrogram/methods/messages/edit_message_text.py
+++ b/pyrogram/methods/messages/edit_message_text.py
@@ -18,15 +18,15 @@
 
 from typing import Union, List, Optional
 
+import pyrogram
 from pyrogram import raw, enums
 from pyrogram import types
 from pyrogram import utils
-from pyrogram.scaffold import Scaffold
 
 
-class EditMessageText(Scaffold):
+class EditMessageText:
     async def edit_message_text(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         message_id: int,
         text: str,
diff --git a/pyrogram/methods/messages/forward_messages.py b/pyrogram/methods/messages/forward_messages.py
index acddd6e65e..12ed56aa97 100644
--- a/pyrogram/methods/messages/forward_messages.py
+++ b/pyrogram/methods/messages/forward_messages.py
@@ -19,14 +19,14 @@
 from datetime import datetime
 from typing import Union, Iterable, List
 
+import pyrogram
 from pyrogram import raw, utils
 from pyrogram import types
-from pyrogram.scaffold import Scaffold
 
 
-class ForwardMessages(Scaffold):
+class ForwardMessages:
     async def forward_messages(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         from_chat_id: Union[int, str],
         message_ids: Union[int, Iterable[int]],
diff --git a/pyrogram/methods/messages/get_discussion_message.py b/pyrogram/methods/messages/get_discussion_message.py
index c7d47261fa..f1de459234 100644
--- a/pyrogram/methods/messages/get_discussion_message.py
+++ b/pyrogram/methods/messages/get_discussion_message.py
@@ -18,14 +18,14 @@
 
 from typing import Union
 
+import pyrogram
 from pyrogram import raw
 from pyrogram import types
-from pyrogram.scaffold import Scaffold
 
 
-class GetDiscussionMessage(Scaffold):
+class GetDiscussionMessage:
     async def get_discussion_message(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         message_id: int,
     ) -> "types.Message":
diff --git a/pyrogram/methods/messages/get_history.py b/pyrogram/methods/messages/get_history.py
index a461b72007..a67d971e65 100644
--- a/pyrogram/methods/messages/get_history.py
+++ b/pyrogram/methods/messages/get_history.py
@@ -20,17 +20,17 @@
 from datetime import datetime
 from typing import Union, List
 
+import pyrogram
 from pyrogram import raw
 from pyrogram import types
 from pyrogram import utils
-from pyrogram.scaffold import Scaffold
 
 log = logging.getLogger(__name__)
 
 
-class GetHistory(Scaffold):
+class GetHistory:
     async def get_history(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         limit: int = 100,
         offset: int = 0,
diff --git a/pyrogram/methods/messages/get_history_count.py b/pyrogram/methods/messages/get_history_count.py
index 9facdbebac..a76c10b51d 100644
--- a/pyrogram/methods/messages/get_history_count.py
+++ b/pyrogram/methods/messages/get_history_count.py
@@ -19,15 +19,15 @@
 import logging
 from typing import Union
 
+import pyrogram
 from pyrogram import raw
-from pyrogram.scaffold import Scaffold
 
 log = logging.getLogger(__name__)
 
 
-class GetHistoryCount(Scaffold):
+class GetHistoryCount:
     async def get_history_count(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str]
     ) -> int:
         """Get the total count of messages in a chat.
diff --git a/pyrogram/methods/messages/get_media_group.py b/pyrogram/methods/messages/get_media_group.py
index 3b33bfd4a9..b50d4785af 100644
--- a/pyrogram/methods/messages/get_media_group.py
+++ b/pyrogram/methods/messages/get_media_group.py
@@ -19,15 +19,15 @@
 import logging
 from typing import Union, List
 
+import pyrogram
 from pyrogram import types
-from pyrogram.scaffold import Scaffold
 
 log = logging.getLogger(__name__)
 
 
-class GetMediaGroup(Scaffold):
+class GetMediaGroup:
     async def get_media_group(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         message_id: int
     ) -> List["types.Message"]:
diff --git a/pyrogram/methods/messages/get_messages.py b/pyrogram/methods/messages/get_messages.py
index b9c0fbf7ca..e9e408b5e2 100644
--- a/pyrogram/methods/messages/get_messages.py
+++ b/pyrogram/methods/messages/get_messages.py
@@ -19,10 +19,10 @@
 import logging
 from typing import Union, Iterable, List
 
+import pyrogram
 from pyrogram import raw
 from pyrogram import types
 from pyrogram import utils
-from pyrogram.scaffold import Scaffold
 
 log = logging.getLogger(__name__)
 
@@ -30,9 +30,9 @@
 # TODO: Rewrite using a flag for replied messages and have message_ids non-optional
 
 
-class GetMessages(Scaffold):
+class GetMessages:
     async def get_messages(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         message_ids: Union[int, Iterable[int]] = None,
         reply_to_message_ids: Union[int, Iterable[int]] = None,
diff --git a/pyrogram/methods/messages/iter_history.py b/pyrogram/methods/messages/iter_history.py
index c5eda28871..4914cd1d41 100644
--- a/pyrogram/methods/messages/iter_history.py
+++ b/pyrogram/methods/messages/iter_history.py
@@ -19,13 +19,13 @@
 from datetime import datetime
 from typing import Union, Optional, AsyncGenerator
 
+import pyrogram
 from pyrogram import types
-from pyrogram.scaffold import Scaffold
 
 
-class IterHistory(Scaffold):
+class IterHistory:
     async def iter_history(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         limit: int = 0,
         offset: int = 0,
diff --git a/pyrogram/methods/messages/read_history.py b/pyrogram/methods/messages/read_history.py
index 881b59ad7c..66b8bf50f7 100644
--- a/pyrogram/methods/messages/read_history.py
+++ b/pyrogram/methods/messages/read_history.py
@@ -18,13 +18,13 @@
 
 from typing import Union
 
+import pyrogram
 from pyrogram import raw
-from pyrogram.scaffold import Scaffold
 
 
-class ReadHistory(Scaffold):
+class ReadHistory:
     async def read_history(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         max_id: int = 0
     ) -> bool:
diff --git a/pyrogram/methods/messages/retract_vote.py b/pyrogram/methods/messages/retract_vote.py
index 4baba81157..f49807cd7a 100644
--- a/pyrogram/methods/messages/retract_vote.py
+++ b/pyrogram/methods/messages/retract_vote.py
@@ -18,14 +18,14 @@
 
 from typing import Union
 
+import pyrogram
 from pyrogram import raw
 from pyrogram import types
-from pyrogram.scaffold import Scaffold
 
 
-class RetractVote(Scaffold):
+class RetractVote:
     async def retract_vote(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         message_id: int
     ) -> "types.Poll":
diff --git a/pyrogram/methods/messages/search_global.py b/pyrogram/methods/messages/search_global.py
index 6dff7bf0af..3f0d4e9ff3 100644
--- a/pyrogram/methods/messages/search_global.py
+++ b/pyrogram/methods/messages/search_global.py
@@ -18,15 +18,15 @@
 
 from typing import AsyncGenerator, Optional
 
+import pyrogram
 from pyrogram import raw, enums
 from pyrogram import types
 from pyrogram import utils
-from pyrogram.scaffold import Scaffold
 
 
-class SearchGlobal(Scaffold):
+class SearchGlobal:
     async def search_global(
-        self,
+        self: "pyrogram.Client",
         query: str = "",
         filter: "enums.MessagesFilter" = enums.MessagesFilter.ANY,
         limit: int = 0,
diff --git a/pyrogram/methods/messages/search_global_count.py b/pyrogram/methods/messages/search_global_count.py
index d8da0163e5..c848546ef3 100644
--- a/pyrogram/methods/messages/search_global_count.py
+++ b/pyrogram/methods/messages/search_global_count.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 pyrogram
 from pyrogram import raw, enums
-from pyrogram.scaffold import Scaffold
 
 
-class SearchGlobalCount(Scaffold):
+class SearchGlobalCount:
     async def search_global_count(
-        self,
+        self: "pyrogram.Client",
         query: str = "",
         filter: "enums.MessagesFilter" = enums.MessagesFilter.ANY,
     ) -> int:
diff --git a/pyrogram/methods/messages/search_messages.py b/pyrogram/methods/messages/search_messages.py
index 07728193e4..b448a1c204 100644
--- a/pyrogram/methods/messages/search_messages.py
+++ b/pyrogram/methods/messages/search_messages.py
@@ -18,13 +18,13 @@
 
 from typing import Union, List, AsyncGenerator, Optional
 
+import pyrogram
 from pyrogram import raw, types, utils, enums
-from pyrogram.scaffold import Scaffold
 
 
 # noinspection PyShadowingBuiltins
 async def get_chunk(
-    client: Scaffold,
+    client,
     chat_id: Union[int, str],
     query: str = "",
     filter: "enums.MessagesFilter" = enums.MessagesFilter.ANY,
@@ -57,10 +57,10 @@ async def get_chunk(
     return await utils.parse_messages(client, r)
 
 
-class SearchMessages(Scaffold):
+class SearchMessages:
     # noinspection PyShadowingBuiltins
     async def search_messages(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         query: str = "",
         offset: int = 0,
diff --git a/pyrogram/methods/messages/search_messages_count.py b/pyrogram/methods/messages/search_messages_count.py
index 87e0a74572..85c25d0618 100644
--- a/pyrogram/methods/messages/search_messages_count.py
+++ b/pyrogram/methods/messages/search_messages_count.py
@@ -18,13 +18,13 @@
 
 from typing import Union
 
+import pyrogram
 from pyrogram import raw, enums
-from pyrogram.scaffold import Scaffold
 
 
-class SearchMessagesCount(Scaffold):
+class SearchMessagesCount:
     async def search_messages_count(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         query: str = "",
         filter: "enums.MessagesFilter" = enums.MessagesFilter.ANY,
diff --git a/pyrogram/methods/messages/send_animation.py b/pyrogram/methods/messages/send_animation.py
index 4e6a9f407b..2e84fe8267 100644
--- a/pyrogram/methods/messages/send_animation.py
+++ b/pyrogram/methods/messages/send_animation.py
@@ -19,20 +19,20 @@
 import os
 import re
 from datetime import datetime
-from typing import Union, BinaryIO, List, Optional
+from typing import Union, BinaryIO, List, Optional, Callable
 
+import pyrogram
 from pyrogram import StopTransmission, enums
 from pyrogram import raw
 from pyrogram import types
 from pyrogram import utils
 from pyrogram.errors import FilePartMissing
 from pyrogram.file_id import FileType
-from pyrogram.scaffold import Scaffold
 
 
-class SendAnimation(Scaffold):
+class SendAnimation:
     async def send_animation(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         animation: Union[str, BinaryIO],
         caption: str = "",
@@ -54,7 +54,7 @@ async def send_animation(
             "types.ReplyKeyboardRemove",
             "types.ForceReply"
         ] = None,
-        progress: callable = None,
+        progress: Callable = None,
         progress_args: tuple = ()
     ) -> Optional["types.Message"]:
         """Send animation files (animation or H.264/MPEG-4 AVC video without sound).
@@ -122,7 +122,7 @@ async def send_animation(
                 Additional interface options. An object for an inline keyboard, custom reply keyboard,
                 instructions to remove reply keyboard or to force a reply from the user.
 
-            progress (``callable``, *optional*):
+            progress (``Callable``, *optional*):
                 Pass a callback function to view the file transmission progress.
                 The function must take *(current, total)* as positional arguments (look at Other Parameters below for a
                 detailed description) and will be called back each time a new file chunk has been successfully
diff --git a/pyrogram/methods/messages/send_audio.py b/pyrogram/methods/messages/send_audio.py
index 6cef5b47dd..47e914a8b4 100644
--- a/pyrogram/methods/messages/send_audio.py
+++ b/pyrogram/methods/messages/send_audio.py
@@ -18,21 +18,21 @@
 
 import os
 import re
-from typing import Union, BinaryIO, List, Optional
+from datetime import datetime
+from typing import Union, BinaryIO, List, Optional, Callable
 
+import pyrogram
 from pyrogram import StopTransmission, enums
 from pyrogram import raw
 from pyrogram import types
 from pyrogram import utils
 from pyrogram.errors import FilePartMissing
 from pyrogram.file_id import FileType
-from pyrogram.scaffold import Scaffold
-from datetime import datetime
 
 
-class SendAudio(Scaffold):
+class SendAudio:
     async def send_audio(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         audio: Union[str, BinaryIO],
         caption: str = "",
@@ -53,7 +53,7 @@ async def send_audio(
             "types.ReplyKeyboardRemove",
             "types.ForceReply"
         ] = None,
-        progress: callable = None,
+        progress: Callable = None,
         progress_args: tuple = ()
     ) -> Optional["types.Message"]:
         """Send audio files.
@@ -119,7 +119,7 @@ async def send_audio(
                 Additional interface options. An object for an inline keyboard, custom reply keyboard,
                 instructions to remove reply keyboard or to force a reply from the user.
 
-            progress (``callable``, *optional*):
+            progress (``Callable``, *optional*):
                 Pass a callback function to view the file transmission progress.
                 The function must take *(current, total)* as positional arguments (look at Other Parameters below for a
                 detailed description) and will be called back each time a new file chunk has been successfully
diff --git a/pyrogram/methods/messages/send_cached_media.py b/pyrogram/methods/messages/send_cached_media.py
index 60832553fb..4763e94d46 100644
--- a/pyrogram/methods/messages/send_cached_media.py
+++ b/pyrogram/methods/messages/send_cached_media.py
@@ -19,15 +19,15 @@
 from datetime import datetime
 from typing import Union, List, Optional
 
+import pyrogram
 from pyrogram import raw, enums
 from pyrogram import types
 from pyrogram import utils
-from pyrogram.scaffold import Scaffold
 
 
-class SendCachedMedia(Scaffold):
+class SendCachedMedia:
     async def send_cached_media(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         file_id: str,
         caption: str = "",
diff --git a/pyrogram/methods/messages/send_chat_action.py b/pyrogram/methods/messages/send_chat_action.py
index bbff1d4421..9145653e7a 100644
--- a/pyrogram/methods/messages/send_chat_action.py
+++ b/pyrogram/methods/messages/send_chat_action.py
@@ -20,12 +20,11 @@
 
 import pyrogram
 from pyrogram import raw
-from pyrogram.scaffold import Scaffold
 
 
-class SendChatAction(Scaffold):
+class SendChatAction:
     async def send_chat_action(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         action: "pyrogram.enums.ChatAction"
     ) -> bool:
diff --git a/pyrogram/methods/messages/send_contact.py b/pyrogram/methods/messages/send_contact.py
index f9c84f4c9f..6285b502ff 100644
--- a/pyrogram/methods/messages/send_contact.py
+++ b/pyrogram/methods/messages/send_contact.py
@@ -19,14 +19,14 @@
 from datetime import datetime
 from typing import Union
 
-from pyrogram import raw
+import pyrogram
+from pyrogram import raw, utils
 from pyrogram import types
-from pyrogram.scaffold import Scaffold
 
 
-class SendContact(Scaffold):
+class SendContact:
     async def send_contact(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         phone_number: str,
         first_name: str,
diff --git a/pyrogram/methods/messages/send_dice.py b/pyrogram/methods/messages/send_dice.py
index 9c7f5d1a70..c3a6bb42fb 100644
--- a/pyrogram/methods/messages/send_dice.py
+++ b/pyrogram/methods/messages/send_dice.py
@@ -16,17 +16,17 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
+from datetime import datetime
 from typing import Union, Optional
 
-from pyrogram import raw
+import pyrogram
+from pyrogram import raw, utils
 from pyrogram import types
-from pyrogram.scaffold import Scaffold
-from datetime import datetime
 
 
-class SendDice(Scaffold):
+class SendDice:
     async def send_dice(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         emoji: str = "🎲",
         disable_notification: bool = None,
diff --git a/pyrogram/methods/messages/send_document.py b/pyrogram/methods/messages/send_document.py
index 5844d2d232..79f6e160ef 100644
--- a/pyrogram/methods/messages/send_document.py
+++ b/pyrogram/methods/messages/send_document.py
@@ -19,20 +19,20 @@
 import os
 import re
 from datetime import datetime
-from typing import Union, BinaryIO, List, Optional
+from typing import Union, BinaryIO, List, Optional, Callable
 
+import pyrogram
 from pyrogram import StopTransmission, enums
 from pyrogram import raw
 from pyrogram import types
 from pyrogram import utils
 from pyrogram.errors import FilePartMissing
 from pyrogram.file_id import FileType
-from pyrogram.scaffold import Scaffold
 
 
-class SendDocument(Scaffold):
+class SendDocument:
     async def send_document(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         document: Union[str, BinaryIO],
         thumb: Union[str, BinaryIO] = None,
@@ -51,7 +51,7 @@ async def send_document(
             "types.ReplyKeyboardRemove",
             "types.ForceReply"
         ] = None,
-        progress: callable = None,
+        progress: Callable = None,
         progress_args: tuple = ()
     ) -> Optional["types.Message"]:
         """Send generic files.
@@ -111,7 +111,7 @@ async def send_document(
                 Additional interface options. An object for an inline keyboard, custom reply keyboard,
                 instructions to remove reply keyboard or to force a reply from the user.
 
-            progress (``callable``, *optional*):
+            progress (``Callable``, *optional*):
                 Pass a callback function to view the file transmission progress.
                 The function must take *(current, total)* as positional arguments (look at Other Parameters below for a
                 detailed description) and will be called back each time a new file chunk has been successfully
diff --git a/pyrogram/methods/messages/send_location.py b/pyrogram/methods/messages/send_location.py
index 6c33c1b4fc..3e9bf37c75 100644
--- a/pyrogram/methods/messages/send_location.py
+++ b/pyrogram/methods/messages/send_location.py
@@ -19,14 +19,14 @@
 from datetime import datetime
 from typing import Union
 
-from pyrogram import raw
+import pyrogram
+from pyrogram import raw, utils
 from pyrogram import types
-from pyrogram.scaffold import Scaffold
 
 
-class SendLocation(Scaffold):
+class SendLocation:
     async def send_location(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         latitude: float,
         longitude: float,
diff --git a/pyrogram/methods/messages/send_media_group.py b/pyrogram/methods/messages/send_media_group.py
index 04c91c3b44..ccb6136163 100644
--- a/pyrogram/methods/messages/send_media_group.py
+++ b/pyrogram/methods/messages/send_media_group.py
@@ -22,19 +22,19 @@
 from datetime import datetime
 from typing import Union, List
 
+import pyrogram
 from pyrogram import raw
 from pyrogram import types
 from pyrogram import utils
 from pyrogram.file_id import FileType
-from pyrogram.scaffold import Scaffold
 
 log = logging.getLogger(__name__)
 
 
-class SendMediaGroup(Scaffold):
+class SendMediaGroup:
     # TODO: Add progress parameter
     async def send_media_group(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         media: List[Union[
             "types.InputMediaPhoto",
diff --git a/pyrogram/methods/messages/send_message.py b/pyrogram/methods/messages/send_message.py
index 34063625dd..70544178f9 100644
--- a/pyrogram/methods/messages/send_message.py
+++ b/pyrogram/methods/messages/send_message.py
@@ -16,18 +16,17 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
+from datetime import datetime
 from typing import Union, List, Optional
 
+import pyrogram
 from pyrogram import raw, utils, enums
 from pyrogram import types
-from pyrogram.scaffold import Scaffold
-
-from datetime import datetime
 
 
-class SendMessage(Scaffold):
+class SendMessage:
     async def send_message(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         text: str,
         parse_mode: Optional["enums.ParseMode"] = None,
diff --git a/pyrogram/methods/messages/send_photo.py b/pyrogram/methods/messages/send_photo.py
index bf7b482cbe..5cacfb39c3 100644
--- a/pyrogram/methods/messages/send_photo.py
+++ b/pyrogram/methods/messages/send_photo.py
@@ -19,7 +19,7 @@
 import os
 import re
 from datetime import datetime
-from typing import Union, BinaryIO, List, Optional
+from typing import Union, BinaryIO, List, Optional, Callable
 
 import pyrogram
 from pyrogram import raw, enums
@@ -27,12 +27,11 @@
 from pyrogram import utils
 from pyrogram.errors import FilePartMissing
 from pyrogram.file_id import FileType
-from pyrogram.scaffold import Scaffold
 
 
-class SendPhoto(Scaffold):
+class SendPhoto:
     async def send_photo(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         photo: Union[str, BinaryIO],
         caption: str = "",
@@ -49,7 +48,7 @@ async def send_photo(
             "types.ReplyKeyboardRemove",
             "types.ForceReply"
         ] = None,
-        progress: callable = None,
+        progress: Callable = None,
         progress_args: tuple = ()
     ) -> Optional["types.Message"]:
         """Send photos.
@@ -99,7 +98,7 @@ async def send_photo(
                 Additional interface options. An object for an inline keyboard, custom reply keyboard,
                 instructions to remove reply keyboard or to force a reply from the user.
 
-            progress (``callable``, *optional*):
+            progress (``Callable``, *optional*):
                 Pass a callback function to view the file transmission progress.
                 The function must take *(current, total)* as positional arguments (look at Other Parameters below for a
                 detailed description) and will be called back each time a new file chunk has been successfully
diff --git a/pyrogram/methods/messages/send_poll.py b/pyrogram/methods/messages/send_poll.py
index a02c52663c..10545256eb 100644
--- a/pyrogram/methods/messages/send_poll.py
+++ b/pyrogram/methods/messages/send_poll.py
@@ -19,14 +19,14 @@
 from datetime import datetime
 from typing import Union, List
 
-from pyrogram import raw
+import pyrogram
+from pyrogram import raw, utils
 from pyrogram import types, enums
-from pyrogram.scaffold import Scaffold
 
 
-class SendPoll(Scaffold):
+class SendPoll:
     async def send_poll(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         question: str,
         options: List[str],
diff --git a/pyrogram/methods/messages/send_reaction.py b/pyrogram/methods/messages/send_reaction.py
index 9e6c353cd1..b096934fc2 100644
--- a/pyrogram/methods/messages/send_reaction.py
+++ b/pyrogram/methods/messages/send_reaction.py
@@ -18,13 +18,13 @@
 
 from typing import Union
 
+import pyrogram
 from pyrogram import raw
-from pyrogram.scaffold import Scaffold
 
 
-class SendReaction(Scaffold):
+class SendReaction:
     async def send_reaction(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         message_id: int,
         emoji: str = ""
diff --git a/pyrogram/methods/messages/send_sticker.py b/pyrogram/methods/messages/send_sticker.py
index 76571d9999..460cfc6304 100644
--- a/pyrogram/methods/messages/send_sticker.py
+++ b/pyrogram/methods/messages/send_sticker.py
@@ -19,20 +19,20 @@
 import os
 import re
 from datetime import datetime
-from typing import Union, BinaryIO, Optional
+from typing import Union, BinaryIO, Optional, Callable
 
+import pyrogram
 from pyrogram import StopTransmission
 from pyrogram import raw
 from pyrogram import types
 from pyrogram import utils
 from pyrogram.errors import FilePartMissing
 from pyrogram.file_id import FileType
-from pyrogram.scaffold import Scaffold
 
 
-class SendSticker(Scaffold):
+class SendSticker:
     async def send_sticker(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         sticker: Union[str, BinaryIO],
         disable_notification: bool = None,
@@ -45,7 +45,7 @@ async def send_sticker(
             "types.ReplyKeyboardRemove",
             "types.ForceReply"
         ] = None,
-        progress: callable = None,
+        progress: Callable = None,
         progress_args: tuple = ()
     ) -> Optional["types.Message"]:
         """Send static .webp or animated .tgs stickers.
@@ -80,7 +80,7 @@ async def send_sticker(
                 Additional interface options. An object for an inline keyboard, custom reply keyboard,
                 instructions to remove reply keyboard or to force a reply from the user.
 
-            progress (``callable``, *optional*):
+            progress (``Callable``, *optional*):
                 Pass a callback function to view the file transmission progress.
                 The function must take *(current, total)* as positional arguments (look at Other Parameters below for a
                 detailed description) and will be called back each time a new file chunk has been successfully
diff --git a/pyrogram/methods/messages/send_venue.py b/pyrogram/methods/messages/send_venue.py
index 5293cf01df..4dd81f7a3f 100644
--- a/pyrogram/methods/messages/send_venue.py
+++ b/pyrogram/methods/messages/send_venue.py
@@ -19,14 +19,14 @@
 from datetime import datetime
 from typing import Union
 
-from pyrogram import raw
+import pyrogram
+from pyrogram import raw, utils
 from pyrogram import types
-from pyrogram.scaffold import Scaffold
 
 
-class SendVenue(Scaffold):
+class SendVenue:
     async def send_venue(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         latitude: float,
         longitude: float,
diff --git a/pyrogram/methods/messages/send_video.py b/pyrogram/methods/messages/send_video.py
index 96f2a9c3bb..9fd8521358 100644
--- a/pyrogram/methods/messages/send_video.py
+++ b/pyrogram/methods/messages/send_video.py
@@ -19,20 +19,20 @@
 import os
 import re
 from datetime import datetime
-from typing import Union, BinaryIO, List, Optional
+from typing import Union, BinaryIO, List, Optional, Callable
 
+import pyrogram
 from pyrogram import StopTransmission, enums
 from pyrogram import raw
 from pyrogram import types
 from pyrogram import utils
 from pyrogram.errors import FilePartMissing
 from pyrogram.file_id import FileType
-from pyrogram.scaffold import Scaffold
 
 
-class SendVideo(Scaffold):
+class SendVideo:
     async def send_video(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         video: Union[str, BinaryIO],
         caption: str = "",
@@ -55,7 +55,7 @@ async def send_video(
             "types.ReplyKeyboardRemove",
             "types.ForceReply"
         ] = None,
-        progress: callable = None,
+        progress: Callable = None,
         progress_args: tuple = ()
     ) -> Optional["types.Message"]:
         """Send video files.
@@ -128,7 +128,7 @@ async def send_video(
                 Additional interface options. An object for an inline keyboard, custom reply keyboard,
                 instructions to remove reply keyboard or to force a reply from the user.
 
-            progress (``callable``, *optional*):
+            progress (``Callable``, *optional*):
                 Pass a callback function to view the file transmission progress.
                 The function must take *(current, total)* as positional arguments (look at Other Parameters below for a
                 detailed description) and will be called back each time a new file chunk has been successfully
diff --git a/pyrogram/methods/messages/send_video_note.py b/pyrogram/methods/messages/send_video_note.py
index c2f0a159ab..b4fd8891f5 100644
--- a/pyrogram/methods/messages/send_video_note.py
+++ b/pyrogram/methods/messages/send_video_note.py
@@ -18,20 +18,20 @@
 
 import os
 from datetime import datetime
-from typing import Union, BinaryIO, Optional
+from typing import Union, BinaryIO, Optional, Callable
 
+import pyrogram
 from pyrogram import StopTransmission
 from pyrogram import raw
 from pyrogram import types
 from pyrogram import utils
 from pyrogram.errors import FilePartMissing
 from pyrogram.file_id import FileType
-from pyrogram.scaffold import Scaffold
 
 
-class SendVideoNote(Scaffold):
+class SendVideoNote:
     async def send_video_note(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         video_note: Union[str, BinaryIO],
         duration: int = 0,
@@ -47,7 +47,7 @@ async def send_video_note(
             "types.ReplyKeyboardRemove",
             "types.ForceReply"
         ] = None,
-        progress: callable = None,
+        progress: Callable = None,
         progress_args: tuple = ()
     ) -> Optional["types.Message"]:
         """Send video messages.
@@ -94,7 +94,7 @@ async def send_video_note(
                 Additional interface options. An object for an inline keyboard, custom reply keyboard,
                 instructions to remove reply keyboard or to force a reply from the user.
 
-            progress (``callable``, *optional*):
+            progress (``Callable``, *optional*):
                 Pass a callback function to view the file transmission progress.
                 The function must take *(current, total)* as positional arguments (look at Other Parameters below for a
                 detailed description) and will be called back each time a new file chunk has been successfully
diff --git a/pyrogram/methods/messages/send_voice.py b/pyrogram/methods/messages/send_voice.py
index 79fefcab1d..5179866e0d 100644
--- a/pyrogram/methods/messages/send_voice.py
+++ b/pyrogram/methods/messages/send_voice.py
@@ -19,20 +19,20 @@
 import os
 import re
 from datetime import datetime
-from typing import Union, BinaryIO, List, Optional
+from typing import Union, BinaryIO, List, Optional, Callable
 
+import pyrogram
 from pyrogram import StopTransmission, enums
 from pyrogram import raw
 from pyrogram import types
 from pyrogram import utils
 from pyrogram.errors import FilePartMissing
 from pyrogram.file_id import FileType
-from pyrogram.scaffold import Scaffold
 
 
-class SendVoice(Scaffold):
+class SendVoice:
     async def send_voice(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         voice: Union[str, BinaryIO],
         caption: str = "",
@@ -49,7 +49,7 @@ async def send_voice(
             "types.ReplyKeyboardRemove",
             "types.ForceReply"
         ] = None,
-        progress: callable = None,
+        progress: Callable = None,
         progress_args: tuple = ()
     ) -> Optional["types.Message"]:
         """Send audio files.
@@ -97,7 +97,7 @@ async def send_voice(
                 Additional interface options. An object for an inline keyboard, custom reply keyboard,
                 instructions to remove reply keyboard or to force a reply from the user.
 
-            progress (``callable``, *optional*):
+            progress (``Callable``, *optional*):
                 Pass a callback function to view the file transmission progress.
                 The function must take *(current, total)* as positional arguments (look at Other Parameters below for a
                 detailed description) and will be called back each time a new file chunk has been successfully
diff --git a/pyrogram/methods/messages/stop_poll.py b/pyrogram/methods/messages/stop_poll.py
index 113f1618aa..3fdba750d3 100644
--- a/pyrogram/methods/messages/stop_poll.py
+++ b/pyrogram/methods/messages/stop_poll.py
@@ -18,14 +18,14 @@
 
 from typing import Union
 
+import pyrogram
 from pyrogram import raw
 from pyrogram import types
-from pyrogram.scaffold import Scaffold
 
 
-class StopPoll(Scaffold):
+class StopPoll:
     async def stop_poll(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         message_id: int,
         reply_markup: "types.InlineKeyboardMarkup" = None
diff --git a/pyrogram/methods/messages/vote_poll.py b/pyrogram/methods/messages/vote_poll.py
index d6753a275a..70a5036574 100644
--- a/pyrogram/methods/messages/vote_poll.py
+++ b/pyrogram/methods/messages/vote_poll.py
@@ -18,14 +18,14 @@
 
 from typing import Union, List
 
+import pyrogram
 from pyrogram import raw
 from pyrogram import types
-from pyrogram.scaffold import Scaffold
 
 
-class VotePoll(Scaffold):
+class VotePoll:
     async def vote_poll(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         message_id: id,
         options: Union[int, List[int]]
diff --git a/pyrogram/methods/password/change_cloud_password.py b/pyrogram/methods/password/change_cloud_password.py
index f950c65a70..a0e8296382 100644
--- a/pyrogram/methods/password/change_cloud_password.py
+++ b/pyrogram/methods/password/change_cloud_password.py
@@ -18,14 +18,14 @@
 
 import os
 
+import pyrogram
 from pyrogram import raw
-from pyrogram.scaffold import Scaffold
 from pyrogram.utils import compute_password_hash, compute_password_check, btoi, itob
 
 
-class ChangeCloudPassword(Scaffold):
+class ChangeCloudPassword:
     async def change_cloud_password(
-        self,
+        self: "pyrogram.Client",
         current_password: str,
         new_password: str,
         new_hint: str = ""
diff --git a/pyrogram/methods/password/enable_cloud_password.py b/pyrogram/methods/password/enable_cloud_password.py
index 7073af59c2..840acfdd49 100644
--- a/pyrogram/methods/password/enable_cloud_password.py
+++ b/pyrogram/methods/password/enable_cloud_password.py
@@ -18,14 +18,14 @@
 
 import os
 
+import pyrogram
 from pyrogram import raw
-from pyrogram.scaffold import Scaffold
 from pyrogram.utils import compute_password_hash, btoi, itob
 
 
-class EnableCloudPassword(Scaffold):
+class EnableCloudPassword:
     async def enable_cloud_password(
-        self,
+        self: "pyrogram.Client",
         password: str,
         hint: str = "",
         email: str = None
diff --git a/pyrogram/methods/password/remove_cloud_password.py b/pyrogram/methods/password/remove_cloud_password.py
index 18ae31e5c1..1a1c9a0b56 100644
--- a/pyrogram/methods/password/remove_cloud_password.py
+++ b/pyrogram/methods/password/remove_cloud_password.py
@@ -16,14 +16,14 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
+import pyrogram
 from pyrogram import raw
-from pyrogram.scaffold import Scaffold
 from pyrogram.utils import compute_password_check
 
 
-class RemoveCloudPassword(Scaffold):
+class RemoveCloudPassword:
     async def remove_cloud_password(
-        self,
+        self: "pyrogram.Client",
         password: str
     ) -> bool:
         """Turn off the Two-Step Verification security feature (Cloud Password) on your account.
diff --git a/pyrogram/methods/users/block_user.py b/pyrogram/methods/users/block_user.py
index 0efda2b9a8..b1d96537fb 100644
--- a/pyrogram/methods/users/block_user.py
+++ b/pyrogram/methods/users/block_user.py
@@ -18,13 +18,13 @@
 
 from typing import Union
 
+import pyrogram
 from pyrogram import raw
-from pyrogram.scaffold import Scaffold
 
 
-class BlockUser(Scaffold):
+class BlockUser:
     async def block_user(
-        self,
+        self: "pyrogram.Client",
         user_id: Union[int, str]
     ) -> bool:
         """Block a user.
diff --git a/pyrogram/methods/users/delete_profile_photos.py b/pyrogram/methods/users/delete_profile_photos.py
index 64f3ce7763..a1df82eb7a 100644
--- a/pyrogram/methods/users/delete_profile_photos.py
+++ b/pyrogram/methods/users/delete_profile_photos.py
@@ -18,15 +18,15 @@
 
 from typing import List, Union
 
+import pyrogram
 from pyrogram import raw
 from pyrogram import utils
 from pyrogram.file_id import FileType
-from pyrogram.scaffold import Scaffold
 
 
-class DeleteProfilePhotos(Scaffold):
+class DeleteProfilePhotos:
     async def delete_profile_photos(
-        self,
+        self: "pyrogram.Client",
         photo_ids: Union[str, List[str]]
     ) -> bool:
         """Delete your own profile photos.
diff --git a/pyrogram/methods/users/get_common_chats.py b/pyrogram/methods/users/get_common_chats.py
index 7969647a0c..e083e3c981 100644
--- a/pyrogram/methods/users/get_common_chats.py
+++ b/pyrogram/methods/users/get_common_chats.py
@@ -18,13 +18,16 @@
 
 from typing import Union
 
+import pyrogram
 from pyrogram import raw
 from pyrogram import types
-from pyrogram.scaffold import Scaffold
 
 
-class GetCommonChats(Scaffold):
-    async def get_common_chats(self, user_id: Union[int, str]) -> list:
+class GetCommonChats:
+    async def get_common_chats(
+        self: "pyrogram.Client",
+        user_id: Union[int, str]
+    ) -> list:
         """Get the common chats you have with a user.
 
         Parameters:
diff --git a/pyrogram/methods/users/get_me.py b/pyrogram/methods/users/get_me.py
index b7ebf2d745..17ffe3b837 100644
--- a/pyrogram/methods/users/get_me.py
+++ b/pyrogram/methods/users/get_me.py
@@ -16,13 +16,15 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
+import pyrogram
 from pyrogram import raw
 from pyrogram import types
-from pyrogram.scaffold import Scaffold
 
 
-class GetMe(Scaffold):
-    async def get_me(self) -> "types.User":
+class GetMe:
+    async def get_me(
+        self: "pyrogram.Client"
+    ) -> "types.User":
         """Get your own user identity.
 
         Returns:
diff --git a/pyrogram/methods/users/get_profile_photos.py b/pyrogram/methods/users/get_profile_photos.py
index fb8c75c497..62341550dd 100644
--- a/pyrogram/methods/users/get_profile_photos.py
+++ b/pyrogram/methods/users/get_profile_photos.py
@@ -18,15 +18,15 @@
 
 from typing import Union, List
 
+import pyrogram
 from pyrogram import raw
 from pyrogram import types
 from pyrogram import utils
-from pyrogram.scaffold import Scaffold
 
 
-class GetProfilePhotos(Scaffold):
+class GetProfilePhotos:
     async def get_profile_photos(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         offset: int = 0,
         limit: int = 100
diff --git a/pyrogram/methods/users/get_profile_photos_count.py b/pyrogram/methods/users/get_profile_photos_count.py
index dcb7ee4b4c..c0065dd7e0 100644
--- a/pyrogram/methods/users/get_profile_photos_count.py
+++ b/pyrogram/methods/users/get_profile_photos_count.py
@@ -18,12 +18,15 @@
 
 from typing import Union
 
+import pyrogram
 from pyrogram import raw
-from pyrogram.scaffold import Scaffold
 
 
-class GetProfilePhotosCount(Scaffold):
-    async def get_profile_photos_count(self, chat_id: Union[int, str]) -> int:
+class GetProfilePhotosCount:
+    async def get_profile_photos_count(
+        self: "pyrogram.Client",
+        chat_id: Union[int, str]
+    ) -> int:
         """Get the total count of profile pictures for a user.
 
         Parameters:
diff --git a/pyrogram/methods/users/get_users.py b/pyrogram/methods/users/get_users.py
index 43ae008423..0e9c961411 100644
--- a/pyrogram/methods/users/get_users.py
+++ b/pyrogram/methods/users/get_users.py
@@ -19,14 +19,14 @@
 import asyncio
 from typing import Iterable, Union, List
 
+import pyrogram
 from pyrogram import raw
 from pyrogram import types
-from pyrogram.scaffold import Scaffold
 
 
-class GetUsers(Scaffold):
+class GetUsers:
     async def get_users(
-        self,
+        self: "pyrogram.Client",
         user_ids: Union[Iterable[Union[int, str]], int, str]
     ) -> Union["types.User", List["types.User"]]:
         """Get information about a user.
diff --git a/pyrogram/methods/users/iter_profile_photos.py b/pyrogram/methods/users/iter_profile_photos.py
index eda77a8889..88c02c3a54 100644
--- a/pyrogram/methods/users/iter_profile_photos.py
+++ b/pyrogram/methods/users/iter_profile_photos.py
@@ -18,13 +18,13 @@
 
 from typing import Union, AsyncGenerator, Optional
 
+import pyrogram
 from pyrogram import types
-from pyrogram.scaffold import Scaffold
 
 
-class IterProfilePhotos(Scaffold):
+class IterProfilePhotos:
     async def iter_profile_photos(
-        self,
+        self: "pyrogram.Client",
         chat_id: Union[int, str],
         offset: int = 0,
         limit: int = 0,
diff --git a/pyrogram/methods/users/set_profile_photo.py b/pyrogram/methods/users/set_profile_photo.py
index 9b1c5c04c5..a68db7cda2 100644
--- a/pyrogram/methods/users/set_profile_photo.py
+++ b/pyrogram/methods/users/set_profile_photo.py
@@ -18,13 +18,13 @@
 
 from typing import Union, BinaryIO
 
+import pyrogram
 from pyrogram import raw
-from pyrogram.scaffold import Scaffold
 
 
-class SetProfilePhoto(Scaffold):
+class SetProfilePhoto:
     async def set_profile_photo(
-        self,
+        self: "pyrogram.Client",
         *,
         photo: Union[str, BinaryIO] = None,
         video: Union[str, BinaryIO] = None
diff --git a/pyrogram/methods/users/set_username.py b/pyrogram/methods/users/set_username.py
index 881f2ae685..eeffc25f6c 100644
--- a/pyrogram/methods/users/set_username.py
+++ b/pyrogram/methods/users/set_username.py
@@ -18,13 +18,13 @@
 
 from typing import Optional
 
+import pyrogram
 from pyrogram import raw
-from pyrogram.scaffold import Scaffold
 
 
-class SetUsername(Scaffold):
+class SetUsername:
     async def set_username(
-        self,
+        self: "pyrogram.Client",
         username: Optional[str]
     ) -> bool:
         """Set your own username.
diff --git a/pyrogram/methods/users/unblock_user.py b/pyrogram/methods/users/unblock_user.py
index 4218bb1e55..433105acf1 100644
--- a/pyrogram/methods/users/unblock_user.py
+++ b/pyrogram/methods/users/unblock_user.py
@@ -18,13 +18,13 @@
 
 from typing import Union
 
+import pyrogram
 from pyrogram import raw
-from pyrogram.scaffold import Scaffold
 
 
-class UnblockUser(Scaffold):
+class UnblockUser:
     async def unblock_user(
-        self,
+        self: "pyrogram.Client",
         user_id: Union[int, str]
     ) -> bool:
         """Unblock a user.
diff --git a/pyrogram/methods/users/update_profile.py b/pyrogram/methods/users/update_profile.py
index 4dc81204e4..c77c8b4b5b 100644
--- a/pyrogram/methods/users/update_profile.py
+++ b/pyrogram/methods/users/update_profile.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 pyrogram
 from pyrogram import raw
-from pyrogram.scaffold import Scaffold
 
 
-class UpdateProfile(Scaffold):
+class UpdateProfile:
     async def update_profile(
-        self,
+        self: "pyrogram.Client",
         first_name: str = None,
         last_name: str = None,
         bio: str = None
diff --git a/pyrogram/methods/utilities/add_handler.py b/pyrogram/methods/utilities/add_handler.py
index d0ef15ff64..12b41bfe60 100644
--- a/pyrogram/methods/utilities/add_handler.py
+++ b/pyrogram/methods/utilities/add_handler.py
@@ -16,13 +16,17 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
+import pyrogram
 from pyrogram.handlers import DisconnectHandler
 from pyrogram.handlers.handler import Handler
-from pyrogram.scaffold import Scaffold
 
 
-class AddHandler(Scaffold):
-    def add_handler(self, handler: "Handler", group: int = 0):
+class AddHandler:
+    def add_handler(
+        self: "pyrogram.Client",
+        handler: "Handler",
+        group: int = 0
+    ):
         """Register an update handler.
 
         You can register multiple handlers, but at most one handler within a group will be used for a single update.
diff --git a/pyrogram/methods/utilities/export_session_string.py b/pyrogram/methods/utilities/export_session_string.py
index cd1741bdb4..8177c456a9 100644
--- a/pyrogram/methods/utilities/export_session_string.py
+++ b/pyrogram/methods/utilities/export_session_string.py
@@ -16,11 +16,13 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-from pyrogram.scaffold import Scaffold
+import pyrogram
 
 
-class ExportSessionString(Scaffold):
-    async def export_session_string(self):
+class ExportSessionString:
+    async def export_session_string(
+        self: "pyrogram.Client"
+    ):
         """Export the current authorized session as a serialized string.
 
         Session strings are useful for storing in-memory authorized sessions in a portable, serialized string.
diff --git a/pyrogram/methods/utilities/remove_handler.py b/pyrogram/methods/utilities/remove_handler.py
index 12be00b481..fca4a879a9 100644
--- a/pyrogram/methods/utilities/remove_handler.py
+++ b/pyrogram/methods/utilities/remove_handler.py
@@ -16,13 +16,17 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
+import pyrogram
 from pyrogram.handlers import DisconnectHandler
 from pyrogram.handlers.handler import Handler
-from pyrogram.scaffold import Scaffold
 
 
-class RemoveHandler(Scaffold):
-    def remove_handler(self, handler: "Handler", group: int = 0):
+class RemoveHandler:
+    def remove_handler(
+        self: "pyrogram.Client",
+        handler: "Handler",
+        group: int = 0
+    ):
         """Remove a previously-registered update handler.
 
         Make sure to provide the right group where the handler was added in. You can use the return value of the
diff --git a/pyrogram/methods/utilities/restart.py b/pyrogram/methods/utilities/restart.py
index e750137fa4..66c246085f 100644
--- a/pyrogram/methods/utilities/restart.py
+++ b/pyrogram/methods/utilities/restart.py
@@ -16,11 +16,14 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-from pyrogram.scaffold import Scaffold
+import pyrogram
 
 
-class Restart(Scaffold):
-    async def restart(self, block: bool = True):
+class Restart:
+    async def restart(
+        self: "pyrogram.Client",
+        block: bool = True
+    ):
         """Restart the Client.
 
         This method will first call :meth:`~pyrogram.Client.stop` and then :meth:`~pyrogram.Client.start` in a row in
diff --git a/pyrogram/methods/utilities/run.py b/pyrogram/methods/utilities/run.py
index 40decbbd82..6247b936c9 100644
--- a/pyrogram/methods/utilities/run.py
+++ b/pyrogram/methods/utilities/run.py
@@ -19,12 +19,15 @@
 import asyncio
 import inspect
 
+import pyrogram
 from pyrogram.methods.utilities.idle import idle
-from pyrogram.scaffold import Scaffold
 
 
-class Run(Scaffold):
-    def run(self, coroutine=None):
+class Run:
+    def run(
+        self: "pyrogram.Client",
+        coroutine=None
+    ):
         """Start the client, idle the main script and finally stop the client.
 
         This is a convenience method that calls :meth:`~pyrogram.Client.start`, :meth:`~pyrogram.idle` and
diff --git a/pyrogram/methods/utilities/start.py b/pyrogram/methods/utilities/start.py
index a8144d3ff5..61ce87a24a 100644
--- a/pyrogram/methods/utilities/start.py
+++ b/pyrogram/methods/utilities/start.py
@@ -18,14 +18,16 @@
 
 import logging
 
+import pyrogram
 from pyrogram import raw
-from pyrogram.scaffold import Scaffold
 
 log = logging.getLogger(__name__)
 
 
-class Start(Scaffold):
-    async def start(self):
+class Start:
+    async def start(
+        self: "pyrogram.Client"
+    ):
         """Start the client.
 
         This method connects the client to Telegram and, in case of new sessions, automatically manages the full
diff --git a/pyrogram/methods/utilities/stop.py b/pyrogram/methods/utilities/stop.py
index 3536caa1e2..92b99fc5f3 100644
--- a/pyrogram/methods/utilities/stop.py
+++ b/pyrogram/methods/utilities/stop.py
@@ -16,11 +16,14 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-from pyrogram.scaffold import Scaffold
+import pyrogram
 
 
-class Stop(Scaffold):
-    async def stop(self, block: bool = True):
+class Stop:
+    async def stop(
+        self: "pyrogram.Client",
+        block: bool = True
+    ):
         """Stop the Client.
 
         This method disconnects the client from Telegram and stops the underlying tasks.
diff --git a/pyrogram/methods/utilities/stop_transmission.py b/pyrogram/methods/utilities/stop_transmission.py
index 70bd58d450..0639eab8f3 100644
--- a/pyrogram/methods/utilities/stop_transmission.py
+++ b/pyrogram/methods/utilities/stop_transmission.py
@@ -17,10 +17,9 @@
 #  along with Pyrogram.  If not, see .
 
 import pyrogram
-from pyrogram.scaffold import Scaffold
 
 
-class StopTransmission(Scaffold):
+class StopTransmission:
     def stop_transmission(self):
         """Stop downloading or uploading a file.
 
diff --git a/pyrogram/scaffold.py b/pyrogram/scaffold.py
deleted file mode 100644
index d87d1522c8..0000000000
--- a/pyrogram/scaffold.py
+++ /dev/null
@@ -1,201 +0,0 @@
-#  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-present Dan 
-#
-#  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 asyncio
-import os
-import platform
-import re
-import sys
-from io import StringIO
-from mimetypes import MimeTypes
-from pathlib import Path
-
-import pyrogram
-from pyrogram import __version__, enums
-from pyrogram.parser import Parser
-from pyrogram.session.internals import MsgId
-from .mime_types import mime_types
-
-
-class Scaffold:
-    APP_VERSION = f"Pyrogram {__version__}"
-    DEVICE_MODEL = f"{platform.python_implementation()} {platform.python_version()}"
-    SYSTEM_VERSION = f"{platform.system()} {platform.release()}"
-
-    LANG_CODE = "en"
-
-    PARENT_DIR = Path(sys.argv[0]).parent
-
-    INVITE_LINK_RE = re.compile(r"^(?:https?://)?(?:www\.)?(?:t(?:elegram)?\.(?:org|me|dog)/(?:joinchat/|\+))([\w-]+)$")
-    WORKERS = min(32, os.cpu_count() + 4)
-    WORKDIR = PARENT_DIR
-    CONFIG_FILE = PARENT_DIR / "config.ini"
-
-    mimetypes = MimeTypes()
-    mimetypes.readfp(StringIO(mime_types))
-
-    def __init__(self):
-        try:
-            asyncio.get_event_loop()
-        except RuntimeError:
-            # This happens when creating Client instances inside different threads that don't have an event loop.
-            # Set the main event loop in this thread.
-            asyncio.set_event_loop(pyrogram.main_event_loop)
-
-        self.session_name = None
-        self.api_id = None
-        self.api_hash = None
-        self.app_version = None
-        self.device_model = None
-        self.system_version = None
-        self.lang_code = None
-        self.ipv6 = None
-        self.proxy = None
-        self.test_mode = None
-        self.bot_token = None
-        self.phone_number = None
-        self.phone_code = None
-        self.password = None
-        self.force_sms = None
-        self.workers = None
-        self.workdir = None
-        self.config_file = None
-        self.plugins = None
-        self.parse_mode = None
-        self.no_updates = None
-        self.takeout = None
-        self.sleep_threshold = None
-
-        self.executor = None
-
-        self.storage = None
-
-        self.rnd_id = MsgId
-
-        self.parser = Parser(self)
-        self.parse_mode = enums.ParseMode.DEFAULT
-
-        self.session = None
-
-        self.media_sessions = {}
-        self.media_sessions_lock = asyncio.Lock()
-
-        self.is_connected = None
-        self.is_initialized = None
-
-        self.no_updates = None
-        self.takeout_id = None
-
-        self.dispatcher = None
-
-        self.disconnect_handler = None
-
-        self.loop = None
-
-    async def send(self, *args, **kwargs):
-        pass
-
-    async def resolve_peer(self, *args, **kwargs):
-        pass
-
-    def fetch_peers(self, *args, **kwargs):
-        pass
-
-    def add_handler(self, *args, **kwargs):
-        pass
-
-    async def save_file(self, *args, **kwargs):
-        pass
-
-    async def get_messages(self, *args, **kwargs):
-        pass
-
-    async def get_history(self, *args, **kwargs):
-        pass
-
-    async def get_dialogs(self, *args, **kwargs):
-        pass
-
-    async def get_chat_members(self, *args, **kwargs):
-        pass
-
-    async def get_chat_members_count(self, *args, **kwargs):
-        pass
-
-    async def answer_inline_query(self, *args, **kwargs):
-        pass
-
-    async def get_profile_photos(self, *args, **kwargs):
-        pass
-
-    async def edit_message_text(self, *args, **kwargs):
-        pass
-
-    async def edit_inline_text(self, *args, **kwargs):
-        pass
-
-    async def edit_message_media(self, *args, **kwargs):
-        pass
-
-    async def edit_inline_media(self, *args, **kwargs):
-        pass
-
-    async def edit_message_reply_markup(self, *args, **kwargs):
-        pass
-
-    async def edit_inline_reply_markup(self, *args, **kwargs):
-        pass
-
-    def guess_mime_type(self, *args, **kwargs):
-        pass
-
-    def guess_extension(self, *args, **kwargs):
-        pass
-
-    def load_config(self, *args, **kwargs):
-        pass
-
-    def load_session(self, *args, **kwargs):
-        pass
-
-    def load_plugins(self, *args, **kwargs):
-        pass
-
-    async def handle_download(self, *args, **kwargs):
-        pass
-
-    async def start(self, *args, **kwargs):
-        pass
-
-    async def stop(self, *args, **kwargs):
-        pass
-
-    async def connect(self, *args, **kwargs):
-        pass
-
-    async def authorize(self, *args, **kwargs):
-        pass
-
-    async def disconnect(self, *args, **kwargs):
-        pass
-
-    async def initialize(self, *args, **kwargs):
-        pass
-
-    async def terminate(self, *args, **kwargs):
-        pass
diff --git a/pyrogram/types/inline_mode/inline_query_result_audio.py b/pyrogram/types/inline_mode/inline_query_result_audio.py
index 06ab5f944e..a39021000f 100644
--- a/pyrogram/types/inline_mode/inline_query_result_audio.py
+++ b/pyrogram/types/inline_mode/inline_query_result_audio.py
@@ -16,7 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-from typing import Union, List, Optional
+from typing import List, Optional
 
 import pyrogram
 from pyrogram import raw, types, utils, enums
diff --git a/pyrogram/types/messages_and_media/__init__.py b/pyrogram/types/messages_and_media/__init__.py
index 944d7e6e99..cbed4f0f9d 100644
--- a/pyrogram/types/messages_and_media/__init__.py
+++ b/pyrogram/types/messages_and_media/__init__.py
@@ -28,6 +28,7 @@
 from .photo import Photo
 from .poll import Poll
 from .poll_option import PollOption
+from .reaction import Reaction
 from .sticker import Sticker
 from .stripped_thumbnail import StrippedThumbnail
 from .thumbnail import Thumbnail
@@ -36,7 +37,6 @@
 from .video_note import VideoNote
 from .voice import Voice
 from .webpage import WebPage
-from .reaction import Reaction
 
 __all__ = [
     "Animation", "Audio", "Contact", "Document", "Game", "Location", "Message", "MessageEntity", "Photo", "Thumbnail",
diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py
index 56ffd691bd..e9f3cab6d2 100644
--- a/pyrogram/types/messages_and_media/message.py
+++ b/pyrogram/types/messages_and_media/message.py
@@ -19,7 +19,7 @@
 import logging
 from datetime import datetime
 from functools import partial
-from typing import List, Match, Union, BinaryIO, Optional
+from typing import List, Match, Union, BinaryIO, Optional, Callable
 
 import pyrogram
 from pyrogram import raw, enums
@@ -867,7 +867,7 @@ async def reply_text(
         reply_to_message_id: int = None,
         schedule_date: datetime = None,
         protect_content: bool = None,
-        reply_markup = None
+        reply_markup=None
     ) -> "Message":
         """Bound method *reply_text* of :obj:`~pyrogram.types.Message`.
 
@@ -970,7 +970,7 @@ async def reply_animation(
             "types.ForceReply"
         ] = None,
         reply_to_message_id: int = None,
-        progress: callable = None,
+        progress: Callable = None,
         progress_args: tuple = ()
     ) -> "Message":
         """Bound method *reply_animation* :obj:`~pyrogram.types.Message`.
@@ -1037,7 +1037,7 @@ async def reply_animation(
                 Additional interface options. An object for an inline keyboard, custom reply keyboard,
                 instructions to remove reply keyboard or to force a reply from the user.
 
-            progress (``callable``, *optional*):
+            progress (``Callable``, *optional*):
                 Pass a callback function to view the file transmission progress.
                 The function must take *(current, total)* as positional arguments (look at Other Parameters below for a
                 detailed description) and will be called back each time a new file chunk has been successfully
@@ -1109,7 +1109,7 @@ async def reply_audio(
             "types.ReplyKeyboardRemove",
             "types.ForceReply"
         ] = None,
-        progress: callable = None,
+        progress: Callable = None,
         progress_args: tuple = ()
     ) -> "Message":
         """Bound method *reply_audio* of :obj:`~pyrogram.types.Message`.
@@ -1176,7 +1176,7 @@ async def reply_audio(
                 Additional interface options. An object for an inline keyboard, custom reply keyboard,
                 instructions to remove reply keyboard or to force a reply from the user.
 
-            progress (``callable``, *optional*):
+            progress (``Callable``, *optional*):
                 Pass a callback function to view the file transmission progress.
                 The function must take *(current, total)* as positional arguments (look at Other Parameters below for a
                 detailed description) and will be called back each time a new file chunk has been successfully
@@ -1457,7 +1457,7 @@ async def reply_document(
             "types.ReplyKeyboardRemove",
             "types.ForceReply"
         ] = None,
-        progress: callable = None,
+        progress: Callable = None,
         progress_args: tuple = ()
     ) -> "Message":
         """Bound method *reply_document* of :obj:`~pyrogram.types.Message`.
@@ -1527,7 +1527,7 @@ async def reply_document(
                 Additional interface options. An object for an inline keyboard, custom reply keyboard,
                 instructions to remove reply keyboard or to force a reply from the user.
 
-            progress (``callable``, *optional*):
+            progress (``Callable``, *optional*):
                 Pass a callback function to view the file transmission progress.
                 The function must take *(current, total)* as positional arguments (look at Other Parameters below for a
                 detailed description) and will be called back each time a new file chunk has been successfully
@@ -1865,7 +1865,7 @@ async def reply_photo(
             "types.ReplyKeyboardRemove",
             "types.ForceReply"
         ] = None,
-        progress: callable = None,
+        progress: Callable = None,
         progress_args: tuple = ()
     ) -> "Message":
         """Bound method *reply_photo* of :obj:`~pyrogram.types.Message`.
@@ -1922,7 +1922,7 @@ async def reply_photo(
                 Additional interface options. An object for an inline keyboard, custom reply keyboard,
                 instructions to remove reply keyboard or to force a reply from the user.
 
-            progress (``callable``, *optional*):
+            progress (``Callable``, *optional*):
                 Pass a callback function to view the file transmission progress.
                 The function must take *(current, total)* as positional arguments (look at Other Parameters below for a
                 detailed description) and will be called back each time a new file chunk has been successfully
@@ -2088,7 +2088,7 @@ async def reply_sticker(
             "types.ReplyKeyboardRemove",
             "types.ForceReply"
         ] = None,
-        progress: callable = None,
+        progress: Callable = None,
         progress_args: tuple = ()
     ) -> "Message":
         """Bound method *reply_sticker* of :obj:`~pyrogram.types.Message`.
@@ -2130,7 +2130,7 @@ async def reply_sticker(
                 Additional interface options. An object for an inline keyboard, custom reply keyboard,
                 instructions to remove reply keyboard or to force a reply from the user.
 
-            progress (``callable``, *optional*):
+            progress (``Callable``, *optional*):
                 Pass a callback function to view the file transmission progress.
                 The function must take *(current, total)* as positional arguments (look at Other Parameters below for a
                 detailed description) and will be called back each time a new file chunk has been successfully
@@ -2295,7 +2295,7 @@ async def reply_video(
             "types.ReplyKeyboardRemove",
             "types.ForceReply"
         ] = None,
-        progress: callable = None,
+        progress: Callable = None,
         progress_args: tuple = ()
     ) -> "Message":
         """Bound method *reply_video* of :obj:`~pyrogram.types.Message`.
@@ -2370,7 +2370,7 @@ async def reply_video(
                 Additional interface options. An object for an inline keyboard, custom reply keyboard,
                 instructions to remove reply keyboard or to force a reply from the user.
 
-            progress (``callable``, *optional*):
+            progress (``Callable``, *optional*):
                 Pass a callback function to view the file transmission progress.
                 The function must take *(current, total)* as positional arguments (look at Other Parameters below for a
                 detailed description) and will be called back each time a new file chunk has been successfully
@@ -2440,7 +2440,7 @@ async def reply_video_note(
             "types.ReplyKeyboardRemove",
             "types.ForceReply"
         ] = None,
-        progress: callable = None,
+        progress: Callable = None,
         progress_args: tuple = ()
     ) -> "Message":
         """Bound method *reply_video_note* of :obj:`~pyrogram.types.Message`.
@@ -2494,7 +2494,7 @@ async def reply_video_note(
                 Additional interface options. An object for an inline keyboard, custom reply keyboard,
                 instructions to remove reply keyboard or to force a reply from the user.
 
-            progress (``callable``, *optional*):
+            progress (``Callable``, *optional*):
                 Pass a callback function to view the file transmission progress.
                 The function must take *(current, total)* as positional arguments (look at Other Parameters below for a
                 detailed description) and will be called back each time a new file chunk has been successfully
@@ -2559,7 +2559,7 @@ async def reply_voice(
             "types.ReplyKeyboardRemove",
             "types.ForceReply"
         ] = None,
-        progress: callable = None,
+        progress: Callable = None,
         progress_args: tuple = ()
     ) -> "Message":
         """Bound method *reply_voice* of :obj:`~pyrogram.types.Message`.
@@ -2614,7 +2614,7 @@ async def reply_voice(
                 Additional interface options. An object for an inline keyboard, custom reply keyboard,
                 instructions to remove reply keyboard or to force a reply from the user.
 
-            progress (``callable``, *optional*):
+            progress (``Callable``, *optional*):
                 Pass a callback function to view the file transmission progress.
                 The function must take *(current, total)* as positional arguments (look at Other Parameters below for a
                 detailed description) and will be called back each time a new file chunk has been successfully
@@ -3321,7 +3321,7 @@ async def download(
         self,
         file_name: str = "",
         block: bool = True,
-        progress: callable = None,
+        progress: Callable = None,
         progress_args: tuple = ()
     ) -> str:
         """Bound method *download* of :obj:`~pyrogram.types.Message`.
@@ -3348,7 +3348,7 @@ async def download(
                 Blocks the code execution until the file has been downloaded.
                 Defaults to True.
 
-            progress (``callable``, *optional*):
+            progress (``Callable``, *optional*):
                 Pass a callback function to view the file transmission progress.
                 The function must take *(current, total)* as positional arguments (look at Other Parameters below for a
                 detailed description) and will be called back each time a new file chunk has been successfully
diff --git a/pyrogram/types/messages_and_media/video_note.py b/pyrogram/types/messages_and_media/video_note.py
index 450d536db1..3e6b40d0c9 100644
--- a/pyrogram/types/messages_and_media/video_note.py
+++ b/pyrogram/types/messages_and_media/video_note.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 datetime import datetime
 from typing import List
 
 import pyrogram
@@ -23,7 +24,6 @@
 from pyrogram import types
 from pyrogram.file_id import FileId, FileType, FileUniqueId, FileUniqueType
 from ..object import Object
-from datetime import datetime
 
 
 class VideoNote(Object):
diff --git a/pyrogram/types/object.py b/pyrogram/types/object.py
index 601bcd6e6f..caf18373e1 100644
--- a/pyrogram/types/object.py
+++ b/pyrogram/types/object.py
@@ -24,12 +24,7 @@
 import pyrogram
 
 
-class Meta(type, metaclass=type("", (type,), {"__str__": lambda _: "~hi"})):
-    def __str__(self):
-        return f""
-
-
-class Object(metaclass=Meta):
+class Object:
     def __init__(self, client: "pyrogram.Client" = None):
         self._client = client
 
diff --git a/pyrogram/types/user_and_chats/chat_event.py b/pyrogram/types/user_and_chats/chat_event.py
index fa539be868..a5292c6418 100644
--- a/pyrogram/types/user_and_chats/chat_event.py
+++ b/pyrogram/types/user_and_chats/chat_event.py
@@ -254,10 +254,10 @@ def __init__(
 
     @staticmethod
     async def _parse(
-            client: "pyrogram.Client",
-            event: "raw.base.ChannelAdminLogEvent",
-            users: List["raw.base.User"],
-            chats: List["raw.base.Chat"]
+        client: "pyrogram.Client",
+        event: "raw.base.ChannelAdminLogEvent",
+        users: List["raw.base.User"],
+        chats: List["raw.base.Chat"]
     ):
         users = {i.id: i for i in users}
         chats = {i.id: i for i in chats}
diff --git a/pyrogram/types/user_and_chats/chat_member.py b/pyrogram/types/user_and_chats/chat_member.py
index 06f45b8d34..9b585d7cb5 100644
--- a/pyrogram/types/user_and_chats/chat_member.py
+++ b/pyrogram/types/user_and_chats/chat_member.py
@@ -108,8 +108,8 @@ def __init__(
 
     @staticmethod
     def _parse(
-            client: "pyrogram.Client",
-            member: Union["raw.base.ChatParticipant", "raw.base.ChannelParticipant"],
+        client: "pyrogram.Client",
+        member: Union["raw.base.ChatParticipant", "raw.base.ChannelParticipant"],
         users: Dict[int, "raw.base.User"],
         chats: Dict[int, "raw.base.Chat"]
     ) -> "ChatMember":
diff --git a/pyrogram/utils.py b/pyrogram/utils.py
index 60f6ca9d8a..b3f04e6018 100644
--- a/pyrogram/utils.py
+++ b/pyrogram/utils.py
@@ -297,7 +297,7 @@ async def parse_text_entities(
     text: str,
     parse_mode: enums.ParseMode,
     entities: List["types.MessageEntity"]
-) -> Dict[str, raw.base.MessageEntity]:
+) -> Dict[str, Union[str, List[raw.base.MessageEntity]]]:
     if entities:
         # Inject the client instance because parsing user mentions requires it
         for entity in entities:

From 4f6ce8bec17af1ab76ca7832d3d4167c028ec483 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 24 Apr 2022 11:56:06 +0200
Subject: [PATCH 0789/1185] Add pyproject.toml

---
 pyproject.toml | 3 +++
 1 file changed, 3 insertions(+)
 create mode 100644 pyproject.toml

diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000000..07de284aa5
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,3 @@
+[build-system]
+requires = ["setuptools", "wheel"]
+build-backend = "setuptools.build_meta"
\ No newline at end of file

From 78efb04b404047cdd6a196f891324d5c779a383c Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 24 Apr 2022 11:56:07 +0200
Subject: [PATCH 0790/1185] Rename Client.send to Client.invoke

---
 pyrogram/client.py                            | 18 +++++------
 pyrogram/methods/advanced/__init__.py         |  6 ++--
 .../methods/advanced/{send.py => invoke.py}   | 18 +++++------
 pyrogram/methods/advanced/resolve_peer.py     |  8 ++---
 pyrogram/methods/advanced/save_file.py        |  2 +-
 .../methods/auth/accept_terms_of_service.py   |  2 +-
 pyrogram/methods/auth/check_password.py       |  4 +--
 pyrogram/methods/auth/get_password_hint.py    |  2 +-
 pyrogram/methods/auth/log_out.py              |  2 +-
 pyrogram/methods/auth/recover_password.py     |  2 +-
 pyrogram/methods/auth/resend_code.py          |  2 +-
 pyrogram/methods/auth/send_code.py            |  2 +-
 pyrogram/methods/auth/send_recovery_code.py   |  2 +-
 pyrogram/methods/auth/sign_in.py              |  2 +-
 pyrogram/methods/auth/sign_in_bot.py          |  2 +-
 pyrogram/methods/auth/sign_up.py              |  2 +-
 pyrogram/methods/auth/terminate.py            |  2 +-
 .../methods/bots/answer_callback_query.py     |  2 +-
 pyrogram/methods/bots/answer_inline_query.py  |  2 +-
 pyrogram/methods/bots/get_game_high_scores.py |  2 +-
 .../methods/bots/get_inline_bot_results.py    |  2 +-
 .../methods/bots/request_callback_answer.py   |  2 +-
 pyrogram/methods/bots/send_game.py            |  2 +-
 .../methods/bots/send_inline_bot_result.py    |  2 +-
 pyrogram/methods/bots/set_bot_commands.py     |  2 +-
 pyrogram/methods/bots/set_game_score.py       |  2 +-
 pyrogram/methods/chats/add_chat_members.py    |  4 +--
 pyrogram/methods/chats/archive_chats.py       |  2 +-
 pyrogram/methods/chats/ban_chat_member.py     |  4 +--
 pyrogram/methods/chats/create_channel.py      |  2 +-
 pyrogram/methods/chats/create_group.py        |  2 +-
 pyrogram/methods/chats/create_supergroup.py   |  2 +-
 pyrogram/methods/chats/delete_channel.py      |  2 +-
 pyrogram/methods/chats/delete_chat_photo.py   |  4 +--
 pyrogram/methods/chats/delete_supergroup.py   |  2 +-
 pyrogram/methods/chats/delete_user_history.py |  2 +-
 pyrogram/methods/chats/get_chat.py            |  8 ++---
 pyrogram/methods/chats/get_chat_event_log.py  |  2 +-
 pyrogram/methods/chats/get_chat_member.py     |  4 +--
 pyrogram/methods/chats/get_chat_members.py    |  4 +--
 .../methods/chats/get_chat_members_count.py   |  4 +--
 .../methods/chats/get_chat_online_count.py    |  2 +-
 pyrogram/methods/chats/get_dialogs.py         |  4 +--
 pyrogram/methods/chats/get_dialogs_count.py   |  4 +--
 pyrogram/methods/chats/get_nearby_chats.py    |  2 +-
 pyrogram/methods/chats/get_send_as_chats.py   |  2 +-
 pyrogram/methods/chats/iter_dialogs.py        |  2 +-
 pyrogram/methods/chats/join_chat.py           |  4 +--
 pyrogram/methods/chats/leave_chat.py          |  6 ++--
 pyrogram/methods/chats/mark_chat_unread.py    |  6 ++--
 pyrogram/methods/chats/pin_chat_message.py    |  2 +-
 pyrogram/methods/chats/promote_chat_member.py |  4 +--
 .../methods/chats/restrict_chat_member.py     |  2 +-
 .../methods/chats/set_administrator_title.py  |  4 +--
 .../methods/chats/set_chat_description.py     |  2 +-
 .../methods/chats/set_chat_permissions.py     |  2 +-
 pyrogram/methods/chats/set_chat_photo.py      |  4 +--
 .../chats/set_chat_protected_content.py       |  6 ++--
 pyrogram/methods/chats/set_chat_title.py      |  4 +--
 pyrogram/methods/chats/set_chat_username.py   |  2 +-
 pyrogram/methods/chats/set_send_as_chat.py    |  2 +-
 pyrogram/methods/chats/set_slow_mode.py       |  2 +-
 pyrogram/methods/chats/unarchive_chats.py     |  2 +-
 pyrogram/methods/chats/unban_chat_member.py   |  2 +-
 .../methods/chats/unpin_all_chat_messages.py  |  2 +-
 pyrogram/methods/chats/unpin_chat_message.py  |  2 +-
 pyrogram/methods/contacts/add_contact.py      |  2 +-
 pyrogram/methods/contacts/delete_contacts.py  |  2 +-
 pyrogram/methods/contacts/get_contacts.py     |  2 +-
 .../methods/contacts/get_contacts_count.py    |  2 +-
 pyrogram/methods/contacts/import_contacts.py  |  2 +-
 .../invite_links/approve_chat_join_request.py |  2 +-
 .../invite_links/create_chat_invite_link.py   |  2 +-
 .../invite_links/decline_chat_join_request.py |  2 +-
 .../delete_chat_admin_invite_links.py         |  2 +-
 .../invite_links/delete_chat_invite_link.py   |  2 +-
 .../invite_links/edit_chat_invite_link.py     |  2 +-
 .../invite_links/export_chat_invite_link.py   |  2 +-
 .../get_chat_admin_invite_links.py            |  2 +-
 .../get_chat_admin_invite_links_count.py      |  2 +-
 .../get_chat_admins_with_invite_links.py      |  2 +-
 .../invite_links/get_chat_invite_link.py      |  2 +-
 .../get_chat_invite_link_members.py           |  2 +-
 .../get_chat_invite_link_members_count.py     |  2 +-
 .../invite_links/revoke_chat_invite_link.py   |  2 +-
 pyrogram/methods/messages/copy_media_group.py |  2 +-
 pyrogram/methods/messages/delete_messages.py  |  4 +--
 .../methods/messages/edit_inline_media.py     |  2 +-
 .../messages/edit_inline_reply_markup.py      |  2 +-
 pyrogram/methods/messages/edit_inline_text.py |  2 +-
 .../methods/messages/edit_message_media.py    | 12 +++----
 .../messages/edit_message_reply_markup.py     |  2 +-
 .../methods/messages/edit_message_text.py     |  2 +-
 pyrogram/methods/messages/forward_messages.py |  2 +-
 .../messages/get_discussion_message.py        |  2 +-
 pyrogram/methods/messages/get_history.py      |  2 +-
 .../methods/messages/get_history_count.py     |  2 +-
 pyrogram/methods/messages/get_messages.py     |  2 +-
 pyrogram/methods/messages/inline_session.py   |  4 +--
 pyrogram/methods/messages/read_history.py     |  2 +-
 pyrogram/methods/messages/retract_vote.py     |  2 +-
 pyrogram/methods/messages/search_global.py    |  2 +-
 .../methods/messages/search_global_count.py   |  2 +-
 pyrogram/methods/messages/search_messages.py  |  2 +-
 .../methods/messages/search_messages_count.py |  2 +-
 pyrogram/methods/messages/send_animation.py   |  4 +--
 pyrogram/methods/messages/send_audio.py       |  2 +-
 .../methods/messages/send_cached_media.py     |  2 +-
 pyrogram/methods/messages/send_chat_action.py |  2 +-
 pyrogram/methods/messages/send_contact.py     |  2 +-
 pyrogram/methods/messages/send_dice.py        |  2 +-
 pyrogram/methods/messages/send_document.py    |  2 +-
 pyrogram/methods/messages/send_location.py    |  2 +-
 pyrogram/methods/messages/send_media_group.py | 26 +++++++--------
 pyrogram/methods/messages/send_message.py     |  2 +-
 pyrogram/methods/messages/send_photo.py       |  2 +-
 pyrogram/methods/messages/send_poll.py        |  2 +-
 pyrogram/methods/messages/send_reaction.py    |  2 +-
 pyrogram/methods/messages/send_sticker.py     |  2 +-
 pyrogram/methods/messages/send_venue.py       |  2 +-
 pyrogram/methods/messages/send_video.py       |  2 +-
 pyrogram/methods/messages/send_video_note.py  |  2 +-
 pyrogram/methods/messages/send_voice.py       |  2 +-
 pyrogram/methods/messages/stop_poll.py        |  2 +-
 pyrogram/methods/messages/vote_poll.py        |  2 +-
 .../methods/password/change_cloud_password.py |  4 +--
 .../methods/password/enable_cloud_password.py |  4 +--
 .../methods/password/remove_cloud_password.py |  4 +--
 pyrogram/methods/users/block_user.py          |  2 +-
 .../methods/users/delete_profile_photos.py    |  2 +-
 pyrogram/methods/users/get_common_chats.py    |  2 +-
 pyrogram/methods/users/get_me.py              |  2 +-
 pyrogram/methods/users/get_profile_photos.py  |  6 ++--
 .../methods/users/get_profile_photos_count.py |  4 +--
 pyrogram/methods/users/get_users.py           |  2 +-
 pyrogram/methods/users/set_profile_photo.py   |  2 +-
 pyrogram/methods/users/set_username.py        |  2 +-
 pyrogram/methods/users/unblock_user.py        |  2 +-
 pyrogram/methods/users/update_profile.py      |  2 +-
 pyrogram/methods/utilities/start.py           |  4 +--
 pyrogram/session/auth.py                      |  8 ++---
 pyrogram/session/session.py                   | 32 +++++++++----------
 pyrogram/types/messages_and_media/message.py  |  2 +-
 pyrogram/types/messages_and_media/sticker.py  |  6 ++--
 144 files changed, 234 insertions(+), 236 deletions(-)
 rename pyrogram/methods/advanced/{send.py => invoke.py} (87%)

diff --git a/pyrogram/client.py b/pyrogram/client.py
index cfef3a8d94..920b723311 100644
--- a/pyrogram/client.py
+++ b/pyrogram/client.py
@@ -567,7 +567,7 @@ async def handle_updates(self, updates):
 
                     if not isinstance(message, raw.types.MessageEmpty):
                         try:
-                            diff = await self.send(
+                            diff = await self.invoke(
                                 raw.functions.updates.GetChannelDifference(
                                     channel=await self.resolve_peer(utils.get_channel_id(channel_id)),
                                     filter=raw.types.ChannelMessagesFilter(
@@ -589,7 +589,7 @@ async def handle_updates(self, updates):
 
                 self.dispatcher.updates_queue.put_nowait((update, users, chats))
         elif isinstance(updates, (raw.types.UpdateShortMessage, raw.types.UpdateShortChatMessage)):
-            diff = await self.send(
+            diff = await self.invoke(
                 raw.functions.updates.GetDifference(
                     pts=updates.pts - updates.pts_count,
                     date=updates.date,
@@ -847,14 +847,14 @@ async def get_file(
                     await session.start()
 
                     for _ in range(3):
-                        exported_auth = await self.send(
+                        exported_auth = await self.invoke(
                             raw.functions.auth.ExportAuthorization(
                                 dc_id=dc_id
                             )
                         )
 
                         try:
-                            await session.send(
+                            await session.invoke(
                                 raw.functions.auth.ImportAuthorization(
                                     id=exported_auth.id,
                                     bytes=exported_auth.bytes
@@ -920,7 +920,7 @@ async def get_file(
         file_name = ""
 
         try:
-            r = await session.send(
+            r = await session.invoke(
                 raw.functions.upload.GetFile(
                     location=location,
                     offset=offset,
@@ -958,7 +958,7 @@ async def get_file(
                         if len(chunk) < limit:
                             break
 
-                        r = await session.send(
+                        r = await session.invoke(
                             raw.functions.upload.GetFile(
                                 location=location,
                                 offset=offset,
@@ -986,7 +986,7 @@ async def get_file(
                         file_name = f.name
 
                         while True:
-                            r2 = await cdn_session.send(
+                            r2 = await cdn_session.invoke(
                                 raw.functions.upload.GetCdnFile(
                                     file_token=r.file_token,
                                     offset=offset,
@@ -996,7 +996,7 @@ async def get_file(
 
                             if isinstance(r2, raw.types.upload.CdnFileReuploadNeeded):
                                 try:
-                                    await session.send(
+                                    await session.invoke(
                                         raw.functions.upload.ReuploadCdnFile(
                                             file_token=r.file_token,
                                             request_token=r2.request_token
@@ -1019,7 +1019,7 @@ async def get_file(
                                 )
                             )
 
-                            hashes = await session.send(
+                            hashes = await session.invoke(
                                 raw.functions.upload.GetCdnFileHashes(
                                     file_token=r.file_token,
                                     offset=offset
diff --git a/pyrogram/methods/advanced/__init__.py b/pyrogram/methods/advanced/__init__.py
index a3cf461af7..bf19658a3c 100644
--- a/pyrogram/methods/advanced/__init__.py
+++ b/pyrogram/methods/advanced/__init__.py
@@ -16,14 +16,14 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
+from .invoke import Invoke
 from .resolve_peer import ResolvePeer
 from .save_file import SaveFile
-from .send import Send
 
 
 class Advanced(
+    Invoke,
     ResolvePeer,
-    SaveFile,
-    Send
+    SaveFile
 ):
     pass
diff --git a/pyrogram/methods/advanced/send.py b/pyrogram/methods/advanced/invoke.py
similarity index 87%
rename from pyrogram/methods/advanced/send.py
rename to pyrogram/methods/advanced/invoke.py
index 6e5551cd97..9ae25d0084 100644
--- a/pyrogram/methods/advanced/send.py
+++ b/pyrogram/methods/advanced/invoke.py
@@ -26,15 +26,15 @@
 log = logging.getLogger(__name__)
 
 
-class Send:
-    async def send(
+class Invoke:
+    async def invoke(
         self: "pyrogram.Client",
-        data: TLObject,
+        query: TLObject,
         retries: int = Session.MAX_RETRIES,
         timeout: float = Session.WAIT_TIMEOUT,
         sleep_threshold: float = None
     ):
-        """Send raw Telegram queries.
+        """Invoke raw Telegram functions.
 
         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
@@ -47,7 +47,7 @@ async def send(
             available yet in the Client class as an easy-to-use method).
 
         Parameters:
-            data (``RawFunction``):
+            query (``RawFunction``):
                 The API Schema function filled with proper arguments.
 
             retries (``int``):
@@ -69,13 +69,13 @@ async def send(
             raise ConnectionError("Client has not been started yet")
 
         if self.no_updates:
-            data = raw.functions.InvokeWithoutUpdates(query=data)
+            query = raw.functions.InvokeWithoutUpdates(query=query)
 
         if self.takeout_id:
-            data = raw.functions.InvokeWithTakeout(takeout_id=self.takeout_id, query=data)
+            query = raw.functions.InvokeWithTakeout(takeout_id=self.takeout_id, query=query)
 
-        r = await self.session.send(
-            data, retries, timeout,
+        r = await self.session.invoke(
+            query, retries, timeout,
             (sleep_threshold
              if sleep_threshold is not None
              else self.sleep_threshold)
diff --git a/pyrogram/methods/advanced/resolve_peer.py b/pyrogram/methods/advanced/resolve_peer.py
index db4def9aa2..80fe7975b0 100644
--- a/pyrogram/methods/advanced/resolve_peer.py
+++ b/pyrogram/methods/advanced/resolve_peer.py
@@ -71,7 +71,7 @@ async def resolve_peer(
                     try:
                         return await self.storage.get_peer_by_username(peer_id)
                     except KeyError:
-                        await self.send(
+                        await self.invoke(
                             raw.functions.contacts.ResolveUsername(
                                 username=peer_id
                             )
@@ -88,7 +88,7 @@ async def resolve_peer(
 
             if peer_type == "user":
                 await self.fetch_peers(
-                    await self.send(
+                    await self.invoke(
                         raw.functions.users.GetUsers(
                             id=[
                                 raw.types.InputUser(
@@ -100,13 +100,13 @@ async def resolve_peer(
                     )
                 )
             elif peer_type == "chat":
-                await self.send(
+                await self.invoke(
                     raw.functions.messages.GetChats(
                         id=[-peer_id]
                     )
                 )
             else:
-                await self.send(
+                await self.invoke(
                     raw.functions.channels.GetChannels(
                         id=[
                             raw.types.InputChannel(
diff --git a/pyrogram/methods/advanced/save_file.py b/pyrogram/methods/advanced/save_file.py
index 9c92651dba..706f28e04f 100644
--- a/pyrogram/methods/advanced/save_file.py
+++ b/pyrogram/methods/advanced/save_file.py
@@ -103,7 +103,7 @@ async def worker(session):
                     return
 
                 try:
-                    await session.send(data)
+                    await session.invoke(data)
                 except Exception as e:
                     log.error(e)
 
diff --git a/pyrogram/methods/auth/accept_terms_of_service.py b/pyrogram/methods/auth/accept_terms_of_service.py
index ea041d6c36..3bc5fbad27 100644
--- a/pyrogram/methods/auth/accept_terms_of_service.py
+++ b/pyrogram/methods/auth/accept_terms_of_service.py
@@ -31,7 +31,7 @@ async def accept_terms_of_service(
             terms_of_service_id (``str``):
                 The terms of service identifier.
         """
-        r = await self.send(
+        r = await self.invoke(
             raw.functions.help.AcceptTermsOfService(
                 id=raw.types.DataJSON(
                     data=terms_of_service_id
diff --git a/pyrogram/methods/auth/check_password.py b/pyrogram/methods/auth/check_password.py
index 1f1d142ced..9d8b08abfa 100644
--- a/pyrogram/methods/auth/check_password.py
+++ b/pyrogram/methods/auth/check_password.py
@@ -43,10 +43,10 @@ async def check_password(
         Raises:
             BadRequest: In case the password is invalid.
         """
-        r = await self.send(
+        r = await self.invoke(
             raw.functions.auth.CheckPassword(
                 password=compute_password_check(
-                    await self.send(raw.functions.account.GetPassword()),
+                    await self.invoke(raw.functions.account.GetPassword()),
                     password
                 )
             )
diff --git a/pyrogram/methods/auth/get_password_hint.py b/pyrogram/methods/auth/get_password_hint.py
index d900210681..af6557589a 100644
--- a/pyrogram/methods/auth/get_password_hint.py
+++ b/pyrogram/methods/auth/get_password_hint.py
@@ -33,4 +33,4 @@ async def get_password_hint(
         Returns:
             ``str``: On success, the password hint as string is returned.
         """
-        return (await self.send(raw.functions.account.GetPassword())).hint
+        return (await self.invoke(raw.functions.account.GetPassword())).hint
diff --git a/pyrogram/methods/auth/log_out.py b/pyrogram/methods/auth/log_out.py
index 2f8ad019b7..b4a29f8273 100644
--- a/pyrogram/methods/auth/log_out.py
+++ b/pyrogram/methods/auth/log_out.py
@@ -42,7 +42,7 @@ async def log_out(
                 # Log out.
                 app.log_out()
         """
-        await self.send(raw.functions.auth.LogOut())
+        await self.invoke(raw.functions.auth.LogOut())
         await self.stop()
         await self.storage.delete()
 
diff --git a/pyrogram/methods/auth/recover_password.py b/pyrogram/methods/auth/recover_password.py
index 600ac86a0b..9f75a93f79 100644
--- a/pyrogram/methods/auth/recover_password.py
+++ b/pyrogram/methods/auth/recover_password.py
@@ -43,7 +43,7 @@ async def recover_password(
         Raises:
             BadRequest: In case the recovery code is invalid.
         """
-        r = await self.send(
+        r = await self.invoke(
             raw.functions.auth.RecoverPassword(
                 code=recovery_code
             )
diff --git a/pyrogram/methods/auth/resend_code.py b/pyrogram/methods/auth/resend_code.py
index d17cc395f8..4210e04d9b 100644
--- a/pyrogram/methods/auth/resend_code.py
+++ b/pyrogram/methods/auth/resend_code.py
@@ -52,7 +52,7 @@ async def resend_code(
         """
         phone_number = phone_number.strip(" +")
 
-        r = await self.send(
+        r = await self.invoke(
             raw.functions.auth.ResendCode(
                 phone_number=phone_number,
                 phone_code_hash=phone_code_hash
diff --git a/pyrogram/methods/auth/send_code.py b/pyrogram/methods/auth/send_code.py
index 3d42fa6b92..5a4f87ea31 100644
--- a/pyrogram/methods/auth/send_code.py
+++ b/pyrogram/methods/auth/send_code.py
@@ -49,7 +49,7 @@ async def send_code(
 
         while True:
             try:
-                r = await self.send(
+                r = await self.invoke(
                     raw.functions.auth.SendCode(
                         phone_number=phone_number,
                         api_id=self.api_id,
diff --git a/pyrogram/methods/auth/send_recovery_code.py b/pyrogram/methods/auth/send_recovery_code.py
index 40d2b7ddb2..d1f23bf965 100644
--- a/pyrogram/methods/auth/send_recovery_code.py
+++ b/pyrogram/methods/auth/send_recovery_code.py
@@ -36,6 +36,6 @@ async def send_recovery_code(
         Raises:
             BadRequest: In case no recovery email was set up.
         """
-        return (await self.send(
+        return (await self.invoke(
             raw.functions.auth.RequestPasswordRecovery()
         )).email_pattern
diff --git a/pyrogram/methods/auth/sign_in.py b/pyrogram/methods/auth/sign_in.py
index d8079c95c5..c328c95850 100644
--- a/pyrogram/methods/auth/sign_in.py
+++ b/pyrogram/methods/auth/sign_in.py
@@ -58,7 +58,7 @@ async def sign_in(
         """
         phone_number = phone_number.strip(" +")
 
-        r = await self.send(
+        r = await self.invoke(
             raw.functions.auth.SignIn(
                 phone_number=phone_number,
                 phone_code_hash=phone_code_hash,
diff --git a/pyrogram/methods/auth/sign_in_bot.py b/pyrogram/methods/auth/sign_in_bot.py
index db4515a4ef..5f2e68b510 100644
--- a/pyrogram/methods/auth/sign_in_bot.py
+++ b/pyrogram/methods/auth/sign_in_bot.py
@@ -46,7 +46,7 @@ async def sign_in_bot(
         """
         while True:
             try:
-                r = await self.send(
+                r = await self.invoke(
                     raw.functions.auth.ImportBotAuthorization(
                         flags=0,
                         api_id=self.api_id,
diff --git a/pyrogram/methods/auth/sign_up.py b/pyrogram/methods/auth/sign_up.py
index 4e769ab1b4..6700fee481 100644
--- a/pyrogram/methods/auth/sign_up.py
+++ b/pyrogram/methods/auth/sign_up.py
@@ -56,7 +56,7 @@ async def sign_up(
         """
         phone_number = phone_number.strip(" +")
 
-        r = await self.send(
+        r = await self.invoke(
             raw.functions.auth.SignUp(
                 phone_number=phone_number,
                 first_name=first_name,
diff --git a/pyrogram/methods/auth/terminate.py b/pyrogram/methods/auth/terminate.py
index d8cca6ba81..548d030cb4 100644
--- a/pyrogram/methods/auth/terminate.py
+++ b/pyrogram/methods/auth/terminate.py
@@ -41,7 +41,7 @@ async def terminate(
             raise ConnectionError("Client is already terminated")
 
         if self.takeout_id:
-            await self.send(raw.functions.account.FinishTakeoutSession())
+            await self.invoke(raw.functions.account.FinishTakeoutSession())
             log.warning(f"Takeout session {self.takeout_id} finished")
 
         await Syncer.remove(self)
diff --git a/pyrogram/methods/bots/answer_callback_query.py b/pyrogram/methods/bots/answer_callback_query.py
index 73d5ce5569..af2c8f7252 100644
--- a/pyrogram/methods/bots/answer_callback_query.py
+++ b/pyrogram/methods/bots/answer_callback_query.py
@@ -68,7 +68,7 @@ async def answer_callback_query(
                 # Answer with alert
                 app.answer_callback_query(query_id, text=text, show_alert=True)
         """
-        return await self.send(
+        return await self.invoke(
             raw.functions.messages.SetBotCallbackAnswer(
                 query_id=int(callback_query_id),
                 cache_time=cache_time,
diff --git a/pyrogram/methods/bots/answer_inline_query.py b/pyrogram/methods/bots/answer_inline_query.py
index 1751171d53..ebdfab2397 100644
--- a/pyrogram/methods/bots/answer_inline_query.py
+++ b/pyrogram/methods/bots/answer_inline_query.py
@@ -94,7 +94,7 @@ async def answer_inline_query(
                             InputTextMessageContent("Message content"))])
         """
 
-        return await self.send(
+        return await self.invoke(
             raw.functions.messages.SetInlineBotResults(
                 query_id=int(inline_query_id),
                 results=[await r.write(self) for r in results],
diff --git a/pyrogram/methods/bots/get_game_high_scores.py b/pyrogram/methods/bots/get_game_high_scores.py
index e4a2ed150c..312cc632b4 100644
--- a/pyrogram/methods/bots/get_game_high_scores.py
+++ b/pyrogram/methods/bots/get_game_high_scores.py
@@ -59,7 +59,7 @@ async def get_game_high_scores(
         """
         # TODO: inline_message_id
 
-        r = await self.send(
+        r = await self.invoke(
             raw.functions.messages.GetGameHighScores(
                 peer=await self.resolve_peer(chat_id),
                 id=message_id,
diff --git a/pyrogram/methods/bots/get_inline_bot_results.py b/pyrogram/methods/bots/get_inline_bot_results.py
index 2c41fec7ec..a71fbad8ec 100644
--- a/pyrogram/methods/bots/get_inline_bot_results.py
+++ b/pyrogram/methods/bots/get_inline_bot_results.py
@@ -70,7 +70,7 @@ async def get_inline_bot_results(
         # TODO: Don't return the raw type
 
         try:
-            return await self.send(
+            return await self.invoke(
                 raw.functions.messages.GetInlineBotResults(
                     bot=await self.resolve_peer(bot),
                     peer=raw.types.InputPeerSelf(),
diff --git a/pyrogram/methods/bots/request_callback_answer.py b/pyrogram/methods/bots/request_callback_answer.py
index ff6ae0d3ef..908c8ecae6 100644
--- a/pyrogram/methods/bots/request_callback_answer.py
+++ b/pyrogram/methods/bots/request_callback_answer.py
@@ -64,7 +64,7 @@ async def request_callback_answer(
         # 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 await self.send(
+        return await self.invoke(
             raw.functions.messages.GetBotCallbackAnswer(
                 peer=await self.resolve_peer(chat_id),
                 msg_id=message_id,
diff --git a/pyrogram/methods/bots/send_game.py b/pyrogram/methods/bots/send_game.py
index 7f9e856bb0..c8eee66eff 100644
--- a/pyrogram/methods/bots/send_game.py
+++ b/pyrogram/methods/bots/send_game.py
@@ -71,7 +71,7 @@ async def send_game(
 
                 app.send_game(chat_id, "gamename")
         """
-        r = await self.send(
+        r = await self.invoke(
             raw.functions.messages.SendMedia(
                 peer=await self.resolve_peer(chat_id),
                 media=raw.types.InputMediaGame(
diff --git a/pyrogram/methods/bots/send_inline_bot_result.py b/pyrogram/methods/bots/send_inline_bot_result.py
index 299aaaf6f7..00b4204309 100644
--- a/pyrogram/methods/bots/send_inline_bot_result.py
+++ b/pyrogram/methods/bots/send_inline_bot_result.py
@@ -61,7 +61,7 @@ async def send_inline_bot_result(
 
                 app.send_inline_bot_result(chat_id, query_id, result_id)
         """
-        return await self.send(
+        return await self.invoke(
             raw.functions.messages.SendInlineBotResult(
                 peer=await self.resolve_peer(chat_id),
                 query_id=query_id,
diff --git a/pyrogram/methods/bots/set_bot_commands.py b/pyrogram/methods/bots/set_bot_commands.py
index 8c9baa2e6a..6df1a2e443 100644
--- a/pyrogram/methods/bots/set_bot_commands.py
+++ b/pyrogram/methods/bots/set_bot_commands.py
@@ -62,7 +62,7 @@ async def set_bot_commands(
                     BotCommand("settings", "Bot settings")])
         """
 
-        return await self.send(
+        return await self.invoke(
             raw.functions.bots.SetBotCommands(
                 commands=[c.write() for c in commands],
                 scope=await scope.write(self),
diff --git a/pyrogram/methods/bots/set_game_score.py b/pyrogram/methods/bots/set_game_score.py
index ef644b60db..855e4a2ece 100644
--- a/pyrogram/methods/bots/set_game_score.py
+++ b/pyrogram/methods/bots/set_game_score.py
@@ -75,7 +75,7 @@ async def set_game_score(
                 # Force set new score
                 app.set_game_score(user_id, 25, force=True)
         """
-        r = await self.send(
+        r = await self.invoke(
             raw.functions.messages.SetGameScore(
                 peer=await self.resolve_peer(chat_id),
                 score=score,
diff --git a/pyrogram/methods/chats/add_chat_members.py b/pyrogram/methods/chats/add_chat_members.py
index cda789aaba..2d053c4e8e 100644
--- a/pyrogram/methods/chats/add_chat_members.py
+++ b/pyrogram/methods/chats/add_chat_members.py
@@ -67,7 +67,7 @@ async def add_chat_members(
 
         if isinstance(peer, raw.types.InputPeerChat):
             for user_id in user_ids:
-                await self.send(
+                await self.invoke(
                     raw.functions.messages.AddChatUser(
                         chat_id=peer.chat_id,
                         user_id=await self.resolve_peer(user_id),
@@ -75,7 +75,7 @@ async def add_chat_members(
                     )
                 )
         else:
-            await self.send(
+            await self.invoke(
                 raw.functions.channels.InviteToChannel(
                     channel=peer,
                     users=[
diff --git a/pyrogram/methods/chats/archive_chats.py b/pyrogram/methods/chats/archive_chats.py
index aa5b50d5ec..cba27371cb 100644
--- a/pyrogram/methods/chats/archive_chats.py
+++ b/pyrogram/methods/chats/archive_chats.py
@@ -60,7 +60,7 @@ async def archive_chats(
                 )
             )
 
-        await self.send(
+        await self.invoke(
             raw.functions.folders.EditPeerFolders(
                 folder_peers=folder_peers
             )
diff --git a/pyrogram/methods/chats/ban_chat_member.py b/pyrogram/methods/chats/ban_chat_member.py
index c01b70462e..d8a207aba8 100644
--- a/pyrogram/methods/chats/ban_chat_member.py
+++ b/pyrogram/methods/chats/ban_chat_member.py
@@ -73,7 +73,7 @@ async def ban_chat_member(
         user_peer = await self.resolve_peer(user_id)
 
         if isinstance(chat_peer, raw.types.InputPeerChannel):
-            r = await self.send(
+            r = await self.invoke(
                 raw.functions.channels.EditBanned(
                     channel=chat_peer,
                     participant=user_peer,
@@ -91,7 +91,7 @@ async def ban_chat_member(
                 )
             )
         else:
-            r = await self.send(
+            r = await self.invoke(
                 raw.functions.messages.DeleteChatUser(
                     chat_id=abs(chat_id),
                     user_id=user_peer
diff --git a/pyrogram/methods/chats/create_channel.py b/pyrogram/methods/chats/create_channel.py
index 5920d5ff70..1b054b6ed6 100644
--- a/pyrogram/methods/chats/create_channel.py
+++ b/pyrogram/methods/chats/create_channel.py
@@ -43,7 +43,7 @@ async def create_channel(
 
                 app.create_channel("Channel Title", "Channel Description")
         """
-        r = await self.send(
+        r = await self.invoke(
             raw.functions.channels.CreateChannel(
                 title=title,
                 about=description,
diff --git a/pyrogram/methods/chats/create_group.py b/pyrogram/methods/chats/create_group.py
index d01a2e683d..78240f9fb0 100644
--- a/pyrogram/methods/chats/create_group.py
+++ b/pyrogram/methods/chats/create_group.py
@@ -55,7 +55,7 @@ async def create_group(
         if not isinstance(users, list):
             users = [users]
 
-        r = await self.send(
+        r = await self.invoke(
             raw.functions.messages.CreateChat(
                 title=title,
                 users=[await self.resolve_peer(u) for u in users]
diff --git a/pyrogram/methods/chats/create_supergroup.py b/pyrogram/methods/chats/create_supergroup.py
index 348fc72609..eb922c324f 100644
--- a/pyrogram/methods/chats/create_supergroup.py
+++ b/pyrogram/methods/chats/create_supergroup.py
@@ -47,7 +47,7 @@ async def create_supergroup(
 
                 app.create_supergroup("Supergroup Title", "Supergroup Description")
         """
-        r = await self.send(
+        r = await self.invoke(
             raw.functions.channels.CreateChannel(
                 title=title,
                 about=description,
diff --git a/pyrogram/methods/chats/delete_channel.py b/pyrogram/methods/chats/delete_channel.py
index 246c930af8..210c81f1cd 100644
--- a/pyrogram/methods/chats/delete_channel.py
+++ b/pyrogram/methods/chats/delete_channel.py
@@ -41,7 +41,7 @@ async def delete_channel(
 
                 app.delete_channel(channel_id)
         """
-        await self.send(
+        await self.invoke(
             raw.functions.channels.DeleteChannel(
                 channel=await self.resolve_peer(chat_id)
             )
diff --git a/pyrogram/methods/chats/delete_chat_photo.py b/pyrogram/methods/chats/delete_chat_photo.py
index 0b4d6488c6..ac485603cd 100644
--- a/pyrogram/methods/chats/delete_chat_photo.py
+++ b/pyrogram/methods/chats/delete_chat_photo.py
@@ -49,14 +49,14 @@ async def delete_chat_photo(
         peer = await self.resolve_peer(chat_id)
 
         if isinstance(peer, raw.types.InputPeerChat):
-            await self.send(
+            await self.invoke(
                 raw.functions.messages.EditChatPhoto(
                     chat_id=peer.chat_id,
                     photo=raw.types.InputChatPhotoEmpty()
                 )
             )
         elif isinstance(peer, raw.types.InputPeerChannel):
-            await self.send(
+            await self.invoke(
                 raw.functions.channels.EditPhoto(
                     channel=peer,
                     photo=raw.types.InputChatPhotoEmpty()
diff --git a/pyrogram/methods/chats/delete_supergroup.py b/pyrogram/methods/chats/delete_supergroup.py
index 5f6e8168ea..8fb069fff8 100644
--- a/pyrogram/methods/chats/delete_supergroup.py
+++ b/pyrogram/methods/chats/delete_supergroup.py
@@ -41,7 +41,7 @@ async def delete_supergroup(
 
                 app.delete_supergroup(supergroup_id)
         """
-        await self.send(
+        await self.invoke(
             raw.functions.channels.DeleteChannel(
                 channel=await self.resolve_peer(chat_id)
             )
diff --git a/pyrogram/methods/chats/delete_user_history.py b/pyrogram/methods/chats/delete_user_history.py
index 867100bb6e..4411ddbd24 100644
--- a/pyrogram/methods/chats/delete_user_history.py
+++ b/pyrogram/methods/chats/delete_user_history.py
@@ -41,7 +41,7 @@ async def delete_user_history(
             ``bool``: True on success, False otherwise.
         """
 
-        r = await self.send(
+        r = await self.invoke(
             raw.functions.channels.DeleteParticipantHistory(
                 channel=await self.resolve_peer(chat_id),
                 participant=await self.resolve_peer(user_id)
diff --git a/pyrogram/methods/chats/get_chat.py b/pyrogram/methods/chats/get_chat.py
index 99e5756647..fc19801910 100644
--- a/pyrogram/methods/chats/get_chat.py
+++ b/pyrogram/methods/chats/get_chat.py
@@ -56,7 +56,7 @@ async def get_chat(
         match = self.INVITE_LINK_RE.match(str(chat_id))
 
         if match:
-            r = await self.send(
+            r = await self.invoke(
                 raw.functions.messages.CheckChatInvite(
                     hash=match.group(1)
                 )
@@ -76,10 +76,10 @@ async def get_chat(
         peer = await self.resolve_peer(chat_id)
 
         if isinstance(peer, raw.types.InputPeerChannel):
-            r = await self.send(raw.functions.channels.GetFullChannel(channel=peer))
+            r = await self.invoke(raw.functions.channels.GetFullChannel(channel=peer))
         elif isinstance(peer, (raw.types.InputPeerUser, raw.types.InputPeerSelf)):
-            r = await self.send(raw.functions.users.GetFullUser(id=peer))
+            r = await self.invoke(raw.functions.users.GetFullUser(id=peer))
         else:
-            r = await self.send(raw.functions.messages.GetFullChat(chat_id=peer.chat_id))
+            r = await self.invoke(raw.functions.messages.GetFullChat(chat_id=peer.chat_id))
 
         return await types.Chat._parse_full(self, r)
diff --git a/pyrogram/methods/chats/get_chat_event_log.py b/pyrogram/methods/chats/get_chat_event_log.py
index db27709094..2325bad99a 100644
--- a/pyrogram/methods/chats/get_chat_event_log.py
+++ b/pyrogram/methods/chats/get_chat_event_log.py
@@ -70,7 +70,7 @@ async def get_chat_event_log(
         limit = min(100, total)
 
         while True:
-            r: raw.base.channels.AdminLogResults = await self.send(
+            r: raw.base.channels.AdminLogResults = await self.invoke(
                 raw.functions.channels.GetAdminLog(
                     channel=await self.resolve_peer(chat_id),
                     q=query,
diff --git a/pyrogram/methods/chats/get_chat_member.py b/pyrogram/methods/chats/get_chat_member.py
index 9717d01953..1b24d2135c 100644
--- a/pyrogram/methods/chats/get_chat_member.py
+++ b/pyrogram/methods/chats/get_chat_member.py
@@ -54,7 +54,7 @@ async def get_chat_member(
         user = await self.resolve_peer(user_id)
 
         if isinstance(chat, raw.types.InputPeerChat):
-            r = await self.send(
+            r = await self.invoke(
                 raw.functions.messages.GetFullChat(
                     chat_id=chat.chat_id
                 )
@@ -75,7 +75,7 @@ async def get_chat_member(
             else:
                 raise UserNotParticipant
         elif isinstance(chat, raw.types.InputPeerChannel):
-            r = await self.send(
+            r = await self.invoke(
                 raw.functions.channels.GetParticipant(
                     channel=chat,
                     participant=user
diff --git a/pyrogram/methods/chats/get_chat_members.py b/pyrogram/methods/chats/get_chat_members.py
index 7e6b8898fc..a192c3e3ea 100644
--- a/pyrogram/methods/chats/get_chat_members.py
+++ b/pyrogram/methods/chats/get_chat_members.py
@@ -92,7 +92,7 @@ async def get_chat_members(
         peer = await self.resolve_peer(chat_id)
 
         if isinstance(peer, raw.types.InputPeerChat):
-            r = await self.send(
+            r = await self.invoke(
                 raw.functions.messages.GetFullChat(
                     chat_id=peer.chat_id
                 )
@@ -120,7 +120,7 @@ async def get_chat_members(
             else:
                 raise ValueError(f'Invalid filter "{filter}"')
 
-            r = await self.send(
+            r = await self.invoke(
                 raw.functions.channels.GetParticipants(
                     channel=peer,
                     filter=filter,
diff --git a/pyrogram/methods/chats/get_chat_members_count.py b/pyrogram/methods/chats/get_chat_members_count.py
index a7dae4be05..9c57a11e80 100644
--- a/pyrogram/methods/chats/get_chat_members_count.py
+++ b/pyrogram/methods/chats/get_chat_members_count.py
@@ -48,7 +48,7 @@ async def get_chat_members_count(
         peer = await self.resolve_peer(chat_id)
 
         if isinstance(peer, raw.types.InputPeerChat):
-            r = await self.send(
+            r = await self.invoke(
                 raw.functions.messages.GetChats(
                     id=[peer.chat_id]
                 )
@@ -56,7 +56,7 @@ async def get_chat_members_count(
 
             return r.chats[0].participants_count
         elif isinstance(peer, raw.types.InputPeerChannel):
-            r = await self.send(
+            r = await self.invoke(
                 raw.functions.channels.GetFullChannel(
                     channel=peer
                 )
diff --git a/pyrogram/methods/chats/get_chat_online_count.py b/pyrogram/methods/chats/get_chat_online_count.py
index 3f8e5d6aee..19924542f5 100644
--- a/pyrogram/methods/chats/get_chat_online_count.py
+++ b/pyrogram/methods/chats/get_chat_online_count.py
@@ -42,7 +42,7 @@ async def get_chat_online_count(
                 online = app.get_chat_online_count(chat_id)
                 print(online)
         """
-        return (await self.send(
+        return (await self.invoke(
             raw.functions.messages.GetOnlines(
                 peer=await self.resolve_peer(chat_id)
             )
diff --git a/pyrogram/methods/chats/get_dialogs.py b/pyrogram/methods/chats/get_dialogs.py
index 7276c80b64..4aaa95ffc4 100644
--- a/pyrogram/methods/chats/get_dialogs.py
+++ b/pyrogram/methods/chats/get_dialogs.py
@@ -67,12 +67,12 @@ async def get_dialogs(
         """
 
         if pinned_only:
-            r = await self.send(
+            r = await self.invoke(
                 raw.functions.messages.GetPinnedDialogs(folder_id=0),
                 sleep_threshold=60
             )
         else:
-            r = await self.send(
+            r = await self.invoke(
                 raw.functions.messages.GetDialogs(
                     offset_date=utils.datetime_to_timestamp(offset_date),
                     offset_id=0,
diff --git a/pyrogram/methods/chats/get_dialogs_count.py b/pyrogram/methods/chats/get_dialogs_count.py
index 3f869909fc..8ca237cff5 100644
--- a/pyrogram/methods/chats/get_dialogs_count.py
+++ b/pyrogram/methods/chats/get_dialogs_count.py
@@ -42,9 +42,9 @@ async def get_dialogs_count(
         """
 
         if pinned_only:
-            return len((await self.send(raw.functions.messages.GetPinnedDialogs(folder_id=0))).dialogs)
+            return len((await self.invoke(raw.functions.messages.GetPinnedDialogs(folder_id=0))).dialogs)
         else:
-            r = await self.send(
+            r = await self.invoke(
                 raw.functions.messages.GetDialogs(
                     offset_date=0,
                     offset_id=0,
diff --git a/pyrogram/methods/chats/get_nearby_chats.py b/pyrogram/methods/chats/get_nearby_chats.py
index 0dff05aae2..c7c36cc61d 100644
--- a/pyrogram/methods/chats/get_nearby_chats.py
+++ b/pyrogram/methods/chats/get_nearby_chats.py
@@ -49,7 +49,7 @@ async def get_nearby_chats(
                 print(chats)
         """
 
-        r = await self.send(
+        r = await self.invoke(
             raw.functions.contacts.GetLocated(
                 geo_point=raw.types.InputGeoPoint(
                     lat=latitude,
diff --git a/pyrogram/methods/chats/get_send_as_chats.py b/pyrogram/methods/chats/get_send_as_chats.py
index 147da217ce..2db4d5f05a 100644
--- a/pyrogram/methods/chats/get_send_as_chats.py
+++ b/pyrogram/methods/chats/get_send_as_chats.py
@@ -43,7 +43,7 @@ async def get_send_as_chats(
                 chats = app.get_send_as_chats(chat_id)
                 print(chats)
         """
-        r = await self.send(
+        r = await self.invoke(
             raw.functions.channels.GetSendAs(
                 peer=await self.resolve_peer(chat_id)
             )
diff --git a/pyrogram/methods/chats/iter_dialogs.py b/pyrogram/methods/chats/iter_dialogs.py
index 009bdfa8c2..72a396f758 100644
--- a/pyrogram/methods/chats/iter_dialogs.py
+++ b/pyrogram/methods/chats/iter_dialogs.py
@@ -57,7 +57,7 @@ async def iter_dialogs(
         offset_peer = raw.types.InputPeerEmpty()
 
         while True:
-            r = (await self.send(
+            r = (await self.invoke(
                 raw.functions.messages.GetDialogs(
                     offset_date=offset_date,
                     offset_id=offset_id,
diff --git a/pyrogram/methods/chats/join_chat.py b/pyrogram/methods/chats/join_chat.py
index c1a850af92..2534442ac6 100644
--- a/pyrogram/methods/chats/join_chat.py
+++ b/pyrogram/methods/chats/join_chat.py
@@ -53,7 +53,7 @@ async def join_chat(
         match = self.INVITE_LINK_RE.match(str(chat_id))
 
         if match:
-            chat = await self.send(
+            chat = await self.invoke(
                 raw.functions.messages.ImportChatInvite(
                     hash=match.group(1)
                 )
@@ -63,7 +63,7 @@ async def join_chat(
             elif isinstance(chat.chats[0], raw.types.Channel):
                 return types.Chat._parse_channel_chat(self, chat.chats[0])
         else:
-            chat = await self.send(
+            chat = await self.invoke(
                 raw.functions.channels.JoinChannel(
                     channel=await self.resolve_peer(chat_id)
                 )
diff --git a/pyrogram/methods/chats/leave_chat.py b/pyrogram/methods/chats/leave_chat.py
index 56369e1ce7..7a6eb85dff 100644
--- a/pyrogram/methods/chats/leave_chat.py
+++ b/pyrogram/methods/chats/leave_chat.py
@@ -51,13 +51,13 @@ async def leave_chat(
         peer = await self.resolve_peer(chat_id)
 
         if isinstance(peer, raw.types.InputPeerChannel):
-            return await self.send(
+            return await self.invoke(
                 raw.functions.channels.LeaveChannel(
                     channel=await self.resolve_peer(chat_id)
                 )
             )
         elif isinstance(peer, raw.types.InputPeerChat):
-            r = await self.send(
+            r = await self.invoke(
                 raw.functions.messages.DeleteChatUser(
                     chat_id=peer.chat_id,
                     user_id=raw.types.InputUserSelf()
@@ -65,7 +65,7 @@ async def leave_chat(
             )
 
             if delete:
-                await self.send(
+                await self.invoke(
                     raw.functions.messages.DeleteHistory(
                         peer=peer,
                         max_id=0
diff --git a/pyrogram/methods/chats/mark_chat_unread.py b/pyrogram/methods/chats/mark_chat_unread.py
index 32251e2a7d..62cb8bee31 100644
--- a/pyrogram/methods/chats/mark_chat_unread.py
+++ b/pyrogram/methods/chats/mark_chat_unread.py
@@ -19,7 +19,7 @@
 from typing import Union
 
 import pyrogram
-from pyrogram.raw import functions
+from pyrogram import raw
 
 
 class MarkChatUnread:
@@ -37,8 +37,8 @@ async def mark_chat_unread(
             ``bool``: On success, True is returned.
         """
 
-        return await self.send(
-            functions.messages.MarkDialogUnread(
+        return await self.invoke(
+            raw.functions.messages.MarkDialogUnread(
                 peer=await self.resolve_peer(chat_id),
                 unread=True
             )
diff --git a/pyrogram/methods/chats/pin_chat_message.py b/pyrogram/methods/chats/pin_chat_message.py
index 9e34e1aa6c..1f5ac91239 100644
--- a/pyrogram/methods/chats/pin_chat_message.py
+++ b/pyrogram/methods/chats/pin_chat_message.py
@@ -61,7 +61,7 @@ async def pin_chat_message(
                 # Pin without notification
                 app.pin_chat_message(chat_id, message_id, disable_notification=True)
         """
-        r = await self.send(
+        r = await self.invoke(
             raw.functions.messages.UpdatePinnedMessage(
                 peer=await self.resolve_peer(chat_id),
                 id=message_id,
diff --git a/pyrogram/methods/chats/promote_chat_member.py b/pyrogram/methods/chats/promote_chat_member.py
index 63a844bb2a..b64a17996f 100644
--- a/pyrogram/methods/chats/promote_chat_member.py
+++ b/pyrogram/methods/chats/promote_chat_member.py
@@ -57,7 +57,7 @@ async def promote_chat_member(
         chat_id = await self.resolve_peer(chat_id)
         user_id = await self.resolve_peer(user_id)
 
-        raw_chat_member = (await self.send(
+        raw_chat_member = (await self.invoke(
             raw.functions.channels.GetParticipant(
                 channel=chat_id,
                 participant=user_id
@@ -68,7 +68,7 @@ async def promote_chat_member(
         if isinstance(raw_chat_member, raw.types.ChannelParticipantAdmin):
             rank = raw_chat_member.rank
 
-        await self.send(
+        await self.invoke(
             raw.functions.channels.EditAdmin(
                 channel=chat_id,
                 user_id=user_id,
diff --git a/pyrogram/methods/chats/restrict_chat_member.py b/pyrogram/methods/chats/restrict_chat_member.py
index a8ab21730d..52b264e841 100644
--- a/pyrogram/methods/chats/restrict_chat_member.py
+++ b/pyrogram/methods/chats/restrict_chat_member.py
@@ -72,7 +72,7 @@ async def restrict_chat_member(
                 # Chat member can only send text messages
                 app.restrict_chat_member(chat_id, user_id, ChatPermissions(can_send_messages=True))
         """
-        r = await self.send(
+        r = await self.invoke(
             raw.functions.channels.EditBanned(
                 channel=await self.resolve_peer(chat_id),
                 participant=await self.resolve_peer(user_id),
diff --git a/pyrogram/methods/chats/set_administrator_title.py b/pyrogram/methods/chats/set_administrator_title.py
index ed59b342f5..2ea0dccf6c 100644
--- a/pyrogram/methods/chats/set_administrator_title.py
+++ b/pyrogram/methods/chats/set_administrator_title.py
@@ -57,7 +57,7 @@ async def set_administrator_title(
         chat_id = await self.resolve_peer(chat_id)
         user_id = await self.resolve_peer(user_id)
 
-        r = (await self.send(
+        r = (await self.invoke(
             raw.functions.channels.GetParticipant(
                 channel=chat_id,
                 participant=user_id
@@ -71,7 +71,7 @@ async def set_administrator_title(
         else:
             raise ValueError("Custom titles can only be applied to owners or administrators of supergroups")
 
-        await self.send(
+        await self.invoke(
             raw.functions.channels.EditAdmin(
                 channel=chat_id,
                 user_id=user_id,
diff --git a/pyrogram/methods/chats/set_chat_description.py b/pyrogram/methods/chats/set_chat_description.py
index 440b405013..4a93530b64 100644
--- a/pyrogram/methods/chats/set_chat_description.py
+++ b/pyrogram/methods/chats/set_chat_description.py
@@ -52,7 +52,7 @@ async def set_chat_description(
         peer = await self.resolve_peer(chat_id)
 
         if isinstance(peer, (raw.types.InputPeerChannel, raw.types.InputPeerChat)):
-            await self.send(
+            await self.invoke(
                 raw.functions.messages.EditChatAbout(
                     peer=peer,
                     about=description
diff --git a/pyrogram/methods/chats/set_chat_permissions.py b/pyrogram/methods/chats/set_chat_permissions.py
index dfbd5d917d..51d74fee8c 100644
--- a/pyrogram/methods/chats/set_chat_permissions.py
+++ b/pyrogram/methods/chats/set_chat_permissions.py
@@ -62,7 +62,7 @@ async def set_chat_permissions(
                 )
         """
 
-        r = await self.send(
+        r = await self.invoke(
             raw.functions.messages.EditChatDefaultBannedRights(
                 peer=await self.resolve_peer(chat_id),
                 banned_rights=raw.types.ChatBannedRights(
diff --git a/pyrogram/methods/chats/set_chat_photo.py b/pyrogram/methods/chats/set_chat_photo.py
index 1097b2377b..6da4330bcb 100644
--- a/pyrogram/methods/chats/set_chat_photo.py
+++ b/pyrogram/methods/chats/set_chat_photo.py
@@ -100,14 +100,14 @@ async def set_chat_photo(
             )
 
         if isinstance(peer, raw.types.InputPeerChat):
-            await self.send(
+            await self.invoke(
                 raw.functions.messages.EditChatPhoto(
                     chat_id=peer.chat_id,
                     photo=photo,
                 )
             )
         elif isinstance(peer, raw.types.InputPeerChannel):
-            await self.send(
+            await self.invoke(
                 raw.functions.channels.EditPhoto(
                     channel=peer,
                     photo=photo
diff --git a/pyrogram/methods/chats/set_chat_protected_content.py b/pyrogram/methods/chats/set_chat_protected_content.py
index 1372481c99..ee72d7228e 100644
--- a/pyrogram/methods/chats/set_chat_protected_content.py
+++ b/pyrogram/methods/chats/set_chat_protected_content.py
@@ -19,7 +19,7 @@
 from typing import Union
 
 import pyrogram
-from pyrogram.raw import functions
+from pyrogram import raw
 
 
 class SetChatProtectedContent:
@@ -41,8 +41,8 @@ async def set_chat_protected_content(
             ``bool``: On success, True is returned.
         """
 
-        await self.send(
-            functions.messages.ToggleNoForwards(
+        await self.invoke(
+            raw.functions.messages.ToggleNoForwards(
                 peer=await self.resolve_peer(chat_id),
                 enabled=enabled
             )
diff --git a/pyrogram/methods/chats/set_chat_title.py b/pyrogram/methods/chats/set_chat_title.py
index ac30b078fe..edebf17624 100644
--- a/pyrogram/methods/chats/set_chat_title.py
+++ b/pyrogram/methods/chats/set_chat_title.py
@@ -57,14 +57,14 @@ async def set_chat_title(
         peer = await self.resolve_peer(chat_id)
 
         if isinstance(peer, raw.types.InputPeerChat):
-            await self.send(
+            await self.invoke(
                 raw.functions.messages.EditChatTitle(
                     chat_id=peer.chat_id,
                     title=title
                 )
             )
         elif isinstance(peer, raw.types.InputPeerChannel):
-            await self.send(
+            await self.invoke(
                 raw.functions.channels.EditTitle(
                     channel=peer,
                     title=title
diff --git a/pyrogram/methods/chats/set_chat_username.py b/pyrogram/methods/chats/set_chat_username.py
index c63207aa33..c1f6d17ddc 100644
--- a/pyrogram/methods/chats/set_chat_username.py
+++ b/pyrogram/methods/chats/set_chat_username.py
@@ -55,7 +55,7 @@ async def set_chat_username(
 
         if isinstance(peer, raw.types.InputPeerChannel):
             return bool(
-                await self.send(
+                await self.invoke(
                     raw.functions.channels.UpdateUsername(
                         channel=peer,
                         username=username or ""
diff --git a/pyrogram/methods/chats/set_send_as_chat.py b/pyrogram/methods/chats/set_send_as_chat.py
index dabb4066f8..bbe210a41e 100644
--- a/pyrogram/methods/chats/set_send_as_chat.py
+++ b/pyrogram/methods/chats/set_send_as_chat.py
@@ -47,7 +47,7 @@ async def set_send_as_chat(
 
                 app.set_send_as_chat(chat_id, send_as_chat_id)
         """
-        return await self.send(
+        return await self.invoke(
             raw.functions.messages.SaveDefaultSendAs(
                 peer=await self.resolve_peer(chat_id),
                 send_as=await self.resolve_peer(send_as_chat_id)
diff --git a/pyrogram/methods/chats/set_slow_mode.py b/pyrogram/methods/chats/set_slow_mode.py
index 3bc00cb21a..e3237ad34f 100644
--- a/pyrogram/methods/chats/set_slow_mode.py
+++ b/pyrogram/methods/chats/set_slow_mode.py
@@ -51,7 +51,7 @@ async def set_slow_mode(
                 app.set_slow_mode(chat_id, None)
         """
 
-        await self.send(
+        await self.invoke(
             raw.functions.channels.ToggleSlowMode(
                 channel=await self.resolve_peer(chat_id),
                 seconds=seconds or 0
diff --git a/pyrogram/methods/chats/unarchive_chats.py b/pyrogram/methods/chats/unarchive_chats.py
index a6b77d1845..726eb4a07e 100644
--- a/pyrogram/methods/chats/unarchive_chats.py
+++ b/pyrogram/methods/chats/unarchive_chats.py
@@ -60,7 +60,7 @@ async def unarchive_chats(
                 )
             )
 
-        await self.send(
+        await self.invoke(
             raw.functions.folders.EditPeerFolders(
                 folder_peers=folder_peers
             )
diff --git a/pyrogram/methods/chats/unban_chat_member.py b/pyrogram/methods/chats/unban_chat_member.py
index 9176fe3361..c331e823d4 100644
--- a/pyrogram/methods/chats/unban_chat_member.py
+++ b/pyrogram/methods/chats/unban_chat_member.py
@@ -49,7 +49,7 @@ async def unban_chat_member(
                 # Unban chat member right now
                 app.unban_chat_member(chat_id, user_id)
         """
-        await self.send(
+        await self.invoke(
             raw.functions.channels.EditBanned(
                 channel=await self.resolve_peer(chat_id),
                 participant=await self.resolve_peer(user_id),
diff --git a/pyrogram/methods/chats/unpin_all_chat_messages.py b/pyrogram/methods/chats/unpin_all_chat_messages.py
index bd75c40d6c..93b6eaec90 100644
--- a/pyrogram/methods/chats/unpin_all_chat_messages.py
+++ b/pyrogram/methods/chats/unpin_all_chat_messages.py
@@ -44,7 +44,7 @@ async def unpin_all_chat_messages(
                 # Unpin all chat messages
                 app.unpin_all_chat_messages(chat_id)
         """
-        await self.send(
+        await self.invoke(
             raw.functions.messages.UnpinAllMessages(
                 peer=await self.resolve_peer(chat_id)
             )
diff --git a/pyrogram/methods/chats/unpin_chat_message.py b/pyrogram/methods/chats/unpin_chat_message.py
index cf3f9e9d5e..6723f5a2a4 100644
--- a/pyrogram/methods/chats/unpin_chat_message.py
+++ b/pyrogram/methods/chats/unpin_chat_message.py
@@ -48,7 +48,7 @@ async def unpin_chat_message(
 
                 app.unpin_chat_message(chat_id, message_id)
         """
-        await self.send(
+        await self.invoke(
             raw.functions.messages.UpdatePinnedMessage(
                 peer=await self.resolve_peer(chat_id),
                 id=message_id,
diff --git a/pyrogram/methods/contacts/add_contact.py b/pyrogram/methods/contacts/add_contact.py
index 72bdd88acf..433c4c90b6 100644
--- a/pyrogram/methods/contacts/add_contact.py
+++ b/pyrogram/methods/contacts/add_contact.py
@@ -60,7 +60,7 @@ async def add_contact(
                 app.add_contact(12345678, "Foo")
                 app.add_contact("username", "Bar")
         """
-        r = await self.send(
+        r = await self.invoke(
             raw.functions.contacts.AddContact(
                 id=await self.resolve_peer(user_id),
                 first_name=first_name,
diff --git a/pyrogram/methods/contacts/delete_contacts.py b/pyrogram/methods/contacts/delete_contacts.py
index e6ef32583b..03238beb88 100644
--- a/pyrogram/methods/contacts/delete_contacts.py
+++ b/pyrogram/methods/contacts/delete_contacts.py
@@ -50,7 +50,7 @@ async def delete_contacts(
         if not is_user_ids_list:
             user_ids = [user_ids]
 
-        r = await self.send(
+        r = await self.invoke(
             raw.functions.contacts.DeleteContacts(
                 id=[await self.resolve_peer(i) for i in user_ids]
             )
diff --git a/pyrogram/methods/contacts/get_contacts.py b/pyrogram/methods/contacts/get_contacts.py
index 0b41c3d6cb..ca8888764b 100644
--- a/pyrogram/methods/contacts/get_contacts.py
+++ b/pyrogram/methods/contacts/get_contacts.py
@@ -41,5 +41,5 @@ async def get_contacts(
                 contacts = app.get_contacts()
                 print(contacts)
         """
-        contacts = await self.send(raw.functions.contacts.GetContacts(hash=0))
+        contacts = await self.invoke(raw.functions.contacts.GetContacts(hash=0))
         return types.List(types.User._parse(self, user) for user in contacts.users)
diff --git a/pyrogram/methods/contacts/get_contacts_count.py b/pyrogram/methods/contacts/get_contacts_count.py
index b7e5d371c8..d32ae0500f 100644
--- a/pyrogram/methods/contacts/get_contacts_count.py
+++ b/pyrogram/methods/contacts/get_contacts_count.py
@@ -36,4 +36,4 @@ async def get_contacts_count(
                 print(count)
         """
 
-        return len((await self.send(raw.functions.contacts.GetContacts(hash=0))).contacts)
+        return len((await self.invoke(raw.functions.contacts.GetContacts(hash=0))).contacts)
diff --git a/pyrogram/methods/contacts/import_contacts.py b/pyrogram/methods/contacts/import_contacts.py
index 7a6e31420f..de802cff72 100644
--- a/pyrogram/methods/contacts/import_contacts.py
+++ b/pyrogram/methods/contacts/import_contacts.py
@@ -47,7 +47,7 @@ async def import_contacts(
                     InputPhoneContact("+1-456-789-0123", "Bar"),
                     InputPhoneContact("+1-789-012-3456", "Baz")])
         """
-        imported_contacts = await self.send(
+        imported_contacts = await self.invoke(
             raw.functions.contacts.ImportContacts(
                 contacts=contacts
             )
diff --git a/pyrogram/methods/invite_links/approve_chat_join_request.py b/pyrogram/methods/invite_links/approve_chat_join_request.py
index a18a1c146a..3cd2248902 100644
--- a/pyrogram/methods/invite_links/approve_chat_join_request.py
+++ b/pyrogram/methods/invite_links/approve_chat_join_request.py
@@ -44,7 +44,7 @@ async def approve_chat_join_request(
         Returns:
             ``bool``: True on success.
         """
-        await self.send(
+        await self.invoke(
             raw.functions.messages.HideChatJoinRequest(
                 peer=await self.resolve_peer(chat_id),
                 user_id=await self.resolve_peer(user_id),
diff --git a/pyrogram/methods/invite_links/create_chat_invite_link.py b/pyrogram/methods/invite_links/create_chat_invite_link.py
index 15ca034162..0e3e63a751 100644
--- a/pyrogram/methods/invite_links/create_chat_invite_link.py
+++ b/pyrogram/methods/invite_links/create_chat_invite_link.py
@@ -72,7 +72,7 @@ async def create_chat_invite_link(
                 # Create a new link for up to 7 new users
                 link = app.create_chat_invite_link(chat_id, member_limit=7)
         """
-        r = await self.send(
+        r = await self.invoke(
             raw.functions.messages.ExportChatInvite(
                 peer=await self.resolve_peer(chat_id),
                 expire_date=utils.datetime_to_timestamp(expire_date),
diff --git a/pyrogram/methods/invite_links/decline_chat_join_request.py b/pyrogram/methods/invite_links/decline_chat_join_request.py
index d7c3d2f2e0..94e3c2fe7f 100644
--- a/pyrogram/methods/invite_links/decline_chat_join_request.py
+++ b/pyrogram/methods/invite_links/decline_chat_join_request.py
@@ -44,7 +44,7 @@ async def decline_chat_join_request(
         Returns:
             ``bool``: True on success.
         """
-        await self.send(
+        await self.invoke(
             raw.functions.messages.HideChatJoinRequest(
                 peer=await self.resolve_peer(chat_id),
                 user_id=await self.resolve_peer(user_id),
diff --git a/pyrogram/methods/invite_links/delete_chat_admin_invite_links.py b/pyrogram/methods/invite_links/delete_chat_admin_invite_links.py
index c051717830..8ba6754d0a 100644
--- a/pyrogram/methods/invite_links/delete_chat_admin_invite_links.py
+++ b/pyrogram/methods/invite_links/delete_chat_admin_invite_links.py
@@ -44,7 +44,7 @@ async def delete_chat_admin_invite_links(
             ``bool``: On success ``True`` is returned.
         """
 
-        return await self.send(
+        return await self.invoke(
             raw.functions.messages.DeleteRevokedExportedChatInvites(
                 peer=await self.resolve_peer(chat_id),
                 admin_id=await self.resolve_peer(admin_id),
diff --git a/pyrogram/methods/invite_links/delete_chat_invite_link.py b/pyrogram/methods/invite_links/delete_chat_invite_link.py
index 19f4b49b3c..1f38e46cc4 100644
--- a/pyrogram/methods/invite_links/delete_chat_invite_link.py
+++ b/pyrogram/methods/invite_links/delete_chat_invite_link.py
@@ -42,7 +42,7 @@ async def delete_chat_invite_link(
             ``bool``: On success ``True`` is returned.
         """
 
-        return await self.send(
+        return await self.invoke(
             raw.functions.messages.DeleteExportedChatInvite(
                 peer=await self.resolve_peer(chat_id),
                 link=invite_link,
diff --git a/pyrogram/methods/invite_links/edit_chat_invite_link.py b/pyrogram/methods/invite_links/edit_chat_invite_link.py
index 4a6755d6ea..96dcf79ddb 100644
--- a/pyrogram/methods/invite_links/edit_chat_invite_link.py
+++ b/pyrogram/methods/invite_links/edit_chat_invite_link.py
@@ -74,7 +74,7 @@ async def edit_chat_invite_link(
                 # Set no expiration date of a link
                 link = app.edit_chat_invite_link(chat_id, invite_link, expire_date=0)
         """
-        r = await self.send(
+        r = await self.invoke(
             raw.functions.messages.EditExportedChatInvite(
                 peer=await self.resolve_peer(chat_id),
                 link=invite_link,
diff --git a/pyrogram/methods/invite_links/export_chat_invite_link.py b/pyrogram/methods/invite_links/export_chat_invite_link.py
index 9734470fea..66fb0227a4 100644
--- a/pyrogram/methods/invite_links/export_chat_invite_link.py
+++ b/pyrogram/methods/invite_links/export_chat_invite_link.py
@@ -53,7 +53,7 @@ async def export_chat_invite_link(
                 # Generate a new primary link
                 link = app.export_chat_invite_link(chat_id)
         """
-        r = await self.send(
+        r = await self.invoke(
             raw.functions.messages.ExportChatInvite(
                 peer=await self.resolve_peer(chat_id),
                 legacy_revoke_permanent=True
diff --git a/pyrogram/methods/invite_links/get_chat_admin_invite_links.py b/pyrogram/methods/invite_links/get_chat_admin_invite_links.py
index 0c2660828a..1c79ce273c 100644
--- a/pyrogram/methods/invite_links/get_chat_admin_invite_links.py
+++ b/pyrogram/methods/invite_links/get_chat_admin_invite_links.py
@@ -70,7 +70,7 @@ async def get_chat_admin_invite_links(
         offset_link = None
 
         while True:
-            r = await self.send(
+            r = await self.invoke(
                 raw.functions.messages.GetExportedChatInvites(
                     peer=await self.resolve_peer(chat_id),
                     admin_id=await self.resolve_peer(admin_id),
diff --git a/pyrogram/methods/invite_links/get_chat_admin_invite_links_count.py b/pyrogram/methods/invite_links/get_chat_admin_invite_links_count.py
index 419c76a776..c26af5063c 100644
--- a/pyrogram/methods/invite_links/get_chat_admin_invite_links_count.py
+++ b/pyrogram/methods/invite_links/get_chat_admin_invite_links_count.py
@@ -48,7 +48,7 @@ async def get_chat_admin_invite_links_count(
         Returns:
             ``int``: On success, the invite links count is returned.
         """
-        r = await self.send(
+        r = await self.invoke(
             raw.functions.messages.GetExportedChatInvites(
                 peer=await self.resolve_peer(chat_id),
                 admin_id=await self.resolve_peer(admin_id),
diff --git a/pyrogram/methods/invite_links/get_chat_admins_with_invite_links.py b/pyrogram/methods/invite_links/get_chat_admins_with_invite_links.py
index 0f41925a10..61e082bc0e 100644
--- a/pyrogram/methods/invite_links/get_chat_admins_with_invite_links.py
+++ b/pyrogram/methods/invite_links/get_chat_admins_with_invite_links.py
@@ -40,7 +40,7 @@ async def get_chat_admins_with_invite_links(
             List of :obj:`~pyrogram.types.ChatAdminWithInviteLink`: On success, the list of admins that have exported
             invite links is returned.
         """
-        r = await self.send(
+        r = await self.invoke(
             raw.functions.messages.GetAdminsWithInvites(
                 peer=await self.resolve_peer(chat_id)
             )
diff --git a/pyrogram/methods/invite_links/get_chat_invite_link.py b/pyrogram/methods/invite_links/get_chat_invite_link.py
index 0fe0da8e9e..7447933658 100644
--- a/pyrogram/methods/invite_links/get_chat_invite_link.py
+++ b/pyrogram/methods/invite_links/get_chat_invite_link.py
@@ -42,7 +42,7 @@ async def get_chat_invite_link(
         Returns:
             :obj:`~pyrogram.types.ChatInviteLink`: On success, the invite link is returned.
         """
-        r = await self.send(
+        r = await self.invoke(
             raw.functions.messages.GetExportedChatInvite(
                 peer=await self.resolve_peer(chat_id),
                 link=invite_link
diff --git a/pyrogram/methods/invite_links/get_chat_invite_link_members.py b/pyrogram/methods/invite_links/get_chat_invite_link_members.py
index 8269a3465b..28121cccb7 100644
--- a/pyrogram/methods/invite_links/get_chat_invite_link_members.py
+++ b/pyrogram/methods/invite_links/get_chat_invite_link_members.py
@@ -58,7 +58,7 @@ async def get_chat_invite_link_members(
         offset_user = raw.types.InputUserEmpty()
 
         while True:
-            r = await self.send(
+            r = await self.invoke(
                 raw.functions.messages.GetChatInviteImporters(
                     peer=await self.resolve_peer(chat_id),
                     link=invite_link,
diff --git a/pyrogram/methods/invite_links/get_chat_invite_link_members_count.py b/pyrogram/methods/invite_links/get_chat_invite_link_members_count.py
index c37258fe3c..d542895716 100644
--- a/pyrogram/methods/invite_links/get_chat_invite_link_members_count.py
+++ b/pyrogram/methods/invite_links/get_chat_invite_link_members_count.py
@@ -41,7 +41,7 @@ async def get_chat_invite_link_members_count(
         Returns:
             ``int``: On success, the joined chat members count is returned.
         """
-        r = await self.send(
+        r = await self.invoke(
             raw.functions.messages.GetChatInviteImporters(
                 peer=await self.resolve_peer(chat_id),
                 link=invite_link,
diff --git a/pyrogram/methods/invite_links/revoke_chat_invite_link.py b/pyrogram/methods/invite_links/revoke_chat_invite_link.py
index 64af8e5f73..a334bb8de2 100644
--- a/pyrogram/methods/invite_links/revoke_chat_invite_link.py
+++ b/pyrogram/methods/invite_links/revoke_chat_invite_link.py
@@ -47,7 +47,7 @@ async def revoke_chat_invite_link(
             :obj:`~pyrogram.types.ChatInviteLink`: On success, the invite link object is returned.
         """
 
-        r = await self.send(
+        r = await self.invoke(
             raw.functions.messages.EditExportedChatInvite(
                 peer=await self.resolve_peer(chat_id),
                 link=invite_link,
diff --git a/pyrogram/methods/messages/copy_media_group.py b/pyrogram/methods/messages/copy_media_group.py
index 367cf47c57..b204999f3e 100644
--- a/pyrogram/methods/messages/copy_media_group.py
+++ b/pyrogram/methods/messages/copy_media_group.py
@@ -109,7 +109,7 @@ async def copy_media_group(
                 )
             )
 
-        r = await self.send(
+        r = await self.invoke(
             raw.functions.messages.SendMultiMedia(
                 peer=await self.resolve_peer(chat_id),
                 multi_media=multi_media,
diff --git a/pyrogram/methods/messages/delete_messages.py b/pyrogram/methods/messages/delete_messages.py
index 30438944fb..f8c3e7feed 100644
--- a/pyrogram/methods/messages/delete_messages.py
+++ b/pyrogram/methods/messages/delete_messages.py
@@ -66,14 +66,14 @@ async def delete_messages(
         message_ids = list(message_ids) if not isinstance(message_ids, int) else [message_ids]
 
         if isinstance(peer, raw.types.InputPeerChannel):
-            r = await self.send(
+            r = await self.invoke(
                 raw.functions.channels.DeleteMessages(
                     channel=peer,
                     id=message_ids
                 )
             )
         else:
-            r = await self.send(
+            r = await self.invoke(
                 raw.functions.messages.DeleteMessages(
                     id=message_ids,
                     revoke=revoke or None
diff --git a/pyrogram/methods/messages/edit_inline_media.py b/pyrogram/methods/messages/edit_inline_media.py
index 0613bd7368..1bf8d4d249 100644
--- a/pyrogram/methods/messages/edit_inline_media.py
+++ b/pyrogram/methods/messages/edit_inline_media.py
@@ -179,7 +179,7 @@ async def edit_inline_media(
 
         session = await get_session(self, dc_id)
 
-        return await session.send(
+        return await session.invoke(
             raw.functions.messages.EditInlineBotMessage(
                 id=unpacked,
                 media=media,
diff --git a/pyrogram/methods/messages/edit_inline_reply_markup.py b/pyrogram/methods/messages/edit_inline_reply_markup.py
index b3760c6751..92c6851e4f 100644
--- a/pyrogram/methods/messages/edit_inline_reply_markup.py
+++ b/pyrogram/methods/messages/edit_inline_reply_markup.py
@@ -58,7 +58,7 @@ async def edit_inline_reply_markup(
 
         session = await get_session(self, dc_id)
 
-        return await session.send(
+        return await session.invoke(
             raw.functions.messages.EditInlineBotMessage(
                 id=unpacked,
                 reply_markup=await reply_markup.write(self) if reply_markup else None,
diff --git a/pyrogram/methods/messages/edit_inline_text.py b/pyrogram/methods/messages/edit_inline_text.py
index ae4cab3661..9a50d8639b 100644
--- a/pyrogram/methods/messages/edit_inline_text.py
+++ b/pyrogram/methods/messages/edit_inline_text.py
@@ -75,7 +75,7 @@ async def edit_inline_text(
 
         session = await get_session(self, dc_id)
 
-        return await session.send(
+        return await session.invoke(
             raw.functions.messages.EditInlineBotMessage(
                 id=unpacked,
                 no_webpage=disable_web_page_preview or None,
diff --git a/pyrogram/methods/messages/edit_message_media.py b/pyrogram/methods/messages/edit_message_media.py
index 1593bfb6f8..0e3f360c22 100644
--- a/pyrogram/methods/messages/edit_message_media.py
+++ b/pyrogram/methods/messages/edit_message_media.py
@@ -87,7 +87,7 @@ async def edit_message_media(
 
         if isinstance(media, types.InputMediaPhoto):
             if os.path.isfile(media.media):
-                media = await self.send(
+                media = await self.invoke(
                     raw.functions.messages.UploadMedia(
                         peer=await self.resolve_peer(chat_id),
                         media=raw.types.InputMediaUploadedPhoto(
@@ -111,7 +111,7 @@ async def edit_message_media(
                 media = utils.get_input_media_from_file_id(media.media, FileType.PHOTO)
         elif isinstance(media, types.InputMediaVideo):
             if os.path.isfile(media.media):
-                media = await self.send(
+                media = await self.invoke(
                     raw.functions.messages.UploadMedia(
                         peer=await self.resolve_peer(chat_id),
                         media=raw.types.InputMediaUploadedDocument(
@@ -148,7 +148,7 @@ async def edit_message_media(
                 media = utils.get_input_media_from_file_id(media.media, FileType.VIDEO)
         elif isinstance(media, types.InputMediaAudio):
             if os.path.isfile(media.media):
-                media = await self.send(
+                media = await self.invoke(
                     raw.functions.messages.UploadMedia(
                         peer=await self.resolve_peer(chat_id),
                         media=raw.types.InputMediaUploadedDocument(
@@ -184,7 +184,7 @@ async def edit_message_media(
                 media = utils.get_input_media_from_file_id(media.media, FileType.AUDIO)
         elif isinstance(media, types.InputMediaAnimation):
             if os.path.isfile(media.media):
-                media = await self.send(
+                media = await self.invoke(
                     raw.functions.messages.UploadMedia(
                         peer=await self.resolve_peer(chat_id),
                         media=raw.types.InputMediaUploadedDocument(
@@ -222,7 +222,7 @@ async def edit_message_media(
                 media = utils.get_input_media_from_file_id(media.media, FileType.ANIMATION)
         elif isinstance(media, types.InputMediaDocument):
             if os.path.isfile(media.media):
-                media = await self.send(
+                media = await self.invoke(
                     raw.functions.messages.UploadMedia(
                         peer=await self.resolve_peer(chat_id),
                         media=raw.types.InputMediaUploadedDocument(
@@ -252,7 +252,7 @@ async def edit_message_media(
             else:
                 media = utils.get_input_media_from_file_id(media.media, FileType.DOCUMENT)
 
-        r = await self.send(
+        r = await self.invoke(
             raw.functions.messages.EditMessage(
                 peer=await self.resolve_peer(chat_id),
                 id=message_id,
diff --git a/pyrogram/methods/messages/edit_message_reply_markup.py b/pyrogram/methods/messages/edit_message_reply_markup.py
index 91b6fcd3e2..c164afbf77 100644
--- a/pyrogram/methods/messages/edit_message_reply_markup.py
+++ b/pyrogram/methods/messages/edit_message_reply_markup.py
@@ -58,7 +58,7 @@ async def edit_message_reply_markup(
                     InlineKeyboardMarkup([[
                         InlineKeyboardButton("New button", callback_data="new_data")]]))
         """
-        r = await self.send(
+        r = await self.invoke(
             raw.functions.messages.EditMessage(
                 peer=await self.resolve_peer(chat_id),
                 id=message_id,
diff --git a/pyrogram/methods/messages/edit_message_text.py b/pyrogram/methods/messages/edit_message_text.py
index 0f409cfe0a..551beaa143 100644
--- a/pyrogram/methods/messages/edit_message_text.py
+++ b/pyrogram/methods/messages/edit_message_text.py
@@ -77,7 +77,7 @@ async def edit_message_text(
                     disable_web_page_preview=True)
         """
 
-        r = await self.send(
+        r = await self.invoke(
             raw.functions.messages.EditMessage(
                 peer=await self.resolve_peer(chat_id),
                 id=message_id,
diff --git a/pyrogram/methods/messages/forward_messages.py b/pyrogram/methods/messages/forward_messages.py
index 12ed56aa97..dbc5534e5a 100644
--- a/pyrogram/methods/messages/forward_messages.py
+++ b/pyrogram/methods/messages/forward_messages.py
@@ -79,7 +79,7 @@ async def forward_messages(
         is_iterable = not isinstance(message_ids, int)
         message_ids = list(message_ids) if is_iterable else [message_ids]
 
-        r = await self.send(
+        r = await self.invoke(
             raw.functions.messages.ForwardMessages(
                 to_peer=await self.resolve_peer(chat_id),
                 from_peer=await self.resolve_peer(from_chat_id),
diff --git a/pyrogram/methods/messages/get_discussion_message.py b/pyrogram/methods/messages/get_discussion_message.py
index f1de459234..6c72b484de 100644
--- a/pyrogram/methods/messages/get_discussion_message.py
+++ b/pyrogram/methods/messages/get_discussion_message.py
@@ -49,7 +49,7 @@ async def get_discussion_message(
                 # Comment to the post by replying
                 m.reply("comment")
         """
-        r = await self.send(
+        r = await self.invoke(
             raw.functions.messages.GetDiscussionMessage(
                 peer=await self.resolve_peer(chat_id),
                 msg_id=message_id
diff --git a/pyrogram/methods/messages/get_history.py b/pyrogram/methods/messages/get_history.py
index a67d971e65..ae8e1f37a8 100644
--- a/pyrogram/methods/messages/get_history.py
+++ b/pyrogram/methods/messages/get_history.py
@@ -86,7 +86,7 @@ async def get_history(
 
         messages = await utils.parse_messages(
             self,
-            await self.send(
+            await self.invoke(
                 raw.functions.messages.GetHistory(
                     peer=await self.resolve_peer(chat_id),
                     offset_id=offset_id,
diff --git a/pyrogram/methods/messages/get_history_count.py b/pyrogram/methods/messages/get_history_count.py
index a76c10b51d..12e068ae84 100644
--- a/pyrogram/methods/messages/get_history_count.py
+++ b/pyrogram/methods/messages/get_history_count.py
@@ -51,7 +51,7 @@ async def get_history_count(
                 app.get_history_count(chat_id)
         """
 
-        r = await self.send(
+        r = await self.invoke(
             raw.functions.messages.GetHistory(
                 peer=await self.resolve_peer(chat_id),
                 offset_id=0,
diff --git a/pyrogram/methods/messages/get_messages.py b/pyrogram/methods/messages/get_messages.py
index e9e408b5e2..a6a361af8c 100644
--- a/pyrogram/methods/messages/get_messages.py
+++ b/pyrogram/methods/messages/get_messages.py
@@ -111,7 +111,7 @@ async def get_messages(
         else:
             rpc = raw.functions.messages.GetMessages(id=ids)
 
-        r = await self.send(rpc, sleep_threshold=-1)
+        r = await self.invoke(rpc, sleep_threshold=-1)
 
         messages = await utils.parse_messages(self, r, replies=replies)
 
diff --git a/pyrogram/methods/messages/inline_session.py b/pyrogram/methods/messages/inline_session.py
index c4ac50aa89..57fa794584 100644
--- a/pyrogram/methods/messages/inline_session.py
+++ b/pyrogram/methods/messages/inline_session.py
@@ -40,14 +40,14 @@ async def get_session(client: "pyrogram.Client", dc_id: int):
         await session.start()
 
         for _ in range(3):
-            exported_auth = await client.send(
+            exported_auth = await client.invoke(
                 raw.functions.auth.ExportAuthorization(
                     dc_id=dc_id
                 )
             )
 
             try:
-                await session.send(
+                await session.invoke(
                     raw.functions.auth.ImportAuthorization(
                         id=exported_auth.id,
                         bytes=exported_auth.bytes
diff --git a/pyrogram/methods/messages/read_history.py b/pyrogram/methods/messages/read_history.py
index 66b8bf50f7..204d9e3114 100644
--- a/pyrogram/methods/messages/read_history.py
+++ b/pyrogram/methods/messages/read_history.py
@@ -66,6 +66,6 @@ async def read_history(
                 max_id=max_id
             )
 
-        await self.send(q)
+        await self.invoke(q)
 
         return True
diff --git a/pyrogram/methods/messages/retract_vote.py b/pyrogram/methods/messages/retract_vote.py
index f49807cd7a..8aab69f016 100644
--- a/pyrogram/methods/messages/retract_vote.py
+++ b/pyrogram/methods/messages/retract_vote.py
@@ -48,7 +48,7 @@ async def retract_vote(
 
                 app.retract_vote(chat_id, message_id)
         """
-        r = await self.send(
+        r = await self.invoke(
             raw.functions.messages.SendVote(
                 peer=await self.resolve_peer(chat_id),
                 msg_id=message_id,
diff --git a/pyrogram/methods/messages/search_global.py b/pyrogram/methods/messages/search_global.py
index 3f0d4e9ff3..a6ab555744 100644
--- a/pyrogram/methods/messages/search_global.py
+++ b/pyrogram/methods/messages/search_global.py
@@ -79,7 +79,7 @@ async def search_global(
         while True:
             messages = await utils.parse_messages(
                 self,
-                await self.send(
+                await self.invoke(
                     raw.functions.messages.SearchGlobal(
                         q=query,
                         filter=filter.value(),
diff --git a/pyrogram/methods/messages/search_global_count.py b/pyrogram/methods/messages/search_global_count.py
index c848546ef3..afdad4c188 100644
--- a/pyrogram/methods/messages/search_global_count.py
+++ b/pyrogram/methods/messages/search_global_count.py
@@ -41,7 +41,7 @@ async def search_global_count(
         Returns:
             ``int``: On success, the messages count is returned.
         """
-        r = await self.send(
+        r = await self.invoke(
             raw.functions.messages.SearchGlobal(
                 q=query,
                 filter=filter.value(),
diff --git a/pyrogram/methods/messages/search_messages.py b/pyrogram/methods/messages/search_messages.py
index b448a1c204..b40826d9a2 100644
--- a/pyrogram/methods/messages/search_messages.py
+++ b/pyrogram/methods/messages/search_messages.py
@@ -32,7 +32,7 @@ async def get_chunk(
     limit: int = 100,
     from_user: Union[int, str] = None
 ) -> List["types.Message"]:
-    r = await client.send(
+    r = await client.invoke(
         raw.functions.messages.Search(
             peer=await client.resolve_peer(chat_id),
             q=query,
diff --git a/pyrogram/methods/messages/search_messages_count.py b/pyrogram/methods/messages/search_messages_count.py
index 85c25d0618..301563e140 100644
--- a/pyrogram/methods/messages/search_messages_count.py
+++ b/pyrogram/methods/messages/search_messages_count.py
@@ -55,7 +55,7 @@ async def search_messages_count(
         Returns:
             ``int``: On success, the messages count is returned.
         """
-        r = await self.send(
+        r = await self.invoke(
             raw.functions.messages.Search(
                 peer=await self.resolve_peer(chat_id),
                 q=query,
diff --git a/pyrogram/methods/messages/send_animation.py b/pyrogram/methods/messages/send_animation.py
index 2e84fe8267..9403b1e49c 100644
--- a/pyrogram/methods/messages/send_animation.py
+++ b/pyrogram/methods/messages/send_animation.py
@@ -216,7 +216,7 @@ def progress(current, total):
 
             while True:
                 try:
-                    r = await self.send(
+                    r = await self.invoke(
                         raw.functions.messages.SendMedia(
                             peer=await self.resolve_peer(chat_id),
                             media=media,
@@ -249,7 +249,7 @@ def progress(current, total):
                                     document.file_id, FileType.ANIMATION
                                 ).id
 
-                                await self.send(
+                                await self.invoke(
                                     raw.functions.messages.SaveGif(
                                         id=document_id,
                                         unsave=True
diff --git a/pyrogram/methods/messages/send_audio.py b/pyrogram/methods/messages/send_audio.py
index 47e914a8b4..e0a98f85d5 100644
--- a/pyrogram/methods/messages/send_audio.py
+++ b/pyrogram/methods/messages/send_audio.py
@@ -210,7 +210,7 @@ def progress(current, total):
 
             while True:
                 try:
-                    r = await self.send(
+                    r = await self.invoke(
                         raw.functions.messages.SendMedia(
                             peer=await self.resolve_peer(chat_id),
                             media=media,
diff --git a/pyrogram/methods/messages/send_cached_media.py b/pyrogram/methods/messages/send_cached_media.py
index 4763e94d46..88a5f30921 100644
--- a/pyrogram/methods/messages/send_cached_media.py
+++ b/pyrogram/methods/messages/send_cached_media.py
@@ -96,7 +96,7 @@ async def send_cached_media(
                 app.send_cached_media("me", file_id)
         """
 
-        r = await self.send(
+        r = await self.invoke(
             raw.functions.messages.SendMedia(
                 peer=await self.resolve_peer(chat_id),
                 media=utils.get_input_media_from_file_id(file_id),
diff --git a/pyrogram/methods/messages/send_chat_action.py b/pyrogram/methods/messages/send_chat_action.py
index 9145653e7a..39f8d85f86 100644
--- a/pyrogram/methods/messages/send_chat_action.py
+++ b/pyrogram/methods/messages/send_chat_action.py
@@ -70,7 +70,7 @@ async def send_chat_action(
         else:
             action = action.value()
 
-        return await self.send(
+        return await self.invoke(
             raw.functions.messages.SetTyping(
                 peer=await self.resolve_peer(chat_id),
                 action=action
diff --git a/pyrogram/methods/messages/send_contact.py b/pyrogram/methods/messages/send_contact.py
index 6285b502ff..b4683338d5 100644
--- a/pyrogram/methods/messages/send_contact.py
+++ b/pyrogram/methods/messages/send_contact.py
@@ -88,7 +88,7 @@ async def send_contact(
 
                 app.send_contact("me", "+1-123-456-7890", "Name")
         """
-        r = await self.send(
+        r = await self.invoke(
             raw.functions.messages.SendMedia(
                 peer=await self.resolve_peer(chat_id),
                 media=raw.types.InputMediaContact(
diff --git a/pyrogram/methods/messages/send_dice.py b/pyrogram/methods/messages/send_dice.py
index c3a6bb42fb..b15cfe2d0b 100644
--- a/pyrogram/methods/messages/send_dice.py
+++ b/pyrogram/methods/messages/send_dice.py
@@ -88,7 +88,7 @@ async def send_dice(
                 app.send_dice(chat_id, "🏀")
         """
 
-        r = await self.send(
+        r = await self.invoke(
             raw.functions.messages.SendMedia(
                 peer=await self.resolve_peer(chat_id),
                 media=raw.types.InputMediaDice(emoticon=emoji),
diff --git a/pyrogram/methods/messages/send_document.py b/pyrogram/methods/messages/send_document.py
index 79f6e160ef..5b480cb3ea 100644
--- a/pyrogram/methods/messages/send_document.py
+++ b/pyrogram/methods/messages/send_document.py
@@ -188,7 +188,7 @@ def progress(current, total):
 
             while True:
                 try:
-                    r = await self.send(
+                    r = await self.invoke(
                         raw.functions.messages.SendMedia(
                             peer=await self.resolve_peer(chat_id),
                             media=media,
diff --git a/pyrogram/methods/messages/send_location.py b/pyrogram/methods/messages/send_location.py
index 3e9bf37c75..1364543b43 100644
--- a/pyrogram/methods/messages/send_location.py
+++ b/pyrogram/methods/messages/send_location.py
@@ -80,7 +80,7 @@ async def send_location(
 
                 app.send_location("me", 51.500729, -0.124583)
         """
-        r = await self.send(
+        r = await self.invoke(
             raw.functions.messages.SendMedia(
                 peer=await self.resolve_peer(chat_id),
                 media=raw.types.InputMediaGeoPoint(
diff --git a/pyrogram/methods/messages/send_media_group.py b/pyrogram/methods/messages/send_media_group.py
index ccb6136163..382b2a92a0 100644
--- a/pyrogram/methods/messages/send_media_group.py
+++ b/pyrogram/methods/messages/send_media_group.py
@@ -94,7 +94,7 @@ async def send_media_group(
             if isinstance(i, types.InputMediaPhoto):
                 if isinstance(i.media, str):
                     if os.path.isfile(i.media):
-                        media = await self.send(
+                        media = await self.invoke(
                             raw.functions.messages.UploadMedia(
                                 peer=await self.resolve_peer(chat_id),
                                 media=raw.types.InputMediaUploadedPhoto(
@@ -111,7 +111,7 @@ async def send_media_group(
                             )
                         )
                     elif re.match("^https?://", i.media):
-                        media = await self.send(
+                        media = await self.invoke(
                             raw.functions.messages.UploadMedia(
                                 peer=await self.resolve_peer(chat_id),
                                 media=raw.types.InputMediaPhotoExternal(
@@ -130,7 +130,7 @@ async def send_media_group(
                     else:
                         media = utils.get_input_media_from_file_id(i.media, FileType.PHOTO)
                 else:
-                    media = await self.send(
+                    media = await self.invoke(
                         raw.functions.messages.UploadMedia(
                             peer=await self.resolve_peer(chat_id),
                             media=raw.types.InputMediaUploadedPhoto(
@@ -149,7 +149,7 @@ async def send_media_group(
             elif isinstance(i, types.InputMediaVideo):
                 if isinstance(i.media, str):
                     if os.path.isfile(i.media):
-                        media = await self.send(
+                        media = await self.invoke(
                             raw.functions.messages.UploadMedia(
                                 peer=await self.resolve_peer(chat_id),
                                 media=raw.types.InputMediaUploadedDocument(
@@ -177,7 +177,7 @@ async def send_media_group(
                             )
                         )
                     elif re.match("^https?://", i.media):
-                        media = await self.send(
+                        media = await self.invoke(
                             raw.functions.messages.UploadMedia(
                                 peer=await self.resolve_peer(chat_id),
                                 media=raw.types.InputMediaDocumentExternal(
@@ -196,7 +196,7 @@ async def send_media_group(
                     else:
                         media = utils.get_input_media_from_file_id(i.media, FileType.VIDEO)
                 else:
-                    media = await self.send(
+                    media = await self.invoke(
                         raw.functions.messages.UploadMedia(
                             peer=await self.resolve_peer(chat_id),
                             media=raw.types.InputMediaUploadedDocument(
@@ -226,7 +226,7 @@ async def send_media_group(
             elif isinstance(i, types.InputMediaAudio):
                 if isinstance(i.media, str):
                     if os.path.isfile(i.media):
-                        media = await self.send(
+                        media = await self.invoke(
                             raw.functions.messages.UploadMedia(
                                 peer=await self.resolve_peer(chat_id),
                                 media=raw.types.InputMediaUploadedDocument(
@@ -253,7 +253,7 @@ async def send_media_group(
                             )
                         )
                     elif re.match("^https?://", i.media):
-                        media = await self.send(
+                        media = await self.invoke(
                             raw.functions.messages.UploadMedia(
                                 peer=await self.resolve_peer(chat_id),
                                 media=raw.types.InputMediaDocumentExternal(
@@ -272,7 +272,7 @@ async def send_media_group(
                     else:
                         media = utils.get_input_media_from_file_id(i.media, FileType.AUDIO)
                 else:
-                    media = await self.send(
+                    media = await self.invoke(
                         raw.functions.messages.UploadMedia(
                             peer=await self.resolve_peer(chat_id),
                             media=raw.types.InputMediaUploadedDocument(
@@ -301,7 +301,7 @@ async def send_media_group(
             elif isinstance(i, types.InputMediaDocument):
                 if isinstance(i.media, str):
                     if os.path.isfile(i.media):
-                        media = await self.send(
+                        media = await self.invoke(
                             raw.functions.messages.UploadMedia(
                                 peer=await self.resolve_peer(chat_id),
                                 media=raw.types.InputMediaUploadedDocument(
@@ -323,7 +323,7 @@ async def send_media_group(
                             )
                         )
                     elif re.match("^https?://", i.media):
-                        media = await self.send(
+                        media = await self.invoke(
                             raw.functions.messages.UploadMedia(
                                 peer=await self.resolve_peer(chat_id),
                                 media=raw.types.InputMediaDocumentExternal(
@@ -342,7 +342,7 @@ async def send_media_group(
                     else:
                         media = utils.get_input_media_from_file_id(i.media, FileType.DOCUMENT)
                 else:
-                    media = await self.send(
+                    media = await self.invoke(
                         raw.functions.messages.UploadMedia(
                             peer=await self.resolve_peer(chat_id),
                             media=raw.types.InputMediaUploadedDocument(
@@ -376,7 +376,7 @@ async def send_media_group(
                 )
             )
 
-        r = await self.send(
+        r = await self.invoke(
             raw.functions.messages.SendMultiMedia(
                 peer=await self.resolve_peer(chat_id),
                 multi_media=multi_media,
diff --git a/pyrogram/methods/messages/send_message.py b/pyrogram/methods/messages/send_message.py
index 70544178f9..414f744764 100644
--- a/pyrogram/methods/messages/send_message.py
+++ b/pyrogram/methods/messages/send_message.py
@@ -123,7 +123,7 @@ async def send_message(
 
         message, entities = (await utils.parse_text_entities(self, text, parse_mode, entities)).values()
 
-        r = await self.send(
+        r = await self.invoke(
             raw.functions.messages.SendMessage(
                 peer=await self.resolve_peer(chat_id),
                 no_webpage=disable_web_page_preview or None,
diff --git a/pyrogram/methods/messages/send_photo.py b/pyrogram/methods/messages/send_photo.py
index 5cacfb39c3..e536d3a000 100644
--- a/pyrogram/methods/messages/send_photo.py
+++ b/pyrogram/methods/messages/send_photo.py
@@ -165,7 +165,7 @@ async def send_photo(
 
             while True:
                 try:
-                    r = await self.send(
+                    r = await self.invoke(
                         raw.functions.messages.SendMedia(
                             peer=await self.resolve_peer(chat_id),
                             media=media,
diff --git a/pyrogram/methods/messages/send_poll.py b/pyrogram/methods/messages/send_poll.py
index 10545256eb..0dd877b9e4 100644
--- a/pyrogram/methods/messages/send_poll.py
+++ b/pyrogram/methods/messages/send_poll.py
@@ -100,7 +100,7 @@ async def send_poll(
 
                 app.send_poll(chat_id, "Is this a poll question?", ["Yes", "No", "Maybe"])
         """
-        r = await self.send(
+        r = await self.invoke(
             raw.functions.messages.SendMedia(
                 peer=await self.resolve_peer(chat_id),
                 media=raw.types.InputMediaPoll(
diff --git a/pyrogram/methods/messages/send_reaction.py b/pyrogram/methods/messages/send_reaction.py
index b096934fc2..34c2d92c0a 100644
--- a/pyrogram/methods/messages/send_reaction.py
+++ b/pyrogram/methods/messages/send_reaction.py
@@ -54,7 +54,7 @@ async def send_reaction(
                 # Retract a reaction
                 app.send_reaction(chat_id, message_id)
         """
-        await self.send(
+        await self.invoke(
             raw.functions.messages.SendReaction(
                 peer=await self.resolve_peer(chat_id),
                 msg_id=message_id,
diff --git a/pyrogram/methods/messages/send_sticker.py b/pyrogram/methods/messages/send_sticker.py
index 460cfc6304..ca6f47a593 100644
--- a/pyrogram/methods/messages/send_sticker.py
+++ b/pyrogram/methods/messages/send_sticker.py
@@ -147,7 +147,7 @@ async def send_sticker(
 
             while True:
                 try:
-                    r = await self.send(
+                    r = await self.invoke(
                         raw.functions.messages.SendMedia(
                             peer=await self.resolve_peer(chat_id),
                             media=media,
diff --git a/pyrogram/methods/messages/send_venue.py b/pyrogram/methods/messages/send_venue.py
index 4dd81f7a3f..7ddb98efdc 100644
--- a/pyrogram/methods/messages/send_venue.py
+++ b/pyrogram/methods/messages/send_venue.py
@@ -99,7 +99,7 @@ async def send_venue(
                     "me", 51.500729, -0.124583,
                     "Elizabeth Tower", "Westminster, London SW1A 0AA, UK")
         """
-        r = await self.send(
+        r = await self.invoke(
             raw.functions.messages.SendMedia(
                 peer=await self.resolve_peer(chat_id),
                 media=raw.types.InputMediaVenue(
diff --git a/pyrogram/methods/messages/send_video.py b/pyrogram/methods/messages/send_video.py
index 9fd8521358..e1c245b1ad 100644
--- a/pyrogram/methods/messages/send_video.py
+++ b/pyrogram/methods/messages/send_video.py
@@ -222,7 +222,7 @@ def progress(current, total):
 
             while True:
                 try:
-                    r = await self.send(
+                    r = await self.invoke(
                         raw.functions.messages.SendMedia(
                             peer=await self.resolve_peer(chat_id),
                             media=media,
diff --git a/pyrogram/methods/messages/send_video_note.py b/pyrogram/methods/messages/send_video_note.py
index b4fd8891f5..7412e0eaf6 100644
--- a/pyrogram/methods/messages/send_video_note.py
+++ b/pyrogram/methods/messages/send_video_note.py
@@ -171,7 +171,7 @@ async def send_video_note(
 
             while True:
                 try:
-                    r = await self.send(
+                    r = await self.invoke(
                         raw.functions.messages.SendMedia(
                             peer=await self.resolve_peer(chat_id),
                             media=media,
diff --git a/pyrogram/methods/messages/send_voice.py b/pyrogram/methods/messages/send_voice.py
index 5179866e0d..08e1bdc0f1 100644
--- a/pyrogram/methods/messages/send_voice.py
+++ b/pyrogram/methods/messages/send_voice.py
@@ -172,7 +172,7 @@ async def send_voice(
 
             while True:
                 try:
-                    r = await self.send(
+                    r = await self.invoke(
                         raw.functions.messages.SendMedia(
                             peer=await self.resolve_peer(chat_id),
                             media=media,
diff --git a/pyrogram/methods/messages/stop_poll.py b/pyrogram/methods/messages/stop_poll.py
index 3fdba750d3..e642e1b666 100644
--- a/pyrogram/methods/messages/stop_poll.py
+++ b/pyrogram/methods/messages/stop_poll.py
@@ -56,7 +56,7 @@ async def stop_poll(
         """
         poll = (await self.get_messages(chat_id, message_id)).poll
 
-        r = await self.send(
+        r = await self.invoke(
             raw.functions.messages.EditMessage(
                 peer=await self.resolve_peer(chat_id),
                 id=message_id,
diff --git a/pyrogram/methods/messages/vote_poll.py b/pyrogram/methods/messages/vote_poll.py
index 70a5036574..3fea2e22b9 100644
--- a/pyrogram/methods/messages/vote_poll.py
+++ b/pyrogram/methods/messages/vote_poll.py
@@ -56,7 +56,7 @@ async def vote_poll(
         poll = (await self.get_messages(chat_id, message_id)).poll
         options = [options] if not isinstance(options, list) else options
 
-        r = await self.send(
+        r = await self.invoke(
             raw.functions.messages.SendVote(
                 peer=await self.resolve_peer(chat_id),
                 msg_id=message_id,
diff --git a/pyrogram/methods/password/change_cloud_password.py b/pyrogram/methods/password/change_cloud_password.py
index a0e8296382..3f7dee0017 100644
--- a/pyrogram/methods/password/change_cloud_password.py
+++ b/pyrogram/methods/password/change_cloud_password.py
@@ -57,7 +57,7 @@ async def change_cloud_password(
                 # Change password and hint
                 app.change_cloud_password("current_password", "new_password", new_hint="hint")
         """
-        r = await self.send(raw.functions.account.GetPassword())
+        r = await self.invoke(raw.functions.account.GetPassword())
 
         if not r.has_password:
             raise ValueError("There is no cloud password to change")
@@ -66,7 +66,7 @@ async def change_cloud_password(
         new_hash = btoi(compute_password_hash(r.new_algo, new_password))
         new_hash = itob(pow(r.new_algo.g, new_hash, btoi(r.new_algo.p)))
 
-        await self.send(
+        await self.invoke(
             raw.functions.account.UpdatePasswordSettings(
                 password=compute_password_check(r, current_password),
                 new_settings=raw.types.account.PasswordInputSettings(
diff --git a/pyrogram/methods/password/enable_cloud_password.py b/pyrogram/methods/password/enable_cloud_password.py
index 840acfdd49..fd8b3dbfd1 100644
--- a/pyrogram/methods/password/enable_cloud_password.py
+++ b/pyrogram/methods/password/enable_cloud_password.py
@@ -62,7 +62,7 @@ async def enable_cloud_password(
                 # Enable password with hint and email
                 app.enable_cloud_password("password", hint="hint", email="user@email.com")
         """
-        r = await self.send(raw.functions.account.GetPassword())
+        r = await self.invoke(raw.functions.account.GetPassword())
 
         if r.has_password:
             raise ValueError("There is already a cloud password enabled")
@@ -71,7 +71,7 @@ async def enable_cloud_password(
         new_hash = btoi(compute_password_hash(r.new_algo, password))
         new_hash = itob(pow(r.new_algo.g, new_hash, btoi(r.new_algo.p)))
 
-        await self.send(
+        await self.invoke(
             raw.functions.account.UpdatePasswordSettings(
                 password=raw.types.InputCheckPasswordEmpty(),
                 new_settings=raw.types.account.PasswordInputSettings(
diff --git a/pyrogram/methods/password/remove_cloud_password.py b/pyrogram/methods/password/remove_cloud_password.py
index 1a1c9a0b56..845547d67c 100644
--- a/pyrogram/methods/password/remove_cloud_password.py
+++ b/pyrogram/methods/password/remove_cloud_password.py
@@ -43,12 +43,12 @@ async def remove_cloud_password(
 
                 app.remove_cloud_password("password")
         """
-        r = await self.send(raw.functions.account.GetPassword())
+        r = await self.invoke(raw.functions.account.GetPassword())
 
         if not r.has_password:
             raise ValueError("There is no cloud password to remove")
 
-        await self.send(
+        await self.invoke(
             raw.functions.account.UpdatePasswordSettings(
                 password=compute_password_check(r, password),
                 new_settings=raw.types.account.PasswordInputSettings(
diff --git a/pyrogram/methods/users/block_user.py b/pyrogram/methods/users/block_user.py
index b1d96537fb..3298e60190 100644
--- a/pyrogram/methods/users/block_user.py
+++ b/pyrogram/methods/users/block_user.py
@@ -44,7 +44,7 @@ async def block_user(
                 app.block_user(user_id)
         """
         return bool(
-            await self.send(
+            await self.invoke(
                 raw.functions.contacts.Block(
                     id=await self.resolve_peer(user_id)
                 )
diff --git a/pyrogram/methods/users/delete_profile_photos.py b/pyrogram/methods/users/delete_profile_photos.py
index a1df82eb7a..107f11a6a3 100644
--- a/pyrogram/methods/users/delete_profile_photos.py
+++ b/pyrogram/methods/users/delete_profile_photos.py
@@ -54,7 +54,7 @@ async def delete_profile_photos(
         photo_ids = photo_ids if isinstance(photo_ids, list) else [photo_ids]
         input_photos = [utils.get_input_media_from_file_id(i, FileType.PHOTO).id for i in photo_ids]
 
-        return bool(await self.send(
+        return bool(await self.invoke(
             raw.functions.photos.DeletePhotos(
                 id=input_photos
             )
diff --git a/pyrogram/methods/users/get_common_chats.py b/pyrogram/methods/users/get_common_chats.py
index e083e3c981..6c7d5ce786 100644
--- a/pyrogram/methods/users/get_common_chats.py
+++ b/pyrogram/methods/users/get_common_chats.py
@@ -52,7 +52,7 @@ async def get_common_chats(
         peer = await self.resolve_peer(user_id)
 
         if isinstance(peer, raw.types.InputPeerUser):
-            r = await self.send(
+            r = await self.invoke(
                 raw.functions.messages.GetCommonChats(
                     user_id=peer,
                     max_id=0,
diff --git a/pyrogram/methods/users/get_me.py b/pyrogram/methods/users/get_me.py
index 17ffe3b837..2869f85c8c 100644
--- a/pyrogram/methods/users/get_me.py
+++ b/pyrogram/methods/users/get_me.py
@@ -36,7 +36,7 @@ async def get_me(
                 me = app.get_me()
                 print(me)
         """
-        r = await self.send(
+        r = await self.invoke(
             raw.functions.users.GetFullUser(
                 id=raw.types.InputUserSelf()
             )
diff --git a/pyrogram/methods/users/get_profile_photos.py b/pyrogram/methods/users/get_profile_photos.py
index 62341550dd..ec35aa9eb1 100644
--- a/pyrogram/methods/users/get_profile_photos.py
+++ b/pyrogram/methods/users/get_profile_photos.py
@@ -65,7 +65,7 @@ async def get_profile_photos(
         peer_id = await self.resolve_peer(chat_id)
 
         if isinstance(peer_id, raw.types.InputPeerChannel):
-            r = await self.send(
+            r = await self.invoke(
                 raw.functions.channels.GetFullChannel(
                     channel=peer_id
                 )
@@ -75,7 +75,7 @@ async def get_profile_photos(
 
             r = await utils.parse_messages(
                 self,
-                await self.send(
+                await self.invoke(
                     raw.functions.messages.Search(
                         peer=peer_id,
                         q="",
@@ -107,7 +107,7 @@ async def get_profile_photos(
 
             return types.List(photos[offset:limit])
         else:
-            r = await self.send(
+            r = await self.invoke(
                 raw.functions.photos.GetUserPhotos(
                     user_id=peer_id,
                     offset=offset,
diff --git a/pyrogram/methods/users/get_profile_photos_count.py b/pyrogram/methods/users/get_profile_photos_count.py
index c0065dd7e0..41e50f5e8a 100644
--- a/pyrogram/methods/users/get_profile_photos_count.py
+++ b/pyrogram/methods/users/get_profile_photos_count.py
@@ -48,7 +48,7 @@ async def get_profile_photos_count(
         peer_id = await self.resolve_peer(chat_id)
 
         if isinstance(peer_id, raw.types.InputPeerChannel):
-            r = await self.send(
+            r = await self.invoke(
                 raw.functions.messages.GetSearchCounters(
                     peer=peer_id,
                     filters=[raw.types.InputMessagesFilterChatPhotos()],
@@ -57,7 +57,7 @@ async def get_profile_photos_count(
 
             return r[0].count
         else:
-            r = await self.send(
+            r = await self.invoke(
                 raw.functions.photos.GetUserPhotos(
                     user_id=peer_id,
                     offset=0,
diff --git a/pyrogram/methods/users/get_users.py b/pyrogram/methods/users/get_users.py
index 0e9c961411..6f085c4f51 100644
--- a/pyrogram/methods/users/get_users.py
+++ b/pyrogram/methods/users/get_users.py
@@ -56,7 +56,7 @@ async def get_users(
         user_ids = list(user_ids) if is_iterable else [user_ids]
         user_ids = await asyncio.gather(*[self.resolve_peer(i) for i in user_ids])
 
-        r = await self.send(
+        r = await self.invoke(
             raw.functions.users.GetUsers(
                 id=user_ids
             )
diff --git a/pyrogram/methods/users/set_profile_photo.py b/pyrogram/methods/users/set_profile_photo.py
index a68db7cda2..a7d59092b3 100644
--- a/pyrogram/methods/users/set_profile_photo.py
+++ b/pyrogram/methods/users/set_profile_photo.py
@@ -64,7 +64,7 @@ async def set_profile_photo(
         """
 
         return bool(
-            await self.send(
+            await self.invoke(
                 raw.functions.photos.UploadProfilePhoto(
                     file=await self.save_file(photo),
                     video=await self.save_file(video)
diff --git a/pyrogram/methods/users/set_username.py b/pyrogram/methods/users/set_username.py
index eeffc25f6c..68e443f149 100644
--- a/pyrogram/methods/users/set_username.py
+++ b/pyrogram/methods/users/set_username.py
@@ -47,7 +47,7 @@ async def set_username(
         """
 
         return bool(
-            await self.send(
+            await self.invoke(
                 raw.functions.account.UpdateUsername(
                     username=username or ""
                 )
diff --git a/pyrogram/methods/users/unblock_user.py b/pyrogram/methods/users/unblock_user.py
index 433105acf1..7593065862 100644
--- a/pyrogram/methods/users/unblock_user.py
+++ b/pyrogram/methods/users/unblock_user.py
@@ -44,7 +44,7 @@ async def unblock_user(
                 app.unblock_user(user_id)
         """
         return bool(
-            await self.send(
+            await self.invoke(
                 raw.functions.contacts.Unblock(
                     id=await self.resolve_peer(user_id)
                 )
diff --git a/pyrogram/methods/users/update_profile.py b/pyrogram/methods/users/update_profile.py
index c77c8b4b5b..779aa6cf5c 100644
--- a/pyrogram/methods/users/update_profile.py
+++ b/pyrogram/methods/users/update_profile.py
@@ -60,7 +60,7 @@ async def update_profile(
         """
 
         return bool(
-            await self.send(
+            await self.invoke(
                 raw.functions.account.UpdateProfile(
                     first_name=first_name,
                     last_name=last_name,
diff --git a/pyrogram/methods/utilities/start.py b/pyrogram/methods/utilities/start.py
index 61ce87a24a..ab4aef823d 100644
--- a/pyrogram/methods/utilities/start.py
+++ b/pyrogram/methods/utilities/start.py
@@ -58,10 +58,10 @@ async def start(
                 await self.authorize()
 
             if not await self.storage.is_bot() and self.takeout:
-                self.takeout_id = (await self.send(raw.functions.account.InitTakeoutSession())).id
+                self.takeout_id = (await self.invoke(raw.functions.account.InitTakeoutSession())).id
                 log.warning(f"Takeout session {self.takeout_id} initiated")
 
-            await self.send(raw.functions.updates.GetState())
+            await self.invoke(raw.functions.updates.GetState())
         except (Exception, KeyboardInterrupt):
             await self.disconnect()
             raise
diff --git a/pyrogram/session/auth.py b/pyrogram/session/auth.py
index d4083b2179..7df4fede30 100644
--- a/pyrogram/session/auth.py
+++ b/pyrogram/session/auth.py
@@ -59,7 +59,7 @@ def unpack(b: BytesIO):
         b.seek(20)  # Skip auth_key_id (8), message_id (8) and message_length (4)
         return TLObject.read(b)
 
-    async def send(self, data: TLObject):
+    async def invoke(self, data: TLObject):
         data = self.pack(data)
         await self.connection.send(data)
         response = BytesIO(await self.connection.recv())
@@ -86,7 +86,7 @@ async def create(self):
                 # Step 1; Step 2
                 nonce = int.from_bytes(urandom(16), "little", signed=True)
                 log.debug(f"Send req_pq: {nonce}")
-                res_pq = await self.send(raw.functions.ReqPqMulti(nonce=nonce))
+                res_pq = await self.invoke(raw.functions.ReqPqMulti(nonce=nonce))
                 log.debug(f"Got ResPq: {res_pq.server_nonce}")
                 log.debug(f"Server public key fingerprints: {res_pq.server_public_key_fingerprints}")
 
@@ -130,7 +130,7 @@ async 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 = await self.send(
+                server_dh_params = await self.invoke(
                     raw.functions.ReqDHParams(
                         nonce=nonce,
                         server_nonce=server_nonce,
@@ -190,7 +190,7 @@ async 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 = await self.send(
+                set_client_dh_params_answer = await self.invoke(
                     raw.functions.SetClientDHParams(
                         nonce=nonce,
                         server_nonce=server_nonce,
diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py
index 05d1fd4a3f..3b0edca292 100644
--- a/pyrogram/session/session.py
+++ b/pyrogram/session/session.py
@@ -106,10 +106,10 @@ async def start(self):
 
                 self.network_task = self.loop.create_task(self.network_worker())
 
-                await self._send(raw.functions.Ping(ping_id=0), timeout=self.START_TIMEOUT)
+                await self.send(raw.functions.Ping(ping_id=0), timeout=self.START_TIMEOUT)
 
                 if not self.is_cdn:
-                    await self._send(
+                    await self.send(
                         raw.functions.InvokeWithLayer(
                             layer=layer,
                             query=raw.functions.InitConnection(
@@ -240,7 +240,7 @@ async def handle_packet(self, packet):
             log.debug(f"Send {len(self.pending_acks)} acks")
 
             try:
-                await self._send(raw.types.MsgsAck(msg_ids=list(self.pending_acks)), False)
+                await self.send(raw.types.MsgsAck(msg_ids=list(self.pending_acks)), False)
             except (OSError, TimeoutError):
                 pass
             else:
@@ -258,7 +258,7 @@ async def ping_worker(self):
                 break
 
             try:
-                await self._send(
+                await self.send(
                     raw.functions.PingDelayDisconnect(
                         ping_id=0, disconnect_delay=self.WAIT_TIMEOUT + 10
                     ), False
@@ -287,7 +287,7 @@ async def network_worker(self):
 
         log.info("NetworkTask stopped")
 
-    async def _send(self, data: TLObject, wait_response: bool = True, timeout: float = WAIT_TIMEOUT):
+    async def send(self, data: TLObject, wait_response: bool = True, timeout: float = WAIT_TIMEOUT):
         message = self.msg_factory(data)
         msg_id = message.msg_id
 
@@ -334,13 +334,13 @@ async def _send(self, data: TLObject, wait_response: bool = True, timeout: float
                 raise BadMsgNotification(result.error_code)
             elif isinstance(result, raw.types.BadServerSalt):
                 self.salt = result.new_server_salt
-                return await self._send(data, wait_response, timeout)
+                return await self.send(data, wait_response, timeout)
             else:
                 return result
 
-    async def send(
+    async def invoke(
         self,
-        data: TLObject,
+        query: TLObject,
         retries: int = MAX_RETRIES,
         timeout: float = WAIT_TIMEOUT,
         sleep_threshold: float = SLEEP_THRESHOLD
@@ -350,16 +350,14 @@ async def send(
         except asyncio.TimeoutError:
             pass
 
-        if isinstance(data, (raw.functions.InvokeWithoutUpdates, raw.functions.InvokeWithTakeout)):
-            query = data.query
-        else:
-            query = data
+        if isinstance(query, (raw.functions.InvokeWithoutUpdates, raw.functions.InvokeWithTakeout)):
+            query = query.query
 
-        query = ".".join(query.QUALNAME.split(".")[1:])
+        query_name = ".".join(query.QUALNAME.split(".")[1:])
 
         while True:
             try:
-                return await self._send(data, timeout=timeout)
+                return await self.send(query, timeout=timeout)
             except FloodWait as e:
                 amount = e.x
 
@@ -367,7 +365,7 @@ async def send(
                     raise
 
                 log.warning(f'[{self.client.session_name}] Waiting for {amount} seconds before continuing '
-                            f'(required by "{query}")')
+                            f'(required by "{query_name}")')
 
                 await asyncio.sleep(amount)
             except (OSError, TimeoutError, InternalServerError, ServiceUnavailable) as e:
@@ -375,8 +373,8 @@ async def send(
                     raise e from None
 
                 (log.warning if retries < 2 else log.info)(
-                    f'[{Session.MAX_RETRIES - retries + 1}] Retrying "{query}" due to {str(e) or repr(e)}')
+                    f'[{Session.MAX_RETRIES - retries + 1}] Retrying "{query_name}" due to {str(e) or repr(e)}')
 
                 await asyncio.sleep(0.5)
 
-                return await self.send(data, retries - 1, timeout)
+                return await self.invoke(query, retries - 1, timeout)
diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py
index e9f3cab6d2..f7965e6a21 100644
--- a/pyrogram/types/messages_and_media/message.py
+++ b/pyrogram/types/messages_and_media/message.py
@@ -462,7 +462,7 @@ async def _parse(
         if isinstance(message.from_id, raw.types.PeerUser) and isinstance(message.peer_id, raw.types.PeerUser):
             if from_id not in users or peer_id not in users:
                 try:
-                    r = await client.send(
+                    r = await client.invoke(
                         raw.functions.users.GetUsers(
                             id=[
                                 await client.resolve_peer(from_id),
diff --git a/pyrogram/types/messages_and_media/sticker.py b/pyrogram/types/messages_and_media/sticker.py
index fa6e0be3fb..201b579f17 100644
--- a/pyrogram/types/messages_and_media/sticker.py
+++ b/pyrogram/types/messages_and_media/sticker.py
@@ -112,7 +112,7 @@ def __init__(
     cache = {}
 
     @staticmethod
-    async def _get_sticker_set_name(send, input_sticker_set_id):
+    async def _get_sticker_set_name(invoke, input_sticker_set_id):
         try:
             set_id = input_sticker_set_id[0]
             set_access_hash = input_sticker_set_id[1]
@@ -122,7 +122,7 @@ async def _get_sticker_set_name(send, input_sticker_set_id):
             if name is not None:
                 return name
 
-            name = (await send(
+            name = (await invoke(
                 raw.functions.messages.GetStickerSet(
                     stickerset=raw.types.InputStickerSetID(
                         id=set_id,
@@ -154,7 +154,7 @@ async def _parse(
 
         if isinstance(sticker_set, raw.types.InputStickerSetID):
             input_sticker_set_id = (sticker_set.id, sticker_set.access_hash)
-            set_name = await Sticker._get_sticker_set_name(client.send, input_sticker_set_id)
+            set_name = await Sticker._get_sticker_set_name(client.invoke, input_sticker_set_id)
         else:
             set_name = None
 

From 41f16a17c9e59d159985bfad0e14c0fc3999be17 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 24 Apr 2022 11:56:07 +0200
Subject: [PATCH 0791/1185] Fix filters.command not working with multiple
 running bots Closes #864

---
 pyrogram/client.py                  |  3 +++
 pyrogram/filters.py                 | 12 +-----------
 pyrogram/methods/auth/initialize.py |  1 +
 3 files changed, 5 insertions(+), 11 deletions(-)

diff --git a/pyrogram/client.py b/pyrogram/client.py
index 920b723311..ac753f8849 100644
--- a/pyrogram/client.py
+++ b/pyrogram/client.py
@@ -286,6 +286,9 @@ def __init__(
 
         self.disconnect_handler = None
 
+        # Username used for mentioned bot commands, e.g.: /start@usernamebot
+        self.username = None
+
         self.loop = asyncio.get_event_loop()
 
     def __enter__(self):
diff --git a/pyrogram/filters.py b/pyrogram/filters.py
index 2eeef95c59..348e6e7a72 100644
--- a/pyrogram/filters.py
+++ b/pyrogram/filters.py
@@ -745,11 +745,6 @@ async def linked_channel_filter(_, __, m: Message):
 
 
 # region command_filter
-
-# Used by the command filter below
-username = None
-
-
 def command(commands: Union[str, List[str]], prefixes: Union[str, List[str]] = "/", case_sensitive: bool = False):
     """Filter commands, i.e.: text messages starting with "/" or any other custom prefix.
 
@@ -772,12 +767,7 @@ def command(commands: Union[str, List[str]], prefixes: Union[str, List[str]] = "
     command_re = re.compile(r"([\"'])(.*?)(?
Date: Sun, 24 Apr 2022 11:56:07 +0200
Subject: [PATCH 0792/1185] Fix invited_by being optional

---
 pyrogram/types/user_and_chats/chat_member.py | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/pyrogram/types/user_and_chats/chat_member.py b/pyrogram/types/user_and_chats/chat_member.py
index 9b585d7cb5..cff2ac430d 100644
--- a/pyrogram/types/user_and_chats/chat_member.py
+++ b/pyrogram/types/user_and_chats/chat_member.py
@@ -151,7 +151,10 @@ def _parse(
                 user=types.User._parse(client, users[member.user_id]),
                 joined_date=utils.timestamp_to_datetime(member.date),
                 promoted_by=types.User._parse(client, users[member.promoted_by]),
-                invited_by=types.User._parse(client, users[member.inviter_id]),
+                invited_by=(
+                    types.User._parse(client, users[member.inviter_id])
+                    if member.inviter_id else None
+                ),
                 custom_title=member.rank,
                 can_be_edited=member.can_edit,
                 privileges=types.ChatPrivileges._parse(member.admin_rights),

From bf8a334e328d07be14023512c3b32a9073f6980c Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 24 Apr 2022 11:56:07 +0200
Subject: [PATCH 0793/1185] Revamp get_chat_history related methods

---
 compiler/docs/compiler.py                     |  7 +--
 pyrogram/methods/messages/__init__.py         | 12 ++--
 .../{iter_history.py => get_chat_history.py}  | 61 ++++++++++++-------
 ...ory_count.py => get_chat_history_count.py} |  4 +-
 .../{read_history.py => read_chat_history.py} |  4 +-
 5 files changed, 53 insertions(+), 35 deletions(-)
 rename pyrogram/methods/messages/{iter_history.py => get_chat_history.py} (70%)
 rename pyrogram/methods/messages/{get_history_count.py => get_chat_history_count.py} (97%)
 rename pyrogram/methods/messages/{read_history.py => read_chat_history.py} (97%)

diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py
index 53ceb91cef..4c632bffad 100644
--- a/compiler/docs/compiler.py
+++ b/compiler/docs/compiler.py
@@ -171,10 +171,9 @@ def get_title_list(s: str) -> list:
             delete_messages
             get_messages
             get_media_group
-            get_history
-            get_history_count
-            read_history
-            iter_history
+            get_chat_history
+            get_chat_history_count
+            read_chat_history
             send_poll
             vote_poll
             stop_poll
diff --git a/pyrogram/methods/messages/__init__.py b/pyrogram/methods/messages/__init__.py
index 6b78eeb8ce..18cd65e311 100644
--- a/pyrogram/methods/messages/__init__.py
+++ b/pyrogram/methods/messages/__init__.py
@@ -29,13 +29,13 @@
 from .edit_message_reply_markup import EditMessageReplyMarkup
 from .edit_message_text import EditMessageText
 from .forward_messages import ForwardMessages
+from .get_chat_history import GetChatHistory
+from .get_chat_history_count import GetChatHistoryCount
 from .get_discussion_message import GetDiscussionMessage
 from .get_history import GetHistory
-from .get_history_count import GetHistoryCount
 from .get_media_group import GetMediaGroup
 from .get_messages import GetMessages
-from .iter_history import IterHistory
-from .read_history import ReadHistory
+from .read_chat_history import ReadChatHistory
 from .retract_vote import RetractVote
 from .search_global import SearchGlobal
 from .search_global_count import SearchGlobalCount
@@ -92,10 +92,10 @@ class Messages(
     StopPoll,
     RetractVote,
     DownloadMedia,
-    IterHistory,
+    GetChatHistory,
     SendCachedMedia,
-    GetHistoryCount,
-    ReadHistory,
+    GetChatHistoryCount,
+    ReadChatHistory,
     EditInlineText,
     EditInlineCaption,
     EditInlineMedia,
diff --git a/pyrogram/methods/messages/iter_history.py b/pyrogram/methods/messages/get_chat_history.py
similarity index 70%
rename from pyrogram/methods/messages/iter_history.py
rename to pyrogram/methods/messages/get_chat_history.py
index 4914cd1d41..bc2ed41fa7 100644
--- a/pyrogram/methods/messages/iter_history.py
+++ b/pyrogram/methods/messages/get_chat_history.py
@@ -20,24 +20,47 @@
 from typing import Union, Optional, AsyncGenerator
 
 import pyrogram
-from pyrogram import types
-
-
-class IterHistory:
-    async def iter_history(
+from pyrogram import types, raw, utils
+
+
+async def get_chunk(
+    *,
+    client: "pyrogram.Client",
+    chat_id: Union[int, str],
+    limit: int = 0,
+    offset: int = 0,
+    from_message_id: int = 0,
+    from_date: datetime = datetime.fromtimestamp(0)
+):
+    messages = await client.invoke(
+        raw.functions.messages.GetHistory(
+            peer=await client.resolve_peer(chat_id),
+            offset_id=from_message_id,
+            offset_date=utils.datetime_to_timestamp(from_date),
+            add_offset=offset,
+            limit=limit,
+            max_id=0,
+            min_id=0,
+            hash=0
+        ),
+        sleep_threshold=60
+    )
+
+    return await utils.parse_messages(client, messages, replies=0)
+
+
+class GetChatHistory:
+    async def get_chat_history(
         self: "pyrogram.Client",
         chat_id: Union[int, str],
         limit: int = 0,
         offset: int = 0,
         offset_id: int = 0,
-        offset_date: datetime = datetime.fromtimestamp(0),
-        reverse: bool = False
+        offset_date: datetime = datetime.fromtimestamp(0)
     ) -> Optional[AsyncGenerator["types.Message", None]]:
-        """Iterate through a chat history sequentially.
+        """Get messages from a chat history.
 
-        This convenience method does the same as repeatedly calling :meth:`~pyrogram.Client.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.
+        The messages are returned in reverse chronological order.
 
         Parameters:
             chat_id (``int`` | ``str``):
@@ -59,37 +82,33 @@ async def iter_history(
             offset_date (:py:obj:`~datetime.datetime`, *optional*):
                 Pass a date as offset to retrieve only older messages starting from that date.
 
-            reverse (``bool``, *optional*):
-                Pass True to retrieve the messages in reversed order (from older to most recent).
-
         Returns:
             ``Generator``: A generator yielding :obj:`~pyrogram.types.Message` objects.
 
         Example:
             .. code-block:: python
 
-                for message in app.iter_history("pyrogram"):
+                for message in app.get_chat_history(chat_id):
                     print(message.text)
         """
-        offset_id = offset_id or (1 if reverse else 0)
         current = 0
         total = limit or (1 << 31) - 1
         limit = min(100, total)
 
         while True:
-            messages = await self.get_history(
+            messages = await get_chunk(
+                client=self,
                 chat_id=chat_id,
                 limit=limit,
                 offset=offset,
-                offset_id=offset_id,
-                offset_date=offset_date,
-                reverse=reverse
+                from_message_id=offset_id,
+                from_date=offset_date
             )
 
             if not messages:
                 return
 
-            offset_id = messages[-1].id + (1 if reverse else 0)
+            offset_id = messages[-1].id
 
             for message in messages:
                 yield message
diff --git a/pyrogram/methods/messages/get_history_count.py b/pyrogram/methods/messages/get_chat_history_count.py
similarity index 97%
rename from pyrogram/methods/messages/get_history_count.py
rename to pyrogram/methods/messages/get_chat_history_count.py
index 12e068ae84..fbe6bfcad9 100644
--- a/pyrogram/methods/messages/get_history_count.py
+++ b/pyrogram/methods/messages/get_chat_history_count.py
@@ -25,8 +25,8 @@
 log = logging.getLogger(__name__)
 
 
-class GetHistoryCount:
-    async def get_history_count(
+class GetChatHistoryCount:
+    async def get_chat_history_count(
         self: "pyrogram.Client",
         chat_id: Union[int, str]
     ) -> int:
diff --git a/pyrogram/methods/messages/read_history.py b/pyrogram/methods/messages/read_chat_history.py
similarity index 97%
rename from pyrogram/methods/messages/read_history.py
rename to pyrogram/methods/messages/read_chat_history.py
index 204d9e3114..caa228fd28 100644
--- a/pyrogram/methods/messages/read_history.py
+++ b/pyrogram/methods/messages/read_chat_history.py
@@ -22,8 +22,8 @@
 from pyrogram import raw
 
 
-class ReadHistory:
-    async def read_history(
+class ReadChatHistory:
+    async def read_chat_history(
         self: "pyrogram.Client",
         chat_id: Union[int, str],
         max_id: int = 0

From 5f6788ad69da03f45e958794b042f60a71a15444 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 24 Apr 2022 11:56:07 +0200
Subject: [PATCH 0794/1185] Improvements to the documentation

---
 compiler/docs/compiler.py                          |  2 +-
 ...-send-raised-exception-oserror-timeouterror.rst | 14 ++++++++------
 docs/source/topics/advanced-usage.rst              |  2 +-
 3 files changed, 10 insertions(+), 8 deletions(-)

diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py
index 4c632bffad..78231f7d15 100644
--- a/compiler/docs/compiler.py
+++ b/compiler/docs/compiler.py
@@ -310,7 +310,7 @@ def get_title_list(s: str) -> list:
         """,
         advanced="""
         Advanced
-            send
+            invoke
             resolve_peer
             save_file
         """
diff --git a/docs/source/faq/socket-send-raised-exception-oserror-timeouterror.rst b/docs/source/faq/socket-send-raised-exception-oserror-timeouterror.rst
index e6a934d2e9..21ee1a900c 100644
--- a/docs/source/faq/socket-send-raised-exception-oserror-timeouterror.rst
+++ b/docs/source/faq/socket-send-raised-exception-oserror-timeouterror.rst
@@ -1,10 +1,12 @@
 socket.send() raised exception, OSError(), TimeoutError()
 =========================================================
 
-If you get this error chances are you are blocking the event loop for too long.
-In general, it means you are executing thread-blocking code that prevents the event loop from
-running properly. For example:
+If you get this error chances are you are blocking the event loop for too long, most likely due to an improper use of
+non-asynchronous or threaded operations which may lead to blocking code that prevents the event loop from running
+properly.
 
-- You are using ``time.sleep()`` instead of ``asyncio.sleep()``.
-- You are running processing loops that take too much time to complete.
-- You are reading/writing files to disk that take too much time to complete.
\ No newline at end of file
+You can consider the following:
+
+- Use Pyrogram asynchronously in its intended way.
+- Use shorter non-asynchronous processing loops.
+- Use ``asyncio.sleep()`` instead of ``time.sleep()``.
diff --git a/docs/source/topics/advanced-usage.rst b/docs/source/topics/advanced-usage.rst
index 0b0f068323..c84528e4e1 100644
--- a/docs/source/topics/advanced-usage.rst
+++ b/docs/source/topics/advanced-usage.rst
@@ -38,7 +38,7 @@ live in their respective packages (and sub-packages): ``pyrogram.raw.functions``
 as Python classes, meaning you need to create an instance of each every time you need them and fill them in with the
 correct values using named arguments.
 
-Next, to actually invoke the raw function you have to use the :meth:`~pyrogram.Client.send` method provided by the
+Next, to actually invoke the raw function you have to use the :meth:`~pyrogram.Client.invoke` method provided by the
 Client class and pass the function object you created.
 
 Here's some examples:

From d1bdaae81de0f73e8b0c37bed69955a74ba939bd Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 24 Apr 2022 11:56:07 +0200
Subject: [PATCH 0795/1185] Add methods related to discussion threads and
 comments

---
 compiler/docs/compiler.py                     |  2 +
 pyrogram/methods/messages/__init__.py         |  6 +-
 .../messages/get_discussion_message.py        |  5 +-
 .../messages/get_discussion_replies.py        | 84 +++++++++++++++++++
 .../messages/get_discussion_replies_count.py  | 60 +++++++++++++
 5 files changed, 154 insertions(+), 3 deletions(-)
 create mode 100644 pyrogram/methods/messages/get_discussion_replies.py
 create mode 100644 pyrogram/methods/messages/get_discussion_replies_count.py

diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py
index 78231f7d15..3357d91157 100644
--- a/compiler/docs/compiler.py
+++ b/compiler/docs/compiler.py
@@ -185,6 +185,8 @@ def get_title_list(s: str) -> list:
             search_global_count
             download_media
             get_discussion_message
+            get_discussion_replies
+            get_discussion_replies_count
         """,
         chats="""
         Chats
diff --git a/pyrogram/methods/messages/__init__.py b/pyrogram/methods/messages/__init__.py
index 18cd65e311..62509d9e0b 100644
--- a/pyrogram/methods/messages/__init__.py
+++ b/pyrogram/methods/messages/__init__.py
@@ -32,6 +32,8 @@
 from .get_chat_history import GetChatHistory
 from .get_chat_history_count import GetChatHistoryCount
 from .get_discussion_message import GetDiscussionMessage
+from .get_discussion_replies import GetDiscussionReplies
+from .get_discussion_replies_count import GetDiscussionRepliesCount
 from .get_history import GetHistory
 from .get_media_group import GetMediaGroup
 from .get_messages import GetMessages
@@ -108,6 +110,8 @@ class Messages(
     SearchMessagesCount,
     SearchGlobalCount,
     GetDiscussionMessage,
-    SendReaction
+    SendReaction,
+    GetDiscussionReplies,
+    GetDiscussionRepliesCount
 ):
     pass
diff --git a/pyrogram/methods/messages/get_discussion_message.py b/pyrogram/methods/messages/get_discussion_message.py
index 6c72b484de..cbcc7bfed2 100644
--- a/pyrogram/methods/messages/get_discussion_message.py
+++ b/pyrogram/methods/messages/get_discussion_message.py
@@ -29,9 +29,10 @@ async def get_discussion_message(
         chat_id: Union[int, str],
         message_id: int,
     ) -> "types.Message":
-        """Get the discussion message from the linked discussion group of a channel post.
+        """Get the first discussion message of a channel post or a discussion thread in a group.
 
-        Reply to the returned message to leave a comment on the linked channel post.
+        Reply to the returned message to leave a comment on the linked channel post or to continue
+        the discussion thread.
 
         Parameters:
             chat_id (``int`` | ``str``):
diff --git a/pyrogram/methods/messages/get_discussion_replies.py b/pyrogram/methods/messages/get_discussion_replies.py
new file mode 100644
index 0000000000..b1279de542
--- /dev/null
+++ b/pyrogram/methods/messages/get_discussion_replies.py
@@ -0,0 +1,84 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  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 Union, Optional, AsyncGenerator
+
+import pyrogram
+from pyrogram import types, raw
+
+
+class GetDiscussionReplies:
+    async def get_discussion_replies(
+        self: "pyrogram.Client",
+        chat_id: Union[int, str],
+        message_id: int,
+        limit: int = 0,
+    ) -> Optional[AsyncGenerator["types.Message", None]]:
+        """Get the message replies of a discussion thread.
+
+        Parameters:
+            chat_id (``int`` | ``str``):
+                Unique identifier (int) or username (str) of the target chat.
+
+            message_id (``int``):
+                Message id.
+
+            limit (``int``, *optional*):
+                Limits the number of messages to be retrieved.
+                By default, no limit is applied and all messages are returned.
+
+        Example:
+            .. code-block:: python
+
+            for m in app.get_discussion_replies(chat_id, message_id):
+                print(m)
+        """
+
+        current = 0
+        total = limit or (1 << 31) - 1
+        limit = min(100, total)
+
+        while True:
+            r = await self.invoke(
+                raw.functions.messages.GetReplies(
+                    peer=await self.resolve_peer(chat_id),
+                    msg_id=message_id,
+                    offset_id=0,
+                    offset_date=0,
+                    add_offset=current,
+                    limit=limit,
+                    max_id=0,
+                    min_id=0,
+                    hash=0
+                )
+            )
+
+            users = {u.id: u for u in r.users}
+            chats = {c.id: c for c in r.chats}
+            messages = r.messages
+
+            if not messages:
+                return
+
+            for message in messages:
+                yield await types.Message._parse(self, message, users, chats, replies=0)
+
+                current += 1
+
+                if current >= total:
+                    return
diff --git a/pyrogram/methods/messages/get_discussion_replies_count.py b/pyrogram/methods/messages/get_discussion_replies_count.py
new file mode 100644
index 0000000000..cb5c1e8ba3
--- /dev/null
+++ b/pyrogram/methods/messages/get_discussion_replies_count.py
@@ -0,0 +1,60 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  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 Union
+
+import pyrogram
+from pyrogram import raw
+
+
+class GetDiscussionRepliesCount:
+    async def get_discussion_replies_count(
+        self: "pyrogram.Client",
+        chat_id: Union[int, str],
+        message_id: int,
+    ) -> int:
+        """Get the total count of replies in a discussion thread.
+
+        Parameters:
+            chat_id (``int`` | ``str``):
+                Unique identifier (int) or username (str) of the target chat.
+
+            message_id (``int``):
+                Message id.
+
+        Example:
+            .. code-block:: python
+
+            count = app.get_discussion_replies_count(chat_id, message_id)
+        """
+
+        r = await self.invoke(
+            raw.functions.messages.GetReplies(
+                peer=await self.resolve_peer(chat_id),
+                msg_id=message_id,
+                offset_id=0,
+                offset_date=0,
+                add_offset=0,
+                limit=1,
+                max_id=0,
+                min_id=0,
+                hash=0
+            )
+        )
+
+        return r.count

From 04eef0909716c489866688af92b16672ab8acf6e Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 24 Apr 2022 11:56:07 +0200
Subject: [PATCH 0796/1185] Don't fetch reply-to messages in search_messages

---
 pyrogram/methods/messages/search_messages.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/methods/messages/search_messages.py b/pyrogram/methods/messages/search_messages.py
index b40826d9a2..da24f31708 100644
--- a/pyrogram/methods/messages/search_messages.py
+++ b/pyrogram/methods/messages/search_messages.py
@@ -54,7 +54,7 @@ async def get_chunk(
         sleep_threshold=60
     )
 
-    return await utils.parse_messages(client, r)
+    return await utils.parse_messages(client, r, replies=0)
 
 
 class SearchMessages:

From bbc109d73eb111803af74c31d4dc7124173a07e9 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 24 Apr 2022 11:56:07 +0200
Subject: [PATCH 0797/1185] Remove unused method

---
 pyrogram/methods/messages/__init__.py    |   2 -
 pyrogram/methods/messages/get_history.py | 107 -----------------------
 2 files changed, 109 deletions(-)
 delete mode 100644 pyrogram/methods/messages/get_history.py

diff --git a/pyrogram/methods/messages/__init__.py b/pyrogram/methods/messages/__init__.py
index 62509d9e0b..0bf34900d0 100644
--- a/pyrogram/methods/messages/__init__.py
+++ b/pyrogram/methods/messages/__init__.py
@@ -34,7 +34,6 @@
 from .get_discussion_message import GetDiscussionMessage
 from .get_discussion_replies import GetDiscussionReplies
 from .get_discussion_replies_count import GetDiscussionRepliesCount
-from .get_history import GetHistory
 from .get_media_group import GetMediaGroup
 from .get_messages import GetMessages
 from .read_chat_history import ReadChatHistory
@@ -72,7 +71,6 @@ class Messages(
     EditMessageMedia,
     EditMessageText,
     ForwardMessages,
-    GetHistory,
     GetMediaGroup,
     GetMessages,
     SendAudio,
diff --git a/pyrogram/methods/messages/get_history.py b/pyrogram/methods/messages/get_history.py
deleted file mode 100644
index ae8e1f37a8..0000000000
--- a/pyrogram/methods/messages/get_history.py
+++ /dev/null
@@ -1,107 +0,0 @@
-#  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-present Dan 
-#
-#  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
-from datetime import datetime
-from typing import Union, List
-
-import pyrogram
-from pyrogram import raw
-from pyrogram import types
-from pyrogram import utils
-
-log = logging.getLogger(__name__)
-
-
-class GetHistory:
-    async def get_history(
-        self: "pyrogram.Client",
-        chat_id: Union[int, str],
-        limit: int = 100,
-        offset: int = 0,
-        offset_id: int = 0,
-        offset_date: datetime = datetime.fromtimestamp(0),
-        reverse: bool = False
-    ) -> List["types.Message"]:
-        """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:`~pyrogram.Client.iter_history`.
-
-        Parameters:
-            chat_id (``int`` | ``str``):
-                Unique identifier (int) or username (str) of the target chat.
-                For your personal cloud (Saved Messages) you can simply use "me" or "self".
-                For a contact that exists in your Telegram address book you can use his phone number (str).
-
-            limit (``int``, *optional*):
-                Limits the number of messages to be retrieved.
-                By default, the first 100 messages are returned.
-
-            offset (``int``, *optional*):
-                Sequential number of the first message to be returned. Defaults to 0 (most recent message).
-                Negative values are also accepted and become useful in case you set offset_id or offset_date.
-
-            offset_id (``int``, *optional*):
-                Pass a message identifier as offset to retrieve only older messages starting from that message.
-
-            offset_date (:py:obj:`~datetime.datetime`, *optional*):
-                Pass a date as offset to retrieve only older messages starting from that date.
-
-            reverse (``bool``, *optional*):
-                Pass True to retrieve the messages in reversed order (from older to most recent).
-
-        Returns:
-            List of :obj:`~pyrogram.types.Message` - On success, a list of the retrieved messages is returned.
-
-        Example:
-            .. code-block:: python
-
-                # Get the last 100 messages of a chat
-                app.get_history(chat_id)
-
-                # Get the last 3 messages of a chat
-                app.get_history(chat_id, limit=3)
-
-                # Get 3 messages after skipping the first 5
-                app.get_history(chat_id, offset=5, limit=3)
-        """
-
-        offset_id = offset_id or (1 if reverse else 0)
-
-        messages = await utils.parse_messages(
-            self,
-            await self.invoke(
-                raw.functions.messages.GetHistory(
-                    peer=await self.resolve_peer(chat_id),
-                    offset_id=offset_id,
-                    offset_date=utils.datetime_to_timestamp(offset_date),
-                    add_offset=offset * (-1 if reverse else 1) - (limit if reverse else 0),
-                    limit=limit,
-                    max_id=0,
-                    min_id=0,
-                    hash=0
-                ),
-                sleep_threshold=60
-            )
-        )
-
-        if reverse:
-            messages.reverse()
-
-        return messages

From 84f0b3a97a63ad5d7284060ebdc4e7650d2192d3 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 24 Apr 2022 11:56:07 +0200
Subject: [PATCH 0798/1185] Rename some enums

---
 pyrogram/enums/chat_members_filter.py              | 4 ++--
 pyrogram/enums/messages_filter.py                  | 4 ++--
 pyrogram/methods/messages/search_global.py         | 2 +-
 pyrogram/methods/messages/search_global_count.py   | 2 +-
 pyrogram/methods/messages/search_messages.py       | 4 ++--
 pyrogram/methods/messages/search_messages_count.py | 2 +-
 pyrogram/types/user_and_chats/chat.py              | 2 +-
 7 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/pyrogram/enums/chat_members_filter.py b/pyrogram/enums/chat_members_filter.py
index 089f3d4bb9..bf0e7ef358 100644
--- a/pyrogram/enums/chat_members_filter.py
+++ b/pyrogram/enums/chat_members_filter.py
@@ -23,8 +23,8 @@
 class ChatMembersFilter(AutoName):
     """Chat members filter enumeration used in :meth:`~pyrogram.Client.get_chat_members`"""
 
-    ANY = raw.types.ChannelParticipantsSearch
-    "Any kind of member"
+    SEARCH = raw.types.ChannelParticipantsSearch
+    "Search for members"
 
     BANNED = raw.types.ChannelParticipantsKicked
     "Banned members"
diff --git a/pyrogram/enums/messages_filter.py b/pyrogram/enums/messages_filter.py
index 64d856aa98..b9c84fc939 100644
--- a/pyrogram/enums/messages_filter.py
+++ b/pyrogram/enums/messages_filter.py
@@ -23,8 +23,8 @@
 class MessagesFilter(AutoName):
     """Messages filter enumeration used in used in :meth:`~pyrogram.Client.search_messages` and used in :meth:`~pyrogram.Client.search_global`"""
 
-    ANY = raw.types.InputMessagesFilterEmpty
-    "Any kind of message"
+    EMPTY = raw.types.InputMessagesFilterEmpty
+    "Empty filter (any kind of messages)"
 
     PHOTO = raw.types.InputMessagesFilterPhotos
     "Photo messages"
diff --git a/pyrogram/methods/messages/search_global.py b/pyrogram/methods/messages/search_global.py
index a6ab555744..b53dbf3218 100644
--- a/pyrogram/methods/messages/search_global.py
+++ b/pyrogram/methods/messages/search_global.py
@@ -28,7 +28,7 @@ class SearchGlobal:
     async def search_global(
         self: "pyrogram.Client",
         query: str = "",
-        filter: "enums.MessagesFilter" = enums.MessagesFilter.ANY,
+        filter: "enums.MessagesFilter" = enums.MessagesFilter.EMPTY,
         limit: int = 0,
     ) -> Optional[AsyncGenerator["types.Message", None]]:
         """Search messages globally from all of your chats.
diff --git a/pyrogram/methods/messages/search_global_count.py b/pyrogram/methods/messages/search_global_count.py
index afdad4c188..2e559ded9f 100644
--- a/pyrogram/methods/messages/search_global_count.py
+++ b/pyrogram/methods/messages/search_global_count.py
@@ -24,7 +24,7 @@ class SearchGlobalCount:
     async def search_global_count(
         self: "pyrogram.Client",
         query: str = "",
-        filter: "enums.MessagesFilter" = enums.MessagesFilter.ANY,
+        filter: "enums.MessagesFilter" = enums.MessagesFilter.EMPTY,
     ) -> int:
         """Get the count of messages resulting from a global search.
 
diff --git a/pyrogram/methods/messages/search_messages.py b/pyrogram/methods/messages/search_messages.py
index da24f31708..0e31871c6c 100644
--- a/pyrogram/methods/messages/search_messages.py
+++ b/pyrogram/methods/messages/search_messages.py
@@ -27,7 +27,7 @@ async def get_chunk(
     client,
     chat_id: Union[int, str],
     query: str = "",
-    filter: "enums.MessagesFilter" = enums.MessagesFilter.ANY,
+    filter: "enums.MessagesFilter" = enums.MessagesFilter.EMPTY,
     offset: int = 0,
     limit: int = 100,
     from_user: Union[int, str] = None
@@ -64,7 +64,7 @@ async def search_messages(
         chat_id: Union[int, str],
         query: str = "",
         offset: int = 0,
-        filter: "enums.MessagesFilter" = enums.MessagesFilter.ANY,
+        filter: "enums.MessagesFilter" = enums.MessagesFilter.EMPTY,
         limit: int = 0,
         from_user: Union[int, str] = None
     ) -> Optional[AsyncGenerator["types.Message", None]]:
diff --git a/pyrogram/methods/messages/search_messages_count.py b/pyrogram/methods/messages/search_messages_count.py
index 301563e140..09f778559b 100644
--- a/pyrogram/methods/messages/search_messages_count.py
+++ b/pyrogram/methods/messages/search_messages_count.py
@@ -27,7 +27,7 @@ async def search_messages_count(
         self: "pyrogram.Client",
         chat_id: Union[int, str],
         query: str = "",
-        filter: "enums.MessagesFilter" = enums.MessagesFilter.ANY,
+        filter: "enums.MessagesFilter" = enums.MessagesFilter.EMPTY,
         from_user: Union[int, str] = None
     ) -> int:
         """Get the count of messages resulting from a search inside a chat.
diff --git a/pyrogram/types/user_and_chats/chat.py b/pyrogram/types/user_and_chats/chat.py
index 870391a49d..f48f3c0f01 100644
--- a/pyrogram/types/user_and_chats/chat.py
+++ b/pyrogram/types/user_and_chats/chat.py
@@ -843,7 +843,7 @@ async def get_members(
         offset: int = 0,
         limit: int = 200,
         query: str = "",
-        filter: "enums.ChatMembersFilter" = enums.ChatMembersFilter.ANY
+        filter: "enums.ChatMembersFilter" = enums.ChatMembersFilter.SEARCH
     ) -> List["types.ChatMember"]:
         """Bound method *get_members* of :obj:`~pyrogram.types.Chat`.
 

From 405528c74b2a9a27d20ffd7084df3ca75edc0b66 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 24 Apr 2022 11:56:07 +0200
Subject: [PATCH 0799/1185] Revamp get_chat_members related methods

---
 compiler/docs/compiler.py                  |   1 -
 pyrogram/methods/chats/get_chat_members.py | 156 ++++++++++++---------
 2 files changed, 86 insertions(+), 71 deletions(-)

diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py
index 3357d91157..8c75d711d3 100644
--- a/compiler/docs/compiler.py
+++ b/compiler/docs/compiler.py
@@ -209,7 +209,6 @@ def get_title_list(s: str) -> list:
             get_chat_member
             get_chat_members
             get_chat_members_count
-            iter_chat_members
             get_dialogs
             iter_dialogs
             get_dialogs_count
diff --git a/pyrogram/methods/chats/get_chat_members.py b/pyrogram/methods/chats/get_chat_members.py
index a192c3e3ea..c2516a7081 100644
--- a/pyrogram/methods/chats/get_chat_members.py
+++ b/pyrogram/methods/chats/get_chat_members.py
@@ -25,69 +25,90 @@
 log = logging.getLogger(__name__)
 
 
+async def get_chunk(
+    client: "pyrogram.Client",
+    chat_id: Union[int, str],
+    offset: int,
+    filter: "enums.ChatMembersFilter",
+    limit: int,
+    query: str,
+):
+    is_queryable = filter in [enums.ChatMembersFilter.SEARCH,
+                              enums.ChatMembersFilter.BANNED,
+                              enums.ChatMembersFilter.RESTRICTED]
+
+    filter = filter.value(q=query) if is_queryable else filter.value()
+
+    r = await client.invoke(
+        raw.functions.channels.GetParticipants(
+            channel=await client.resolve_peer(chat_id),
+            filter=filter,
+            offset=offset,
+            limit=limit,
+            hash=0
+        ),
+        sleep_threshold=60
+    )
+
+    members = r.participants
+    users = {u.id: u for u in r.users}
+    chats = {c.id: c for c in r.chats}
+
+    return [types.ChatMember._parse(client, member, users, chats) for member in members]
+
+
 class GetChatMembers:
     async def get_chat_members(
         self: "pyrogram.Client",
         chat_id: Union[int, str],
-        offset: int = 0,
-        limit: int = 200,
         query: str = "",
-        filter: "enums.ChatMembersFilter" = enums.ChatMembersFilter.ANY
+        limit: int = 0,
+        filter: "enums.ChatMembersFilter" = enums.ChatMembersFilter.SEARCH
     ) -> List["types.ChatMember"]:
-        """Get a chunk of the members list of a chat.
+        """Get 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.
-        You must be admin to retrieve the members list of a channel (also known as "subscribers").
-        For a more convenient way of getting chat members see :meth:`~pyrogram.Client.iter_chat_members`.
+        Requires administrator rights in channels.
 
         Parameters:
             chat_id (``int`` | ``str``):
                 Unique identifier (int) or username (str) of the target chat.
 
-            offset (``int``, *optional*):
-                Sequential number of the first member to be returned.
-                Only applicable to supergroups and channels. Defaults to 0.
-
-            limit (``int``, *optional*):
-                Limits the number of members to be retrieved.
-                Only applicable to supergroups and channels.
-                Defaults to 200.
-
             query (``str``, *optional*):
                 Query string to filter members based on their display names and usernames.
                 Only applicable to supergroups and channels. Defaults to "" (empty string).
-                A query string is applicable only for *"all"*, *"banned"* and *"restricted"* filters only
+                A query string is applicable only for :obj:`~pyrogram.enums.ChatMembersFilter.SEARCH`,
+                :obj:`~pyrogram.enums.ChatMembersFilter.BANNED` and :obj:`~pyrogram.enums.ChatMembersFilter.RESTRICTED`
+                filters only.
+
+            limit (``int``, *optional*):
+                Limits the number of members to be retrieved.
 
-            filter (``str``, *optional*):
+            filter (:obj:`~pyrogram.enums.ChatMembersFilter`, *optional*):
                 Filter used to select the kind of members you want to retrieve. Only applicable for supergroups
-                and channels. It can be any of the followings:
-                *"all"* - all kind of members,
-                *"banned"* - banned members only,
-                *"restricted"* - restricted members only,
-                *"bots"* - bots only,
-                *"recent"* - recent members only,
-                *"administrators"* - chat administrators only.
-                Only applicable to supergroups and channels.
-                Defaults to *"recent"*.
+                and channels.
 
         Returns:
-            List of :obj:`~pyrogram.types.ChatMember`: On success, a list of chat members is returned.
-
-        Raises:
-            ValueError: In case you used an invalid filter or a chat id that belongs to a user.
+            ``Generator``: On success, a generator yielding :obj:`~pyrogram.types.ChatMember` objects is returned.
 
         Example:
             .. code-block:: python
 
-                # Get first 200 recent members
-                app.get_chat_members(chat_id)
+                from pyrogram import enums
+
+                # Get members
+                for member in app.get_chat_members(chat_id):
+                    print(member)
 
-                # Get all administrators
-                app.get_chat_members(chat_id, filter="administrators")
+                # Get administrators
+                administrators = list(app.get_chat_members(
+                    chat_id,
+                    filter=enums.ChatMembersFilter.ADMINISTRATORS))
 
-                # Get all bots
-                app.get_chat_members(chat_id, filter="bots")
+                # Get bots
+                bots = list(app.get_chat_members(
+                    chat_id,
+                    filter=enums.ChatMembersFilter.BOTS))
         """
         peer = await self.resolve_peer(chat_id)
 
@@ -101,40 +122,35 @@ async def get_chat_members(
             members = getattr(r.full_chat.participants, "participants", [])
             users = {i.id: i for i in r.users}
 
-            return types.List(types.ChatMember._parse(self, member, users, {}) for member in members)
-        elif isinstance(peer, raw.types.InputPeerChannel):
-            filter = filter.lower()
-
-            if filter == enums.ChatMembersFilter.ANY:
-                filter = raw.types.ChannelParticipantsSearch(q=query)
-            elif filter == enums.ChatMembersFilter.BANNED:
-                filter = raw.types.ChannelParticipantsKicked(q=query)
-            elif filter == enums.ChatMembersFilter.RESTRICTED:
-                filter = raw.types.ChannelParticipantsBanned(q=query)
-            elif filter == enums.ChatMembersFilter.BOTS:
-                filter = raw.types.ChannelParticipantsBots()
-            elif filter == enums.ChatMembersFilter.RECENT:
-                filter = raw.types.ChannelParticipantsRecent()
-            elif filter == enums.ChatMembersFilter.ADMINISTRATORS:
-                filter = raw.types.ChannelParticipantsAdmins()
-            else:
-                raise ValueError(f'Invalid filter "{filter}"')
+            for member in members:
+                yield types.ChatMember._parse(self, member, users, {})
 
-            r = await self.invoke(
-                raw.functions.channels.GetParticipants(
-                    channel=peer,
-                    filter=filter,
-                    offset=offset,
-                    limit=limit,
-                    hash=0
-                ),
-                sleep_threshold=60
+            return
+
+        current = 0
+        offset = 0
+        total = abs(limit) or (1 << 31) - 1
+        limit = min(200, total)
+
+        while True:
+            members = await get_chunk(
+                client=self,
+                chat_id=chat_id,
+                offset=offset,
+                filter=filter,
+                limit=limit,
+                query=query
             )
 
-            members = r.participants
-            users = {i.id: i for i in r.users}
-            chats = {i.id: i for i in r.chats}
+            if not members:
+                return
+
+            offset += len(members)
+
+            for member in members:
+                yield member
+
+                current += 1
 
-            return types.List(types.ChatMember._parse(self, member, users, chats) for member in members)
-        else:
-            raise ValueError(f'The chat_id "{chat_id}" belongs to a user')
+                if current >= total:
+                    return

From 68f151bad5b8e4b1ebb14a1c8ee39b27306d0722 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 24 Apr 2022 11:56:07 +0200
Subject: [PATCH 0800/1185] Merge changes

---
 pyrogram/methods/bots/delete_bot_commands.py |  7 +++----
 pyrogram/methods/bots/get_bot_commands.py    | 11 +++++++----
 pyrogram/methods/bots/set_bot_commands.py    |  2 +-
 3 files changed, 11 insertions(+), 9 deletions(-)

diff --git a/pyrogram/methods/bots/delete_bot_commands.py b/pyrogram/methods/bots/delete_bot_commands.py
index edb82debed..98e6d3960f 100644
--- a/pyrogram/methods/bots/delete_bot_commands.py
+++ b/pyrogram/methods/bots/delete_bot_commands.py
@@ -18,15 +18,14 @@
 
 import pyrogram
 from pyrogram import raw, types
-from pyrogram.scaffold import Scaffold
 
 
-class DeleteBotCommands(Scaffold):
+class DeleteBotCommands:
     async def delete_bot_commands(
         self: "pyrogram.Client",
         scope: "types.BotCommandScope" = types.BotCommandScopeDefault(),
         language_code: str = "",
-    ):
+    ) -> bool:
         """Delete the list of the bot's commands for the given scope and user language.
         After deletion, higher level commands will be shown to affected users.
 
@@ -53,7 +52,7 @@ async def delete_bot_commands(
                 app.delete_bot_commands()
         """
 
-        return await self.send(
+        return await self.invoke(
             raw.functions.bots.ResetBotCommands(
                 scope=await scope.write(self),
                 lang_code=language_code,
diff --git a/pyrogram/methods/bots/get_bot_commands.py b/pyrogram/methods/bots/get_bot_commands.py
index c92bd21ea0..fe5be0f3bd 100644
--- a/pyrogram/methods/bots/get_bot_commands.py
+++ b/pyrogram/methods/bots/get_bot_commands.py
@@ -16,17 +16,18 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
+from typing import List
+
 import pyrogram
 from pyrogram import raw, types
-from pyrogram.scaffold import Scaffold
 
 
-class GetBotCommands(Scaffold):
+class GetBotCommands:
     async def get_bot_commands(
         self: "pyrogram.Client",
         scope: "types.BotCommandScope" = types.BotCommandScopeDefault(),
         language_code: str = "",
-    ):
+    ) -> List["types.BotCommand"]:
         """Get the current list of the bot's commands for the given scope and user language.
         Returns Array of BotCommand on success. If commands aren't set, an empty list is returned.
 
@@ -54,9 +55,11 @@ async def get_bot_commands(
                 print(commands)
         """
 
-        return await self.send(
+        r = await self.invoke(
             raw.functions.bots.GetBotCommands(
                 scope=await scope.write(self),
                 lang_code=language_code,
             )
         )
+
+        return types.List(types.BotCommand.read(c) for c in r)
diff --git a/pyrogram/methods/bots/set_bot_commands.py b/pyrogram/methods/bots/set_bot_commands.py
index 6df1a2e443..b59c0fac78 100644
--- a/pyrogram/methods/bots/set_bot_commands.py
+++ b/pyrogram/methods/bots/set_bot_commands.py
@@ -29,7 +29,7 @@ async def set_bot_commands(
         commands: List["types.BotCommand"],
         scope: "types.BotCommandScope" = types.BotCommandScopeDefault(),
         language_code: str = "",
-    ):
+    ) -> bool:
         """Set the list of the bot's commands.
         The commands passed will overwrite any command set previously.
         This method can be used by the own bot only.

From 0e3c2e4412aba73f3fc06860344dc0e4dd95b7f3 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 24 Apr 2022 11:56:07 +0200
Subject: [PATCH 0801/1185] Rename RPCError.x to RPCError.value

---
 pyrogram/errors/rpc_error.py                  | 22 +++++++++----------
 pyrogram/methods/auth/send_code.py            |  2 +-
 pyrogram/methods/auth/sign_in_bot.py          |  2 +-
 .../methods/bots/get_inline_bot_results.py    |  2 +-
 pyrogram/methods/messages/send_animation.py   |  2 +-
 pyrogram/methods/messages/send_audio.py       |  2 +-
 pyrogram/methods/messages/send_document.py    |  2 +-
 pyrogram/methods/messages/send_photo.py       |  2 +-
 pyrogram/methods/messages/send_sticker.py     |  2 +-
 pyrogram/methods/messages/send_video.py       |  2 +-
 pyrogram/methods/messages/send_video_note.py  |  2 +-
 pyrogram/methods/messages/send_voice.py       |  2 +-
 pyrogram/session/session.py                   |  2 +-
 13 files changed, 23 insertions(+), 23 deletions(-)

diff --git a/pyrogram/errors/rpc_error.py b/pyrogram/errors/rpc_error.py
index 444447f2bb..63375f199e 100644
--- a/pyrogram/errors/rpc_error.py
+++ b/pyrogram/errors/rpc_error.py
@@ -30,11 +30,11 @@ class RPCError(Exception):
     ID = None
     CODE = None
     NAME = None
-    MESSAGE = "{x}"
+    MESSAGE = "{value}"
 
     def __init__(
         self,
-        x: Union[int, str, raw.types.RpcError] = None,
+        value: Union[int, str, raw.types.RpcError] = None,
         rpc_name: str = None,
         is_unknown: bool = False,
         is_signed: bool = False
@@ -43,18 +43,18 @@ def __init__(
             "-" if is_signed else "",
             self.CODE,
             self.ID or self.NAME,
-            self.MESSAGE.format(x=x),
+            self.MESSAGE.format(value=value),
             f'(caused by "{rpc_name}")' if rpc_name else ""
         ))
 
         try:
-            self.x = int(x)
+            self.value = int(value)
         except (ValueError, TypeError):
-            self.x = x
+            self.value = value
 
         if is_unknown:
             with open("unknown_errors.txt", "a", encoding="utf-8") as f:
-                f.write(f"{datetime.now()}\t{x}\t{rpc_name}\n")
+                f.write(f"{datetime.now()}\t{value}\t{rpc_name}\n")
 
     @staticmethod
     def raise_it(rpc_error: "raw.types.RpcError", rpc_type: Type[TLObject]):
@@ -68,7 +68,7 @@ def raise_it(rpc_error: "raw.types.RpcError", rpc_type: Type[TLObject]):
 
         if error_code not in exceptions:
             raise UnknownError(
-                x=f"[{error_code} {error_message}]",
+                value=f"[{error_code} {error_message}]",
                 rpc_name=rpc_name,
                 is_unknown=True,
                 is_signed=is_signed
@@ -80,18 +80,18 @@ def raise_it(rpc_error: "raw.types.RpcError", rpc_type: Type[TLObject]):
             raise getattr(
                 import_module("pyrogram.errors"),
                 exceptions[error_code]["_"]
-            )(x=f"[{error_code} {error_message}]",
+            )(value=f"[{error_code} {error_message}]",
               rpc_name=rpc_name,
               is_unknown=True,
               is_signed=is_signed)
 
-        x = re.search(r"_(\d+)", error_message)
-        x = x.group(1) if x is not None else x
+        value = re.search(r"_(\d+)", error_message)
+        value = value.group(1) if value is not None else value
 
         raise getattr(
             import_module("pyrogram.errors"),
             exceptions[error_code][error_id]
-        )(x=x,
+        )(value=value,
           rpc_name=rpc_name,
           is_unknown=False,
           is_signed=is_signed)
diff --git a/pyrogram/methods/auth/send_code.py b/pyrogram/methods/auth/send_code.py
index 5a4f87ea31..5f7f70915d 100644
--- a/pyrogram/methods/auth/send_code.py
+++ b/pyrogram/methods/auth/send_code.py
@@ -60,7 +60,7 @@ async def send_code(
             except (PhoneMigrate, NetworkMigrate) as e:
                 await self.session.stop()
 
-                await self.storage.dc_id(e.x)
+                await self.storage.dc_id(e.value)
                 await self.storage.auth_key(
                     await Auth(
                         self, await self.storage.dc_id(),
diff --git a/pyrogram/methods/auth/sign_in_bot.py b/pyrogram/methods/auth/sign_in_bot.py
index 5f2e68b510..dd322a8e66 100644
--- a/pyrogram/methods/auth/sign_in_bot.py
+++ b/pyrogram/methods/auth/sign_in_bot.py
@@ -57,7 +57,7 @@ async def sign_in_bot(
             except UserMigrate as e:
                 await self.session.stop()
 
-                await self.storage.dc_id(e.x)
+                await self.storage.dc_id(e.value)
                 await self.storage.auth_key(
                     await Auth(
                         self, await self.storage.dc_id(),
diff --git a/pyrogram/methods/bots/get_inline_bot_results.py b/pyrogram/methods/bots/get_inline_bot_results.py
index a71fbad8ec..f145cc8c44 100644
--- a/pyrogram/methods/bots/get_inline_bot_results.py
+++ b/pyrogram/methods/bots/get_inline_bot_results.py
@@ -84,7 +84,7 @@ async def get_inline_bot_results(
             )
         except UnknownError as e:
             # TODO: Add this -503 Timeout error into the Error DB
-            if e.x.error_code == -503 and e.x.error_message == "Timeout":
+            if e.value.error_code == -503 and e.value.error_message == "Timeout":
                 raise TimeoutError("The inline bot didn't answer in time") from None
             else:
                 raise e
diff --git a/pyrogram/methods/messages/send_animation.py b/pyrogram/methods/messages/send_animation.py
index 9403b1e49c..e45a071462 100644
--- a/pyrogram/methods/messages/send_animation.py
+++ b/pyrogram/methods/messages/send_animation.py
@@ -230,7 +230,7 @@ def progress(current, total):
                         )
                     )
                 except FilePartMissing as e:
-                    await self.save_file(animation, file_id=file.id, file_part=e.x)
+                    await self.save_file(animation, file_id=file.id, file_part=e.value)
                 else:
                     for i in r.updates:
                         if isinstance(i, (raw.types.UpdateNewMessage,
diff --git a/pyrogram/methods/messages/send_audio.py b/pyrogram/methods/messages/send_audio.py
index e0a98f85d5..8c2624a8d8 100644
--- a/pyrogram/methods/messages/send_audio.py
+++ b/pyrogram/methods/messages/send_audio.py
@@ -224,7 +224,7 @@ def progress(current, total):
                         )
                     )
                 except FilePartMissing as e:
-                    await self.save_file(audio, file_id=file.id, file_part=e.x)
+                    await self.save_file(audio, file_id=file.id, file_part=e.value)
                 else:
                     for i in r.updates:
                         if isinstance(i, (raw.types.UpdateNewMessage,
diff --git a/pyrogram/methods/messages/send_document.py b/pyrogram/methods/messages/send_document.py
index 5b480cb3ea..c1179cb0e2 100644
--- a/pyrogram/methods/messages/send_document.py
+++ b/pyrogram/methods/messages/send_document.py
@@ -202,7 +202,7 @@ def progress(current, total):
                         )
                     )
                 except FilePartMissing as e:
-                    await self.save_file(document, file_id=file.id, file_part=e.x)
+                    await self.save_file(document, file_id=file.id, file_part=e.value)
                 else:
                     for i in r.updates:
                         if isinstance(i, (raw.types.UpdateNewMessage,
diff --git a/pyrogram/methods/messages/send_photo.py b/pyrogram/methods/messages/send_photo.py
index e536d3a000..5391b2f660 100644
--- a/pyrogram/methods/messages/send_photo.py
+++ b/pyrogram/methods/messages/send_photo.py
@@ -179,7 +179,7 @@ async def send_photo(
                         )
                     )
                 except FilePartMissing as e:
-                    await self.save_file(photo, file_id=file.id, file_part=e.x)
+                    await self.save_file(photo, file_id=file.id, file_part=e.value)
                 else:
                     for i in r.updates:
                         if isinstance(i, (raw.types.UpdateNewMessage,
diff --git a/pyrogram/methods/messages/send_sticker.py b/pyrogram/methods/messages/send_sticker.py
index ca6f47a593..d0b66fb10b 100644
--- a/pyrogram/methods/messages/send_sticker.py
+++ b/pyrogram/methods/messages/send_sticker.py
@@ -161,7 +161,7 @@ async def send_sticker(
                         )
                     )
                 except FilePartMissing as e:
-                    await self.save_file(sticker, file_id=file.id, file_part=e.x)
+                    await self.save_file(sticker, file_id=file.id, file_part=e.value)
                 else:
                     for i in r.updates:
                         if isinstance(i, (raw.types.UpdateNewMessage,
diff --git a/pyrogram/methods/messages/send_video.py b/pyrogram/methods/messages/send_video.py
index e1c245b1ad..49674076bc 100644
--- a/pyrogram/methods/messages/send_video.py
+++ b/pyrogram/methods/messages/send_video.py
@@ -236,7 +236,7 @@ def progress(current, total):
                         )
                     )
                 except FilePartMissing as e:
-                    await self.save_file(video, file_id=file.id, file_part=e.x)
+                    await self.save_file(video, file_id=file.id, file_part=e.value)
                 else:
                     for i in r.updates:
                         if isinstance(i, (raw.types.UpdateNewMessage,
diff --git a/pyrogram/methods/messages/send_video_note.py b/pyrogram/methods/messages/send_video_note.py
index 7412e0eaf6..ce6240d9d5 100644
--- a/pyrogram/methods/messages/send_video_note.py
+++ b/pyrogram/methods/messages/send_video_note.py
@@ -185,7 +185,7 @@ async def send_video_note(
                         )
                     )
                 except FilePartMissing as e:
-                    await 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.value)
                 else:
                     for i in r.updates:
                         if isinstance(i, (raw.types.UpdateNewMessage,
diff --git a/pyrogram/methods/messages/send_voice.py b/pyrogram/methods/messages/send_voice.py
index 08e1bdc0f1..9bc6456519 100644
--- a/pyrogram/methods/messages/send_voice.py
+++ b/pyrogram/methods/messages/send_voice.py
@@ -186,7 +186,7 @@ async def send_voice(
                         )
                     )
                 except FilePartMissing as e:
-                    await self.save_file(voice, file_id=file.id, file_part=e.x)
+                    await self.save_file(voice, file_id=file.id, file_part=e.value)
                 else:
                     for i in r.updates:
                         if isinstance(i, (raw.types.UpdateNewMessage,
diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py
index 3b0edca292..b50bb6d79e 100644
--- a/pyrogram/session/session.py
+++ b/pyrogram/session/session.py
@@ -359,7 +359,7 @@ async def invoke(
             try:
                 return await self.send(query, timeout=timeout)
             except FloodWait as e:
-                amount = e.x
+                amount = e.value
 
                 if amount > sleep_threshold >= 0:
                     raise

From ecc90caba2749e6f558de3c1badb0209f291f91f Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 24 Apr 2022 11:56:07 +0200
Subject: [PATCH 0802/1185] Handle edited messages using a separate handler

---
 docs/source/api/decorators.rst                |   2 +
 docs/source/api/handlers.rst                  |   2 +
 pyrogram/dispatcher.py                        | 110 ++++++++++--------
 pyrogram/filters.py                           |  10 --
 pyrogram/handlers/__init__.py                 |   1 +
 pyrogram/handlers/edited_message_handler.py   |  49 ++++++++
 pyrogram/handlers/message_handler.py          |   4 +-
 pyrogram/methods/decorators/__init__.py       |   2 +
 .../methods/decorators/on_edited_message.py   |  61 ++++++++++
 pyrogram/methods/decorators/on_message.py     |   2 +-
 10 files changed, 184 insertions(+), 59 deletions(-)
 create mode 100644 pyrogram/handlers/edited_message_handler.py
 create mode 100644 pyrogram/methods/decorators/on_edited_message.py

diff --git a/docs/source/api/decorators.rst b/docs/source/api/decorators.rst
index 130f15ceb6..2e7a04f263 100644
--- a/docs/source/api/decorators.rst
+++ b/docs/source/api/decorators.rst
@@ -36,6 +36,7 @@ Index
     :columns: 3
 
     - :meth:`~Client.on_message`
+    - :meth:`~Client.on_edited_message`
     - :meth:`~Client.on_callback_query`
     - :meth:`~Client.on_inline_query`
     - :meth:`~Client.on_chosen_inline_result`
@@ -54,6 +55,7 @@ Details
 
 .. Decorators
 .. autodecorator:: pyrogram.Client.on_message()
+.. autodecorator:: pyrogram.Client.on_edited_message()
 .. autodecorator:: pyrogram.Client.on_callback_query()
 .. autodecorator:: pyrogram.Client.on_inline_query()
 .. autodecorator:: pyrogram.Client.on_chosen_inline_result()
diff --git a/docs/source/api/handlers.rst b/docs/source/api/handlers.rst
index 9694142932..8a8ac71477 100644
--- a/docs/source/api/handlers.rst
+++ b/docs/source/api/handlers.rst
@@ -36,6 +36,7 @@ Index
     :columns: 3
 
     - :class:`MessageHandler`
+    - :class:`EditedMessageHandler`
     - :class:`DeletedMessagesHandler`
     - :class:`CallbackQueryHandler`
     - :class:`InlineQueryHandler`
@@ -53,6 +54,7 @@ Details
 
 .. Handlers
 .. autoclass:: MessageHandler()
+.. autoclass:: EditedMessageHandler()
 .. autoclass:: DeletedMessagesHandler()
 .. autoclass:: CallbackQueryHandler()
 .. autoclass:: InlineQueryHandler()
diff --git a/pyrogram/dispatcher.py b/pyrogram/dispatcher.py
index 0c425f4de3..b0ad87f71a 100644
--- a/pyrogram/dispatcher.py
+++ b/pyrogram/dispatcher.py
@@ -24,7 +24,7 @@
 import pyrogram
 from pyrogram import utils
 from pyrogram.handlers import (
-    CallbackQueryHandler, MessageHandler, DeletedMessagesHandler,
+    CallbackQueryHandler, MessageHandler, EditedMessageHandler, DeletedMessagesHandler,
     UserStatusHandler, RawUpdateHandler, InlineQueryHandler, PollHandler,
     ChosenInlineResultHandler, ChatMemberUpdatedHandler, ChatJoinRequestHandler
 )
@@ -42,33 +42,16 @@
 
 
 class Dispatcher:
-    NEW_MESSAGE_UPDATES = (
-        UpdateNewMessage,
-        UpdateNewChannelMessage,
-        UpdateNewScheduledMessage
-    )
-
-    EDIT_MESSAGE_UPDATES = (
-        UpdateEditMessage,
-        UpdateEditChannelMessage,
-    )
-
-    DELETE_MESSAGES_UPDATES = (
-        UpdateDeleteMessages,
-        UpdateDeleteChannelMessages
-    )
-
-    CALLBACK_QUERY_UPDATES = (
-        UpdateBotCallbackQuery,
-        UpdateInlineBotCallbackQuery
-    )
-
-    CHAT_MEMBER_UPDATES = (
-        UpdateChatParticipant,
-        UpdateChannelParticipant
-    )
-
-    MESSAGE_UPDATES = NEW_MESSAGE_UPDATES + EDIT_MESSAGE_UPDATES
+    NEW_MESSAGE_UPDATES = (UpdateNewMessage, UpdateNewChannelMessage, UpdateNewScheduledMessage)
+    EDIT_MESSAGE_UPDATES = (UpdateEditMessage, UpdateEditChannelMessage)
+    DELETE_MESSAGES_UPDATES = (UpdateDeleteMessages, UpdateDeleteChannelMessages)
+    CALLBACK_QUERY_UPDATES = (UpdateBotCallbackQuery, UpdateInlineBotCallbackQuery)
+    CHAT_MEMBER_UPDATES = (UpdateChatParticipant, UpdateChannelParticipant)
+    USER_STATUS_UPDATES = (UpdateUserStatus,)
+    BOT_INLINE_QUERY_UPDATES = (UpdateBotInlineQuery,)
+    POLL_UPDATES = (UpdateMessagePoll,)
+    CHOSEN_INLINE_RESULT_UPDATES = (UpdateBotInlineSend,)
+    CHAT_JOIN_REQUEST_UPDATES = (UpdateBotChatInviteRequester,)
 
     def __init__(self, client: "pyrogram.Client"):
         self.client = client
@@ -81,45 +64,80 @@ def __init__(self, client: "pyrogram.Client"):
         self.groups = OrderedDict()
 
         async def message_parser(update, users, chats):
-            return await pyrogram.types.Message._parse(
-                self.client, update.message, users, chats,
-                isinstance(update, UpdateNewScheduledMessage)
-            ), MessageHandler
+            return (
+                await pyrogram.types.Message._parse(self.client, update.message, users, chats,
+                                                    isinstance(update, UpdateNewScheduledMessage)),
+                MessageHandler
+            )
+
+        async def edited_message_parser(update, users, chats):
+            # Edited messages are parsed the same way as new messages, but the handler is different
+            parsed, _ = await message_parser(update, users, chats)
+
+            return (
+                parsed,
+                EditedMessageHandler
+            )
 
         async def deleted_messages_parser(update, users, chats):
-            return utils.parse_deleted_messages(self.client, update), DeletedMessagesHandler
+            return (
+                utils.parse_deleted_messages(self.client, update),
+                DeletedMessagesHandler
+            )
 
         async def callback_query_parser(update, users, chats):
-            return await pyrogram.types.CallbackQuery._parse(self.client, update, users), CallbackQueryHandler
+            return (
+                await pyrogram.types.CallbackQuery._parse(self.client, update, users),
+                CallbackQueryHandler
+            )
 
         async def user_status_parser(update, users, chats):
-            return pyrogram.types.User._parse_user_status(self.client, update), UserStatusHandler
+            return (
+                pyrogram.types.User._parse_user_status(self.client, update),
+                UserStatusHandler
+            )
 
         async def inline_query_parser(update, users, chats):
-            return pyrogram.types.InlineQuery._parse(self.client, update, users), InlineQueryHandler
+            return (
+                pyrogram.types.InlineQuery._parse(self.client, update, users),
+                InlineQueryHandler
+            )
 
         async def poll_parser(update, users, chats):
-            return pyrogram.types.Poll._parse_update(self.client, update), PollHandler
+            return (
+                pyrogram.types.Poll._parse_update(self.client, update),
+                PollHandler
+            )
 
         async def chosen_inline_result_parser(update, users, chats):
-            return pyrogram.types.ChosenInlineResult._parse(self.client, update, users), ChosenInlineResultHandler
+            return (
+                pyrogram.types.ChosenInlineResult._parse(self.client, update, users),
+                ChosenInlineResultHandler
+            )
 
         async def chat_member_updated_parser(update, users, chats):
-            return pyrogram.types.ChatMemberUpdated._parse(self.client, update, users, chats), ChatMemberUpdatedHandler
+            return (
+                pyrogram.types.ChatMemberUpdated._parse(self.client, update, users, chats),
+                ChatMemberUpdatedHandler
+            )
 
         async def chat_join_request_parser(update, users, chats):
-            return pyrogram.types.ChatJoinRequest._parse(self.client, update, users, chats), ChatJoinRequestHandler
+            return (
+                pyrogram.types.ChatJoinRequest._parse(self.client, update, users, chats),
+                ChatJoinRequestHandler
+            )
 
         self.update_parsers = {
-            Dispatcher.MESSAGE_UPDATES: message_parser,
+            Dispatcher.NEW_MESSAGE_UPDATES: message_parser,
+            Dispatcher.EDIT_MESSAGE_UPDATES: edited_message_parser,
             Dispatcher.DELETE_MESSAGES_UPDATES: deleted_messages_parser,
             Dispatcher.CALLBACK_QUERY_UPDATES: callback_query_parser,
-            (UpdateUserStatus,): user_status_parser,
-            (UpdateBotInlineQuery,): inline_query_parser,
-            (UpdateMessagePoll,): poll_parser,
-            (UpdateBotInlineSend,): chosen_inline_result_parser,
+            Dispatcher.USER_STATUS_UPDATES: user_status_parser,
+            Dispatcher.BOT_INLINE_QUERY_UPDATES: inline_query_parser,
+            Dispatcher.POLL_UPDATES: poll_parser,
+            Dispatcher.CHOSEN_INLINE_RESULT_UPDATES: chosen_inline_result_parser,
             Dispatcher.CHAT_MEMBER_UPDATES: chat_member_updated_parser,
-            (UpdateBotChatInviteRequester,): chat_join_request_parser
+            Dispatcher.CHAT_JOIN_REQUEST_UPDATES: chat_join_request_parser
         }
 
         self.update_parsers = {key: value for key_tuple, value in self.update_parsers.items() for key in key_tuple}
diff --git a/pyrogram/filters.py b/pyrogram/filters.py
index 348e6e7a72..078ed4fa7e 100644
--- a/pyrogram/filters.py
+++ b/pyrogram/filters.py
@@ -250,16 +250,6 @@ async def caption_filter(_, __, m: Message):
 
 # endregion
 
-# region edited_filter
-async def edited_filter(_, __, m: Message):
-    return bool(m.edit_date)
-
-
-edited = create(edited_filter)
-"""Filter edited messages."""
-
-
-# endregion
 
 # region audio_filter
 async def audio_filter(_, __, m: Message):
diff --git a/pyrogram/handlers/__init__.py b/pyrogram/handlers/__init__.py
index 2b7ef0a201..1c762958a0 100644
--- a/pyrogram/handlers/__init__.py
+++ b/pyrogram/handlers/__init__.py
@@ -22,6 +22,7 @@
 from .chosen_inline_result_handler import ChosenInlineResultHandler
 from .deleted_messages_handler import DeletedMessagesHandler
 from .disconnect_handler import DisconnectHandler
+from .edited_message_handler import EditedMessageHandler
 from .inline_query_handler import InlineQueryHandler
 from .message_handler import MessageHandler
 from .poll_handler import PollHandler
diff --git a/pyrogram/handlers/edited_message_handler.py b/pyrogram/handlers/edited_message_handler.py
new file mode 100644
index 0000000000..78deaf0fa9
--- /dev/null
+++ b/pyrogram/handlers/edited_message_handler.py
@@ -0,0 +1,49 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  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 Callable
+
+from .handler import Handler
+
+
+class EditedMessageHandler(Handler):
+    """The EditedMessage handler class. Used to handle edited messages.
+     It is intended to be used with :meth:`~pyrogram.Client.add_handler`
+
+    For a nicer way to register this handler, have a look at the
+    :meth:`~pyrogram.Client.on_edited_message` decorator.
+
+    Parameters:
+        callback (``Callable``):
+            Pass a function that will be called when a new edited message arrives. It takes *(client, message)*
+            as positional arguments (look at the section below for a detailed description).
+
+        filters (:obj:`Filters`):
+            Pass one or more filters to allow only a subset of messages to be passed
+            in your callback function.
+
+    Other parameters:
+        client (:obj:`~pyrogram.Client`):
+            The Client itself, useful when you want to call other API methods inside the message handler.
+
+        edited_message (:obj:`~pyrogram.types.Message`):
+            The received edited message.
+    """
+
+    def __init__(self, callback: Callable, filters=None):
+        super().__init__(callback, filters)
diff --git a/pyrogram/handlers/message_handler.py b/pyrogram/handlers/message_handler.py
index 63a334fc0d..f5a35b5531 100644
--- a/pyrogram/handlers/message_handler.py
+++ b/pyrogram/handlers/message_handler.py
@@ -22,8 +22,8 @@
 
 
 class MessageHandler(Handler):
-    """The Message handler class. Used to handle text, media and service messages coming from
-    any chat (private, group, channel). It is intended to be used with :meth:`~pyrogram.Client.add_handler`
+    """The Message handler class. Used to handle new messages.
+    It is intended to be used with :meth:`~pyrogram.Client.add_handler`
 
     For a nicer way to register this handler, have a look at the
     :meth:`~pyrogram.Client.on_message` decorator.
diff --git a/pyrogram/methods/decorators/__init__.py b/pyrogram/methods/decorators/__init__.py
index da0ed9ab13..1fc61185c9 100644
--- a/pyrogram/methods/decorators/__init__.py
+++ b/pyrogram/methods/decorators/__init__.py
@@ -22,6 +22,7 @@
 from .on_chosen_inline_result import OnChosenInlineResult
 from .on_deleted_messages import OnDeletedMessages
 from .on_disconnect import OnDisconnect
+from .on_edited_message import OnEditedMessage
 from .on_inline_query import OnInlineQuery
 from .on_message import OnMessage
 from .on_poll import OnPoll
@@ -31,6 +32,7 @@
 
 class Decorators(
     OnMessage,
+    OnEditedMessage,
     OnDeletedMessages,
     OnCallbackQuery,
     OnRawUpdate,
diff --git a/pyrogram/methods/decorators/on_edited_message.py b/pyrogram/methods/decorators/on_edited_message.py
new file mode 100644
index 0000000000..02ebdec3b8
--- /dev/null
+++ b/pyrogram/methods/decorators/on_edited_message.py
@@ -0,0 +1,61 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  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 Callable
+
+import pyrogram
+from pyrogram.filters import Filter
+
+
+class OnEditedMessage:
+    def on_edited_message(
+        self=None,
+        filters=None,
+        group: int = 0
+    ) -> Callable:
+        """Decorator for handling edited messages.
+
+        This does the same thing as :meth:`~pyrogram.Client.add_handler` using the
+        :obj:`~pyrogram.handlers.EditedMessageHandler`.
+
+        Parameters:
+            filters (:obj:`~pyrogram.filters`, *optional*):
+                Pass one or more filters to allow only a subset of messages to be passed
+                in your function.
+
+            group (``int``, *optional*):
+                The group identifier, defaults to 0.
+        """
+
+        def decorator(func: Callable) -> Callable:
+            if isinstance(self, pyrogram.Client):
+                self.add_handler(pyrogram.handlers.EditedMessageHandler(func, filters), group)
+            elif isinstance(self, Filter) or self is None:
+                if not hasattr(func, "handlers"):
+                    func.handlers = []
+
+                func.handlers.append(
+                    (
+                        pyrogram.handlers.MessageHandler(func, self),
+                        group if filters is None else filters
+                    )
+                )
+
+            return func
+
+        return decorator
diff --git a/pyrogram/methods/decorators/on_message.py b/pyrogram/methods/decorators/on_message.py
index e9a3dfdd85..220c12bbcc 100644
--- a/pyrogram/methods/decorators/on_message.py
+++ b/pyrogram/methods/decorators/on_message.py
@@ -28,7 +28,7 @@ def on_message(
         filters=None,
         group: int = 0
     ) -> Callable:
-        """Decorator for handling messages.
+        """Decorator for handling new messages.
 
         This does the same thing as :meth:`~pyrogram.Client.add_handler` using the
         :obj:`~pyrogram.handlers.MessageHandler`.

From b47591e6d232a1c8d3b8dcfb3174dc3aa06ccd81 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 24 Apr 2022 11:56:07 +0200
Subject: [PATCH 0803/1185] Turn examples asynchronous

---
 compiler/docs/compiler.py                     |   1 -
 docs/Makefile                                 |   4 +-
 docs/source/start/errors.rst                  |   4 +-
 docs/source/start/examples/bot_keyboards.rst  |  88 ++++---
 .../start/examples/callback_queries.rst       |   6 +-
 .../examples/{echobot.rst => echo_bot.rst}    |   8 +-
 .../start/examples/get_chat_history.rst       |  20 ++
 .../start/examples/get_chat_members.rst       |  15 +-
 docs/source/start/examples/get_dialogs.rst    |  11 +-
 docs/source/start/examples/get_history.rst    |  15 --
 docs/source/start/examples/hello_world.rst    |  11 +-
 docs/source/start/examples/index.rst          |  12 +-
 docs/source/start/examples/inline_queries.rst |   4 +-
 docs/source/start/examples/raw_updates.rst    |   2 +-
 .../source/start/examples/use_inline_bots.rst |  17 +-
 .../{welcomebot.rst => welcome_bot.rst}       |  17 +-
 docs/source/start/invoking.rst                |  30 +--
 docs/source/start/updates.rst                 |  38 +--
 pyrogram/__init__.py                          |   7 -
 pyrogram/client.py                            |  33 ++-
 .../methods/bots/answer_callback_query.py     |   6 +-
 pyrogram/methods/bots/answer_inline_query.py  |   2 +-
 pyrogram/methods/bots/delete_bot_commands.py  |   2 +-
 pyrogram/methods/bots/get_bot_commands.py     |   2 +-
 pyrogram/methods/bots/get_game_high_scores.py |   2 +-
 .../methods/bots/get_inline_bot_results.py    |   2 +-
 .../methods/bots/request_callback_answer.py   |   2 +-
 pyrogram/methods/bots/send_game.py            |   2 +-
 .../methods/bots/send_inline_bot_result.py    |   2 +-
 pyrogram/methods/bots/set_bot_commands.py     |   2 +-
 pyrogram/methods/bots/set_game_score.py       |   4 +-
 pyrogram/methods/chats/__init__.py            |   2 -
 pyrogram/methods/chats/add_chat_members.py    |   6 +-
 pyrogram/methods/chats/archive_chats.py       |   4 +-
 pyrogram/methods/chats/ban_chat_member.py     |   4 +-
 pyrogram/methods/chats/create_channel.py      |   2 +-
 pyrogram/methods/chats/create_group.py        |   2 +-
 pyrogram/methods/chats/create_supergroup.py   |   2 +-
 pyrogram/methods/chats/delete_channel.py      |   2 +-
 pyrogram/methods/chats/delete_chat_photo.py   |   2 +-
 pyrogram/methods/chats/delete_supergroup.py   |   2 +-
 pyrogram/methods/chats/get_chat.py            |   2 +-
 pyrogram/methods/chats/get_chat_event_log.py  |   6 +
 pyrogram/methods/chats/get_chat_member.py     |   2 +-
 pyrogram/methods/chats/get_chat_members.py    |   6 +-
 .../methods/chats/get_chat_members_count.py   |   2 +-
 .../methods/chats/get_chat_online_count.py    |   2 +-
 pyrogram/methods/chats/get_dialogs.py         |   4 +-
 pyrogram/methods/chats/get_dialogs_count.py   |   2 +-
 pyrogram/methods/chats/get_nearby_chats.py    |   2 +-
 pyrogram/methods/chats/get_send_as_chats.py   |   2 +-
 pyrogram/methods/chats/iter_chat_members.py   | 127 ----------
 pyrogram/methods/chats/iter_dialogs.py        |   2 +-
 pyrogram/methods/chats/join_chat.py           |  10 +-
 pyrogram/methods/chats/leave_chat.py          |   4 +-
 pyrogram/methods/chats/pin_chat_message.py    |   4 +-
 pyrogram/methods/chats/promote_chat_member.py |   8 +-
 .../methods/chats/restrict_chat_member.py     |   9 +-
 .../methods/chats/set_administrator_title.py  |   2 +-
 .../methods/chats/set_chat_description.py     |   2 +-
 .../methods/chats/set_chat_permissions.py     |   4 +-
 pyrogram/methods/chats/set_chat_photo.py      |   8 +-
 pyrogram/methods/chats/set_chat_title.py      |   2 +-
 pyrogram/methods/chats/set_chat_username.py   |   2 +-
 pyrogram/methods/chats/set_send_as_chat.py    |   2 +-
 pyrogram/methods/chats/set_slow_mode.py       |   4 +-
 pyrogram/methods/chats/unarchive_chats.py     |   4 +-
 pyrogram/methods/chats/unban_chat_member.py   |   2 +-
 .../methods/chats/unpin_all_chat_messages.py  |   2 +-
 pyrogram/methods/chats/unpin_chat_message.py  |   2 +-
 pyrogram/methods/contacts/add_contact.py      |   7 +-
 pyrogram/methods/contacts/delete_contacts.py  |   4 +-
 pyrogram/methods/contacts/get_contacts.py     |   2 +-
 .../methods/contacts/get_contacts_count.py    |   2 +-
 pyrogram/methods/contacts/import_contacts.py  |   2 +-
 .../invite_links/create_chat_invite_link.py   |   6 +-
 .../invite_links/edit_chat_invite_link.py     |   4 +-
 .../invite_links/export_chat_invite_link.py   |   2 +-
 pyrogram/methods/messages/copy_media_group.py |   9 +-
 pyrogram/methods/messages/copy_message.py     |   2 +-
 pyrogram/methods/messages/delete_messages.py  |   6 +-
 pyrogram/methods/messages/download_media.py   |   8 +-
 .../methods/messages/edit_inline_caption.py   |   2 +-
 .../methods/messages/edit_inline_media.py     |   6 +-
 .../messages/edit_inline_reply_markup.py      |   2 +-
 pyrogram/methods/messages/edit_inline_text.py |   4 +-
 .../methods/messages/edit_message_caption.py  |   2 +-
 .../methods/messages/edit_message_media.py    |   9 +-
 .../messages/edit_message_reply_markup.py     |   2 +-
 .../methods/messages/edit_message_text.py     |   4 +-
 pyrogram/methods/messages/forward_messages.py |   4 +-
 pyrogram/methods/messages/get_chat_history.py |   2 +-
 .../messages/get_chat_history_count.py        |   2 +-
 .../messages/get_discussion_message.py        |   4 +-
 .../messages/get_discussion_replies.py        |   4 +-
 .../messages/get_discussion_replies_count.py  |   2 +-
 pyrogram/methods/messages/get_messages.py     |  10 +-
 .../methods/messages/read_chat_history.py     |   4 +-
 pyrogram/methods/messages/retract_vote.py     |   2 +-
 pyrogram/methods/messages/search_global.py    |   6 +-
 pyrogram/methods/messages/search_messages.py  |   6 +-
 pyrogram/methods/messages/send_animation.py   |  10 +-
 pyrogram/methods/messages/send_audio.py       |  10 +-
 .../methods/messages/send_cached_media.py     |   2 +-
 pyrogram/methods/messages/send_chat_action.py |  12 +-
 pyrogram/methods/messages/send_contact.py     |   2 +-
 pyrogram/methods/messages/send_dice.py        |   6 +-
 pyrogram/methods/messages/send_document.py    |   8 +-
 pyrogram/methods/messages/send_location.py    |   2 +-
 pyrogram/methods/messages/send_media_group.py |   2 +-
 pyrogram/methods/messages/send_message.py     |  16 +-
 pyrogram/methods/messages/send_photo.py       |   8 +-
 pyrogram/methods/messages/send_poll.py        |   2 +-
 pyrogram/methods/messages/send_reaction.py    |   4 +-
 pyrogram/methods/messages/send_sticker.py     |   4 +-
 pyrogram/methods/messages/send_venue.py       |   4 +-
 pyrogram/methods/messages/send_video.py       |  10 +-
 pyrogram/methods/messages/send_video_note.py  |   4 +-
 pyrogram/methods/messages/send_voice.py       |   6 +-
 pyrogram/methods/messages/stop_poll.py        |   2 +-
 pyrogram/methods/messages/vote_poll.py        |   2 +-
 .../methods/password/change_cloud_password.py |   4 +-
 .../methods/password/enable_cloud_password.py |   6 +-
 .../methods/password/remove_cloud_password.py |   2 +-
 pyrogram/methods/users/block_user.py          |   2 +-
 .../methods/users/delete_profile_photos.py    |   6 +-
 pyrogram/methods/users/get_common_chats.py    |   2 +-
 pyrogram/methods/users/get_me.py              |   2 +-
 pyrogram/methods/users/get_profile_photos.py  |   6 +-
 .../methods/users/get_profile_photos_count.py |   2 +-
 pyrogram/methods/users/get_users.py           |   4 +-
 pyrogram/methods/users/iter_profile_photos.py |   4 +-
 pyrogram/methods/users/set_profile_photo.py   |   4 +-
 pyrogram/methods/users/set_username.py        |   2 +-
 pyrogram/methods/users/unblock_user.py        |   2 +-
 pyrogram/methods/users/update_profile.py      |   6 +-
 pyrogram/methods/utilities/add_handler.py     |   4 +-
 .../utilities/export_session_string.py        |   7 +-
 pyrogram/methods/utilities/idle.py            |  32 +--
 pyrogram/methods/utilities/remove_handler.py  |   4 +-
 pyrogram/methods/utilities/restart.py         |  14 +-
 pyrogram/methods/utilities/run.py             |  29 ++-
 pyrogram/methods/utilities/start.py           |  12 +-
 pyrogram/methods/utilities/stop.py            |  10 +-
 .../methods/utilities/stop_transmission.py    |  16 +-
 .../bots_and_keyboards/callback_query.py      |   4 +-
 pyrogram/types/inline_mode/inline_query.py    |   4 +-
 pyrogram/types/messages_and_media/message.py  | 157 ++++++------
 pyrogram/types/user_and_chats/chat.py         | 234 ++++--------------
 .../types/user_and_chats/chat_join_request.py |   8 +-
 pyrogram/types/user_and_chats/user.py         |  12 +-
 151 files changed, 619 insertions(+), 858 deletions(-)
 rename docs/source/start/examples/{echobot.rst => echo_bot.rst} (82%)
 create mode 100644 docs/source/start/examples/get_chat_history.rst
 delete mode 100644 docs/source/start/examples/get_history.rst
 rename docs/source/start/examples/{welcomebot.rst => welcome_bot.rst} (73%)
 delete mode 100644 pyrogram/methods/chats/iter_chat_members.py

diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py
index 8c75d711d3..91bbad3810 100644
--- a/compiler/docs/compiler.py
+++ b/compiler/docs/compiler.py
@@ -534,7 +534,6 @@ def get_title_list(s: str) -> list:
             Chat.promote_member
             Chat.get_member
             Chat.get_members
-            Chat.iter_members
             Chat.add_members
             Chat.join
             Chat.leave
diff --git a/docs/Makefile b/docs/Makefile
index ceb7494c14..8eacc12a4d 100644
--- a/docs/Makefile
+++ b/docs/Makefile
@@ -20,4 +20,6 @@ help:
 	@$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O)
 
 lhtml: # live html
-	sphinx-autobuild --host $(shell ifconfig | grep "inet " | grep -v 127.0.0.1 | cut -d\  -f2) -b html "$(SOURCEDIR)" "$(BUILDDIR)/html" $(SPHINXOPTS)
+	sphinx-autobuild --host $(shell ifconfig | grep "inet " | grep -v 127.0.0.1 | cut -d\  -f2) \
+	    --watch ../pyrogram \
+	    -b html "$(SOURCEDIR)" "$(BUILDDIR)/html" $(SPHINXOPTS)
diff --git a/docs/source/start/errors.rst b/docs/source/start/errors.rst
index 5f59deef6c..0188107413 100644
--- a/docs/source/start/errors.rst
+++ b/docs/source/start/errors.rst
@@ -89,7 +89,7 @@ Errors with Values
 
 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, some other errors contain the DC number on which the request must be repeated on.
-The value is stored in the ``x`` attribute of the exception object:
+The value is stored in the ``value`` attribute of the exception object:
 
 .. code-block:: python
 
@@ -100,5 +100,5 @@ The value is stored in the ``x`` attribute of the exception object:
         try:
             ...  # Your code
         except FloodWait as e:
-            await asyncio.sleep(e.x)  # Wait "x" seconds before continuing
+            await asyncio.sleep(e.value)  # Wait N seconds before continuing
     ...
\ No newline at end of file
diff --git a/docs/source/start/examples/bot_keyboards.rst b/docs/source/start/examples/bot_keyboards.rst
index a3a549f4df..b774a80e7e 100644
--- a/docs/source/start/examples/bot_keyboards.rst
+++ b/docs/source/start/examples/bot_keyboards.rst
@@ -12,51 +12,57 @@ like send_audio(), send_document(), send_location(), etc...
 .. code-block:: python
 
     from pyrogram import Client
-    from pyrogram.types import ReplyKeyboardMarkup, InlineKeyboardMarkup, InlineKeyboardButton
+    from pyrogram.types import (ReplyKeyboardMarkup, InlineKeyboardMarkup,
+                                InlineKeyboardButton)
 
     # Create a client using your bot token
     app = Client("my_bot", bot_token="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11")
 
-    with app:
-        app.send_message(
-            "me",  # Edit this
-            "This is a ReplyKeyboardMarkup example",
-            reply_markup=ReplyKeyboardMarkup(
-                [
-                    ["A", "B", "C", "D"],  # First row
-                    ["E", "F", "G"],  # Second row
-                    ["H", "I"],  # Third row
-                    ["J"]  # Fourth row
-                ],
-                resize_keyboard=True  # Make the keyboard smaller
-            )
-        )
-
-        app.send_message(
-            "me",  # Edit this
-            "This is a InlineKeyboardMarkup example",
-            reply_markup=InlineKeyboardMarkup(
-                [
-                    [  # First row
-                        InlineKeyboardButton(  # Generates a callback query when pressed
-                            "Button",
-                            callback_data="data"
-                        ),
-                        InlineKeyboardButton(  # Opens a web URL
-                            "URL",
-                            url="https://docs.pyrogram.org"
-                        ),
+
+    async def main():
+        async with app:
+            await app.send_message(
+                "me",  # Edit this
+                "This is a ReplyKeyboardMarkup example",
+                reply_markup=ReplyKeyboardMarkup(
+                    [
+                        ["A", "B", "C", "D"],  # First row
+                        ["E", "F", "G"],  # Second row
+                        ["H", "I"],  # Third row
+                        ["J"]  # Fourth row
                     ],
-                    [  # Second row
-                        InlineKeyboardButton(  # Opens the inline interface
-                            "Choose chat",
-                            switch_inline_query="pyrogram"
-                        ),
-                        InlineKeyboardButton(  # Opens the inline interface in the current chat
-                            "Inline here",
-                            switch_inline_query_current_chat="pyrogram"
-                        )
+                    resize_keyboard=True  # Make the keyboard smaller
+                )
+            )
+
+            await app.send_message(
+                "me",  # Edit this
+                "This is a InlineKeyboardMarkup example",
+                reply_markup=InlineKeyboardMarkup(
+                    [
+                        [  # First row
+                            InlineKeyboardButton(  # Generates a callback query when pressed
+                                "Button",
+                                callback_data="data"
+                            ),
+                            InlineKeyboardButton(  # Opens a web URL
+                                "URL",
+                                url="https://docs.pyrogram.org"
+                            ),
+                        ],
+                        [  # Second row
+                            InlineKeyboardButton(  # Opens the inline interface
+                                "Choose chat",
+                                switch_inline_query="pyrogram"
+                            ),
+                            InlineKeyboardButton(  # Opens the inline interface in the current chat
+                                "Inline here",
+                                switch_inline_query_current_chat="pyrogram"
+                            )
+                        ]
                     ]
-                ]
+                )
             )
-        )
+
+
+    app.run(main())
\ No newline at end of file
diff --git a/docs/source/start/examples/callback_queries.rst b/docs/source/start/examples/callback_queries.rst
index 73273058a1..64da57b39a 100644
--- a/docs/source/start/examples/callback_queries.rst
+++ b/docs/source/start/examples/callback_queries.rst
@@ -12,8 +12,10 @@ It uses the @on_callback_query decorator to register a CallbackQueryHandler.
 
 
     @app.on_callback_query()
-    def answer(client, callback_query):
-        callback_query.answer(f"Button contains: '{callback_query.data}'", show_alert=True)
+    async def answer(client, callback_query):
+        await callback_query.answer(
+            f"Button contains: '{callback_query.data}'",
+            show_alert=True)
 
 
     app.run()  # Automatically start() and idle()
\ No newline at end of file
diff --git a/docs/source/start/examples/echobot.rst b/docs/source/start/examples/echo_bot.rst
similarity index 82%
rename from docs/source/start/examples/echobot.rst
rename to docs/source/start/examples/echo_bot.rst
index 2ff578e97d..de8288b5e4 100644
--- a/docs/source/start/examples/echobot.rst
+++ b/docs/source/start/examples/echo_bot.rst
@@ -1,5 +1,5 @@
-echobot
-=======
+echo_bot
+========
 
 This simple echo bot replies to every private text message.
 
@@ -14,8 +14,8 @@ It uses the ``@on_message`` decorator to register a ``MessageHandler`` and appli
 
 
     @app.on_message(filters.text & filters.private)
-    def echo(client, message):
-        message.reply(message.text)
+    async def echo(client, message):
+        await message.reply(message.text)
 
 
     app.run()  # Automatically start() and idle()
\ No newline at end of file
diff --git a/docs/source/start/examples/get_chat_history.rst b/docs/source/start/examples/get_chat_history.rst
new file mode 100644
index 0000000000..59939948b3
--- /dev/null
+++ b/docs/source/start/examples/get_chat_history.rst
@@ -0,0 +1,20 @@
+get_history
+===========
+
+This example shows how to get the full message history of a chat, starting from the latest message.
+
+.. code-block:: python
+
+    from pyrogram import Client
+
+    app = Client("my_account")
+
+
+    async def main():
+        async with app:
+            # "me" refers to your own chat (Saved Messages)
+            async for message in app.get_chat_history("me"):
+                print(message)
+
+
+    app.run(main())
\ No newline at end of file
diff --git a/docs/source/start/examples/get_chat_members.rst b/docs/source/start/examples/get_chat_members.rst
index 8d47797604..26636ca344 100644
--- a/docs/source/start/examples/get_chat_members.rst
+++ b/docs/source/start/examples/get_chat_members.rst
@@ -7,9 +7,16 @@ This example shows how to get all the members of a chat.
 
     from pyrogram import Client
 
+    # Target channel/supergroup
+    TARGET = -100123456789
+
     app = Client("my_account")
-    target = "pyrogramchat"  # Target channel/supergroup
 
-    with app:
-        for member in app.iter_chat_members(target):
-            print(member.user.first_name)
\ No newline at end of file
+
+    async def main():
+        async with app:
+            async for member in app.get_chat_members(TARGET):
+                print(member)
+
+
+    app.run(main())
\ No newline at end of file
diff --git a/docs/source/start/examples/get_dialogs.rst b/docs/source/start/examples/get_dialogs.rst
index b1a4806427..e5b1060966 100644
--- a/docs/source/start/examples/get_dialogs.rst
+++ b/docs/source/start/examples/get_dialogs.rst
@@ -9,6 +9,11 @@ This example shows how to get the full dialogs list (as user).
 
     app = Client("my_account")
 
-    with app:
-        for dialog in app.iter_dialogs():
-            print(dialog.chat.title or dialog.chat.first_name)
\ No newline at end of file
+
+    async def main():
+        async with app:
+            async for dialog in app.get_dialogs():
+                print(dialog.chat.title or dialog.chat.first_name)
+
+
+    app.run(main())
\ No newline at end of file
diff --git a/docs/source/start/examples/get_history.rst b/docs/source/start/examples/get_history.rst
deleted file mode 100644
index 01433d91fe..0000000000
--- a/docs/source/start/examples/get_history.rst
+++ /dev/null
@@ -1,15 +0,0 @@
-get_history
-===========
-
-This example shows how to get the full message history of a chat, starting from the latest message.
-
-.. code-block:: python
-
-    from pyrogram import Client
-
-    app = Client("my_account")
-    target = "me"  # "me" refers to your own chat (Saved Messages)
-
-    with app:
-        for message in app.iter_history(target):
-            print(message.text)
\ No newline at end of file
diff --git a/docs/source/start/examples/hello_world.rst b/docs/source/start/examples/hello_world.rst
index 997659e237..2902241e8c 100644
--- a/docs/source/start/examples/hello_world.rst
+++ b/docs/source/start/examples/hello_world.rst
@@ -10,6 +10,11 @@ This example demonstrates a basic API usage
     # Create a new Client instance
     app = Client("my_account")
 
-    with app:
-        # Send a message, Markdown is enabled by default
-        app.send_message("me", "Hi there! I'm using **Pyrogram**")
+
+    async def main():
+        async with app:
+            # Send a message, Markdown is enabled by default
+            await app.send_message("me", "Hi there! I'm using **Pyrogram**")
+
+
+    app.run(main())
diff --git a/docs/source/start/examples/index.rst b/docs/source/start/examples/index.rst
index 7d8a69a41d..d47b60e80d 100644
--- a/docs/source/start/examples/index.rst
+++ b/docs/source/start/examples/index.rst
@@ -17,9 +17,9 @@ to give you a basic idea.
     :align: center
 
     :doc:`hello_world`, "Demonstration of basic API usage"
-    :doc:`echobot`, "Echo every private text message"
-    :doc:`welcomebot`, "The Welcome Bot in @PyrogramChat"
-    :doc:`get_history`, "Get the full message history of a chat"
+    :doc:`echo_bot`, "Echo every private text message"
+    :doc:`welcome_bot`, "The Welcome Bot in @PyrogramChat"
+    :doc:`get_chat_history`, "Get the full message history of a chat"
     :doc:`get_chat_members`, "Get all the members of a chat"
     :doc:`get_dialogs`, "Get all of your dialog chats"
     :doc:`callback_queries`, "Handle callback queries (as bot) coming from inline button presses"
@@ -34,9 +34,9 @@ For more advanced examples, see https://snippets.pyrogram.org.
     :hidden:
 
     hello_world
-    echobot
-    welcomebot
-    get_history
+    echo_bot
+    welcome_bot
+    get_chat_history
     get_chat_members
     get_dialogs
     callback_queries
diff --git a/docs/source/start/examples/inline_queries.rst b/docs/source/start/examples/inline_queries.rst
index 09d226ef6b..b78c6e1cb8 100644
--- a/docs/source/start/examples/inline_queries.rst
+++ b/docs/source/start/examples/inline_queries.rst
@@ -16,8 +16,8 @@ It uses the @on_inline_query decorator to register an InlineQueryHandler.
 
 
     @app.on_inline_query()
-    def answer(client, inline_query):
-        inline_query.answer(
+    async def answer(client, inline_query):
+        await inline_query.answer(
             results=[
                 InlineQueryResultArticle(
                     title="Installation",
diff --git a/docs/source/start/examples/raw_updates.rst b/docs/source/start/examples/raw_updates.rst
index 6086a9686c..463a45a80b 100644
--- a/docs/source/start/examples/raw_updates.rst
+++ b/docs/source/start/examples/raw_updates.rst
@@ -11,7 +11,7 @@ This example shows how to handle raw updates.
 
 
     @app.on_raw_update()
-    def raw(client, update, users, chats):
+    async def raw(client, update, users, chats):
         print(update)
 
 
diff --git a/docs/source/start/examples/use_inline_bots.rst b/docs/source/start/examples/use_inline_bots.rst
index 63e4698506..8a2a72acc4 100644
--- a/docs/source/start/examples/use_inline_bots.rst
+++ b/docs/source/start/examples/use_inline_bots.rst
@@ -10,9 +10,16 @@ This example shows how to query an inline bot (as user).
     # Create a new Client
     app = Client("my_account")
 
-    with app:
-        # Get bot results for "hello" from the inline bot @vid
-        bot_results = app.get_inline_bot_results("vid", "hello")
 
-        # Send the first result (bot_results.results[0]) to your own chat (Saved Messages)
-        app.send_inline_bot_result("me", bot_results.query_id, bot_results.results[0].id)
\ No newline at end of file
+    async def main():
+        async with app:
+            # Get bot results for "hello" from the inline bot @vid
+            bot_results = await app.get_inline_bot_results("vid", "hello")
+
+            # Send the first result to your own chat (Saved Messages)
+            await app.send_inline_bot_result(
+                "me", bot_results.query_id,
+                bot_results.results[0].id)
+
+
+    app.run(main())
\ No newline at end of file
diff --git a/docs/source/start/examples/welcomebot.rst b/docs/source/start/examples/welcome_bot.rst
similarity index 73%
rename from docs/source/start/examples/welcomebot.rst
rename to docs/source/start/examples/welcome_bot.rst
index a742059007..4e30ea7f0a 100644
--- a/docs/source/start/examples/welcomebot.rst
+++ b/docs/source/start/examples/welcome_bot.rst
@@ -1,5 +1,5 @@
-welcomebot
-==========
+welcome_bot
+===========
 
 This example uses the ``emoji`` module to easily add emoji in your text messages and ``filters``
 to make it only work for specific messages in a specific chat.
@@ -8,24 +8,23 @@ to make it only work for specific messages in a specific chat.
 
     from pyrogram import Client, emoji, filters
 
-    TARGET = -100123456789  # 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.org/)'s group chat {}!"  # Welcome message
+    # Target chat. Can also be a list of multiple chat ids/usernames
+    TARGET = -100123456789
+    # Welcome message template
+    MESSAGE = "{} Welcome to [Pyrogram](https://docs.pyrogram.org/)'s group chat {}!"
 
     app = Client("my_account")
 
 
     # Filter in only new_chat_members updates generated in TARGET chat
     @app.on_message(filters.chat(TARGET) & filters.new_chat_members)
-    def welcome(client, message):
+    async def welcome(client, message):
         # Build the new members list (with mentions) by using their first_name
         new_members = [u.mention for u in message.new_chat_members]
-
         # Build the welcome message by using an emoji and the list we built above
         text = MESSAGE.format(emoji.SPARKLES, ", ".join(new_members))
-
         # Send the welcome message, without the web page preview
-        message.reply_text(text, disable_web_page_preview=True)
+        await message.reply_text(text, disable_web_page_preview=True)
 
 
     app.run()  # Automatically start() and idle()
\ No newline at end of file
diff --git a/docs/source/start/invoking.rst b/docs/source/start/invoking.rst
index de1e321ed1..ab91e7306b 100644
--- a/docs/source/start/invoking.rst
+++ b/docs/source/start/invoking.rst
@@ -1,5 +1,5 @@
-Calling Methods
-===============
+Invoking Methods
+================
 
 At this point, we have successfully :doc:`installed Pyrogram <../intro/install>` and :doc:`authorized ` our
 account; we are now aiming towards the core of the framework.
@@ -14,7 +14,7 @@ account; we are now aiming towards the core of the framework.
 Basic Usage
 -----------
 
-Making API method calls with Pyrogram is very simple. Here's a basic example we are going to examine step by step:
+Making API calls with Pyrogram is very simple. Here's a basic example we are going to examine step by step:
 
 .. code-block:: python
 
@@ -43,7 +43,7 @@ Step-by-step
 
         app = Client("my_account")
 
-#.  Async methods can't be executed at the top level, because they must be inside an async context.
+#.  Async methods must be invoked within an async context.
     Here we define an async function and put our code inside. Also notice the ``await`` keyword in front of the method
     call; this is required for all asynchronous methods.
 
@@ -101,24 +101,4 @@ be instantiated inside the main function.
         async with app:
             await app.send_message("me", "Hi!")
 
-    asyncio.run(main())
-
-Synchronous Calls
-------------------
-
-Pyrogram is an asynchronous framework, but it also provides a convenience way for calling methods without the need
-of async/await keywords and the extra boilerplate. In case you want Pyrogram to run synchronously, simply  use the
-synchronous context manager:
-
-.. code-block:: python
-
-    from pyrogram import Client
-
-    app = Client("my_account")
-
-    with app:
-        app.send_message("me", "Hi!")
-
-As you can see, the non-async example becomes less cluttered. Use Pyrogram in this non-asynchronous way only when you
-want to write something without the boilerplate or in case you want to combine Pyrogram with other libraries that are
-not async.
\ No newline at end of file
+    asyncio.run(main())
\ No newline at end of file
diff --git a/docs/source/start/updates.rst b/docs/source/start/updates.rst
index dee5a11589..0cdf76507f 100644
--- a/docs/source/start/updates.rst
+++ b/docs/source/start/updates.rst
@@ -1,8 +1,8 @@
 Handling Updates
 ================
 
-Calling :doc:`API methods ` sequentially is one way to use Pyrogram, but how to react when, for example, a
-new message arrives? This page deals with updates and how to handle such events in Pyrogram.
+:doc:`Invoking API methods ` sequentially is one way to use Pyrogram. This page deals with Telegram updates
+and how to handle new incoming messages or other events in Pyrogram.
 
 .. contents:: Contents
     :backlinks: none
@@ -14,7 +14,7 @@ new message arrives? This page deals with updates and how to handle such events
 Defining Updates
 ----------------
 
-As hinted already, updates are simply events that happen in your Telegram account (incoming messages, new members join,
+As hinted already, updates are 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 has changed. These updates are
 handled by registering one or more callback functions in your app using :doc:`Handlers <../api/handlers>`.
 
@@ -52,25 +52,6 @@ In the last line we see again the :meth:`~pyrogram.Client.run` method, this time
 Its purpose here is simply to automatically :meth:`~pyrogram.Client.start`, keep the Client online so that it can listen
 for updates and :meth:`~pyrogram.Client.stop` it once you hit ``CTRL+C``.
 
-Synchronous handlers
-^^^^^^^^^^^^^^^^^^^^^
-
-You can also have synchronous handlers; you only need to define the callback function without using ``async def`` and
-call API methods by not placing ``await`` in front of them:
-
-.. code-block:: python
-
-    @app.on_message()
-    def my_handler(client, message):
-        message.forward("me")
-
-.. note::
-
-    You can mix ``def`` and ``async def`` handlers as much as you like, Pyrogram will still work concurrently and
-    efficiently regardless of what you choose. However, it is recommended to use Pyrogram in its native, asynchronous
-    form at all times, unless you want to write something without the boilerplate or in case you want to combine
-    Pyrogram with other libraries that are not async.
-
 Using add_handler()
 ^^^^^^^^^^^^^^^^^^^
 
@@ -91,16 +72,3 @@ function and registers it in your Client. It is useful in case you want to progr
     app.add_handler(my_handler)
 
     app.run()
-
-The same about synchronous handlers applies for :meth:`~pyrogram.Client.add_handler`:
-
-.. code-block:: python
-
-    def my_function(client, message):
-        message.forward("me")
-
-.. note::
-
-    From now on, you'll see examples using synchronous code (i.e.: without ``async`` and ``await``, unless when actually
-    relevant). This is done to keep snippets concise and more readable. Once you get the idea behind a feature, you can
-    easily turn examples asynchronous later on.
diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index 5e5ea69c39..bcef9c1d98 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -35,15 +35,8 @@ class ContinuePropagation(StopAsyncIteration):
     pass
 
 
-import asyncio
-
 from . import raw, types, filters, handlers, emoji, enums
 from .client import Client
 from .sync import idle
 
-# Save the main thread loop for future references
-main_event_loop = asyncio.get_event_loop()
-
-CRYPTO_EXECUTOR_SIZE_THRESHOLD = 512
-
 crypto_executor = ThreadPoolExecutor(1, thread_name_prefix="CryptoWorker")
diff --git a/pyrogram/client.py b/pyrogram/client.py
index ac753f8849..525ecf4a51 100644
--- a/pyrogram/client.py
+++ b/pyrogram/client.py
@@ -452,29 +452,26 @@ def set_parse_mode(self, parse_mode: Optional["enums.ParseMode"]):
         Example:
             .. code-block:: python
 
-                from pyrogram import Client, enums
+                from pyrogram import enums
 
-                app = Client("my_account")
+                # Default combined mode: Markdown + HTML
+                await app.send_message("me", "1. **markdown** and html")
 
-                with app:
-                    # Default combined mode: Markdown + HTML
-                    app.send_message("me", "1. **markdown** and html")
+                # Force Markdown-only, HTML is disabled
+                app.set_parse_mode(enums.ParseMode.MARKDOWN)
+                await app.send_message("me", "2. **markdown** and html")
 
-                    # Force Markdown-only, HTML is disabled
-                    app.set_parse_mode(enums.ParseMode.MARKDOWN)
-                    app.send_message("me", "2. **markdown** and html")
+                # Force HTML-only, Markdown is disabled
+                app.set_parse_mode(enums.ParseMode.HTML)
+                await app.send_message("me", "3. **markdown** and html")
 
-                    # Force HTML-only, Markdown is disabled
-                    app.set_parse_mode(enums.ParseMode.HTML)
-                    app.send_message("me", "3. **markdown** and html")
+                # Disable the parser completely
+                app.set_parse_mode(enums.ParseMode.DISABLED)
+                await app.send_message("me", "4. **markdown** and html")
 
-                    # Disable the parser completely
-                    app.set_parse_mode(enums.ParseMode.DISABLED)
-                    app.send_message("me", "4. **markdown** and html")
-
-                    # Bring back the default combined mode
-                    app.set_parse_mode(enums.ParseMode.DEFAULT)
-                    app.send_message("me", "5. **markdown** and html")
+                # Bring back the default combined mode
+                app.set_parse_mode(enums.ParseMode.DEFAULT)
+                await app.send_message("me", "5. **markdown** and html")
         """
 
         self.parse_mode = parse_mode
diff --git a/pyrogram/methods/bots/answer_callback_query.py b/pyrogram/methods/bots/answer_callback_query.py
index af2c8f7252..95d479314a 100644
--- a/pyrogram/methods/bots/answer_callback_query.py
+++ b/pyrogram/methods/bots/answer_callback_query.py
@@ -60,13 +60,13 @@ async def answer_callback_query(
             .. code-block:: python
 
                 # Answer only (remove the spinning circles)
-                app.answer_callback_query(query_id)
+                await app.answer_callback_query(query_id)
 
                 # Answer without alert
-                app.answer_callback_query(query_id, text=text)
+                await app.answer_callback_query(query_id, text=text)
 
                 # Answer with alert
-                app.answer_callback_query(query_id, text=text, show_alert=True)
+                await app.answer_callback_query(query_id, text=text, show_alert=True)
         """
         return await self.invoke(
             raw.functions.messages.SetBotCallbackAnswer(
diff --git a/pyrogram/methods/bots/answer_inline_query.py b/pyrogram/methods/bots/answer_inline_query.py
index ebdfab2397..e9acd9375d 100644
--- a/pyrogram/methods/bots/answer_inline_query.py
+++ b/pyrogram/methods/bots/answer_inline_query.py
@@ -86,7 +86,7 @@ async def answer_inline_query(
 
                 from pyrogram.types import InlineQueryResultArticle, InputTextMessageContent
 
-                app.answer_inline_query(
+                await app.answer_inline_query(
                     inline_query_id,
                     results=[
                         InlineQueryResultArticle(
diff --git a/pyrogram/methods/bots/delete_bot_commands.py b/pyrogram/methods/bots/delete_bot_commands.py
index 98e6d3960f..2e638aed7f 100644
--- a/pyrogram/methods/bots/delete_bot_commands.py
+++ b/pyrogram/methods/bots/delete_bot_commands.py
@@ -49,7 +49,7 @@ async def delete_bot_commands(
             .. code-block:: python
 
                 # Delete commands
-                app.delete_bot_commands()
+                await app.delete_bot_commands()
         """
 
         return await self.invoke(
diff --git a/pyrogram/methods/bots/get_bot_commands.py b/pyrogram/methods/bots/get_bot_commands.py
index fe5be0f3bd..f62b63754a 100644
--- a/pyrogram/methods/bots/get_bot_commands.py
+++ b/pyrogram/methods/bots/get_bot_commands.py
@@ -51,7 +51,7 @@ async def get_bot_commands(
             .. code-block:: python
 
                 # Get commands
-                commands = app.get_bot_commands()
+                commands = await app.get_bot_commands()
                 print(commands)
         """
 
diff --git a/pyrogram/methods/bots/get_game_high_scores.py b/pyrogram/methods/bots/get_game_high_scores.py
index 312cc632b4..555e9af5e3 100644
--- a/pyrogram/methods/bots/get_game_high_scores.py
+++ b/pyrogram/methods/bots/get_game_high_scores.py
@@ -54,7 +54,7 @@ async def get_game_high_scores(
         Example:
             .. code-block:: python
 
-                scores = app.get_game_high_scores(user_id, chat_id, message_id)
+                scores = await app.get_game_high_scores(user_id, chat_id, message_id)
                 print(scores)
         """
         # TODO: inline_message_id
diff --git a/pyrogram/methods/bots/get_inline_bot_results.py b/pyrogram/methods/bots/get_inline_bot_results.py
index f145cc8c44..3ef1d4dea0 100644
--- a/pyrogram/methods/bots/get_inline_bot_results.py
+++ b/pyrogram/methods/bots/get_inline_bot_results.py
@@ -64,7 +64,7 @@ async def get_inline_bot_results(
         Example:
             .. code-block:: python
 
-                results = app.get_inline_bot_results("pyrogrambot")
+                results = await app.get_inline_bot_results("pyrogrambot")
                 print(results)
         """
         # TODO: Don't return the raw type
diff --git a/pyrogram/methods/bots/request_callback_answer.py b/pyrogram/methods/bots/request_callback_answer.py
index 908c8ecae6..9c0626a56e 100644
--- a/pyrogram/methods/bots/request_callback_answer.py
+++ b/pyrogram/methods/bots/request_callback_answer.py
@@ -58,7 +58,7 @@ async def request_callback_answer(
         Example:
             .. code-block:: python
 
-                app.request_callback_answer(chat_id, message_id, "callback_data")
+                await app.request_callback_answer(chat_id, message_id, "callback_data")
         """
 
         # Telegram only wants bytes, but we are allowed to pass strings too.
diff --git a/pyrogram/methods/bots/send_game.py b/pyrogram/methods/bots/send_game.py
index c8eee66eff..e4181166f7 100644
--- a/pyrogram/methods/bots/send_game.py
+++ b/pyrogram/methods/bots/send_game.py
@@ -69,7 +69,7 @@ async def send_game(
         Example:
             .. code-block:: python
 
-                app.send_game(chat_id, "gamename")
+                await app.send_game(chat_id, "gamename")
         """
         r = await self.invoke(
             raw.functions.messages.SendMedia(
diff --git a/pyrogram/methods/bots/send_inline_bot_result.py b/pyrogram/methods/bots/send_inline_bot_result.py
index 00b4204309..ebe5ca8d24 100644
--- a/pyrogram/methods/bots/send_inline_bot_result.py
+++ b/pyrogram/methods/bots/send_inline_bot_result.py
@@ -59,7 +59,7 @@ async def send_inline_bot_result(
         Example:
             .. code-block:: python
 
-                app.send_inline_bot_result(chat_id, query_id, result_id)
+                await app.send_inline_bot_result(chat_id, query_id, result_id)
         """
         return await self.invoke(
             raw.functions.messages.SendInlineBotResult(
diff --git a/pyrogram/methods/bots/set_bot_commands.py b/pyrogram/methods/bots/set_bot_commands.py
index b59c0fac78..7dfbf10979 100644
--- a/pyrogram/methods/bots/set_bot_commands.py
+++ b/pyrogram/methods/bots/set_bot_commands.py
@@ -57,7 +57,7 @@ async def set_bot_commands(
                 from pyrogram.types import BotCommand
 
                 # Set new commands
-                app.set_bot_commands([
+                await app.set_bot_commands([
                     BotCommand("start", "Start the bot"),
                     BotCommand("settings", "Bot settings")])
         """
diff --git a/pyrogram/methods/bots/set_game_score.py b/pyrogram/methods/bots/set_game_score.py
index 855e4a2ece..f9008e4435 100644
--- a/pyrogram/methods/bots/set_game_score.py
+++ b/pyrogram/methods/bots/set_game_score.py
@@ -70,10 +70,10 @@ async def set_game_score(
             .. code-block:: python
 
                 # Set new score
-                app.set_game_score(user_id, 1000)
+                await app.set_game_score(user_id, 1000)
 
                 # Force set new score
-                app.set_game_score(user_id, 25, force=True)
+                await app.set_game_score(user_id, 25, force=True)
         """
         r = await self.invoke(
             raw.functions.messages.SetGameScore(
diff --git a/pyrogram/methods/chats/__init__.py b/pyrogram/methods/chats/__init__.py
index 6eedbd32e6..1b744a0569 100644
--- a/pyrogram/methods/chats/__init__.py
+++ b/pyrogram/methods/chats/__init__.py
@@ -36,7 +36,6 @@
 from .get_dialogs_count import GetDialogsCount
 from .get_nearby_chats import GetNearbyChats
 from .get_send_as_chats import GetSendAsChats
-from .iter_chat_members import IterChatMembers
 from .iter_dialogs import IterDialogs
 from .join_chat import JoinChat
 from .leave_chat import LeaveChat
@@ -78,7 +77,6 @@ class Chats(
     GetDialogs,
     GetChatMembersCount,
     IterDialogs,
-    IterChatMembers,
     SetChatUsername,
     SetChatPermissions,
     GetDialogsCount,
diff --git a/pyrogram/methods/chats/add_chat_members.py b/pyrogram/methods/chats/add_chat_members.py
index 2d053c4e8e..2bf145f907 100644
--- a/pyrogram/methods/chats/add_chat_members.py
+++ b/pyrogram/methods/chats/add_chat_members.py
@@ -52,13 +52,13 @@ async def add_chat_members(
             .. code-block:: python
 
                 # Add one member to a group or channel
-                app.add_chat_members(chat_id, user_id)
+                await app.add_chat_members(chat_id, user_id)
 
                 # Add multiple members to a group or channel
-                app.add_chat_members(chat_id, [user_id1, user_id2, user_id3])
+                await app.add_chat_members(chat_id, [user_id1, user_id2, user_id3])
 
                 # Change forward_limit (for basic groups only)
-                app.add_chat_members(chat_id, user_id, forward_limit=25)
+                await app.add_chat_members(chat_id, user_id, forward_limit=25)
         """
         peer = await self.resolve_peer(chat_id)
 
diff --git a/pyrogram/methods/chats/archive_chats.py b/pyrogram/methods/chats/archive_chats.py
index cba27371cb..40544e0fdc 100644
--- a/pyrogram/methods/chats/archive_chats.py
+++ b/pyrogram/methods/chats/archive_chats.py
@@ -41,10 +41,10 @@ async def archive_chats(
             .. code-block:: python
 
                 # Archive chat
-                app.archive_chats(chat_id)
+                await app.archive_chats(chat_id)
 
                 # Archive multiple chats at once
-                app.archive_chats([chat_id1, chat_id2, chat_id3])
+                await app.archive_chats([chat_id1, chat_id2, chat_id3])
         """
 
         if not isinstance(chat_ids, list):
diff --git a/pyrogram/methods/chats/ban_chat_member.py b/pyrogram/methods/chats/ban_chat_member.py
index d8a207aba8..8e1bca1eda 100644
--- a/pyrogram/methods/chats/ban_chat_member.py
+++ b/pyrogram/methods/chats/ban_chat_member.py
@@ -64,10 +64,10 @@ async def ban_chat_member(
                 from datetime import datetime, timedelta
 
                 # Ban chat member forever
-                app.ban_chat_member(chat_id, user_id)
+                await app.ban_chat_member(chat_id, user_id)
 
                 # Ban chat member and automatically unban after 24h
-                app.ban_chat_member(chat_id, user_id, datetime.now() + timedelta(days=1))
+                await app.ban_chat_member(chat_id, user_id, datetime.now() + timedelta(days=1))
         """
         chat_peer = await self.resolve_peer(chat_id)
         user_peer = await self.resolve_peer(user_id)
diff --git a/pyrogram/methods/chats/create_channel.py b/pyrogram/methods/chats/create_channel.py
index 1b054b6ed6..2a1497789d 100644
--- a/pyrogram/methods/chats/create_channel.py
+++ b/pyrogram/methods/chats/create_channel.py
@@ -41,7 +41,7 @@ async def create_channel(
         Example:
             .. code-block:: python
 
-                app.create_channel("Channel Title", "Channel Description")
+                await app.create_channel("Channel Title", "Channel Description")
         """
         r = await self.invoke(
             raw.functions.channels.CreateChannel(
diff --git a/pyrogram/methods/chats/create_group.py b/pyrogram/methods/chats/create_group.py
index 78240f9fb0..a2ffa8de66 100644
--- a/pyrogram/methods/chats/create_group.py
+++ b/pyrogram/methods/chats/create_group.py
@@ -50,7 +50,7 @@ async def create_group(
         Example:
             .. code-block:: python
 
-                app.create_group("Group Title", user_id)
+                await app.create_group("Group Title", user_id)
         """
         if not isinstance(users, list):
             users = [users]
diff --git a/pyrogram/methods/chats/create_supergroup.py b/pyrogram/methods/chats/create_supergroup.py
index eb922c324f..04da148a90 100644
--- a/pyrogram/methods/chats/create_supergroup.py
+++ b/pyrogram/methods/chats/create_supergroup.py
@@ -45,7 +45,7 @@ async def create_supergroup(
         Example:
             .. code-block:: python
 
-                app.create_supergroup("Supergroup Title", "Supergroup Description")
+                await app.create_supergroup("Supergroup Title", "Supergroup Description")
         """
         r = await self.invoke(
             raw.functions.channels.CreateChannel(
diff --git a/pyrogram/methods/chats/delete_channel.py b/pyrogram/methods/chats/delete_channel.py
index 210c81f1cd..7aad47d033 100644
--- a/pyrogram/methods/chats/delete_channel.py
+++ b/pyrogram/methods/chats/delete_channel.py
@@ -39,7 +39,7 @@ async def delete_channel(
         Example:
             .. code-block:: python
 
-                app.delete_channel(channel_id)
+                await app.delete_channel(channel_id)
         """
         await self.invoke(
             raw.functions.channels.DeleteChannel(
diff --git a/pyrogram/methods/chats/delete_chat_photo.py b/pyrogram/methods/chats/delete_chat_photo.py
index ac485603cd..058b7fdb29 100644
--- a/pyrogram/methods/chats/delete_chat_photo.py
+++ b/pyrogram/methods/chats/delete_chat_photo.py
@@ -44,7 +44,7 @@ async def delete_chat_photo(
         Example:
             .. code-block:: python
 
-                app.delete_chat_photo(chat_id)
+                await app.delete_chat_photo(chat_id)
         """
         peer = await self.resolve_peer(chat_id)
 
diff --git a/pyrogram/methods/chats/delete_supergroup.py b/pyrogram/methods/chats/delete_supergroup.py
index 8fb069fff8..39d5a07d91 100644
--- a/pyrogram/methods/chats/delete_supergroup.py
+++ b/pyrogram/methods/chats/delete_supergroup.py
@@ -39,7 +39,7 @@ async def delete_supergroup(
         Example:
             .. code-block:: python
 
-                app.delete_supergroup(supergroup_id)
+                await app.delete_supergroup(supergroup_id)
         """
         await self.invoke(
             raw.functions.channels.DeleteChannel(
diff --git a/pyrogram/methods/chats/get_chat.py b/pyrogram/methods/chats/get_chat.py
index fc19801910..d3246447a7 100644
--- a/pyrogram/methods/chats/get_chat.py
+++ b/pyrogram/methods/chats/get_chat.py
@@ -50,7 +50,7 @@ async def get_chat(
         Example:
             .. code-block:: python
 
-                chat = app.get_chat("pyrogram")
+                chat = await app.get_chat("pyrogram")
                 print(chat)
         """
         match = self.INVITE_LINK_RE.match(str(chat_id))
diff --git a/pyrogram/methods/chats/get_chat_event_log.py b/pyrogram/methods/chats/get_chat_event_log.py
index 2325bad99a..622fe0bc0f 100644
--- a/pyrogram/methods/chats/get_chat_event_log.py
+++ b/pyrogram/methods/chats/get_chat_event_log.py
@@ -64,6 +64,12 @@ async def get_chat_event_log(
 
         Yields:
             :obj:`~pyrogram.types.ChatEvent` objects.
+
+        Example:
+            .. code-block:: python
+
+                async for event in app.get_chat_event_log(chat_id):
+                    print(event)
         """
         current = 0
         total = abs(limit) or (1 << 31)
diff --git a/pyrogram/methods/chats/get_chat_member.py b/pyrogram/methods/chats/get_chat_member.py
index 1b24d2135c..526cf744ca 100644
--- a/pyrogram/methods/chats/get_chat_member.py
+++ b/pyrogram/methods/chats/get_chat_member.py
@@ -47,7 +47,7 @@ async def get_chat_member(
         Example:
             .. code-block:: python
 
-                member = app.get_chat_member(chat_id, "me")
+                member = await app.get_chat_member(chat_id, "me")
                 print(member)
         """
         chat = await self.resolve_peer(chat_id)
diff --git a/pyrogram/methods/chats/get_chat_members.py b/pyrogram/methods/chats/get_chat_members.py
index c2516a7081..065e262c7b 100644
--- a/pyrogram/methods/chats/get_chat_members.py
+++ b/pyrogram/methods/chats/get_chat_members.py
@@ -97,16 +97,16 @@ async def get_chat_members(
                 from pyrogram import enums
 
                 # Get members
-                for member in app.get_chat_members(chat_id):
+                async for member in app.get_chat_members(chat_id):
                     print(member)
 
                 # Get administrators
-                administrators = list(app.get_chat_members(
+                administrators = list(await app.get_chat_members(
                     chat_id,
                     filter=enums.ChatMembersFilter.ADMINISTRATORS))
 
                 # Get bots
-                bots = list(app.get_chat_members(
+                bots = list(await app.get_chat_members(
                     chat_id,
                     filter=enums.ChatMembersFilter.BOTS))
         """
diff --git a/pyrogram/methods/chats/get_chat_members_count.py b/pyrogram/methods/chats/get_chat_members_count.py
index 9c57a11e80..e2f7fa8b92 100644
--- a/pyrogram/methods/chats/get_chat_members_count.py
+++ b/pyrogram/methods/chats/get_chat_members_count.py
@@ -42,7 +42,7 @@ async def get_chat_members_count(
         Example:
             .. code-block:: python
 
-                count = app.get_chat_members_count(chat_id)
+                count = await app.get_chat_members_count(chat_id)
                 print(count)
         """
         peer = await self.resolve_peer(chat_id)
diff --git a/pyrogram/methods/chats/get_chat_online_count.py b/pyrogram/methods/chats/get_chat_online_count.py
index 19924542f5..45c60d978b 100644
--- a/pyrogram/methods/chats/get_chat_online_count.py
+++ b/pyrogram/methods/chats/get_chat_online_count.py
@@ -39,7 +39,7 @@ async def get_chat_online_count(
         Example:
             .. code-block:: python
 
-                online = app.get_chat_online_count(chat_id)
+                online = await app.get_chat_online_count(chat_id)
                 print(online)
         """
         return (await self.invoke(
diff --git a/pyrogram/methods/chats/get_dialogs.py b/pyrogram/methods/chats/get_dialogs.py
index 4aaa95ffc4..f888c3ef4c 100644
--- a/pyrogram/methods/chats/get_dialogs.py
+++ b/pyrogram/methods/chats/get_dialogs.py
@@ -60,10 +60,10 @@ async def get_dialogs(
             .. code-block:: python
 
                 # Get first 100 dialogs
-                app.get_dialogs()
+                await app.get_dialogs()
 
                 # Get pinned dialogs
-                app.get_dialogs(pinned_only=True)
+                await app.get_dialogs(pinned_only=True)
         """
 
         if pinned_only:
diff --git a/pyrogram/methods/chats/get_dialogs_count.py b/pyrogram/methods/chats/get_dialogs_count.py
index 8ca237cff5..3d96732cf1 100644
--- a/pyrogram/methods/chats/get_dialogs_count.py
+++ b/pyrogram/methods/chats/get_dialogs_count.py
@@ -37,7 +37,7 @@ async def get_dialogs_count(
         Example:
             .. code-block:: python
 
-                count = app.get_dialogs_count()
+                count = await app.get_dialogs_count()
                 print(count)
         """
 
diff --git a/pyrogram/methods/chats/get_nearby_chats.py b/pyrogram/methods/chats/get_nearby_chats.py
index c7c36cc61d..2ebd930644 100644
--- a/pyrogram/methods/chats/get_nearby_chats.py
+++ b/pyrogram/methods/chats/get_nearby_chats.py
@@ -45,7 +45,7 @@ async def get_nearby_chats(
         Example:
             .. code-block:: python
 
-                chats = app.get_nearby_chats(51.500729, -0.124583)
+                chats = await app.get_nearby_chats(latitude, longitude)
                 print(chats)
         """
 
diff --git a/pyrogram/methods/chats/get_send_as_chats.py b/pyrogram/methods/chats/get_send_as_chats.py
index 2db4d5f05a..2ad3b8b561 100644
--- a/pyrogram/methods/chats/get_send_as_chats.py
+++ b/pyrogram/methods/chats/get_send_as_chats.py
@@ -40,7 +40,7 @@ async def get_send_as_chats(
         Example:
             .. code-block:: python
 
-                chats = app.get_send_as_chats(chat_id)
+                chats = await app.get_send_as_chats(chat_id)
                 print(chats)
         """
         r = await self.invoke(
diff --git a/pyrogram/methods/chats/iter_chat_members.py b/pyrogram/methods/chats/iter_chat_members.py
deleted file mode 100644
index f3ccf06c81..0000000000
--- a/pyrogram/methods/chats/iter_chat_members.py
+++ /dev/null
@@ -1,127 +0,0 @@
-#  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-present Dan 
-#
-#  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 Union, AsyncGenerator, Optional
-
-import pyrogram
-from pyrogram import raw
-from pyrogram import types
-
-
-class Filters:
-    ALL = "all"
-    BANNED = "banned"
-    RESTRICTED = "restricted"
-    BOTS = "bots"
-    RECENT = "recent"
-    ADMINISTRATORS = "administrators"
-
-
-class IterChatMembers:
-    async def iter_chat_members(
-        self: "pyrogram.Client",
-        chat_id: Union[int, str],
-        limit: int = 0,
-        query: str = "",
-        filter: str = Filters.RECENT
-    ) -> Optional[AsyncGenerator["types.ChatMember", None]]:
-        """Iterate through the members of a chat sequentially.
-
-        This convenience method does the same as repeatedly calling :meth:`~pyrogram.Client.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 a single call.
-
-        Parameters:
-            chat_id (``int`` | ``str``):
-                Unique identifier (int) or username (str) of the target chat.
-
-            limit (``int``, *optional*):
-                Limits the number of members to be retrieved.
-
-            query (``str``, *optional*):
-                Query string to filter members based on their display names and usernames.
-                Defaults to "" (empty string).
-                A query string is applicable only for *"all"*, *"banned"* and *"restricted"* filters only.
-
-            filter (``str``, *optional*):
-                Filter used to select the kind of members you want to retrieve. Only applicable for supergroups
-                and channels. It can be any of the followings:
-                *"all"* - all kind of members,
-                *"banned"* - banned members only,
-                *"restricted"* - restricted members only,
-                *"bots"* - bots only,
-                *"recent"* - recent members only,
-                *"administrators"* - chat administrators only.
-                Defaults to *"recent"*.
-
-        Returns:
-            ``Generator``: A generator yielding :obj:`~pyrogram.types.ChatMember` objects.
-
-        Example:
-            .. code-block:: python
-
-                # Iterate though all chat members
-                for member in app.iter_chat_members(chat_id):
-                    print(member.user.first_name)
-
-                # Iterate though all administrators
-                for member in app.iter_chat_members(chat_id, filter="administrators"):
-                    print(member.user.first_name)
-
-                # Iterate though all bots
-                for member in app.iter_chat_members(chat_id, filter="bots"):
-                    print(member.user.first_name)
-        """
-        current = 0
-        yielded = set()
-        total = limit or (1 << 31) - 1
-        limit = min(200, total)
-        resolved_chat_id = await self.resolve_peer(chat_id)
-        offset = 0
-
-        while True:
-            chat_members = await self.get_chat_members(
-                chat_id=chat_id,
-                offset=offset,
-                limit=limit,
-                query=query,
-                filter=filter
-            )
-
-            if not chat_members:
-                break
-
-            if isinstance(resolved_chat_id, raw.types.InputPeerChat):
-                total = len(chat_members)
-
-            offset += len(chat_members)
-
-            for chat_member in chat_members:
-                peer_id = chat_member.user.id if chat_member.user else chat_member.chat.id
-
-                if peer_id in yielded:
-                    continue
-
-                yield chat_member
-
-                yielded.add(peer_id)
-
-                current += 1
-
-                if current >= total:
-                    return
diff --git a/pyrogram/methods/chats/iter_dialogs.py b/pyrogram/methods/chats/iter_dialogs.py
index 72a396f758..d0f037bea0 100644
--- a/pyrogram/methods/chats/iter_dialogs.py
+++ b/pyrogram/methods/chats/iter_dialogs.py
@@ -45,7 +45,7 @@ async def iter_dialogs(
             .. code-block:: python
 
                 # Iterate through all dialogs
-                for dialog in app.iter_dialogs():
+                async for dialog in app.iter_dialogs():
                     print(dialog.chat.first_name or dialog.chat.title)
         """
         current = 0
diff --git a/pyrogram/methods/chats/join_chat.py b/pyrogram/methods/chats/join_chat.py
index 2534442ac6..07d7b2a7e2 100644
--- a/pyrogram/methods/chats/join_chat.py
+++ b/pyrogram/methods/chats/join_chat.py
@@ -41,14 +41,14 @@ async def join_chat(
         Example:
             .. code-block:: python
 
-                # Join chat via username
-                app.join_chat("pyrogram")
-
                 # Join chat via invite link
-                app.join_chat("https://t.me/joinchat/AAAAAE0QmSW3IUmm3UFR7A")
+                await app.join_chat("https://t.me/+AbCdEf0123456789")
+
+                # Join chat via username
+                await app.join_chat("pyrogram")
 
                 # Join a linked chat
-                app.join_chat(app.get_chat("pyrogram").linked_chat.id)
+                await app.join_chat(app.get_chat("pyrogram").linked_chat.id)
         """
         match = self.INVITE_LINK_RE.match(str(chat_id))
 
diff --git a/pyrogram/methods/chats/leave_chat.py b/pyrogram/methods/chats/leave_chat.py
index 7a6eb85dff..942173f469 100644
--- a/pyrogram/methods/chats/leave_chat.py
+++ b/pyrogram/methods/chats/leave_chat.py
@@ -43,10 +43,10 @@ async def leave_chat(
             .. code-block:: python
 
                 # Leave chat or channel
-                app.leave_chat(chat_id)
+                await app.leave_chat(chat_id)
 
                 # Leave basic chat and also delete the dialog
-                app.leave_chat(chat_id, delete=True)
+                await app.leave_chat(chat_id, delete=True)
         """
         peer = await self.resolve_peer(chat_id)
 
diff --git a/pyrogram/methods/chats/pin_chat_message.py b/pyrogram/methods/chats/pin_chat_message.py
index 1f5ac91239..0eaa152513 100644
--- a/pyrogram/methods/chats/pin_chat_message.py
+++ b/pyrogram/methods/chats/pin_chat_message.py
@@ -56,10 +56,10 @@ async def pin_chat_message(
             .. code-block:: python
 
                 # Pin with notification
-                app.pin_chat_message(chat_id, message_id)
+                await app.pin_chat_message(chat_id, message_id)
 
                 # Pin without notification
-                app.pin_chat_message(chat_id, message_id, disable_notification=True)
+                await app.pin_chat_message(chat_id, message_id, disable_notification=True)
         """
         r = await self.invoke(
             raw.functions.messages.UpdatePinnedMessage(
diff --git a/pyrogram/methods/chats/promote_chat_member.py b/pyrogram/methods/chats/promote_chat_member.py
index b64a17996f..e6f2171e2e 100644
--- a/pyrogram/methods/chats/promote_chat_member.py
+++ b/pyrogram/methods/chats/promote_chat_member.py
@@ -27,7 +27,7 @@ async def promote_chat_member(
         self: "pyrogram.Client",
         chat_id: Union[int, str],
         user_id: Union[int, str],
-        privileges: "types.ChatPrivileges" = types.ChatPrivileges(),
+        privileges: "types.ChatPrivileges" = None,
     ) -> bool:
         """Promote or demote a user in a supergroup or a channel.
 
@@ -52,11 +52,15 @@ async def promote_chat_member(
             .. code-block:: python
 
                 # Promote chat member to admin
-                app.promote_chat_member(chat_id, user_id)
+                await app.promote_chat_member(chat_id, user_id)
         """
         chat_id = await self.resolve_peer(chat_id)
         user_id = await self.resolve_peer(user_id)
 
+        # See Chat.promote_member for the reason of this (instead of setting types.ChatPrivileges() as default arg).
+        if privileges is None:
+            privileges = types.ChatPrivileges()
+
         raw_chat_member = (await self.invoke(
             raw.functions.channels.GetParticipant(
                 channel=chat_id,
diff --git a/pyrogram/methods/chats/restrict_chat_member.py b/pyrogram/methods/chats/restrict_chat_member.py
index 52b264e841..4ebb5f1877 100644
--- a/pyrogram/methods/chats/restrict_chat_member.py
+++ b/pyrogram/methods/chats/restrict_chat_member.py
@@ -60,17 +60,18 @@ async def restrict_chat_member(
             .. code-block:: python
 
                 from datetime import datetime, timedelta
-
                 from pyrogram.types import ChatPermissions
 
                 # Completely restrict chat member (mute) forever
-                app.restrict_chat_member(chat_id, user_id, ChatPermissions())
+                await app.restrict_chat_member(chat_id, user_id, ChatPermissions())
 
                 # Chat member muted for 24h
-                app.restrict_chat_member(chat_id, user_id, ChatPermissions(), datetime.now() + timedelta(days=1))
+                await app.restrict_chat_member(chat_id, user_id, ChatPermissions(),
+                    datetime.now() + timedelta(days=1))
 
                 # Chat member can only send text messages
-                app.restrict_chat_member(chat_id, user_id, ChatPermissions(can_send_messages=True))
+                await app.restrict_chat_member(chat_id, user_id,
+                    ChatPermissions(can_send_messages=True))
         """
         r = await self.invoke(
             raw.functions.channels.EditBanned(
diff --git a/pyrogram/methods/chats/set_administrator_title.py b/pyrogram/methods/chats/set_administrator_title.py
index 2ea0dccf6c..15f9dea5ef 100644
--- a/pyrogram/methods/chats/set_administrator_title.py
+++ b/pyrogram/methods/chats/set_administrator_title.py
@@ -52,7 +52,7 @@ async def set_administrator_title(
         Example:
             .. code-block:: python
 
-                app.set_administrator_title(chat_id, user_id, "Admin Title")
+                await app.set_administrator_title(chat_id, user_id, "Admin Title")
         """
         chat_id = await self.resolve_peer(chat_id)
         user_id = await self.resolve_peer(user_id)
diff --git a/pyrogram/methods/chats/set_chat_description.py b/pyrogram/methods/chats/set_chat_description.py
index 4a93530b64..3eee92cae2 100644
--- a/pyrogram/methods/chats/set_chat_description.py
+++ b/pyrogram/methods/chats/set_chat_description.py
@@ -47,7 +47,7 @@ async def set_chat_description(
         Example:
             .. code-block:: python
 
-                app.set_chat_description(chat_id, "New Description")
+                await app.set_chat_description(chat_id, "New Description")
         """
         peer = await self.resolve_peer(chat_id)
 
diff --git a/pyrogram/methods/chats/set_chat_permissions.py b/pyrogram/methods/chats/set_chat_permissions.py
index 51d74fee8c..d1280ea725 100644
--- a/pyrogram/methods/chats/set_chat_permissions.py
+++ b/pyrogram/methods/chats/set_chat_permissions.py
@@ -50,10 +50,10 @@ async def set_chat_permissions(
                 from pyrogram.types import ChatPermissions
 
                 # Completely restrict chat
-                app.set_chat_permissions(chat_id, ChatPermissions())
+                await app.set_chat_permissions(chat_id, ChatPermissions())
 
                 # Chat members can only send text messages and media messages
-                app.set_chat_permissions(
+                await app.set_chat_permissions(
                     chat_id,
                     ChatPermissions(
                         can_send_messages=True,
diff --git a/pyrogram/methods/chats/set_chat_photo.py b/pyrogram/methods/chats/set_chat_photo.py
index 6da4330bcb..dd44bb872c 100644
--- a/pyrogram/methods/chats/set_chat_photo.py
+++ b/pyrogram/methods/chats/set_chat_photo.py
@@ -68,17 +68,17 @@ async def set_chat_photo(
             .. code-block:: python
 
                 # Set chat photo using a local file
-                app.set_chat_photo(chat_id, photo="photo.jpg")
+                await app.set_chat_photo(chat_id, photo="photo.jpg")
 
                 # Set chat photo using an exiting Photo file_id
-                app.set_chat_photo(chat_id, photo=photo.file_id)
+                await app.set_chat_photo(chat_id, photo=photo.file_id)
 
 
                 # Set chat video using a local file
-                app.set_chat_photo(chat_id, video="video.mp4")
+                await app.set_chat_photo(chat_id, video="video.mp4")
 
                 # Set chat photo using an exiting Video file_id
-                app.set_chat_photo(chat_id, video=video.file_id)
+                await app.set_chat_photo(chat_id, video=video.file_id)
         """
         peer = await self.resolve_peer(chat_id)
 
diff --git a/pyrogram/methods/chats/set_chat_title.py b/pyrogram/methods/chats/set_chat_title.py
index edebf17624..e2c270e6d2 100644
--- a/pyrogram/methods/chats/set_chat_title.py
+++ b/pyrogram/methods/chats/set_chat_title.py
@@ -52,7 +52,7 @@ async def set_chat_title(
         Example:
             .. code-block:: python
 
-                app.set_chat_title(chat_id, "New Title")
+                await app.set_chat_title(chat_id, "New Title")
         """
         peer = await self.resolve_peer(chat_id)
 
diff --git a/pyrogram/methods/chats/set_chat_username.py b/pyrogram/methods/chats/set_chat_username.py
index c1f6d17ddc..2d4bc08a88 100644
--- a/pyrogram/methods/chats/set_chat_username.py
+++ b/pyrogram/methods/chats/set_chat_username.py
@@ -48,7 +48,7 @@ async def set_chat_username(
         Example:
             .. code-block:: python
 
-                app.set_chat_username(chat_id, "new_username")
+                await app.set_chat_username(chat_id, "new_username")
         """
 
         peer = await self.resolve_peer(chat_id)
diff --git a/pyrogram/methods/chats/set_send_as_chat.py b/pyrogram/methods/chats/set_send_as_chat.py
index bbe210a41e..4cb50aed0c 100644
--- a/pyrogram/methods/chats/set_send_as_chat.py
+++ b/pyrogram/methods/chats/set_send_as_chat.py
@@ -45,7 +45,7 @@ async def set_send_as_chat(
         Example:
             .. code-block:: python
 
-                app.set_send_as_chat(chat_id, send_as_chat_id)
+                await app.set_send_as_chat(chat_id, send_as_chat_id)
         """
         return await self.invoke(
             raw.functions.messages.SaveDefaultSendAs(
diff --git a/pyrogram/methods/chats/set_slow_mode.py b/pyrogram/methods/chats/set_slow_mode.py
index e3237ad34f..ca68c87f46 100644
--- a/pyrogram/methods/chats/set_slow_mode.py
+++ b/pyrogram/methods/chats/set_slow_mode.py
@@ -45,10 +45,10 @@ async def set_slow_mode(
             .. code-block:: python
 
                 # Set slow mode to 60 seconds
-                app.set_slow_mode(chat_id, 60)
+                await app.set_slow_mode(chat_id, 60)
 
                 # Disable slow mode
-                app.set_slow_mode(chat_id, None)
+                await app.set_slow_mode(chat_id, None)
         """
 
         await self.invoke(
diff --git a/pyrogram/methods/chats/unarchive_chats.py b/pyrogram/methods/chats/unarchive_chats.py
index 726eb4a07e..c518fe912c 100644
--- a/pyrogram/methods/chats/unarchive_chats.py
+++ b/pyrogram/methods/chats/unarchive_chats.py
@@ -41,10 +41,10 @@ async def unarchive_chats(
             .. code-block:: python
 
                 # Unarchive chat
-                app.unarchive_chats(chat_id)
+                await app.unarchive_chats(chat_id)
 
                 # Unarchive multiple chats at once
-                app.unarchive_chats([chat_id1, chat_id2, chat_id3])
+                await app.unarchive_chats([chat_id1, chat_id2, chat_id3])
         """
 
         if not isinstance(chat_ids, list):
diff --git a/pyrogram/methods/chats/unban_chat_member.py b/pyrogram/methods/chats/unban_chat_member.py
index c331e823d4..46c2241e72 100644
--- a/pyrogram/methods/chats/unban_chat_member.py
+++ b/pyrogram/methods/chats/unban_chat_member.py
@@ -47,7 +47,7 @@ async def unban_chat_member(
             .. code-block:: python
 
                 # Unban chat member right now
-                app.unban_chat_member(chat_id, user_id)
+                await app.unban_chat_member(chat_id, user_id)
         """
         await self.invoke(
             raw.functions.channels.EditBanned(
diff --git a/pyrogram/methods/chats/unpin_all_chat_messages.py b/pyrogram/methods/chats/unpin_all_chat_messages.py
index 93b6eaec90..866ca7c0b7 100644
--- a/pyrogram/methods/chats/unpin_all_chat_messages.py
+++ b/pyrogram/methods/chats/unpin_all_chat_messages.py
@@ -42,7 +42,7 @@ async def unpin_all_chat_messages(
             .. code-block:: python
 
                 # Unpin all chat messages
-                app.unpin_all_chat_messages(chat_id)
+                await app.unpin_all_chat_messages(chat_id)
         """
         await self.invoke(
             raw.functions.messages.UnpinAllMessages(
diff --git a/pyrogram/methods/chats/unpin_chat_message.py b/pyrogram/methods/chats/unpin_chat_message.py
index 6723f5a2a4..5b3768c096 100644
--- a/pyrogram/methods/chats/unpin_chat_message.py
+++ b/pyrogram/methods/chats/unpin_chat_message.py
@@ -46,7 +46,7 @@ async def unpin_chat_message(
         Example:
             .. code-block:: python
 
-                app.unpin_chat_message(chat_id, message_id)
+                await app.unpin_chat_message(chat_id, message_id)
         """
         await self.invoke(
             raw.functions.messages.UpdatePinnedMessage(
diff --git a/pyrogram/methods/contacts/add_contact.py b/pyrogram/methods/contacts/add_contact.py
index 433c4c90b6..ae7566b2fd 100644
--- a/pyrogram/methods/contacts/add_contact.py
+++ b/pyrogram/methods/contacts/add_contact.py
@@ -57,8 +57,11 @@ async def add_contact(
         Example:
             .. code-block:: python
 
-                app.add_contact(12345678, "Foo")
-                app.add_contact("username", "Bar")
+                # Add contact by id
+                await app.add_contact(12345678, "Foo")
+
+                # Add contact by username
+                await app.add_contact("username", "Bar")
         """
         r = await self.invoke(
             raw.functions.contacts.AddContact(
diff --git a/pyrogram/methods/contacts/delete_contacts.py b/pyrogram/methods/contacts/delete_contacts.py
index 03238beb88..31d2bcbec0 100644
--- a/pyrogram/methods/contacts/delete_contacts.py
+++ b/pyrogram/methods/contacts/delete_contacts.py
@@ -42,8 +42,8 @@ async def delete_contacts(
         Example:
             .. code-block:: python
 
-                app.delete_contacts(user_id)
-                app.delete_contacts([user_id1, user_id2, user_id3])
+                await app.delete_contacts(user_id)
+                await app.delete_contacts([user_id1, user_id2, user_id3])
         """
         is_user_ids_list = isinstance(user_ids, list)
 
diff --git a/pyrogram/methods/contacts/get_contacts.py b/pyrogram/methods/contacts/get_contacts.py
index ca8888764b..110d93a321 100644
--- a/pyrogram/methods/contacts/get_contacts.py
+++ b/pyrogram/methods/contacts/get_contacts.py
@@ -38,7 +38,7 @@ async def get_contacts(
         Example:
             .. code-block:: python
 
-                contacts = app.get_contacts()
+                contacts = await app.get_contacts()
                 print(contacts)
         """
         contacts = await self.invoke(raw.functions.contacts.GetContacts(hash=0))
diff --git a/pyrogram/methods/contacts/get_contacts_count.py b/pyrogram/methods/contacts/get_contacts_count.py
index d32ae0500f..a709b6769f 100644
--- a/pyrogram/methods/contacts/get_contacts_count.py
+++ b/pyrogram/methods/contacts/get_contacts_count.py
@@ -32,7 +32,7 @@ async def get_contacts_count(
         Example:
             .. code-block:: python
 
-                count = app.get_contacts_count()
+                count = await app.get_contacts_count()
                 print(count)
         """
 
diff --git a/pyrogram/methods/contacts/import_contacts.py b/pyrogram/methods/contacts/import_contacts.py
index de802cff72..11df315be8 100644
--- a/pyrogram/methods/contacts/import_contacts.py
+++ b/pyrogram/methods/contacts/import_contacts.py
@@ -42,7 +42,7 @@ async def import_contacts(
 
                 from pyrogram.types import InputPhoneContact
 
-                app.import_contacts([
+                await app.import_contacts([
                     InputPhoneContact("+1-123-456-7890", "Foo"),
                     InputPhoneContact("+1-456-789-0123", "Bar"),
                     InputPhoneContact("+1-789-012-3456", "Baz")])
diff --git a/pyrogram/methods/invite_links/create_chat_invite_link.py b/pyrogram/methods/invite_links/create_chat_invite_link.py
index 0e3e63a751..703d3d1434 100644
--- a/pyrogram/methods/invite_links/create_chat_invite_link.py
+++ b/pyrogram/methods/invite_links/create_chat_invite_link.py
@@ -67,10 +67,10 @@ async def create_chat_invite_link(
             .. code-block:: python
 
                 # Create a new link without limits
-                link = app.create_chat_invite_link(chat_id)
+                link = await app.create_chat_invite_link(chat_id)
 
-                # Create a new link for up to 7 new users
-                link = app.create_chat_invite_link(chat_id, member_limit=7)
+                # Create a new link for up to 3 new users
+                link = await app.create_chat_invite_link(chat_id, member_limit=3)
         """
         r = await self.invoke(
             raw.functions.messages.ExportChatInvite(
diff --git a/pyrogram/methods/invite_links/edit_chat_invite_link.py b/pyrogram/methods/invite_links/edit_chat_invite_link.py
index 96dcf79ddb..29f658ec8a 100644
--- a/pyrogram/methods/invite_links/edit_chat_invite_link.py
+++ b/pyrogram/methods/invite_links/edit_chat_invite_link.py
@@ -69,10 +69,10 @@ async def edit_chat_invite_link(
             .. code-block:: python
 
                 # Edit the member limit of a link
-                link = app.edit_chat_invite_link(chat_id, invite_link, member_limit=9)
+                link = await app.edit_chat_invite_link(chat_id, invite_link, member_limit=5)
 
                 # Set no expiration date of a link
-                link = app.edit_chat_invite_link(chat_id, invite_link, expire_date=0)
+                link = await app.edit_chat_invite_link(chat_id, invite_link, expire_date=0)
         """
         r = await self.invoke(
             raw.functions.messages.EditExportedChatInvite(
diff --git a/pyrogram/methods/invite_links/export_chat_invite_link.py b/pyrogram/methods/invite_links/export_chat_invite_link.py
index 66fb0227a4..ae40391fe0 100644
--- a/pyrogram/methods/invite_links/export_chat_invite_link.py
+++ b/pyrogram/methods/invite_links/export_chat_invite_link.py
@@ -51,7 +51,7 @@ async def export_chat_invite_link(
             .. code-block:: python
 
                 # Generate a new primary link
-                link = app.export_chat_invite_link(chat_id)
+                link = await app.export_chat_invite_link(chat_id)
         """
         r = await self.invoke(
             raw.functions.messages.ExportChatInvite(
diff --git a/pyrogram/methods/messages/copy_media_group.py b/pyrogram/methods/messages/copy_media_group.py
index b204999f3e..de47465d27 100644
--- a/pyrogram/methods/messages/copy_media_group.py
+++ b/pyrogram/methods/messages/copy_media_group.py
@@ -76,9 +76,12 @@ async def copy_media_group(
             .. code-block:: python
 
                 # Copy a media group
-                app.copy_media_group("me", source_chat, message_id)
-                app.copy_media_group("me", source_chat, message_id, captions="single caption")
-                app.copy_media_group("me", source_chat, message_id, captions=["caption 1", None, ""])
+                await app.copy_media_group(to_chat, from_chat, 123)
+
+                await app.copy_media_group(to_chat, from_chat, 123, captions="single caption")
+                
+                await app.copy_media_group(to_chat, from_chat, 123,
+                    captions=["caption 1", None, ""])
         """
 
         media_group = await self.get_media_group(from_chat_id, message_id)
diff --git a/pyrogram/methods/messages/copy_message.py b/pyrogram/methods/messages/copy_message.py
index 94c11d4b50..b114a9e0af 100644
--- a/pyrogram/methods/messages/copy_message.py
+++ b/pyrogram/methods/messages/copy_message.py
@@ -101,7 +101,7 @@ async def copy_message(
             .. code-block:: python
 
                 # Copy a message
-                app.copy_message("me", "pyrogram", 20)
+                await app.copy_message(to_chat, from_chat, 123)
 
         """
         message: types.Message = await self.get_messages(from_chat_id, message_id)
diff --git a/pyrogram/methods/messages/delete_messages.py b/pyrogram/methods/messages/delete_messages.py
index f8c3e7feed..b25c0ad4d5 100644
--- a/pyrogram/methods/messages/delete_messages.py
+++ b/pyrogram/methods/messages/delete_messages.py
@@ -54,13 +54,13 @@ async def delete_messages(
             .. code-block:: python
 
                 # Delete one message
-                app.delete_messages(chat_id, message_id)
+                await app.delete_messages(chat_id, message_id)
 
                 # Delete multiple messages at once
-                app.delete_messages(chat_id, list_of_message_ids)
+                await app.delete_messages(chat_id, list_of_message_ids)
 
                 # Delete messages only on your side (without revoking)
-                app.delete_messages(chat_id, message_id, revoke=False)
+                await app.delete_messages(chat_id, message_id, revoke=False)
         """
         peer = await self.resolve_peer(chat_id)
         message_ids = list(message_ids) if not isinstance(message_ids, int) else [message_ids]
diff --git a/pyrogram/methods/messages/download_media.py b/pyrogram/methods/messages/download_media.py
index d75ba281a0..8b587d2fae 100644
--- a/pyrogram/methods/messages/download_media.py
+++ b/pyrogram/methods/messages/download_media.py
@@ -89,16 +89,16 @@ async def download_media(
             .. code-block:: python
 
                 # Download from Message
-                app.download_media(message)
+                await app.download_media(message)
 
                 # Download from file id
-                app.download_media(message.photo.file_id)
+                await app.download_media(message.photo.file_id)
 
                 # Keep track of the progress while downloading
-                def progress(current, total):
+                async def progress(current, total):
                     print(f"{current * 100 / total:.1f}%")
 
-                app.download_media(message, progress=progress)
+                await app.download_media(message, progress=progress)
         """
         available_media = ("audio", "document", "photo", "sticker", "animation", "video", "voice", "video_note",
                            "new_chat_photo")
diff --git a/pyrogram/methods/messages/edit_inline_caption.py b/pyrogram/methods/messages/edit_inline_caption.py
index 3068098ed9..0996d34e68 100644
--- a/pyrogram/methods/messages/edit_inline_caption.py
+++ b/pyrogram/methods/messages/edit_inline_caption.py
@@ -53,7 +53,7 @@ async def edit_inline_caption(
             .. code-block:: python
 
                 # Bots only
-                app.edit_inline_caption(inline_message_id, "new media caption")
+                await app.edit_inline_caption(inline_message_id, "new media caption")
         """
         return await self.edit_inline_text(
             inline_message_id=inline_message_id,
diff --git a/pyrogram/methods/messages/edit_inline_media.py b/pyrogram/methods/messages/edit_inline_media.py
index 1bf8d4d249..59a18aa7b1 100644
--- a/pyrogram/methods/messages/edit_inline_media.py
+++ b/pyrogram/methods/messages/edit_inline_media.py
@@ -61,13 +61,13 @@ async def edit_inline_media(
                 # Bots only
 
                 # Replace the current media with a local photo
-                app.edit_inline_media(inline_message_id, InputMediaPhoto("new_photo.jpg"))
+                await app.edit_inline_media(inline_message_id, InputMediaPhoto("new_photo.jpg"))
 
                 # Replace the current media with a local video
-                app.edit_inline_media(inline_message_id, InputMediaVideo("new_video.mp4"))
+                await app.edit_inline_media(inline_message_id, InputMediaVideo("new_video.mp4"))
 
                 # Replace the current media with a local audio
-                app.edit_inline_media(inline_message_id, InputMediaAudio("new_audio.mp3"))
+                await app.edit_inline_media(inline_message_id, InputMediaAudio("new_audio.mp3"))
         """
         caption = media.caption
         parse_mode = media.parse_mode
diff --git a/pyrogram/methods/messages/edit_inline_reply_markup.py b/pyrogram/methods/messages/edit_inline_reply_markup.py
index 92c6851e4f..4e06e447f7 100644
--- a/pyrogram/methods/messages/edit_inline_reply_markup.py
+++ b/pyrogram/methods/messages/edit_inline_reply_markup.py
@@ -47,7 +47,7 @@ async def edit_inline_reply_markup(
                 from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton
 
                 # Bots only
-                app.edit_inline_reply_markup(
+                await app.edit_inline_reply_markup(
                     inline_message_id,
                     InlineKeyboardMarkup([[
                         InlineKeyboardButton("New button", callback_data="new_data")]]))
diff --git a/pyrogram/methods/messages/edit_inline_text.py b/pyrogram/methods/messages/edit_inline_text.py
index 9a50d8639b..9caee55aaf 100644
--- a/pyrogram/methods/messages/edit_inline_text.py
+++ b/pyrogram/methods/messages/edit_inline_text.py
@@ -62,10 +62,10 @@ async def edit_inline_text(
                 # Bots only
 
                 # Simple edit text
-                app.edit_inline_text(inline_message_id, "new text")
+                await app.edit_inline_text(inline_message_id, "new text")
 
                 # Take the same text message, remove the web page preview only
-                app.edit_inline_text(
+                await app.edit_inline_text(
                     inline_message_id, message.text,
                     disable_web_page_preview=True)
         """
diff --git a/pyrogram/methods/messages/edit_message_caption.py b/pyrogram/methods/messages/edit_message_caption.py
index fc92b02f1f..d3b311db6e 100644
--- a/pyrogram/methods/messages/edit_message_caption.py
+++ b/pyrogram/methods/messages/edit_message_caption.py
@@ -62,7 +62,7 @@ async def edit_message_caption(
         Example:
             .. code-block:: python
 
-                app.edit_message_caption(chat_id, message_id, "new media caption")
+                await app.edit_message_caption(chat_id, message_id, "new media caption")
         """
         return await self.edit_message_text(
             chat_id=chat_id,
diff --git a/pyrogram/methods/messages/edit_message_media.py b/pyrogram/methods/messages/edit_message_media.py
index 0e3f360c22..3dce81edb9 100644
--- a/pyrogram/methods/messages/edit_message_media.py
+++ b/pyrogram/methods/messages/edit_message_media.py
@@ -69,13 +69,16 @@ async def edit_message_media(
                 from pyrogram.types import InputMediaPhoto, InputMediaVideo, InputMediaAudio
 
                 # Replace the current media with a local photo
-                app.edit_message_media(chat_id, message_id, InputMediaPhoto("new_photo.jpg"))
+                await app.edit_message_media(chat_id, message_id,
+                    InputMediaPhoto("new_photo.jpg"))
 
                 # Replace the current media with a local video
-                app.edit_message_media(chat_id, message_id, InputMediaVideo("new_video.mp4"))
+                await app.edit_message_media(chat_id, message_id,
+                    InputMediaVideo("new_video.mp4"))
 
                 # Replace the current media with a local audio
-                app.edit_message_media(chat_id, message_id, InputMediaAudio("new_audio.mp3"))
+                await app.edit_message_media(chat_id, message_id,
+                    InputMediaAudio("new_audio.mp3"))
         """
         caption = media.caption
         parse_mode = media.parse_mode
diff --git a/pyrogram/methods/messages/edit_message_reply_markup.py b/pyrogram/methods/messages/edit_message_reply_markup.py
index c164afbf77..088e646ddb 100644
--- a/pyrogram/methods/messages/edit_message_reply_markup.py
+++ b/pyrogram/methods/messages/edit_message_reply_markup.py
@@ -53,7 +53,7 @@ async def edit_message_reply_markup(
                 from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton
 
                 # Bots only
-                app.edit_message_reply_markup(
+                await app.edit_message_reply_markup(
                     chat_id, message_id,
                     InlineKeyboardMarkup([[
                         InlineKeyboardButton("New button", callback_data="new_data")]]))
diff --git a/pyrogram/methods/messages/edit_message_text.py b/pyrogram/methods/messages/edit_message_text.py
index 551beaa143..94852de582 100644
--- a/pyrogram/methods/messages/edit_message_text.py
+++ b/pyrogram/methods/messages/edit_message_text.py
@@ -69,10 +69,10 @@ async def edit_message_text(
             .. code-block:: python
 
                 # Simple edit text
-                app.edit_message_text(chat_id, message_id, "new text")
+                await app.edit_message_text(chat_id, message_id, "new text")
 
                 # Take the same text message, remove the web page preview only
-                app.edit_message_text(
+                await app.edit_message_text(
                     chat_id, message_id, message.text,
                     disable_web_page_preview=True)
         """
diff --git a/pyrogram/methods/messages/forward_messages.py b/pyrogram/methods/messages/forward_messages.py
index dbc5534e5a..6ee8492294 100644
--- a/pyrogram/methods/messages/forward_messages.py
+++ b/pyrogram/methods/messages/forward_messages.py
@@ -70,10 +70,10 @@ async def forward_messages(
             .. code-block:: python
 
                 # Forward a single message
-                app.forward_messages("me", "pyrogram", 20)
+                await app.forward_messages(to_chat, from_chat, 123)
 
                 # Forward multiple messages at once
-                app.forward_messages("me", "pyrogram", [3, 20, 27])
+                await app.forward_messages(to_chat, from_chat, [1, 2, 3])
         """
 
         is_iterable = not isinstance(message_ids, int)
diff --git a/pyrogram/methods/messages/get_chat_history.py b/pyrogram/methods/messages/get_chat_history.py
index bc2ed41fa7..f21a081f19 100644
--- a/pyrogram/methods/messages/get_chat_history.py
+++ b/pyrogram/methods/messages/get_chat_history.py
@@ -88,7 +88,7 @@ async def get_chat_history(
         Example:
             .. code-block:: python
 
-                for message in app.get_chat_history(chat_id):
+                async for message in app.get_chat_history(chat_id):
                     print(message.text)
         """
         current = 0
diff --git a/pyrogram/methods/messages/get_chat_history_count.py b/pyrogram/methods/messages/get_chat_history_count.py
index fbe6bfcad9..46f61eb056 100644
--- a/pyrogram/methods/messages/get_chat_history_count.py
+++ b/pyrogram/methods/messages/get_chat_history_count.py
@@ -48,7 +48,7 @@ async def get_chat_history_count(
         Example:
             .. code-block:: python
 
-                app.get_history_count(chat_id)
+                await app.get_history_count(chat_id)
         """
 
         r = await self.invoke(
diff --git a/pyrogram/methods/messages/get_discussion_message.py b/pyrogram/methods/messages/get_discussion_message.py
index cbcc7bfed2..93510e211e 100644
--- a/pyrogram/methods/messages/get_discussion_message.py
+++ b/pyrogram/methods/messages/get_discussion_message.py
@@ -45,10 +45,10 @@ async def get_discussion_message(
             .. code-block:: python
 
                 # Get the discussion message
-                m = app.get_discussion_message(channel_id, message_id)
+                m = await app.get_discussion_message(channel_id, message_id)
 
                 # Comment to the post by replying
-                m.reply("comment")
+                await m.reply("comment")
         """
         r = await self.invoke(
             raw.functions.messages.GetDiscussionMessage(
diff --git a/pyrogram/methods/messages/get_discussion_replies.py b/pyrogram/methods/messages/get_discussion_replies.py
index b1279de542..467cca8d2b 100644
--- a/pyrogram/methods/messages/get_discussion_replies.py
+++ b/pyrogram/methods/messages/get_discussion_replies.py
@@ -45,8 +45,8 @@ async def get_discussion_replies(
         Example:
             .. code-block:: python
 
-            for m in app.get_discussion_replies(chat_id, message_id):
-                print(m)
+                async for message in app.get_discussion_replies(chat_id, message_id):
+                    print(message)
         """
 
         current = 0
diff --git a/pyrogram/methods/messages/get_discussion_replies_count.py b/pyrogram/methods/messages/get_discussion_replies_count.py
index cb5c1e8ba3..260be802eb 100644
--- a/pyrogram/methods/messages/get_discussion_replies_count.py
+++ b/pyrogram/methods/messages/get_discussion_replies_count.py
@@ -40,7 +40,7 @@ async def get_discussion_replies_count(
         Example:
             .. code-block:: python
 
-            count = app.get_discussion_replies_count(chat_id, message_id)
+                count = await app.get_discussion_replies_count(chat_id, message_id)
         """
 
         r = await self.invoke(
diff --git a/pyrogram/methods/messages/get_messages.py b/pyrogram/methods/messages/get_messages.py
index a6a361af8c..8db24dd955 100644
--- a/pyrogram/methods/messages/get_messages.py
+++ b/pyrogram/methods/messages/get_messages.py
@@ -71,19 +71,19 @@ async def get_messages(
             .. code-block:: python
 
                 # Get one message
-                app.get_messages(chat_id, 12345)
+                await app.get_messages(chat_id, 12345)
 
                 # Get more than one message (list of messages)
-                app.get_messages(chat_id, [12345, 12346])
+                await app.get_messages(chat_id, [12345, 12346])
 
                 # Get message by ignoring any replied-to message
-                app.get_messages(chat_id, message_id, replies=0)
+                await app.get_messages(chat_id, message_id, replies=0)
 
                 # Get message with all chained replied-to messages
-                app.get_messages(chat_id, message_id, replies=-1)
+                await app.get_messages(chat_id, message_id, replies=-1)
 
                 # Get the replied-to message of a message
-                app.get_messages(chat_id, reply_to_message_ids=message_id)
+                await app.get_messages(chat_id, reply_to_message_ids=message_id)
 
         Raises:
             ValueError: In case of invalid arguments.
diff --git a/pyrogram/methods/messages/read_chat_history.py b/pyrogram/methods/messages/read_chat_history.py
index caa228fd28..8a58fa264c 100644
--- a/pyrogram/methods/messages/read_chat_history.py
+++ b/pyrogram/methods/messages/read_chat_history.py
@@ -47,10 +47,10 @@ async def read_chat_history(
             .. code-block:: python
 
                 # Mark the whole chat as read
-                app.read_history(chat_id)
+                await app.read_history(chat_id)
 
                 # Mark messages as read only up to the given message id
-                app.read_history(chat_id, 123456)
+                await app.read_history(chat_id, 12345)
         """
 
         peer = await self.resolve_peer(chat_id)
diff --git a/pyrogram/methods/messages/retract_vote.py b/pyrogram/methods/messages/retract_vote.py
index 8aab69f016..8420a4a318 100644
--- a/pyrogram/methods/messages/retract_vote.py
+++ b/pyrogram/methods/messages/retract_vote.py
@@ -46,7 +46,7 @@ async def retract_vote(
         Example:
             .. code-block:: python
 
-                app.retract_vote(chat_id, message_id)
+                await app.retract_vote(chat_id, message_id)
         """
         r = await self.invoke(
             raw.functions.messages.SendVote(
diff --git a/pyrogram/methods/messages/search_global.py b/pyrogram/methods/messages/search_global.py
index b53dbf3218..1a65702e01 100644
--- a/pyrogram/methods/messages/search_global.py
+++ b/pyrogram/methods/messages/search_global.py
@@ -59,12 +59,14 @@ async def search_global(
         Example:
             .. code-block:: python
 
+                from pyrogram import enums
+
                 # Search for "pyrogram". Get the first 50 results
-                for message in app.search_global("pyrogram", limit=50):
+                async for message in app.search_global("pyrogram", limit=50):
                     print(message.text)
 
                 # Search for recent photos from Global. Get the first 20 results
-                for message in app.search_global(filter="photo", limit=20):
+                async for message in app.search_global(filter=enums.MessagesFilter.PHOTO, limit=20):
                     print(message.photo)
         """
         current = 0
diff --git a/pyrogram/methods/messages/search_messages.py b/pyrogram/methods/messages/search_messages.py
index 0e31871c6c..1f9fa3c76e 100644
--- a/pyrogram/methods/messages/search_messages.py
+++ b/pyrogram/methods/messages/search_messages.py
@@ -108,15 +108,15 @@ async def search_messages(
                 from pyrogram import enums
 
                 # Search for text messages in chat. Get the last 120 results
-                for message in app.search_messages(chat_id, query="hello", limit=120):
+                async for message in app.search_messages(chat_id, query="hello", limit=120):
                     print(message.text)
 
                 # Search for pinned messages in chat
-                for message in app.search_messages(chat_id, filter=enums.MessagesFilter.PINNED):
+                async for message in app.search_messages(chat_id, filter=enums.MessagesFilter.PINNED):
                     print(message.text)
 
                 # Search for messages containing "hello" sent by yourself in chat
-                for message in app.search_messages(chat, "hello", from_user="me"):
+                async for message in app.search_messages(chat, "hello", from_user="me"):
                     print(message.text)
         """
 
diff --git a/pyrogram/methods/messages/send_animation.py b/pyrogram/methods/messages/send_animation.py
index e45a071462..efa9cb1671 100644
--- a/pyrogram/methods/messages/send_animation.py
+++ b/pyrogram/methods/messages/send_animation.py
@@ -153,19 +153,19 @@ async def send_animation(
             .. code-block:: python
 
                 # Send animation by uploading from local file
-                app.send_animation("me", "animation.gif")
+                await app.send_animation("me", "animation.gif")
 
                 # Add caption to the animation
-                app.send_animation("me", "animation.gif", caption="animation caption")
+                await app.send_animation("me", "animation.gif", caption="animation caption")
 
                 # Unsave the animation once is sent
-                app.send_animation("me", "animation.gif", unsave=True)
+                await app.send_animation("me", "animation.gif", unsave=True)
 
                 # Keep track of the progress while uploading
-                def progress(current, total):
+                async def progress(current, total):
                     print(f"{current * 100 / total:.1f}%")
 
-                app.send_animation("me", "animation.gif", progress=progress)
+                await app.send_animation("me", "animation.gif", progress=progress)
         """
         file = None
 
diff --git a/pyrogram/methods/messages/send_audio.py b/pyrogram/methods/messages/send_audio.py
index 8c2624a8d8..f4552832fe 100644
--- a/pyrogram/methods/messages/send_audio.py
+++ b/pyrogram/methods/messages/send_audio.py
@@ -149,21 +149,21 @@ async def send_audio(
             .. code-block:: python
 
                 # Send audio file by uploading from file
-                app.send_audio("me", "audio.mp3")
+                await app.send_audio("me", "audio.mp3")
 
                 # Add caption to the audio
-                app.send_audio("me", "audio.mp3", caption="audio caption")
+                await app.send_audio("me", "audio.mp3", caption="audio caption")
 
                 # Set audio metadata
-                app.send_audio(
+                await app.send_audio(
                     "me", "audio.mp3",
                     title="Title", performer="Performer", duration=234)
 
                 # Keep track of the progress while uploading
-                def progress(current, total):
+                async def progress(current, total):
                     print(f"{current * 100 / total:.1f}%")
 
-                app.send_audio("me", "audio.mp3", progress=progress)
+                await app.send_audio("me", "audio.mp3", progress=progress)
         """
         file = None
 
diff --git a/pyrogram/methods/messages/send_cached_media.py b/pyrogram/methods/messages/send_cached_media.py
index 88a5f30921..f0e04d7232 100644
--- a/pyrogram/methods/messages/send_cached_media.py
+++ b/pyrogram/methods/messages/send_cached_media.py
@@ -93,7 +93,7 @@ async def send_cached_media(
         Example:
             .. code-block:: python
 
-                app.send_cached_media("me", file_id)
+                await app.send_cached_media("me", file_id)
         """
 
         r = await self.invoke(
diff --git a/pyrogram/methods/messages/send_chat_action.py b/pyrogram/methods/messages/send_chat_action.py
index 39f8d85f86..15b5ac0853 100644
--- a/pyrogram/methods/messages/send_chat_action.py
+++ b/pyrogram/methods/messages/send_chat_action.py
@@ -19,14 +19,14 @@
 from typing import Union
 
 import pyrogram
-from pyrogram import raw
+from pyrogram import raw, enums
 
 
 class SendChatAction:
     async def send_chat_action(
         self: "pyrogram.Client",
         chat_id: Union[int, str],
-        action: "pyrogram.enums.ChatAction"
+        action: "enums.ChatAction"
     ) -> bool:
         """Tell the other party that something is happening on your side.
 
@@ -51,16 +51,16 @@ async def send_chat_action(
                 from pyrogram import enums
 
                 # Send "typing" chat action
-                app.send_chat_action(chat_id, enums.ChatAction.TYPING)
+                await app.send_chat_action(chat_id, enums.ChatAction.TYPING)
 
                 # Send "upload_video" chat action
-                app.send_chat_action(chat_id, enums.ChatAction.UPLOAD_VIDEO)
+                await app.send_chat_action(chat_id, enums.ChatAction.UPLOAD_VIDEO)
 
                 # Send "playing" chat action
-                app.send_chat_action(chat_id, enums.ChatAction.PLAYING)
+                await app.send_chat_action(chat_id, enums.ChatAction.PLAYING)
 
                 # Cancel any current chat action
-                app.send_chat_action(chat_id, enums.ChatAction.CANCEL)
+                await app.send_chat_action(chat_id, enums.ChatAction.CANCEL)
         """
 
         action_name = action.name.lower()
diff --git a/pyrogram/methods/messages/send_contact.py b/pyrogram/methods/messages/send_contact.py
index b4683338d5..cedcd0b25b 100644
--- a/pyrogram/methods/messages/send_contact.py
+++ b/pyrogram/methods/messages/send_contact.py
@@ -86,7 +86,7 @@ async def send_contact(
         Example:
             .. code-block:: python
 
-                app.send_contact("me", "+1-123-456-7890", "Name")
+                await app.send_contact("me", "+1-123-456-7890", "Name")
         """
         r = await self.invoke(
             raw.functions.messages.SendMedia(
diff --git a/pyrogram/methods/messages/send_dice.py b/pyrogram/methods/messages/send_dice.py
index b15cfe2d0b..942f681ff0 100644
--- a/pyrogram/methods/messages/send_dice.py
+++ b/pyrogram/methods/messages/send_dice.py
@@ -79,13 +79,13 @@ async def send_dice(
             .. code-block:: python
 
                 # Send a dice
-                app.send_dice(chat_id)
+                await app.send_dice(chat_id)
 
                 # Send a dart
-                app.send_dice(chat_id, "🎯")
+                await app.send_dice(chat_id, "🎯")
 
                 # Send a basketball
-                app.send_dice(chat_id, "🏀")
+                await app.send_dice(chat_id, "🏀")
         """
 
         r = await self.invoke(
diff --git a/pyrogram/methods/messages/send_document.py b/pyrogram/methods/messages/send_document.py
index c1179cb0e2..99c90fbb25 100644
--- a/pyrogram/methods/messages/send_document.py
+++ b/pyrogram/methods/messages/send_document.py
@@ -141,16 +141,16 @@ async def send_document(
             .. code-block:: python
 
                 # Send document by uploading from local file
-                app.send_document("me", "document.zip")
+                await app.send_document("me", "document.zip")
 
                 # Add caption to the document file
-                app.send_document("me", "document.zip", caption="document caption")
+                await app.send_document("me", "document.zip", caption="document caption")
 
                 # Keep track of the progress while uploading
-                def progress(current, total):
+                async def progress(current, total):
                     print(f"{current * 100 / total:.1f}%")
 
-                app.send_document("me", "document.zip", progress=progress)
+                await app.send_document("me", "document.zip", progress=progress)
         """
         file = None
 
diff --git a/pyrogram/methods/messages/send_location.py b/pyrogram/methods/messages/send_location.py
index 1364543b43..fe7d1ed16d 100644
--- a/pyrogram/methods/messages/send_location.py
+++ b/pyrogram/methods/messages/send_location.py
@@ -78,7 +78,7 @@ async def send_location(
         Example:
             .. code-block:: python
 
-                app.send_location("me", 51.500729, -0.124583)
+                app.send_location("me", latitude, longitude)
         """
         r = await self.invoke(
             raw.functions.messages.SendMedia(
diff --git a/pyrogram/methods/messages/send_media_group.py b/pyrogram/methods/messages/send_media_group.py
index 382b2a92a0..43eb9d7c2b 100644
--- a/pyrogram/methods/messages/send_media_group.py
+++ b/pyrogram/methods/messages/send_media_group.py
@@ -79,7 +79,7 @@ async def send_media_group(
 
                 from pyrogram.types import InputMediaPhoto, InputMediaVideo
 
-                app.send_media_group(
+                await app.send_media_group(
                     "me",
                     [
                         InputMediaPhoto("photo1.jpg"),
diff --git a/pyrogram/methods/messages/send_message.py b/pyrogram/methods/messages/send_message.py
index 414f744764..0a7ab6d102 100644
--- a/pyrogram/methods/messages/send_message.py
+++ b/pyrogram/methods/messages/send_message.py
@@ -88,31 +88,29 @@ async def send_message(
             .. code-block:: python
 
                 # Simple example
-                app.send_message("me", "Message sent with **Pyrogram**!")
+                await app.send_message("me", "Message sent with **Pyrogram**!")
 
                 # Disable web page previews
-                app.send_message("me", "https://docs.pyrogram.org", disable_web_page_preview=True)
+                await app.send_message("me", "https://docs.pyrogram.org",
+                    disable_web_page_preview=True)
 
                 # Reply to a message using its id
-                app.send_message("me", "this is a reply", reply_to_message_id=12345)
+                await app.send_message("me", "this is a reply", reply_to_message_id=123)
 
-                # Force HTML-only styles for this request only
-                app.send_message("me", "**not bold**, italic", parse_mode="html")
+            .. code-block:: python
 
-                ##
                 # For bots only, send messages with keyboards attached
-                ##
 
                 from pyrogram.types import (
                     ReplyKeyboardMarkup, InlineKeyboardMarkup, InlineKeyboardButton)
 
                 # Send a normal keyboard
-                app.send_message(
+                await app.send_message(
                     chat_id, "Look at that button!",
                     reply_markup=ReplyKeyboardMarkup([["Nice!"]]))
 
                 # Send an inline keyboard
-                app.send_message(
+                await app.send_message(
                     chat_id, "These are inline buttons",
                     reply_markup=InlineKeyboardMarkup(
                         [
diff --git a/pyrogram/methods/messages/send_photo.py b/pyrogram/methods/messages/send_photo.py
index 5391b2f660..85d6fe4b7c 100644
--- a/pyrogram/methods/messages/send_photo.py
+++ b/pyrogram/methods/messages/send_photo.py
@@ -128,16 +128,16 @@ async def send_photo(
             .. code-block:: python
 
                 # Send photo by uploading from local file
-                app.send_photo("me", "photo.jpg")
+                await app.send_photo("me", "photo.jpg")
 
                 # Send photo by uploading from URL
-                app.send_photo("me", "https://i.imgur.com/BQBTP7d.png")
+                await app.send_photo("me", "https://example.com/example.jpg)
 
                 # Add caption to a photo
-                app.send_photo("me", "photo.jpg", caption="Holidays!")
+                await app.send_photo("me", "photo.jpg", caption="Caption")
 
                 # Send self-destructing photo
-                app.send_photo("me", "photo.jpg", ttl_seconds=10)
+                await app.send_photo("me", "photo.jpg", ttl_seconds=10)
         """
         file = None
 
diff --git a/pyrogram/methods/messages/send_poll.py b/pyrogram/methods/messages/send_poll.py
index 0dd877b9e4..1305d2d680 100644
--- a/pyrogram/methods/messages/send_poll.py
+++ b/pyrogram/methods/messages/send_poll.py
@@ -98,7 +98,7 @@ async def send_poll(
         Example:
             .. code-block:: python
 
-                app.send_poll(chat_id, "Is this a poll question?", ["Yes", "No", "Maybe"])
+                await app.send_poll(chat_id, "Is this a poll question?", ["Yes", "No", "Maybe"])
         """
         r = await self.invoke(
             raw.functions.messages.SendMedia(
diff --git a/pyrogram/methods/messages/send_reaction.py b/pyrogram/methods/messages/send_reaction.py
index 34c2d92c0a..7f597cb626 100644
--- a/pyrogram/methods/messages/send_reaction.py
+++ b/pyrogram/methods/messages/send_reaction.py
@@ -49,10 +49,10 @@ async def send_reaction(
             .. code-block:: python
 
                 # Send a reaction
-                app.send_reaction(chat_id, message_id, "🔥")
+                await app.send_reaction(chat_id, message_id, "🔥")
 
                 # Retract a reaction
-                app.send_reaction(chat_id, message_id)
+                await app.send_reaction(chat_id, message_id)
         """
         await self.invoke(
             raw.functions.messages.SendReaction(
diff --git a/pyrogram/methods/messages/send_sticker.py b/pyrogram/methods/messages/send_sticker.py
index d0b66fb10b..2fc4565cb5 100644
--- a/pyrogram/methods/messages/send_sticker.py
+++ b/pyrogram/methods/messages/send_sticker.py
@@ -111,10 +111,10 @@ async def send_sticker(
             .. code-block:: python
 
                 # Send sticker by uploading from local file
-                app.send_sticker("me", "sticker.webp")
+                await app.send_sticker("me", "sticker.webp")
 
                 # Send sticker using file_id
-                app.send_sticker("me", file_id)
+                await app.send_sticker("me", file_id)
         """
         file = None
 
diff --git a/pyrogram/methods/messages/send_venue.py b/pyrogram/methods/messages/send_venue.py
index 7ddb98efdc..e8cc760b30 100644
--- a/pyrogram/methods/messages/send_venue.py
+++ b/pyrogram/methods/messages/send_venue.py
@@ -96,8 +96,8 @@ async def send_venue(
             .. code-block:: python
 
                 app.send_venue(
-                    "me", 51.500729, -0.124583,
-                    "Elizabeth Tower", "Westminster, London SW1A 0AA, UK")
+                    "me", latitude, longitude,
+                    "Venue title", "Venue address")
         """
         r = await self.invoke(
             raw.functions.messages.SendMedia(
diff --git a/pyrogram/methods/messages/send_video.py b/pyrogram/methods/messages/send_video.py
index 49674076bc..299f1b477c 100644
--- a/pyrogram/methods/messages/send_video.py
+++ b/pyrogram/methods/messages/send_video.py
@@ -158,19 +158,19 @@ async def send_video(
             .. code-block:: python
 
                 # Send video by uploading from local file
-                app.send_video("me", "video.mp4")
+                await app.send_video("me", "video.mp4")
 
                 # Add caption to the video
-                app.send_video("me", "video.mp4", caption="video caption")
+                await app.send_video("me", "video.mp4", caption="video caption")
 
                 # Send self-destructing video
-                app.send_video("me", "video.mp4", ttl_seconds=10)
+                await app.send_video("me", "video.mp4", ttl_seconds=10)
 
                 # Keep track of the progress while uploading
-                def progress(current, total):
+                async def progress(current, total):
                     print(f"{current * 100 / total:.1f}%")
 
-                app.send_video("me", "video.mp4", progress=progress)
+                await app.send_video("me", "video.mp4", progress=progress)
         """
         file = None
 
diff --git a/pyrogram/methods/messages/send_video_note.py b/pyrogram/methods/messages/send_video_note.py
index ce6240d9d5..bf33cdb724 100644
--- a/pyrogram/methods/messages/send_video_note.py
+++ b/pyrogram/methods/messages/send_video_note.py
@@ -125,10 +125,10 @@ async def send_video_note(
             .. code-block:: python
 
                 # Send video note by uploading from local file
-                app.send_video_note("me", "video_note.mp4")
+                await app.send_video_note("me", "video_note.mp4")
 
                 # Set video note length
-                app.send_video_note("me", "video_note.mp4", length=25)
+                await app.send_video_note("me", "video_note.mp4", length=25)
         """
         file = None
 
diff --git a/pyrogram/methods/messages/send_voice.py b/pyrogram/methods/messages/send_voice.py
index 9bc6456519..5947ecc23d 100644
--- a/pyrogram/methods/messages/send_voice.py
+++ b/pyrogram/methods/messages/send_voice.py
@@ -127,13 +127,13 @@ async def send_voice(
             .. code-block:: python
 
                 # Send voice note by uploading from local file
-                app.send_voice("me", "voice.ogg")
+                await app.send_voice("me", "voice.ogg")
 
                 # Add caption to the voice note
-                app.send_voice("me", "voice.ogg", caption="voice caption")
+                await app.send_voice("me", "voice.ogg", caption="voice caption")
 
                 # Set voice note duration
-                app.send_voice("me", "voice.ogg", duration=20)
+                await app.send_voice("me", "voice.ogg", duration=20)
         """
         file = None
 
diff --git a/pyrogram/methods/messages/stop_poll.py b/pyrogram/methods/messages/stop_poll.py
index e642e1b666..6104403f5c 100644
--- a/pyrogram/methods/messages/stop_poll.py
+++ b/pyrogram/methods/messages/stop_poll.py
@@ -52,7 +52,7 @@ async def stop_poll(
         Example:
             .. code-block:: python
 
-                app.stop_poll(chat_id, message_id)
+                await app.stop_poll(chat_id, message_id)
         """
         poll = (await self.get_messages(chat_id, message_id)).poll
 
diff --git a/pyrogram/methods/messages/vote_poll.py b/pyrogram/methods/messages/vote_poll.py
index 3fea2e22b9..72a912deae 100644
--- a/pyrogram/methods/messages/vote_poll.py
+++ b/pyrogram/methods/messages/vote_poll.py
@@ -50,7 +50,7 @@ async def vote_poll(
         Example:
             .. code-block:: python
 
-                app.vote_poll(chat_id, message_id, 6)
+                await app.vote_poll(chat_id, message_id, 6)
         """
 
         poll = (await self.get_messages(chat_id, message_id)).poll
diff --git a/pyrogram/methods/password/change_cloud_password.py b/pyrogram/methods/password/change_cloud_password.py
index 3f7dee0017..c5e8bd27c7 100644
--- a/pyrogram/methods/password/change_cloud_password.py
+++ b/pyrogram/methods/password/change_cloud_password.py
@@ -52,10 +52,10 @@ async def change_cloud_password(
             .. code-block:: python
 
                 # Change password only
-                app.change_cloud_password("current_password", "new_password")
+                await app.change_cloud_password("current_password", "new_password")
 
                 # Change password and hint
-                app.change_cloud_password("current_password", "new_password", new_hint="hint")
+                await app.change_cloud_password("current_password", "new_password", new_hint="hint")
         """
         r = await self.invoke(raw.functions.account.GetPassword())
 
diff --git a/pyrogram/methods/password/enable_cloud_password.py b/pyrogram/methods/password/enable_cloud_password.py
index fd8b3dbfd1..a6533dbf70 100644
--- a/pyrogram/methods/password/enable_cloud_password.py
+++ b/pyrogram/methods/password/enable_cloud_password.py
@@ -54,13 +54,13 @@ async def enable_cloud_password(
             .. code-block:: python
 
                 # Enable password without hint and email
-                app.enable_cloud_password("password")
+                await app.enable_cloud_password("password")
 
                 # Enable password with hint and without email
-                app.enable_cloud_password("password", hint="hint")
+                await app.enable_cloud_password("password", hint="hint")
 
                 # Enable password with hint and email
-                app.enable_cloud_password("password", hint="hint", email="user@email.com")
+                await app.enable_cloud_password("password", hint="hint", email="user@email.com")
         """
         r = await self.invoke(raw.functions.account.GetPassword())
 
diff --git a/pyrogram/methods/password/remove_cloud_password.py b/pyrogram/methods/password/remove_cloud_password.py
index 845547d67c..dab37d8635 100644
--- a/pyrogram/methods/password/remove_cloud_password.py
+++ b/pyrogram/methods/password/remove_cloud_password.py
@@ -41,7 +41,7 @@ async def remove_cloud_password(
         Example:
             .. code-block:: python
 
-                app.remove_cloud_password("password")
+                await app.remove_cloud_password("password")
         """
         r = await self.invoke(raw.functions.account.GetPassword())
 
diff --git a/pyrogram/methods/users/block_user.py b/pyrogram/methods/users/block_user.py
index 3298e60190..f14479558e 100644
--- a/pyrogram/methods/users/block_user.py
+++ b/pyrogram/methods/users/block_user.py
@@ -41,7 +41,7 @@ async def block_user(
         Example:
             .. code-block:: python
 
-                app.block_user(user_id)
+                await app.block_user(user_id)
         """
         return bool(
             await self.invoke(
diff --git a/pyrogram/methods/users/delete_profile_photos.py b/pyrogram/methods/users/delete_profile_photos.py
index 107f11a6a3..e7205661eb 100644
--- a/pyrogram/methods/users/delete_profile_photos.py
+++ b/pyrogram/methods/users/delete_profile_photos.py
@@ -43,13 +43,13 @@ async def delete_profile_photos(
             .. code-block:: python
 
                 # Get the photos to be deleted
-                photos = app.get_profile_photos("me")
+                photos = await app.get_profile_photos("me")
 
                 # Delete one photo
-                app.delete_profile_photos(photos[0].file_id)
+                await app.delete_profile_photos(photos[0].file_id)
 
                 # Delete the rest of the photos
-                app.delete_profile_photos([p.file_id for p in photos[1:]])
+                await app.delete_profile_photos([p.file_id for p in photos[1:]])
         """
         photo_ids = photo_ids if isinstance(photo_ids, list) else [photo_ids]
         input_photos = [utils.get_input_media_from_file_id(i, FileType.PHOTO).id for i in photo_ids]
diff --git a/pyrogram/methods/users/get_common_chats.py b/pyrogram/methods/users/get_common_chats.py
index 6c7d5ce786..7269a81ed7 100644
--- a/pyrogram/methods/users/get_common_chats.py
+++ b/pyrogram/methods/users/get_common_chats.py
@@ -45,7 +45,7 @@ async def get_common_chats(
         Example:
             .. code-block:: python
 
-                common = app.get_common_chats(user_id)
+                common = await app.get_common_chats(user_id)
                 print(common)
         """
 
diff --git a/pyrogram/methods/users/get_me.py b/pyrogram/methods/users/get_me.py
index 2869f85c8c..610a11af53 100644
--- a/pyrogram/methods/users/get_me.py
+++ b/pyrogram/methods/users/get_me.py
@@ -33,7 +33,7 @@ async def get_me(
         Example:
             .. code-block:: python
 
-                me = app.get_me()
+                me = await app.get_me()
                 print(me)
         """
         r = await self.invoke(
diff --git a/pyrogram/methods/users/get_profile_photos.py b/pyrogram/methods/users/get_profile_photos.py
index ec35aa9eb1..ba20c89ce1 100644
--- a/pyrogram/methods/users/get_profile_photos.py
+++ b/pyrogram/methods/users/get_profile_photos.py
@@ -54,13 +54,13 @@ async def get_profile_photos(
             .. code-block:: python
 
                 # Get the first 100 profile photos of a user
-                app.get_profile_photos("me")
+                await app.get_profile_photos("me")
 
                 # Get only the first profile photo of a user
-                app.get_profile_photos("me", limit=1)
+                await app.get_profile_photos("me", limit=1)
 
                 # Get 3 profile photos of a user, skip the first 5
-                app.get_profile_photos("me", limit=3, offset=5)
+                await app.get_profile_photos("me", limit=3, offset=5)
         """
         peer_id = await self.resolve_peer(chat_id)
 
diff --git a/pyrogram/methods/users/get_profile_photos_count.py b/pyrogram/methods/users/get_profile_photos_count.py
index 41e50f5e8a..4c0fe5d5f0 100644
--- a/pyrogram/methods/users/get_profile_photos_count.py
+++ b/pyrogram/methods/users/get_profile_photos_count.py
@@ -41,7 +41,7 @@ async def get_profile_photos_count(
         Example:
             .. code-block:: python
 
-                count = app.get_profile_photos_count("me")
+                count = await app.get_profile_photos_count("me")
                 print(count)
         """
 
diff --git a/pyrogram/methods/users/get_users.py b/pyrogram/methods/users/get_users.py
index 6f085c4f51..caef79663f 100644
--- a/pyrogram/methods/users/get_users.py
+++ b/pyrogram/methods/users/get_users.py
@@ -47,10 +47,10 @@ async def get_users(
             .. code-block:: python
 
                 # Get information about one user
-                app.get_users("me")
+                await app.get_users("me")
 
                 # Get information about multiple users at once
-                app.get_users([user1, user2, user3])
+                await app.get_users([user_id1, user_id2, user_id3])
         """
         is_iterable = not isinstance(user_ids, (int, str))
         user_ids = list(user_ids) if is_iterable else [user_ids]
diff --git a/pyrogram/methods/users/iter_profile_photos.py b/pyrogram/methods/users/iter_profile_photos.py
index 88c02c3a54..6665bc9bda 100644
--- a/pyrogram/methods/users/iter_profile_photos.py
+++ b/pyrogram/methods/users/iter_profile_photos.py
@@ -54,8 +54,8 @@ async def iter_profile_photos(
         Example:
             .. code-block:: python
 
-                for photo in app.iter_profile_photos("me"):
-                    print(photo.file_id)
+                async for photo in app.iter_profile_photos("me"):
+                    print(photo)
         """
         current = 0
         total = limit or (1 << 31)
diff --git a/pyrogram/methods/users/set_profile_photo.py b/pyrogram/methods/users/set_profile_photo.py
index a7d59092b3..86aaf31f6d 100644
--- a/pyrogram/methods/users/set_profile_photo.py
+++ b/pyrogram/methods/users/set_profile_photo.py
@@ -57,10 +57,10 @@ async def set_profile_photo(
             .. code-block:: python
 
                 # Set a new profile photo
-                app.set_profile_photo(photo="new_photo.jpg")
+                await app.set_profile_photo(photo="new_photo.jpg")
 
                 # Set a new profile video
-                app.set_profile_photo(video="new_video.mp4")
+                await app.set_profile_photo(video="new_video.mp4")
         """
 
         return bool(
diff --git a/pyrogram/methods/users/set_username.py b/pyrogram/methods/users/set_username.py
index 68e443f149..6f070bc5f2 100644
--- a/pyrogram/methods/users/set_username.py
+++ b/pyrogram/methods/users/set_username.py
@@ -43,7 +43,7 @@ async def set_username(
         Example:
             .. code-block:: python
 
-                app.set_username("new_username")
+                await app.set_username("new_username")
         """
 
         return bool(
diff --git a/pyrogram/methods/users/unblock_user.py b/pyrogram/methods/users/unblock_user.py
index 7593065862..4809aa6ba6 100644
--- a/pyrogram/methods/users/unblock_user.py
+++ b/pyrogram/methods/users/unblock_user.py
@@ -41,7 +41,7 @@ async def unblock_user(
         Example:
             .. code-block:: python
 
-                app.unblock_user(user_id)
+                await app.unblock_user(user_id)
         """
         return bool(
             await self.invoke(
diff --git a/pyrogram/methods/users/update_profile.py b/pyrogram/methods/users/update_profile.py
index 779aa6cf5c..31f12508f5 100644
--- a/pyrogram/methods/users/update_profile.py
+++ b/pyrogram/methods/users/update_profile.py
@@ -50,13 +50,13 @@ async def update_profile(
             .. code-block:: python
 
                 # Update your first name only
-                app.update_profile(first_name="Pyrogram")
+                await app.update_profile(first_name="Pyrogram")
 
                 # Update first name and bio
-                app.update_profile(first_name="Pyrogram", bio="https://docs.pyrogram.org/")
+                await app.update_profile(first_name="Pyrogram", bio="https://docs.pyrogram.org/")
 
                 # Remove the last name
-                app.update_profile(last_name="")
+                await app.update_profile(last_name="")
         """
 
         return bool(
diff --git a/pyrogram/methods/utilities/add_handler.py b/pyrogram/methods/utilities/add_handler.py
index 12b41bfe60..136647d981 100644
--- a/pyrogram/methods/utilities/add_handler.py
+++ b/pyrogram/methods/utilities/add_handler.py
@@ -50,12 +50,12 @@ def add_handler(
                 from pyrogram import Client
                 from pyrogram.handlers import MessageHandler
 
-                def dump(client, message):
+                async def hello(client, message):
                     print(message)
 
                 app = Client("my_account")
 
-                app.add_handler(MessageHandler(dump))
+                app.add_handler(MessageHandler(hello))
 
                 app.run()
         """
diff --git a/pyrogram/methods/utilities/export_session_string.py b/pyrogram/methods/utilities/export_session_string.py
index 8177c456a9..6529484d9b 100644
--- a/pyrogram/methods/utilities/export_session_string.py
+++ b/pyrogram/methods/utilities/export_session_string.py
@@ -35,11 +35,6 @@ async def export_session_string(
         Example:
             .. code-block:: python
 
-                from pyrogram import Client
-
-                app = Client("my_account")
-
-                with app:
-                    print(app.export_session_string())
+                s = await app.export_session_string()
         """
         return await self.storage.export_session_string()
diff --git a/pyrogram/methods/utilities/idle.py b/pyrogram/methods/utilities/idle.py
index c708d6f1fb..bcd685e08d 100644
--- a/pyrogram/methods/utilities/idle.py
+++ b/pyrogram/methods/utilities/idle.py
@@ -41,33 +41,35 @@ async def idle():
     It is useful for event-driven application only, that are, applications which react upon incoming Telegram
     updates through handlers, rather than executing a set of methods sequentially.
 
-    The way Pyrogram works, it will keep your handlers in a pool of worker threads, which are executed concurrently
-    outside the main thread; calling idle() will ensure the client(s) will be kept alive by not letting the main
-    script to end, until you decide to quit.
-
     Once a signal is received (e.g.: from CTRL+C) the function will terminate and your main script will continue.
     Don't forget to call :meth:`~pyrogram.Client.stop` for each running client before the script ends.
 
     Example:
         .. code-block:: python
 
+            import asyncio
             from pyrogram import Client, idle
 
-            app1 = Client("account1")
-            app2 = Client("account2")
-            app3 = Client("account3")
 
-            ...  # Set handlers up
+            async def main():
+                apps = [
+                    Client("account1"),
+                    Client("account2"),
+                    Client("account3")
+                ]
+
+                ...  # Set up handlers
+
+                for app in apps:
+                    await app.start()
+
+                await idle()
 
-            app1.start()
-            app2.start()
-            app3.start()
+                for app in apps:
+                    await app.stop()
 
-            idle()
 
-            app1.stop()
-            app2.stop()
-            app3.stop()
+            asyncio.run(main())
     """
     global is_idling
 
diff --git a/pyrogram/methods/utilities/remove_handler.py b/pyrogram/methods/utilities/remove_handler.py
index fca4a879a9..9f1c974e69 100644
--- a/pyrogram/methods/utilities/remove_handler.py
+++ b/pyrogram/methods/utilities/remove_handler.py
@@ -45,12 +45,12 @@ def remove_handler(
                 from pyrogram import Client
                 from pyrogram.handlers import MessageHandler
 
-                def dump(client, message):
+                async def hello(client, message):
                     print(message)
 
                 app = Client("my_account")
 
-                handler = app.add_handler(MessageHandler(dump))
+                handler = app.add_handler(MessageHandler(hello))
 
                 # Starred expression to unpack (handler, group)
                 app.remove_handler(*handler)
diff --git a/pyrogram/methods/utilities/restart.py b/pyrogram/methods/utilities/restart.py
index 66c246085f..44fd6745ef 100644
--- a/pyrogram/methods/utilities/restart.py
+++ b/pyrogram/methods/utilities/restart.py
@@ -32,7 +32,7 @@ async def restart(
         Parameters:
             block (``bool``, *optional*):
                 Blocks the code execution until the client has been restarted. It is useful with ``block=False`` in case
-                you want to restart the own client *within* an handler in order not to cause a deadlock.
+                you want to restart the own client within an handler in order not to cause a deadlock.
                 Defaults to True.
 
         Returns:
@@ -47,15 +47,17 @@ async def restart(
                 from pyrogram import Client
 
                 app = Client("my_account")
-                app.start()
 
-                ...  # Call API methods
 
-                app.restart()
+                async def main():
+                    await app.start()
+                    ...  # Invoke API methods
+                    await app.restart()
+                    ...  # Invoke other API methods
+                    await app.stop()
 
-                ...  # Call other API methods
 
-                app.stop()
+                app.run(main())
         """
 
         async def do_it():
diff --git a/pyrogram/methods/utilities/run.py b/pyrogram/methods/utilities/run.py
index 6247b936c9..e3890355a9 100644
--- a/pyrogram/methods/utilities/run.py
+++ b/pyrogram/methods/utilities/run.py
@@ -30,8 +30,17 @@ def run(
     ):
         """Start the client, idle the main script and finally stop the client.
 
-        This is a convenience method that calls :meth:`~pyrogram.Client.start`, :meth:`~pyrogram.idle` and
-        :meth:`~pyrogram.Client.stop` in sequence. It makes running a single client less verbose.
+        When calling this method without any argument it acts as a convenience method that calls
+        :meth:`~pyrogram.Client.start`, :meth:`~pyrogram.idle` and :meth:`~pyrogram.Client.stop` in sequence.
+        It makes running a single client less verbose.
+
+        In case a coroutine is passed as argument, runs the coroutine until it's completed and doesn't do any client
+        operation. This is almost the same as :py:obj:`asyncio.run` except for the fact that Pyrogram's ``run`` uses the
+        current event loop instead of a new one.
+
+        Parameters:
+            coroutine (``Coroutine``, *optional*):
+                Pass a coroutine to run it until it completes.
 
         Raises:
             ConnectionError: In case you try to run an already started client.
@@ -42,10 +51,22 @@ def run(
                 from pyrogram import Client
 
                 app = Client("my_account")
-
                 ...  # Set handlers up
-
                 app.run()
+
+            .. code-block:: python
+
+                from pyrogram import Client
+
+                app = Client("my_account")
+
+
+                async def main():
+                    async with app:
+                        print(await app.get_me())
+
+
+                app.run(main())
         """
         loop = asyncio.get_event_loop()
         run = loop.run_until_complete
diff --git a/pyrogram/methods/utilities/start.py b/pyrogram/methods/utilities/start.py
index ab4aef823d..bf5468fbe6 100644
--- a/pyrogram/methods/utilities/start.py
+++ b/pyrogram/methods/utilities/start.py
@@ -30,7 +30,7 @@ async def start(
     ):
         """Start the client.
 
-        This method connects the client to Telegram and, in case of new sessions, automatically manages the full
+        This method connects the client to Telegram and, in case of new sessions, automatically manages the
         authorization process using an interactive prompt.
 
         Returns:
@@ -45,11 +45,15 @@ async def start(
                 from pyrogram import Client
 
                 app = Client("my_account")
-                app.start()
 
-                ...  # Call API methods
 
-                app.stop()
+                async def main():
+                    await app.start()
+                    ...  # Invoke API methods
+                    await app.stop()
+
+
+                app.run(main())
         """
         is_authorized = await self.connect()
 
diff --git a/pyrogram/methods/utilities/stop.py b/pyrogram/methods/utilities/stop.py
index 92b99fc5f3..1b28add046 100644
--- a/pyrogram/methods/utilities/stop.py
+++ b/pyrogram/methods/utilities/stop.py
@@ -46,11 +46,15 @@ async def stop(
                 from pyrogram import Client
 
                 app = Client("my_account")
-                app.start()
 
-                ...  # Call API methods
 
-                app.stop()
+                async def main():
+                    await app.start()
+                    ...  # Invoke API methods
+                    await app.stop()
+
+
+                app.run(main())
         """
 
         async def do_it():
diff --git a/pyrogram/methods/utilities/stop_transmission.py b/pyrogram/methods/utilities/stop_transmission.py
index 0639eab8f3..da64569523 100644
--- a/pyrogram/methods/utilities/stop_transmission.py
+++ b/pyrogram/methods/utilities/stop_transmission.py
@@ -29,17 +29,15 @@ def stop_transmission(self):
         Example:
             .. code-block:: python
 
-                from pyrogram import Client
-
-                app = Client("my_account")
-
-                # Example to stop transmission once the upload progress reaches 50%
-                # Useless in practice, but shows how to stop on command
-                def progress(current, total, client):
+                # Stop transmission once the upload progress reaches 50%
+                async def progress(current, total, client):
                     if (current * 100 / total) > 50:
                         client.stop_transmission()
 
-                with app:
-                    app.send_document("me", "file.zip", progress=progress, progress_args=(app,))
+                async with app:
+                    await app.send_document(
+                        "me", "file.zip",
+                        progress=progress,
+                        progress_args=(app,))
         """
         raise pyrogram.StopTransmission
diff --git a/pyrogram/types/bots_and_keyboards/callback_query.py b/pyrogram/types/bots_and_keyboards/callback_query.py
index 436b6076ea..8c96d6cc61 100644
--- a/pyrogram/types/bots_and_keyboards/callback_query.py
+++ b/pyrogram/types/bots_and_keyboards/callback_query.py
@@ -131,7 +131,7 @@ async def answer(self, text: str = None, show_alert: bool = None, url: str = Non
 
         .. code-block:: python
 
-            client.answer_callback_query(
+            await client.answer_callback_query(
                 callback_query.id,
                 text="Hello",
                 show_alert=True
@@ -140,7 +140,7 @@ async def answer(self, text: str = None, show_alert: bool = None, url: str = Non
         Example:
             .. code-block:: python
 
-                callback_query.answer("Hello", show_alert=True)
+                await callback_query.answer("Hello", show_alert=True)
 
         Parameters:
             text (``str``, *optional*):
diff --git a/pyrogram/types/inline_mode/inline_query.py b/pyrogram/types/inline_mode/inline_query.py
index 6f671059d0..a5f0422e72 100644
--- a/pyrogram/types/inline_mode/inline_query.py
+++ b/pyrogram/types/inline_mode/inline_query.py
@@ -122,7 +122,7 @@ async def answer(
 
         .. code-block:: python
 
-            client.answer_inline_query(
+            await client.answer_inline_query(
                 inline_query.id,
                 results=[...]
             )
@@ -130,7 +130,7 @@ async def answer(
         Example:
             .. code-block:: python
 
-                inline_query.answer([...])
+                await inline_query.answer([...])
 
         Parameters:
             results (List of :obj:`~pyrogram.types.InlineQueryResult`):
diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py
index f7965e6a21..0c639e359c 100644
--- a/pyrogram/types/messages_and_media/message.py
+++ b/pyrogram/types/messages_and_media/message.py
@@ -834,7 +834,7 @@ async def get_media_group(self) -> List["types.Message"]:
         
         .. code-block:: python
 
-            client.get_media_group(
+            await client.get_media_group(
                 chat_id=message.chat.id,
                 message_id=message.id
             )
@@ -842,7 +842,7 @@ async def get_media_group(self) -> List["types.Message"]:
         Example:
             .. code-block:: python
 
-                message.get_media_group()
+                await message.get_media_group()
                 
         Returns:
             List of :obj:`~pyrogram.types.Message`: On success, a list of messages of the media group is returned.
@@ -877,7 +877,7 @@ async def reply_text(
 
         .. code-block:: python
 
-            client.send_message(
+            await client.send_message(
                 chat_id=message.chat.id,
                 text="hello",
                 reply_to_message_id=message.id
@@ -886,7 +886,7 @@ async def reply_text(
         Example:
             .. code-block:: python
 
-                message.reply_text("hello", quote=True)
+                await message.reply_text("hello", quote=True)
 
         Parameters:
             text (``str``):
@@ -979,7 +979,7 @@ async def reply_animation(
 
         .. code-block:: python
 
-            client.send_animation(
+            await client.send_animation(
                 chat_id=message.chat.id,
                 animation=animation
             )
@@ -987,7 +987,7 @@ async def reply_animation(
         Example:
             .. code-block:: python
 
-                message.reply_animation(animation)
+                await message.reply_animation(animation)
 
         Parameters:
             animation (``str``):
@@ -1118,7 +1118,7 @@ async def reply_audio(
 
         .. code-block:: python
 
-            client.send_audio(
+            await client.send_audio(
                 chat_id=message.chat.id,
                 audio=audio
             )
@@ -1126,7 +1126,7 @@ async def reply_audio(
         Example:
             .. code-block:: python
 
-                message.reply_audio(audio)
+                await message.reply_audio(audio)
 
         Parameters:
             audio (``str``):
@@ -1251,7 +1251,7 @@ async def reply_cached_media(
 
         .. code-block:: python
 
-            client.send_cached_media(
+            await client.send_cached_media(
                 chat_id=message.chat.id,
                 file_id=file_id
             )
@@ -1259,7 +1259,7 @@ async def reply_cached_media(
         Example:
             .. code-block:: python
 
-                message.reply_cached_media(file_id)
+                await message.reply_cached_media(file_id)
 
         Parameters:
             file_id (``str``):
@@ -1315,31 +1315,30 @@ async def reply_cached_media(
             reply_markup=reply_markup
         )
 
-    async def reply_chat_action(self, action: str) -> bool:
+    async def reply_chat_action(self, action: "enums.ChatAction") -> bool:
         """Bound method *reply_chat_action* of :obj:`~pyrogram.types.Message`.
 
         Use as a shortcut for:
 
         .. code-block:: python
 
-            client.send_chat_action(
+            from pyrogram import enums
+
+            await client.send_chat_action(
                 chat_id=message.chat.id,
-                action="typing"
+                action=enums.ChatAction.TYPING
             )
 
         Example:
             .. code-block:: python
 
-                message.reply_chat_action("typing")
+                from pyrogram import enums
+
+                await message.reply_chat_action(enums.ChatAction.TYPING)
 
         Parameters:
-            action (``str``):
-                Type of action to broadcast. Choose one, depending on what the user is about to receive: *"typing"* for
-                text messages, *"upload_photo"* for photos, *"record_video"* or *"upload_video"* for videos,
-                *"record_audio"* or *"upload_audio"* for audio files, *"upload_document"* for general files,
-                *"find_location"* for location data, *"record_video_note"* or *"upload_video_note"* for video notes,
-                *"choose_contact"* for contacts, *"playing"* for games or *"cancel"* to cancel any chat action currently
-                displayed.
+            action (:obj:`~pyrogram.enums.ChatAction`):
+                Type of action to broadcast.
 
         Returns:
             ``bool``: On success, True is returned.
@@ -1375,7 +1374,7 @@ async def reply_contact(
 
         .. code-block:: python
 
-            client.send_contact(
+            await client.send_contact(
                 chat_id=message.chat.id,
                 phone_number=phone_number,
                 first_name=first_name
@@ -1384,7 +1383,7 @@ async def reply_contact(
         Example:
             .. code-block:: python
 
-                message.reply_contact("+1-123-456-7890", "Name")
+                await message.reply_contact("+1-123-456-7890", "Name")
 
         Parameters:
             phone_number (``str``):
@@ -1466,7 +1465,7 @@ async def reply_document(
 
         .. code-block:: python
 
-            client.send_document(
+            await client.send_document(
                 chat_id=message.chat.id,
                 document=document
             )
@@ -1474,7 +1473,7 @@ async def reply_document(
         Example:
             .. code-block:: python
 
-                message.reply_document(document)
+                await message.reply_document(document)
 
         Parameters:
             document (``str``):
@@ -1599,7 +1598,7 @@ async def reply_game(
 
         .. code-block:: python
 
-            client.send_game(
+            await client.send_game(
                 chat_id=message.chat.id,
                 game_short_name="lumberjack"
             )
@@ -1607,7 +1606,7 @@ async def reply_game(
         Example:
             .. code-block:: python
 
-                message.reply_game("lumberjack")
+                await message.reply_game("lumberjack")
 
         Parameters:
             game_short_name (``str``):
@@ -1663,7 +1662,7 @@ async def reply_inline_bot_result(
 
         .. code-block:: python
 
-            client.send_inline_bot_result(
+            await client.send_inline_bot_result(
                 chat_id=message.chat.id,
                 query_id=query_id,
                 result_id=result_id
@@ -1672,7 +1671,7 @@ async def reply_inline_bot_result(
         Example:
             .. code-block:: python
 
-                message.reply_inline_bot_result(query_id, result_id)
+                await message.reply_inline_bot_result(query_id, result_id)
 
         Parameters:
             query_id (``int``):
@@ -1733,16 +1732,16 @@ async def reply_location(
 
         .. code-block:: python
 
-            client.send_location(
+            await client.send_location(
                 chat_id=message.chat.id,
-                latitude=41.890251,
-                longitude=12.492373
+                latitude=latitude,
+                longitude=longitude
             )
 
         Example:
             .. code-block:: python
 
-                message.reply_location(41.890251, 12.492373)
+                await message.reply_location(latitude, longitude)
 
         Parameters:
             latitude (``float``):
@@ -1801,7 +1800,7 @@ async def reply_media_group(
 
         .. code-block:: python
 
-            client.send_media_group(
+            await client.send_media_group(
                 chat_id=message.chat.id,
                 media=list_of_media
             )
@@ -1809,7 +1808,7 @@ async def reply_media_group(
         Example:
             .. code-block:: python
 
-                message.reply_media_group(list_of_media)
+                await message.reply_media_group(list_of_media)
 
         Parameters:
             media (``list``):
@@ -1874,7 +1873,7 @@ async def reply_photo(
 
         .. code-block:: python
 
-            client.send_photo(
+            await client.send_photo(
                 chat_id=message.chat.id,
                 photo=photo
             )
@@ -1882,7 +1881,7 @@ async def reply_photo(
         Example:
             .. code-block:: python
 
-                message.reply_photo(photo)
+                await message.reply_photo(photo)
 
         Parameters:
             photo (``str``):
@@ -1997,16 +1996,16 @@ async def reply_poll(
 
         .. code-block:: python
 
-            client.send_poll(
+            await client.send_poll(
                 chat_id=message.chat.id,
-                question="Is Pyrogram the best?",
-                options=["Yes", "Yes"]
+                question="This is a poll",
+                options=["A", "B", "C]
             )
 
         Example:
             .. code-block:: python
 
-                message.reply_poll("Is Pyrogram the best?", ["Yes", "Yes"])
+                await message.reply_poll("This is a poll", ["A", "B", "C"])
 
         Parameters:
             question (``str``):
@@ -2097,7 +2096,7 @@ async def reply_sticker(
 
         .. code-block:: python
 
-            client.send_sticker(
+            await client.send_sticker(
                 chat_id=message.chat.id,
                 sticker=sticker
             )
@@ -2105,7 +2104,7 @@ async def reply_sticker(
         Example:
             .. code-block:: python
 
-                message.reply_sticker(sticker)
+                await message.reply_sticker(sticker)
 
         Parameters:
             sticker (``str``):
@@ -2200,18 +2199,18 @@ async def reply_venue(
 
         .. code-block:: python
 
-            client.send_venue(
+            await client.send_venue(
                 chat_id=message.chat.id,
-                latitude=41.890251,
-                longitude=12.492373,
-                title="Coliseum",
-                address="Piazza del Colosseo, 1, 00184 Roma RM"
+                latitude=latitude,
+                longitude=longitude,
+                title="Venue title",
+                address="Venue address"
             )
 
         Example:
             .. code-block:: python
 
-                message.reply_venue(41.890251, 12.492373, "Coliseum", "Piazza del Colosseo, 1, 00184 Roma RM")
+                await message.reply_venue(latitude, longitude, "Venue title", "Venue address")
 
         Parameters:
             latitude (``float``):
@@ -2304,7 +2303,7 @@ async def reply_video(
 
         .. code-block:: python
 
-            client.send_video(
+            await client.send_video(
                 chat_id=message.chat.id,
                 video=video
             )
@@ -2312,7 +2311,7 @@ async def reply_video(
         Example:
             .. code-block:: python
 
-                message.reply_video(video)
+                await message.reply_video(video)
 
         Parameters:
             video (``str``):
@@ -2449,7 +2448,7 @@ async def reply_video_note(
 
         .. code-block:: python
 
-            client.send_video_note(
+            await client.send_video_note(
                 chat_id=message.chat.id,
                 video_note=video_note
             )
@@ -2457,7 +2456,7 @@ async def reply_video_note(
         Example:
             .. code-block:: python
 
-                message.reply_video_note(video_note)
+                await message.reply_video_note(video_note)
 
         Parameters:
             video_note (``str``):
@@ -2568,7 +2567,7 @@ async def reply_voice(
 
         .. code-block:: python
 
-            client.send_voice(
+            await client.send_voice(
                 chat_id=message.chat.id,
                 voice=voice
             )
@@ -2576,7 +2575,7 @@ async def reply_voice(
         Example:
             .. code-block:: python
 
-                message.reply_voice(voice)
+                await message.reply_voice(voice)
 
         Parameters:
             voice (``str``):
@@ -2680,7 +2679,7 @@ async def edit_text(
 
         .. code-block:: python
 
-            client.edit_message_text(
+            await client.edit_message_text(
                 chat_id=message.chat.id,
                 message_id=message.id,
                 text="hello"
@@ -2689,7 +2688,7 @@ async def edit_text(
         Example:
             .. code-block:: python
 
-                message.edit_text("hello")
+                await message.edit_text("hello")
 
         Parameters:
             text (``str``):
@@ -2739,7 +2738,7 @@ async def edit_caption(
 
         .. code-block:: python
 
-            client.edit_message_caption(
+            await client.edit_message_caption(
                 chat_id=message.chat.id,
                 message_id=message.id,
                 caption="hello"
@@ -2748,7 +2747,7 @@ async def edit_caption(
         Example:
             .. code-block:: python
 
-                message.edit_caption("hello")
+                await message.edit_caption("hello")
 
         Parameters:
             caption (``str``):
@@ -2790,7 +2789,7 @@ async def edit_media(
 
         .. code-block:: python
 
-            client.edit_message_media(
+            await client.edit_message_media(
                 chat_id=message.chat.id,
                 message_id=message.id,
                 media=media
@@ -2799,7 +2798,7 @@ async def edit_media(
         Example:
             .. code-block:: python
 
-                message.edit_media(media)
+                await message.edit_media(media)
 
         Parameters:
             media (:obj:`~pyrogram.types.InputMedia`):
@@ -2828,7 +2827,7 @@ async def edit_reply_markup(self, reply_markup: "types.InlineKeyboardMarkup" = N
 
         .. code-block:: python
 
-            client.edit_message_reply_markup(
+            await client.edit_message_reply_markup(
                 chat_id=message.chat.id,
                 message_id=message.id,
                 reply_markup=inline_reply_markup
@@ -2837,7 +2836,7 @@ async def edit_reply_markup(self, reply_markup: "types.InlineKeyboardMarkup" = N
         Example:
             .. code-block:: python
 
-                message.edit_reply_markup(inline_reply_markup)
+                await message.edit_reply_markup(inline_reply_markup)
 
         Parameters:
             reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`):
@@ -2868,7 +2867,7 @@ async def forward(
 
         .. code-block:: python
 
-            client.forward_messages(
+            await client.forward_messages(
                 chat_id=chat_id,
                 from_chat_id=message.chat.id,
                 message_ids=message.id
@@ -2877,7 +2876,7 @@ async def forward(
         Example:
             .. code-block:: python
 
-                message.forward(chat_id)
+                await message.forward(chat_id)
 
         Parameters:
             chat_id (``int`` | ``str``):
@@ -2929,7 +2928,7 @@ async def copy(
 
         .. code-block:: python
 
-            client.copy_message(
+            await client.copy_message(
                 chat_id=chat_id,
                 from_chat_id=message.chat.id,
                 message_id=message.id
@@ -2938,7 +2937,7 @@ async def copy(
         Example:
             .. code-block:: python
 
-                message.copy(chat_id)
+                await message.copy(chat_id)
 
         Parameters:
             chat_id (``int`` | ``str``):
@@ -3100,7 +3099,7 @@ async def delete(self, revoke: bool = True):
 
         .. code-block:: python
 
-            client.delete_messages(
+            await client.delete_messages(
                 chat_id=chat_id,
                 message_ids=message.id
             )
@@ -3108,7 +3107,7 @@ async def delete(self, revoke: bool = True):
         Example:
             .. code-block:: python
 
-                message.delete()
+                await message.delete()
 
         Parameters:
             revoke (``bool``, *optional*):
@@ -3138,7 +3137,7 @@ async def click(self, x: Union[int, str] = 0, y: int = None, quote: bool = None,
 
         .. code-block:: python
 
-            client.request_callback_answer(
+            await client.request_callback_answer(
                 chat_id=message.chat.id,
                 message_id=message.id,
                 callback_data=message.reply_markup[i][j].callback_data
@@ -3148,7 +3147,7 @@ async def click(self, x: Union[int, str] = 0, y: int = None, quote: bool = None,
 
         .. code-block:: python
 
-            client.send_message(
+            await client.send_message(
                 chat_id=message.chat.id,
                 text=message.reply_markup[i][j].text
             )
@@ -3257,7 +3256,7 @@ async def react(self, emoji: str = "") -> bool:
 
         .. code-block:: python
 
-            client.send_reaction(
+            await client.send_reaction(
                 chat_id=chat_id,
                 message_id=message.message_id,
                 emoji="🔥"
@@ -3266,7 +3265,7 @@ async def react(self, emoji: str = "") -> bool:
         Example:
             .. code-block:: python
 
-                message.react(emoji="🔥")
+                await message.react(emoji="🔥")
 
         Parameters:
             emoji (``str``, *optional*):
@@ -3330,12 +3329,12 @@ async def download(
 
         .. code-block:: python
 
-            client.download_media(message)
+            await lient.download_media(message)
 
         Example:
             .. code-block:: python
 
-                message.download()
+                await message.download()
 
         Parameters:
             file_name (``str``, *optional*):
@@ -3430,7 +3429,7 @@ async def pin(self, disable_notification: bool = False, both_sides: bool = False
 
         .. code-block:: python
 
-            client.pin_chat_message(
+            await client.pin_chat_message(
                 chat_id=message.chat.id,
                 message_id=message_id
             )
@@ -3438,7 +3437,7 @@ async def pin(self, disable_notification: bool = False, both_sides: bool = False
         Example:
             .. code-block:: python
 
-                message.pin()
+                await message.pin()
 
         Parameters:
             disable_notification (``bool``):
@@ -3469,7 +3468,7 @@ async def unpin(self) -> bool:
 
         .. code-block:: python
 
-            client.unpin_chat_message(
+            await client.unpin_chat_message(
                 chat_id=message.chat.id,
                 message_id=message_id
             )
@@ -3477,7 +3476,7 @@ async def unpin(self) -> bool:
         Example:
             .. code-block:: python
 
-                message.unpin()
+                await message.unpin()
 
         Returns:
             True on success.
diff --git a/pyrogram/types/user_and_chats/chat.py b/pyrogram/types/user_and_chats/chat.py
index f48f3c0f01..e36107bf61 100644
--- a/pyrogram/types/user_and_chats/chat.py
+++ b/pyrogram/types/user_and_chats/chat.py
@@ -17,7 +17,7 @@
 #  along with Pyrogram.  If not, see .
 
 from datetime import datetime
-from typing import Union, List, Generator, Optional
+from typing import Union, List
 
 import pyrogram
 from pyrogram import raw, enums
@@ -365,12 +365,12 @@ async def archive(self):
 
         .. code-block:: python
 
-            client.archive_chats(-100123456789)
+            await client.archive_chats(-100123456789)
 
         Example:
             .. code-block:: python
 
-                chat.archive()
+                await chat.archive()
 
         Returns:
             True on success.
@@ -388,12 +388,12 @@ async def unarchive(self):
 
         .. code-block:: python
 
-            client.unarchive_chats(-100123456789)
+            await client.unarchive_chats(-100123456789)
 
         Example:
             .. code-block:: python
 
-                chat.unarchive()
+                await chat.unarchive()
 
         Returns:
             True on success.
@@ -412,7 +412,7 @@ async def set_title(self, title: str) -> bool:
 
         .. code-block:: python
 
-            client.set_chat_title(
+            await client.set_chat_title(
                 chat_id=chat_id,
                 title=title
             )
@@ -420,7 +420,7 @@ async def set_title(self, title: str) -> bool:
         Example:
             .. code-block:: python
 
-                chat.set_title("Lounge")
+                await chat.set_title("Lounge")
 
         Note:
             In regular groups (non-supergroups), this method will only work if the "All Members Are Admins"
@@ -450,7 +450,7 @@ async def set_description(self, description: str) -> bool:
 
         .. code-block:: python
 
-            client.set_chat_description(
+            await client.set_chat_description(
                 chat_id=chat_id,
                 description=description
             )
@@ -458,7 +458,7 @@ async def set_description(self, description: str) -> bool:
         Example:
             .. code-block:: python
 
-                chat.set_chat_description("Don't spam!")
+                await chat.set_chat_description("Don't spam!")
 
         Parameters:
             description (``str``):
@@ -484,7 +484,7 @@ async def set_photo(self, photo: str) -> bool:
 
         .. code-block:: python
 
-            client.set_chat_photo(
+            await client.set_chat_photo(
                 chat_id=chat_id,
                 photo=photo
             )
@@ -492,7 +492,7 @@ async def set_photo(self, photo: str) -> bool:
         Example:
             .. code-block:: python
 
-                chat.set_photo("photo.png")
+                await chat.set_photo("photo.png")
 
         Parameters:
             photo (``str``):
@@ -522,7 +522,7 @@ async def ban_member(
 
         .. code-block:: python
 
-            client.ban_chat_member(
+            await client.ban_chat_member(
                 chat_id=chat_id,
                 user_id=user_id
             )
@@ -530,7 +530,7 @@ async def ban_member(
         Example:
             .. code-block:: python
 
-                chat.ban_member(123456789)
+                await chat.ban_member(123456789)
 
         Note:
             In regular groups (non-supergroups), this method will only work if the "All Members Are Admins" setting is
@@ -571,7 +571,7 @@ async def unban_member(
 
         .. code-block:: python
 
-            client.unban_chat_member(
+            await client.unban_chat_member(
                 chat_id=chat_id,
                 user_id=user_id
             )
@@ -579,7 +579,7 @@ async def unban_member(
         Example:
             .. code-block:: python
 
-                chat.unban_member(123456789)
+                await chat.unban_member(123456789)
 
         Parameters:
             user_id (``int`` | ``str``):
@@ -610,7 +610,7 @@ async def restrict_member(
 
         .. code-block:: python
 
-            client.restrict_chat_member(
+            await client.restrict_chat_member(
                 chat_id=chat_id,
                 user_id=user_id,
                 permissions=ChatPermissions()
@@ -619,7 +619,7 @@ async def restrict_member(
         Example:
             .. code-block:: python
 
-                chat.restrict_member(user_id, ChatPermissions())
+                await chat.restrict_member(user_id, ChatPermissions())
 
         Parameters:
             user_id (``int`` | ``str``):
@@ -648,19 +648,12 @@ async def restrict_member(
             until_date=until_date,
         )
 
+    # Set None as privileges default due to issues with partially initialized module, because at the time Chat
+    # is being initialized, ChatPrivileges would be required here, but was not initialized yet.
     async def promote_member(
         self,
         user_id: Union[int, str],
-        can_manage_chat: bool = True,
-        can_change_info: bool = True,
-        can_post_messages: bool = False,
-        can_edit_messages: bool = False,
-        can_delete_messages: bool = True,
-        can_restrict_members: bool = True,
-        can_invite_users: bool = True,
-        can_pin_messages: bool = False,
-        can_promote_members: bool = False,
-        can_manage_voice_chats: bool = False
+        privileges: "types.ChatPrivileges" = None
     ) -> bool:
         """Bound method *promote_member* of :obj:`~pyrogram.types.Chat`.
 
@@ -668,7 +661,7 @@ async def promote_member(
 
         .. code-block:: python
 
-            client.promote_chat_member(
+            await client.promote_chat_member(
                 chat_id=chat_id,
                 user_id=user_id
             )
@@ -677,46 +670,15 @@ async def promote_member(
 
             .. code-block:: python
 
-                chat.promote_member(123456789)
+                await chat.promote_member(123456789)
 
         Parameters:
             user_id (``int`` | ``str``):
                 Unique identifier (int) or username (str) of the target user.
                 For a contact that exists in your Telegram address book you can use his phone number (str).
 
-            can_manage_chat (``bool``, *optional*):
-                Pass True, if the administrator can access the chat event log, chat statistics, message statistics
-                in channels, see channel members, see anonymous administrators in supergroups and ignore slow mode.
-                Implied by any other administrator privilege.
-
-            can_change_info (``bool``, *optional*):
-                Pass True, if the administrator can change chat title, photo and other settings.
-
-            can_post_messages (``bool``, *optional*):
-                Pass True, if the administrator can create channel posts, channels only.
-
-            can_edit_messages (``bool``, *optional*):
-                Pass True, if the administrator can edit messages of other users and can pin messages, channels only.
-
-            can_delete_messages (``bool``, *optional*):
-                Pass True, if the administrator can delete messages of other users.
-
-            can_restrict_members (``bool``, *optional*):
-                Pass True, if the administrator can restrict, ban or unban chat members.
-
-            can_invite_users (``bool``, *optional*):
-                Pass True, if the administrator can invite new users to the chat.
-
-            can_pin_messages (``bool``, *optional*):
-                Pass True, if the administrator can pin messages, supergroups only.
-
-            can_promote_members (``bool``, *optional*):
-                Pass True, if the administrator can add new administrators with a subset of his own privileges or
-                demote administrators that he has promoted, directly or indirectly (promoted by administrators that
-                were appointed by him).
-
-            can_manage_voice_chats (``bool``, *optional*):
-                Pass True, if the administration can manage voice chats (also called group calls).
+            privileges (:obj:`~pyrogram.types.ChatPrivileges`, *optional*):
+                New user privileges.
 
         Returns:
             ``bool``: True on success.
@@ -728,16 +690,7 @@ async def promote_member(
         return await self._client.promote_chat_member(
             chat_id=self.id,
             user_id=user_id,
-            can_manage_chat=can_manage_chat,
-            can_change_info=can_change_info,
-            can_post_messages=can_post_messages,
-            can_edit_messages=can_edit_messages,
-            can_delete_messages=can_delete_messages,
-            can_restrict_members=can_restrict_members,
-            can_invite_users=can_invite_users,
-            can_pin_messages=can_pin_messages,
-            can_promote_members=can_promote_members,
-            can_manage_voice_chats=can_manage_voice_chats
+            privileges=privileges
         )
 
     async def join(self):
@@ -747,12 +700,12 @@ async def join(self):
 
         .. code-block:: python
 
-            client.join_chat(123456789)
+            await client.join_chat(123456789)
 
         Example:
             .. code-block:: python
 
-                chat.join()
+                await chat.join()
 
         Note:
             This only works for public groups, channels that have set a username or linked chats.
@@ -773,12 +726,12 @@ async def leave(self):
 
         .. code-block:: python
 
-            client.leave_chat(123456789)
+            await client.leave_chat(123456789)
 
         Example:
             .. code-block:: python
 
-                chat.leave()
+                await chat.leave()
 
         Raises:
             RPCError: In case of a Telegram RPC error.
@@ -819,7 +772,7 @@ async def get_member(
 
         .. code-block:: python
 
-            client.get_chat_member(
+            await client.get_chat_member(
                 chat_id=chat_id,
                 user_id=user_id
             )
@@ -827,7 +780,7 @@ async def get_member(
         Example:
             .. code-block:: python
 
-                chat.get_member(user_id)
+                await chat.get_member(user_id)
 
         Returns:
             :obj:`~pyrogram.types.ChatMember`: On success, a chat member is returned.
@@ -840,9 +793,8 @@ async def get_member(
 
     async def get_members(
         self,
-        offset: int = 0,
-        limit: int = 200,
         query: str = "",
+        limit: int = 0,
         filter: "enums.ChatMembersFilter" = enums.ChatMembersFilter.SEARCH
     ) -> List["types.ChatMember"]:
         """Bound method *get_members* of :obj:`~pyrogram.types.Chat`.
@@ -851,120 +803,38 @@ async def get_members(
 
         .. code-block:: python
 
-            client.get_chat_members(chat_id)
-
-
-        Parameters:
-            offset (``int``, *optional*):
-                Sequential number of the first member to be returned.
-                Only applicable to supergroups and channels. Defaults to 0 [1]_.
-
-            limit (``int``, *optional*):
-                Limits the number of members to be retrieved.
-                Only applicable to supergroups and channels.
-                Defaults to 200, which is also the maximum server limit allowed per method call.
-
-            query (``str``, *optional*):
-                Query string to filter members based on their display names and usernames.
-                Only applicable to supergroups and channels. Defaults to "" (empty string) [2]_.
-
-            filter (``str``, *optional*):
-                Filter used to select the kind of members you want to retrieve. Only applicable for supergroups
-                and channels. It can be any of the followings:
-                *"all"* - all kind of members,
-                *"banned"* - banned members only,
-                *"restricted"* - restricted members only,
-                *"bots"* - bots only,
-                *"recent"* - recent members only,
-                *"administrators"* - chat administrators only.
-                Only applicable to supergroups and channels.
-                Defaults to *"recent"*.
-
-        .. [1] Server limit: on supergroups, you can get up to 10,000 members for a single query and up to 200 members
-            on channels.
-
-        .. [2] A query string is applicable only for *"all"*, *"banned"* and *"restricted"* filters only.
+            async for member in client.get_chat_members(chat_id):
+                print(member)
 
         Example:
             .. code-block:: python
 
-                # Get first 200 recent members
-                chat.get_members()
-
-                # Get all administrators
-                chat.get_members(filter="administrators")
-
-                # Get all bots
-                chat.get_members(filter="bots")
-
-        Returns:
-            List of :obj:`~pyrogram.types.ChatMember`: On success, a list of chat members is returned.
-        """
-
-        return await self._client.get_chat_members(
-            self.id,
-            offset=offset,
-            limit=limit,
-            query=query,
-            filter=filter
-        )
-
-    def iter_members(
-        self,
-        limit: int = 0,
-        query: str = "",
-        filter: str = "all"
-    ) -> Optional[Generator["types.ChatMember", None, None]]:
-        """Bound method *iter_members* of :obj:`~pyrogram.types.Chat`.
-
-        Use as a shortcut for:
-
-        .. code-block:: python
+                async for member in chat.get_members():
+                    print(member)
 
         Parameters:
-            limit (``int``, *optional*):
-                Limits the number of members to be retrieved.
-                Only applicable to supergroups and channels.
-                Defaults to 200, which is also the maximum server limit allowed per method call [1]_.
-
             query (``str``, *optional*):
                 Query string to filter members based on their display names and usernames.
-                Only applicable to supergroups and channels. Defaults to "" (empty string) [2]_.
+                Only applicable to supergroups and channels. Defaults to "" (empty string).
+                A query string is applicable only for :obj:`~pyrogram.enums.ChatMembersFilter.SEARCH`,
+                :obj:`~pyrogram.enums.ChatMembersFilter.BANNED` and :obj:`~pyrogram.enums.ChatMembersFilter.RESTRICTED`
+                filters only.
+
+            limit (``int``, *optional*):
+                Limits the number of members to be retrieved.
 
             filter (:obj:`~pyrogram.enums.ChatMembersFilter`, *optional*):
                 Filter used to select the kind of members you want to retrieve. Only applicable for supergroups
                 and channels.
 
-        .. [1] Server limit: on supergroups, you can get up to 10,000 members for a single query and up to 200 members
-            on channels.
-
-        .. [2] A query string is applicable only for *"all"*, *"banned"* and *"restricted"* filters only.
-
-        Example:
-            .. code-block:: python
-
-                from pyrogram import enums
-
-                # Get first 200 recent members
-                for member in chat.get_members():
-                    print(member.user.first_name)
-
-                # Get all administrators
-                for member in chat.iter_members(filter=enums.ChatMembersFilter.ADMINISTRATORS):
-                    print(member.user.first_name)
-
-                # Get first 3 bots
-                for member in chat.iter_members(filter=enums.ChatMembersFilter.BOTS, limit=3):
-                    print(member.user.first_name)
-
         Returns:
-            ``Generator``: A generator yielding :obj:`~pyrogram.types.ChatMember` objects.
+            ``Generator``: On success, a generator yielding :obj:`~pyrogram.types.ChatMember` objects is returned.
         """
 
-        return self._client.iter_chat_members(
+        return self._client.get_chat_members(
             self.id,
-            limit=limit,
             query=query,
+            limit=limit,
             filter=filter
         )
 
@@ -979,12 +849,12 @@ async def add_members(
 
         .. code-block:: python
 
-            client.add_chat_members(chat_id, user_id)
+            await client.add_chat_members(chat_id, user_id)
 
         Example:
             .. code-block:: python
 
-                chat.add_members(user_id)
+                await chat.add_members(user_id)
 
         Returns:
             ``bool``: On success, True is returned.
@@ -1003,12 +873,12 @@ async def mark_unread(self, ) -> bool:
 
         .. code-block:: python
 
-            client.mark_unread(chat_id)
+            await client.mark_unread(chat_id)
 
         Example:
             .. code-block:: python
 
-                chat.mark_unread()
+                await chat.mark_unread()
 
         Returns:
             ``bool``: On success, True is returned.
@@ -1023,7 +893,7 @@ async def set_protected_content(self, enabled: bool) -> bool:
 
         .. code-block:: python
 
-            client.set_chat_protected_content(chat_id, enabled)
+            await client.set_chat_protected_content(chat_id, enabled)
 
         Parameters:
             enabled (``bool``):
@@ -1032,7 +902,7 @@ async def set_protected_content(self, enabled: bool) -> bool:
         Example:
             .. code-block:: python
 
-                chat.set_protected_content(enabled)
+                await chat.set_protected_content(enabled)
 
         Returns:
             ``bool``: On success, True is returned.
diff --git a/pyrogram/types/user_and_chats/chat_join_request.py b/pyrogram/types/user_and_chats/chat_join_request.py
index ee9da3ef2c..b810640b76 100644
--- a/pyrogram/types/user_and_chats/chat_join_request.py
+++ b/pyrogram/types/user_and_chats/chat_join_request.py
@@ -89,7 +89,7 @@ async def approve(self) -> bool:
         
         .. code-block:: python
 
-            client.approve_chat_join_request(
+            await client.approve_chat_join_request(
                 chat_id=request.chat.id,
                 user_id=request.from_user.id
             )
@@ -97,7 +97,7 @@ async def approve(self) -> bool:
         Example:
             .. code-block:: python
 
-                request.approve()
+                await request.approve()
                 
         Returns:
             ``bool``: True on success.
@@ -117,7 +117,7 @@ async def decline(self) -> bool:
         
         .. code-block:: python
 
-            client.decline_chat_join_request(
+            await client.decline_chat_join_request(
                 chat_id=request.chat.id,
                 user_id=request.from_user.id
             )
@@ -125,7 +125,7 @@ async def decline(self) -> bool:
         Example:
             .. code-block:: python
 
-                request.decline()
+                await request.decline()
                 
         Returns:
             ``bool``: True on success.
diff --git a/pyrogram/types/user_and_chats/user.py b/pyrogram/types/user_and_chats/user.py
index 2ec09fb583..ff32e97336 100644
--- a/pyrogram/types/user_and_chats/user.py
+++ b/pyrogram/types/user_and_chats/user.py
@@ -274,12 +274,12 @@ async def archive(self):
 
         .. code-block:: python
 
-            client.archive_chats(123456789)
+            await client.archive_chats(123456789)
 
         Example:
             .. code-block:: python
 
-                user.archive()
+               await user.archive()
 
         Returns:
             True on success.
@@ -297,12 +297,12 @@ async def unarchive(self):
 
         .. code-block:: python
 
-            client.unarchive_chats(123456789)
+            await client.unarchive_chats(123456789)
 
         Example:
             .. code-block:: python
 
-                user.unarchive()
+                await user.unarchive()
 
         Returns:
             True on success.
@@ -320,12 +320,12 @@ def block(self):
 
         .. code-block:: python
 
-            client.block_user(123456789)
+            await client.block_user(123456789)
 
         Example:
             .. code-block:: python
 
-                user.block()
+                await user.block()
 
         Returns:
             True on success.

From 1e66ac26367c3e7de998ada050c4f0a8f0791bd0 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 24 Apr 2022 11:56:07 +0200
Subject: [PATCH 0804/1185] Small documentation fix

---
 pyrogram/types/user_and_chats/chat_member.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/types/user_and_chats/chat_member.py b/pyrogram/types/user_and_chats/chat_member.py
index cff2ac430d..4459a8d1c0 100644
--- a/pyrogram/types/user_and_chats/chat_member.py
+++ b/pyrogram/types/user_and_chats/chat_member.py
@@ -38,7 +38,7 @@ class ChatMember(Object):
             Information about the chat (useful in case of banned channel senders).
 
         joined_date (:py:obj:`~datetime.datetime`, *optional*):
-            Date when the user joined..
+            Date when the user joined.
             Not available for the owner.
 
         custom_title (``str``, *optional*):

From b8f39725c50d4ec1625268882e0bf5e1900a5383 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 24 Apr 2022 11:56:07 +0200
Subject: [PATCH 0805/1185] Remove outdated FAQ

---
 docs/source/faq/index.rst                     |  2 --
 ...-event-handler-triggered-twice-or-more.rst | 28 -------------------
 2 files changed, 30 deletions(-)
 delete mode 100644 docs/source/faq/why-is-the-event-handler-triggered-twice-or-more.rst

diff --git a/docs/source/faq/index.rst b/docs/source/faq/index.rst
index 0b16534943..e5bef2be0f 100644
--- a/docs/source/faq/index.rst
+++ b/docs/source/faq/index.rst
@@ -17,7 +17,6 @@ This FAQ page provides answers to common questions about Pyrogram and, to some e
 - :doc:`code-hangs-when-calling-stop-restart-add-remove-handler`
 - :doc:`unicodeencodeerror-codec-cant-encode`
 - :doc:`uploading-with-urls-gives-error-webpage-curl-failed`
-- :doc:`why-is-the-event-handler-triggered-twice-or-more`
 - :doc:`sqlite3-operationalerror-database-is-locked`
 - :doc:`sqlite3-interfaceerror-error-binding-parameter`
 - :doc:`socket-send-raised-exception-oserror-timeouterror`
@@ -39,7 +38,6 @@ This FAQ page provides answers to common questions about Pyrogram and, to some e
     code-hangs-when-calling-stop-restart-add-remove-handler
     unicodeencodeerror-codec-cant-encode
     uploading-with-urls-gives-error-webpage-curl-failed
-    why-is-the-event-handler-triggered-twice-or-more
     sqlite3-operationalerror-database-is-locked
     sqlite3-interfaceerror-error-binding-parameter
     socket-send-raised-exception-oserror-timeouterror
diff --git a/docs/source/faq/why-is-the-event-handler-triggered-twice-or-more.rst b/docs/source/faq/why-is-the-event-handler-triggered-twice-or-more.rst
deleted file mode 100644
index ada12f1de3..0000000000
--- a/docs/source/faq/why-is-the-event-handler-triggered-twice-or-more.rst
+++ /dev/null
@@ -1,28 +0,0 @@
-Why is the event handler called twice or more?
-==============================================
-
-The event handler is being called twice or more because one or more message edit events have arrived.
-By default, Pyrogram listens to both new and edit message events inside ``on_message`` handlers. To prevent edit events
-from calling the handler, use the "not edited" filter ``~filters.edited``.
-
-For example:
-
-.. code-block:: python
-
-    ...
-
-    @app.on_message(... & ~filters.edited)
-    async def handler(client, message):
-        ...
-
-Or, avoid handling any edited message altogether this way:
-
-.. code-block:: python
-
-    ...
-
-    @app.on_message(filters.edited)
-    async def edited(client, message):
-        pass
-
-    ...  # other handlers
\ No newline at end of file

From 124bcb4db7e126935e5c24630070b0a39bcaa0d5 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 24 Apr 2022 11:56:07 +0200
Subject: [PATCH 0806/1185] Remove API key requirement for existing sessions

---
 compiler/errors/source/303_SEE_OTHER.tsv      |  10 +-
 compiler/errors/source/400_BAD_REQUEST.tsv    |  10 +-
 compiler/errors/source/420_FLOOD.tsv          |  10 +-
 .../source/500_INTERNAL_SERVER_ERROR.tsv      |   4 +-
 .../errors/source/503_SERVICE_UNAVAILABLE.tsv |   4 +-
 docs/source/api/enums/NextCodeType.rst        |   8 +
 docs/source/api/enums/index.rst               |   2 +
 docs/source/index.rst                         |   3 +-
 docs/source/intro/quickstart.rst              |   2 +
 docs/source/intro/setup.rst                   |  60 ------
 docs/source/start/auth.rst                    |  35 +++-
 docs/source/start/invoking.rst                |   6 +
 docs/source/start/setup.rst                   |  40 ++++
 docs/source/start/updates.rst                 |   4 +
 docs/source/topics/config-file.rst            |  97 ---------
 docs/source/topics/proxy.rst                  |  56 ++---
 docs/source/topics/use-filters.rst            |   4 +-
 pyrogram/client.py                            | 197 ++++++------------
 pyrogram/connection/transport/tcp/tcp.py      |  11 +-
 pyrogram/enums/__init__.py                    |   1 +
 pyrogram/enums/next_code_type.py              |  36 ++++
 pyrogram/enums/sent_code_type.py              |   2 +-
 pyrogram/methods/auth/connect.py              |   1 -
 pyrogram/session/session.py                   |   2 +-
 pyrogram/storage/file_storage.py              |  61 +-----
 pyrogram/storage/sqlite_storage.py            |  10 +-
 pyrogram/storage/storage.py                   |   3 +
 pyrogram/types/authorization/sent_code.py     |   6 +-
 28 files changed, 250 insertions(+), 435 deletions(-)
 create mode 100644 docs/source/api/enums/NextCodeType.rst
 delete mode 100644 docs/source/intro/setup.rst
 create mode 100644 docs/source/start/setup.rst
 delete mode 100644 docs/source/topics/config-file.rst
 create mode 100644 pyrogram/enums/next_code_type.py

diff --git a/compiler/errors/source/303_SEE_OTHER.tsv b/compiler/errors/source/303_SEE_OTHER.tsv
index 62c1409df2..301660cefe 100644
--- a/compiler/errors/source/303_SEE_OTHER.tsv
+++ b/compiler/errors/source/303_SEE_OTHER.tsv
@@ -1,6 +1,6 @@
 id	message
-FILE_MIGRATE_X	The file to be accessed is currently stored in DC{x}
-NETWORK_MIGRATE_X	The source IP address is associated with DC{x} (for registration)
-PHONE_MIGRATE_X	The phone number a user is trying to use for authorization is associated with DC{x}
-STATS_MIGRATE_X	The statistics of the group/channel are stored in DC{x}
-USER_MIGRATE_X	The user whose identity is being used to execute queries is associated with DC{x} (for registration)
\ No newline at end of file
+FILE_MIGRATE_X	The file to be accessed is currently stored in DC{value}
+NETWORK_MIGRATE_X	The source IP address is associated with DC{value} (for registration)
+PHONE_MIGRATE_X	The phone number a user is trying to use for authorization is associated with DC{value}
+STATS_MIGRATE_X	The statistics of the group/channel are stored in DC{value}
+USER_MIGRATE_X	The user whose identity is being used to execute queries is associated with DC{value} (for registration)
\ No newline at end of file
diff --git a/compiler/errors/source/400_BAD_REQUEST.tsv b/compiler/errors/source/400_BAD_REQUEST.tsv
index e5c74e010c..f3b42fb397 100644
--- a/compiler/errors/source/400_BAD_REQUEST.tsv
+++ b/compiler/errors/source/400_BAD_REQUEST.tsv
@@ -89,7 +89,7 @@ DOCUMENT_INVALID	The document is invalid
 EMAIL_HASH_EXPIRED	The email hash expired and cannot be used to verify it
 EMAIL_INVALID	The email provided is invalid
 EMAIL_UNCONFIRMED	Email unconfirmed
-EMAIL_UNCONFIRMED_X	The provided email isn't confirmed, {x} is the length of the verification code that was just sent to the email
+EMAIL_UNCONFIRMED_X	The provided email isn't confirmed, {value} is the length of the verification code that was just sent to the email
 EMAIL_VERIFY_EXPIRED	The verification email has expired
 EMOTICON_EMPTY	The emoticon parameter is empty
 EMOTICON_INVALID	The emoticon parameter is invalid
@@ -108,7 +108,7 @@ EXTERNAL_URL_INVALID	The external media URL is invalid
 FIELD_NAME_EMPTY	The field with the name FIELD_NAME is missing
 FIELD_NAME_INVALID	The field with the name FIELD_NAME is invalid
 FILE_ID_INVALID	The file id is invalid
-FILE_MIGRATE_X	The file is in Data Center No. {x}
+FILE_MIGRATE_X	The file is in Data Center No. {value}
 FILE_PARTS_INVALID	Invalid number of parts. The value is not between 1 and 4000
 FILE_PART_EMPTY	The file part sent is empty
 FILE_PART_INVALID	The file part number is invalid. The value is not between 0 and 3999
@@ -116,7 +116,7 @@ FILE_PART_LENGTH_INVALID	The length of a file part is invalid
 FILE_PART_SIZE_CHANGED	The part size is different from the size of one of the previous parts in the same file
 FILE_PART_SIZE_INVALID	512 KB cannot be evenly divided by part_size
 FILE_PART_TOO_BIG	The size limit (512 KB) for the content of the file part has been exceeded
-FILE_PART_X_MISSING	Part {x} of the file is missing from storage
+FILE_PART_X_MISSING	Part {value} of the file is missing from storage
 FILE_REFERENCE_EMPTY	The file id contains an empty file reference, you must obtain a valid one by fetching the message from the origin context
 FILE_REFERENCE_EXPIRED	The file id contains an expired file reference, you must obtain a valid one by fetching the message from the origin context
 FILE_REFERENCE_INVALID	The file id contains an invalid file reference, you must obtain a valid one by fetching the message from the origin context
@@ -198,7 +198,7 @@ PASSWORD_HASH_INVALID	The two-step verification password is invalid
 PASSWORD_MISSING	The account is missing the two-step verification password
 PASSWORD_RECOVERY_NA	The password recovery e-mail is not available
 PASSWORD_REQUIRED	The two-step verification password is required for this method
-PASSWORD_TOO_FRESH_X	The two-step verification password was added recently and you are required to wait {x} seconds
+PASSWORD_TOO_FRESH_X	The two-step verification password was added recently and you are required to wait {value} seconds
 PAYMENT_PROVIDER_INVALID	The payment provider was not recognised or its token was invalid
 PEER_FLOOD	The method can't be used because your account is currently limited
 PEER_ID_INVALID	The peer id being used is invalid or not known yet. Make sure you meet the peer before interacting with it
@@ -349,4 +349,4 @@ WEBDOCUMENT_URL_EMPTY	The web document URL is empty
 WEBDOCUMENT_URL_INVALID	The web document URL is invalid
 WEBPAGE_CURL_FAILED	Telegram server could not fetch the provided URL
 WEBPAGE_MEDIA_EMPTY	The URL doesn't contain any valid media
-YOU_BLOCKED_USER	You blocked this user
+YOU_BLOCKED_USER	You blocked this user
\ No newline at end of file
diff --git a/compiler/errors/source/420_FLOOD.tsv b/compiler/errors/source/420_FLOOD.tsv
index 9afa1fa067..575cc2f5b5 100644
--- a/compiler/errors/source/420_FLOOD.tsv
+++ b/compiler/errors/source/420_FLOOD.tsv
@@ -1,6 +1,6 @@
 id	message
-2FA_CONFIRM_WAIT_X	A wait of {x} seconds is required because this account is active and protected by a 2FA password
-FLOOD_TEST_PHONE_WAIT_X	A wait of {x} seconds is required in the test servers
-FLOOD_WAIT_X	A wait of {x} seconds is required
-SLOWMODE_WAIT_X	A wait of {x} seconds is required to send messages in this chat.
-TAKEOUT_INIT_DELAY_X	You have to confirm the data export request using one of your mobile devices or wait {x} seconds
\ No newline at end of file
+2FA_CONFIRM_WAIT_X	A wait of {value} seconds is required because this account is active and protected by a 2FA password
+FLOOD_TEST_PHONE_WAIT_X	A wait of {value} seconds is required in the test servers
+FLOOD_WAIT_X	A wait of {value} seconds is required
+SLOWMODE_WAIT_X	A wait of {value} seconds is required to send messages in this chat.
+TAKEOUT_INIT_DELAY_X	You have to confirm the data export request using one of your mobile devices or wait {value} seconds
\ No newline at end of file
diff --git a/compiler/errors/source/500_INTERNAL_SERVER_ERROR.tsv b/compiler/errors/source/500_INTERNAL_SERVER_ERROR.tsv
index 9982fc662f..abfc57a39d 100644
--- a/compiler/errors/source/500_INTERNAL_SERVER_ERROR.tsv
+++ b/compiler/errors/source/500_INTERNAL_SERVER_ERROR.tsv
@@ -13,8 +13,8 @@ GROUPCALL_ADD_PARTICIPANTS_FAILED	Failure while adding voice chat member due to
 GROUPED_ID_OCCUPY_FAILED	Telegram is having internal problems. Please try again later
 HISTORY_GET_FAILED	The chat history couldn't be retrieved due to Telegram having internal problems. Please try again later
 IMAGE_ENGINE_DOWN	Image engine down due to Telegram having internal problems. Please try again later
-INTERDC_X_CALL_ERROR	An error occurred while Telegram was intercommunicating with DC{x}. Please try again later
-INTERDC_X_CALL_RICH_ERROR	A rich error occurred while Telegram was intercommunicating with DC{x}. Please try again later
+INTERDC_X_CALL_ERROR	An error occurred while Telegram was intercommunicating with DC{value}. Please try again later
+INTERDC_X_CALL_RICH_ERROR	A rich error occurred while Telegram was intercommunicating with DC{value}. Please try again later
 MEMBER_FETCH_FAILED	Telegram is having internal problems. Please try again later
 MEMBER_NO_LOCATION	Couldn't find the member's location due to Telegram having internal problems. Please try again later
 MEMBER_OCCUPY_PRIMARY_LOC_FAILED	Telegram is having internal problems. Please try again later
diff --git a/compiler/errors/source/503_SERVICE_UNAVAILABLE.tsv b/compiler/errors/source/503_SERVICE_UNAVAILABLE.tsv
index eaa426a814..c28edb0aeb 100644
--- a/compiler/errors/source/503_SERVICE_UNAVAILABLE.tsv
+++ b/compiler/errors/source/503_SERVICE_UNAVAILABLE.tsv
@@ -1,3 +1,3 @@
 id	message
-Timeout	Telegram is having internal problems. Please try again later.
-ApiCallError	Telegram is having internal problems. Please try again later.
\ No newline at end of file
+ApiCallError	Telegram is having internal problems. Please try again later.
+Timeout	Telegram is having internal problems. Please try again later.
\ No newline at end of file
diff --git a/docs/source/api/enums/NextCodeType.rst b/docs/source/api/enums/NextCodeType.rst
new file mode 100644
index 0000000000..46164e47d6
--- /dev/null
+++ b/docs/source/api/enums/NextCodeType.rst
@@ -0,0 +1,8 @@
+NextCodeType
+============
+
+.. autoclass:: pyrogram.enums.NextCodeType()
+    :members:
+
+.. raw:: html
+    :file: ./cleanup.html
\ No newline at end of file
diff --git a/docs/source/api/enums/index.rst b/docs/source/api/enums/index.rst
index 7dfa1f3c24..574b4dd7ce 100644
--- a/docs/source/api/enums/index.rst
+++ b/docs/source/api/enums/index.rst
@@ -25,6 +25,7 @@ to apply only a valid value among the expected ones.
     ParseMode
     PollType
     SentCodeType
+    NextCodeType
     UserStatus
 
 .. toctree::
@@ -42,4 +43,5 @@ to apply only a valid value among the expected ones.
     ParseMode
     PollType
     SentCodeType
+    NextCodeType
     UserStatus
\ No newline at end of file
diff --git a/docs/source/index.rst b/docs/source/index.rst
index 910d031f1f..89a8ae890a 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -111,12 +111,12 @@ Meta
 
     intro/quickstart
     intro/install
-    intro/setup
 
 .. toctree::
     :hidden:
     :caption: Getting Started
 
+    start/setup
     start/auth
     start/invoking
     start/updates
@@ -144,7 +144,6 @@ Meta
     topics/use-filters
     topics/create-filters
     topics/more-on-updates
-    topics/config-file
     topics/smart-plugins
     topics/client-settings
     topics/tgcrypto
diff --git a/docs/source/intro/quickstart.rst b/docs/source/intro/quickstart.rst
index 0981d14794..72f2dd86b4 100644
--- a/docs/source/intro/quickstart.rst
+++ b/docs/source/intro/quickstart.rst
@@ -26,10 +26,12 @@ Get Pyrogram Real Fast
         api_id = 12345
         api_hash = "0123456789abcdef0123456789abcdef"
 
+
         async def main():
             async with Client("my_account", api_id, api_hash) as app:
                 await app.send_message("me", "Greetings from **Pyrogram**!")
 
+
         asyncio.run(main())
 
 4. Replace *api_id* and *api_hash* values with your own.
diff --git a/docs/source/intro/setup.rst b/docs/source/intro/setup.rst
deleted file mode 100644
index 8bffcd86b4..0000000000
--- a/docs/source/intro/setup.rst
+++ /dev/null
@@ -1,60 +0,0 @@
-Project Setup
-=============
-
-We have just :doc:`installed Pyrogram `. In this page we'll discuss what you need to do in order to set up a
-project with the framework.
-
-.. contents:: Contents
-    :backlinks: none
-    :depth: 1
-    :local:
-
------
-
-API Keys
---------
-
-The very first step requires you to obtain a valid Telegram API key (API id/hash pair):
-
-#. Visit https://my.telegram.org/apps and log in with your Telegram Account.
-#. Fill out the form with your details and register a new Telegram application.
-#. Done. The API key consists of two parts: **api_id** and **api_hash**. Keep it secret.
-
-.. note::
-
-    The API key defines a token for a Telegram *application* you are going to build.
-    This means that you are able to authorize multiple users or bots with a single API key.
-
-Configuration
--------------
-
-Having the API key from the previous step in handy, we can now begin to configure a Pyrogram project.
-There are two ways to do so, and you can choose what fits better for you:
-
--   First option: create a new ``config.ini`` file next to your main script, copy-paste the following and
-    replace the *api_id* and *api_hash* values with your own. This method allows you to keep your credentials out of
-    your code without having to deal with how to load them.
-
-    .. code-block:: ini
-
-        [pyrogram]
-        api_id = 12345
-        api_hash = 0123456789abcdef0123456789abcdef
-
--   Alternatively, you can pass your API key to Pyrogram by simply using the *api_id* and *api_hash* parameters of the
-    Client class. This way you can have full control on how to store and load your credentials:
-
-    .. code-block:: python
-
-        from pyrogram import Client
-
-        app = Client(
-            "my_account",
-            api_id=12345,
-            api_hash="0123456789abcdef0123456789abcdef"
-        )
-
-.. note::
-
-    To keep code snippets clean and concise, from now on it is assumed you are making use of the ``config.ini`` file,
-    thus, the *api_id* and *api_hash* parameters usage won't be shown anymore.
diff --git a/docs/source/start/auth.rst b/docs/source/start/auth.rst
index 0e61e59d11..39a4face94 100644
--- a/docs/source/start/auth.rst
+++ b/docs/source/start/auth.rst
@@ -1,7 +1,7 @@
 Authorization
 =============
 
-Once a :doc:`project is set up <../intro/setup>`, you will still have to follow a few steps before you can actually use Pyrogram to make
+Once a :doc:`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 bot.
 
 .. contents:: Contents
@@ -23,7 +23,11 @@ the :meth:`~pyrogram.Client.run` method:
 
     from pyrogram import Client
 
-    app = Client("my_account")
+    api_id = 12345
+    api_hash = "0123456789abcdef0123456789abcdef"
+
+    app = Client("my_account", api_id=api_id, api_hash=api_hash)
+
     app.run()
 
 This starts an interactive shell asking you to input your **phone number**, including your `Country Code`_ (the plus
@@ -38,8 +42,8 @@ authorized or via SMS:
     Logged in successfully
 
 After successfully authorizing yourself, a new file called ``my_account.session`` will be created allowing Pyrogram to
-execute API calls with your identity. This file is personal and will be loaded again when you restart your app, and as
-long as you keep the session alive, Pyrogram won't ask you again to enter your phone number.
+execute API calls with your identity. This file is personal and will be loaded again when you restart your app.
+You can now remove the api_id and api_hash values from the code as they are not needed anymore.
 
 .. note::
 
@@ -51,7 +55,7 @@ Bot Authorization
 
 Bots are a special kind of users that are authorized via their tokens (instead of phone numbers), which are created by
 the `Bot Father`_. Bot tokens replace the users' phone numbers only — you still need to
-:doc:`configure a Telegram API key <../intro/setup>` with Pyrogram, even when using bots.
+:doc:`configure a Telegram API key <../start/setup>` with Pyrogram, even when using bots.
 
 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
@@ -61,12 +65,29 @@ after the session name, which will be ``my_bot.session`` for the example below.
 
     from pyrogram import Client
 
+    api_id = 12345
+    api_hash = "0123456789abcdef0123456789abcdef"
+    bot_token = "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11"
+
     app = Client(
         "my_bot",
-        bot_token="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11"
+        api_id=api_id, api_hash=api_hash,
+        bot_token=bot_token
     )
 
     app.run()
 
 .. _Country Code: https://en.wikipedia.org/wiki/List_of_country_calling_codes
-.. _Bot Father: https://t.me/botfather
\ No newline at end of file
+.. _Bot Father: https://t.me/botfather
+
+.. note::
+
+    The API key (api_id and api_hash) and the bot_token is not needed anymore after a successful authorization.
+    This means you can now simply use:
+
+    .. code-block:: python
+
+        from pyrogram import Client
+
+        app = Client("my_account")
+        app.run()
\ No newline at end of file
diff --git a/docs/source/start/invoking.rst b/docs/source/start/invoking.rst
index ab91e7306b..415ef848b4 100644
--- a/docs/source/start/invoking.rst
+++ b/docs/source/start/invoking.rst
@@ -22,10 +22,12 @@ Making API calls with Pyrogram is very simple. Here's a basic example we are goi
 
     app = Client("my_account")
 
+
     async def main():
         async with app:
             await app.send_message("me", "Hi!")
 
+
     app.run(main())
 
 Step-by-step
@@ -76,11 +78,13 @@ Below there's the same example as above, but without the use of the context mana
 
     app = Client("my_account")
 
+
     async def main():
         await app.start()
         await app.send_message("me", "Hi!")
         await app.stop()
 
+
     app.run(main())
 
 Using asyncio.run()
@@ -95,10 +99,12 @@ be instantiated inside the main function.
     import asyncio
     from pyrogram import Client
 
+
     async def main():
         app = Client("my_account")
 
         async with app:
             await app.send_message("me", "Hi!")
 
+
     asyncio.run(main())
\ No newline at end of file
diff --git a/docs/source/start/setup.rst b/docs/source/start/setup.rst
new file mode 100644
index 0000000000..b8fd6effd7
--- /dev/null
+++ b/docs/source/start/setup.rst
@@ -0,0 +1,40 @@
+Project Setup
+=============
+
+We have just :doc:`installed Pyrogram <../intro/install>`. In this page we'll discuss what you need to do in order to set up a
+project with the framework.
+
+.. contents:: Contents
+    :backlinks: none
+    :depth: 1
+    :local:
+
+-----
+
+API Key
+-------
+
+The first step requires you to obtain a valid Telegram API key (api_id and api_hash pair):
+
+#. Visit https://my.telegram.org/apps and log in with your Telegram account.
+#. Fill out the form with your details and register a new Telegram application.
+#. Done. The API key consists of two parts: **api_id** and **api_hash**. Keep it secret.
+
+.. note::
+
+    The API key defines a token for a Telegram *application* you are going to build.
+    This means that you are able to authorize multiple users or bots with a single API key.
+
+Configuration
+-------------
+
+Having the API key from the previous step in handy, we can now begin to configure a Pyrogram project: pass your API key to Pyrogram by using the *api_id* and *api_hash* parameters of the Client class:
+
+.. code-block:: python
+
+    from pyrogram import Client
+
+    api_id = 12345
+    api_hash = "0123456789abcdef0123456789abcdef"
+
+    app = Client("my_account", api_id=api_id, api_hash=api_hash)
\ No newline at end of file
diff --git a/docs/source/start/updates.rst b/docs/source/start/updates.rst
index 0cdf76507f..450f0d853c 100644
--- a/docs/source/start/updates.rst
+++ b/docs/source/start/updates.rst
@@ -39,10 +39,12 @@ The most elegant way to register a message handler is by using the :meth:`~pyrog
 
     app = Client("my_account")
 
+
     @app.on_message()
     async def my_handler(client, message):
         await message.forward("me")
 
+
     app.run()
 
 The defined function ``my_handler``, which accepts the two arguments *(client, message)*, will be the function that gets
@@ -63,9 +65,11 @@ function and registers it in your Client. It is useful in case you want to progr
     from pyrogram import Client
     from pyrogram.handlers import MessageHandler
 
+
     async def my_function(client, message):
         await message.forward("me")
 
+
     app = Client("my_account")
 
     my_handler = MessageHandler(my_function)
diff --git a/docs/source/topics/config-file.rst b/docs/source/topics/config-file.rst
deleted file mode 100644
index 1657625acd..0000000000
--- a/docs/source/topics/config-file.rst
+++ /dev/null
@@ -1,97 +0,0 @@
-Configuration File
-==================
-
-As already mentioned in previous pages, Pyrogram can be configured by the use of an INI file.
-This page explains how this file is structured, how to use it and why.
-
-.. contents:: Contents
-    :backlinks: none
-    :depth: 1
-    :local:
-
------
-
-Introduction
-------------
-
-The idea behind using a configuration file is to help keeping your code free of private settings information such as
-the API Key and Proxy, without having you to deal with how to load such settings. The configuration file, usually
-referred as ``config.ini`` file, is automatically loaded from the root of your working directory; all you need to do is
-fill in the necessary parts.
-
-.. note::
-
-    The configuration file is optional, but recommended. If, for any reason, you prefer not to use it, there's always an
-    alternative way to configure Pyrogram via Client's parameters. Doing so, you can have full control on how to store
-    and load your settings.
-
-    Settings specified via Client's parameter have higher priority and will override any setting stored in the
-    configuration file.
-
-
-The config.ini File
--------------------
-
-By default, Pyrogram will look for a file named ``config.ini`` placed at the root of your working directory, that is,
-the same folder of your running script. You can change the name or location of your configuration file by specifying it
-in your Client's parameter *config_file*.
-
--   Replace the default *config.ini* file with *my_configuration.ini*:
-
-    .. code-block:: python
-
-        from pyrogram import Client
-
-        app = Client("my_account", config_file="my_configuration.ini")
-
-
-Configuration Sections
-----------------------
-
-These are all the sections Pyrogram uses in its configuration file:
-
-Pyrogram
-^^^^^^^^
-
-The ``[pyrogram]`` section contains your Telegram API credentials: *api_id* and *api_hash*.
-
-.. code-block:: ini
-
-    [pyrogram]
-    api_id = 12345
-    api_hash = 0123456789abcdef0123456789abcdef
-
-`More info about API Key. <../intro/setup#api-keys>`_
-
-Proxy
-^^^^^
-
-The ``[proxy]`` section contains settings about your SOCKS5 proxy.
-
-.. code-block:: ini
-
-    [proxy]
-    enabled = True
-    hostname = 11.22.33.44
-    port = 1080
-    username = 
-    password = 
-
-`More info about SOCKS5 Proxy. `_
-
-Plugins
-^^^^^^^
-
-The ``[plugins]`` section contains settings about Smart Plugins.
-
-.. code-block:: ini
-
-    [plugins]
-    root = plugins
-    include =
-        module
-        folder.module
-    exclude =
-        module fn2
-
-`More info about Smart Plugins. `_
diff --git a/docs/source/topics/proxy.rst b/docs/source/topics/proxy.rst
index 7d7e2c88db..74e054bc26 100644
--- a/docs/source/topics/proxy.rst
+++ b/docs/source/topics/proxy.rst
@@ -1,8 +1,8 @@
-SOCKS5 Proxy
-============
+Proxy Settings
+==============
 
 Pyrogram supports proxies with and without authentication. This feature allows Pyrogram to exchange data with Telegram
-through an intermediate SOCKS5 proxy server.
+through an intermediate SOCKS 4/5 or HTTP (CONNECT) proxy server.
 
 .. contents:: Contents
     :backlinks: none
@@ -14,44 +14,22 @@ through an intermediate SOCKS5 proxy server.
 Usage
 -----
 
--  To use Pyrogram with a proxy, simply append the following to your ``config.ini`` file and replace the values
-   with your own settings:
+To use Pyrogram with a proxy, use the *proxy* parameter in the Client class. If your proxy doesn't require authorization
+you can omit ``username`` and ``password``.
 
-   .. code-block:: ini
+.. code-block:: python
 
-       [proxy]
-       enabled = True
-       hostname = 11.22.33.44
-       port = 1080
-       username = 
-       password = 
+   from pyrogram import Client
 
-   To enable or disable the proxy without deleting your settings from the config file,
-   change the ``enabled`` value as follows:
-
-      -   ``1``, ``yes``, ``True`` or ``on``: Enables the proxy
-      -   ``0``, ``no``, ``False`` or ``off``: Disables the proxy
-
--  Alternatively, you can setup your proxy without the need of the ``config.ini`` file by using the *proxy* parameter
-   in the Client class:
-
-   .. code-block:: python
-
-       from pyrogram import Client
-
-       app = Client(
-           session_name="example",
-           proxy=dict(
-               hostname="11.22.33.44",
-               port=1080,
-               username="",
-               password=""
-           )
+   app = Client(
+       "my_account",
+       proxy=dict(
+           scheme="socks5",  # "socks4", "socks5" and "http" are supported
+           hostname="11.22.33.44",
+           port=1234,
+           username="",
+           password=""
        )
+   )
 
-       app.start()
-
-       ...
-
-.. note:: If your proxy doesn't require authorization you can omit ``username`` and ``password`` by either leaving the
-   values blank/empty or completely delete the lines.
\ No newline at end of file
+   app.run()
diff --git a/docs/source/topics/use-filters.rst b/docs/source/topics/use-filters.rst
index eda6d7addc..62e94d27c8 100644
--- a/docs/source/topics/use-filters.rst
+++ b/docs/source/topics/use-filters.rst
@@ -57,11 +57,11 @@ operators ``~``, ``&`` and ``|``:
 
 Here are some examples:
 
--   Message is a **text** message **and** is **not edited**.
+-   Message is a **text** message **or** a **photo**.
 
     .. code-block:: python
 
-        @app.on_message(filters.text & ~filters.edited)
+        @app.on_message(filters.text | filters.photo)
         def my_handler(client, message):
             print(message)
 
diff --git a/pyrogram/client.py b/pyrogram/client.py
index 525ecf4a51..19ee051082 100644
--- a/pyrogram/client.py
+++ b/pyrogram/client.py
@@ -27,7 +27,6 @@
 import sys
 import tempfile
 from concurrent.futures.thread import ThreadPoolExecutor
-from configparser import ConfigParser
 from hashlib import sha256
 from importlib import import_module
 from io import StringIO
@@ -76,38 +75,37 @@ class Client(Methods):
             pass here as argument.
 
         api_id (``int`` | ``str``, *optional*):
-            The *api_id* part of your Telegram API Key, as integer. E.g.: "12345".
-            This is an alternative way to pass it if you don't want to use the *config.ini* file.
+            The *api_id* part of your Telegram API key, as integer.
+            E.g.: 12345.
 
         api_hash (``str``, *optional*):
-            The *api_hash* part of your Telegram API Key, as string. E.g.: "0123456789abcdef0123456789abcdef".
-            This is an alternative way to set it if you don't want to use the *config.ini* file.
+            The *api_hash* part of your Telegram API key, as string.
+            E.g.: "0123456789abcdef0123456789abcdef".
 
         app_version (``str``, *optional*):
-            Application version. Defaults to "Pyrogram |version|".
-            This is an alternative way to set it if you don't want to use the *config.ini* file.
+            Application version.
+            Defaults to "Pyrogram x.y.z".
 
         device_model (``str``, *optional*):
-            Device model. Defaults to *platform.python_implementation() + " " + platform.python_version()*.
-            This is an alternative way to set it if you don't want to use the *config.ini* file.
+            Device model.
+            Defaults to *platform.python_implementation() + " " + platform.python_version()*.
 
         system_version (``str``, *optional*):
-            Operating System version. Defaults to *platform.system() + " " + platform.release()*.
-            This is an alternative way to set it if you don't want to use the *config.ini* file.
+            Operating System version.
+            Defaults to *platform.system() + " " + platform.release()*.
 
         lang_code (``str``, *optional*):
-            Code of the language used on the client, in ISO 639-1 standard. Defaults to "en".
-            This is an alternative way to set it if you don't want to use the *config.ini* file.
+            Code of the language used on the client, in ISO 639-1 standard.
+            Defaults to "en".
 
         ipv6 (``bool``, *optional*):
             Pass True to connect to Telegram using IPv6.
             Defaults to False (IPv4).
 
         proxy (``dict``, *optional*):
-            Your SOCKS5 Proxy settings as dict,
-            e.g.: *dict(hostname="11.22.33.44", port=1080, username="user", password="pass")*.
+            The Proxy settings as dict.
+            E.g.: *dict(scheme="socks5", hostname="11.22.33.44", port=1234, username="user", password="pass")*.
             The *username* and *password* can be omitted if your proxy doesn't require authorization.
-            This is an alternative way to setup a proxy if you don't want to use the *config.ini* file.
 
         test_mode (``bool``, *optional*):
             Enable or disable login to the test servers.
@@ -117,7 +115,6 @@ class Client(Methods):
         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.
-            This is an alternative way to set it if you don't want to use the *config.ini* file.
 
         phone_number (``str``, *optional*):
             Pass your phone number as string (with your Country Code prefix included) to avoid entering it manually.
@@ -131,11 +128,6 @@ class Client(Methods):
             Pass your Two-Step Verification password as string (if you have one) to avoid entering it manually.
             Only applicable for new sessions.
 
-        force_sms (``bool``, *optional*):
-            Pass True to force Telegram sending the authorization code via SMS.
-            Only applicable for new sessions.
-            Defaults to False.
-
         workers (``int``, *optional*):
             Number of maximum concurrent workers for handling incoming updates.
             Defaults to ``min(32, os.cpu_count() + 4)``.
@@ -145,13 +137,8 @@ class Client(Methods):
             will store your session files.
             Defaults to the parent directory of the main script.
 
-        config_file (``str``, *optional*):
-            Path of the configuration file.
-            Defaults to ./config.ini
-
         plugins (``dict``, *optional*):
             Your Smart Plugins settings as dict, e.g.: *dict(root="plugins")*.
-            This is an alternative way to setup plugins if you don't want to use the *config.ini* file.
 
         parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*):
             The parse mode, can be any of: *"combined"*, for the default combined mode. *"markdown"* or *"md"*
@@ -194,7 +181,6 @@ class Client(Methods):
     INVITE_LINK_RE = re.compile(r"^(?:https?://)?(?:www\.)?(?:t(?:elegram)?\.(?:org|me|dog)/(?:joinchat/|\+))([\w-]+)$")
     WORKERS = min(32, (os.cpu_count() or 0) + 4)  # os.cpu_count() can be None
     WORKDIR = PARENT_DIR
-    CONFIG_FILE = PARENT_DIR / "config.ini"
 
     mimetypes = MimeTypes()
     mimetypes.readfp(StringIO(mime_types))
@@ -204,10 +190,10 @@ def __init__(
         session_name: Union[str, Storage],
         api_id: int = None,
         api_hash: str = None,
-        app_version: str = None,
-        device_model: str = None,
-        system_version: str = None,
-        lang_code: str = None,
+        app_version: str = APP_VERSION,
+        device_model: str = DEVICE_MODEL,
+        system_version: str = SYSTEM_VERSION,
+        lang_code: str = LANG_CODE,
         ipv6: bool = False,
         proxy: dict = None,
         test_mode: bool = False,
@@ -215,10 +201,8 @@ def __init__(
         phone_number: str = None,
         phone_code: str = None,
         password: str = None,
-        force_sms: bool = False,
         workers: int = WORKERS,
         workdir: str = WORKDIR,
-        config_file: str = CONFIG_FILE,
         plugins: dict = None,
         parse_mode: "enums.ParseMode" = enums.ParseMode.DEFAULT,
         no_updates: bool = None,
@@ -236,17 +220,14 @@ def __init__(
         self.system_version = system_version
         self.lang_code = lang_code
         self.ipv6 = ipv6
-        # TODO: Make code consistent, use underscore for private/protected fields
-        self._proxy = proxy
+        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.force_sms = force_sms
         self.workers = workers
         self.workdir = Path(workdir)
-        self.config_file = Path(config_file)
         self.plugins = plugins
         self.parse_mode = parse_mode
         self.no_updates = no_updates
@@ -295,29 +276,19 @@ def __enter__(self):
         return self.start()
 
     def __exit__(self, *args):
-        self.stop()
+        try:
+            self.stop()
+        except ConnectionError:
+            pass
 
     async def __aenter__(self):
         return await self.start()
 
     async def __aexit__(self, *args):
-        await self.stop()
-
-    @property
-    def proxy(self):
-        return self._proxy
-
-    @proxy.setter
-    def proxy(self, value):
-        if value is None:
-            self._proxy = None
-            return
-
-        if self._proxy is None:
-            self._proxy = {}
-
-        self._proxy["enabled"] = bool(value.get("enabled", True))
-        self._proxy.update(value)
+        try:
+            await self.stop()
+        except ConnectionError:
+            pass
 
     async def authorize(self) -> User:
         if self.bot_token:
@@ -355,17 +326,14 @@ async def authorize(self) -> User:
             else:
                 break
 
-        if self.force_sms:
-            sent_code = await self.resend_code(self.phone_number, sent_code.phone_code_hash)
+        sent_code_descriptions = {
+            enums.SentCodeType.APP: "Telegram app",
+            enums.SentCodeType.SMS: "SMS",
+            enums.SentCodeType.CALL: "phone call",
+            enums.SentCodeType.FLASH_CALL: "phone flash call"
+        }
 
-        print("The confirmation code has been sent via {}".format(
-            {
-                "app": "Telegram app",
-                "sms": "SMS",
-                "call": "phone call",
-                "flash_call": "phone flash call"
-            }[sent_code.type]
-        ))
+        print(f"The confirmation code has been sent via {sent_code_descriptions[sent_code.type]}")
 
         while True:
             if not self.phone_code:
@@ -615,79 +583,6 @@ async def handle_updates(self, updates):
         elif isinstance(updates, raw.types.UpdatesTooLong):
             log.info(updates)
 
-    def load_config(self):
-        parser = ConfigParser()
-        parser.read(str(self.config_file))
-
-        if self.bot_token:
-            pass
-        else:
-            self.bot_token = parser.get("pyrogram", "bot_token", fallback=None)
-
-        if self.api_id and self.api_hash:
-            pass
-        else:
-            if parser.has_section("pyrogram"):
-                self.api_id = parser.getint("pyrogram", "api_id")
-                self.api_hash = parser.get("pyrogram", "api_hash")
-            else:
-                raise AttributeError("No API Key found. More info: https://docs.pyrogram.org/intro/setup")
-
-        for option in ["app_version", "device_model", "system_version", "lang_code"]:
-            if getattr(self, option):
-                pass
-            else:
-                if parser.has_section("pyrogram"):
-                    setattr(self, option, parser.get(
-                        "pyrogram",
-                        option,
-                        fallback=getattr(Client, option.upper())
-                    ))
-                else:
-                    setattr(self, option, getattr(Client, option.upper()))
-
-        if self._proxy:
-            self._proxy["enabled"] = bool(self._proxy.get("enabled", True))
-        else:
-            self._proxy = {}
-
-            if parser.has_section("proxy"):
-                self._proxy["enabled"] = parser.getboolean("proxy", "enabled", fallback=True)
-                self._proxy["hostname"] = parser.get("proxy", "hostname")
-                self._proxy["port"] = parser.getint("proxy", "port")
-                self._proxy["username"] = parser.get("proxy", "username", fallback=None) or None
-                self._proxy["password"] = parser.get("proxy", "password", fallback=None) or None
-
-        if self.plugins:
-            self.plugins = {
-                "enabled": bool(self.plugins.get("enabled", True)),
-                "root": self.plugins.get("root", None),
-                "include": self.plugins.get("include", []),
-                "exclude": self.plugins.get("exclude", [])
-            }
-        else:
-            try:
-                section = parser["plugins"]
-
-                self.plugins = {
-                    "enabled": section.getboolean("enabled", True),
-                    "root": section.get("root", None),
-                    "include": section.get("include", []),
-                    "exclude": section.get("exclude", [])
-                }
-
-                include = self.plugins["include"]
-                exclude = self.plugins["exclude"]
-
-                if include:
-                    self.plugins["include"] = include.strip().split("\n")
-
-                if exclude:
-                    self.plugins["exclude"] = exclude.strip().split("\n")
-
-            except KeyError:
-                self.plugins = None
-
     async def load_session(self):
         await self.storage.open()
 
@@ -699,6 +594,12 @@ async def load_session(self):
         ])
 
         if session_empty:
+            if not self.api_id or not self.api_hash:
+                raise AttributeError("The API key is required for new authorizations. "
+                                     "More info: https://docs.pyrogram.org/start/auth")
+
+            await self.storage.api_id(self.api_id)
+
             await self.storage.dc_id(2)
             await self.storage.date(0)
 
@@ -711,6 +612,24 @@ async def load_session(self):
             )
             await self.storage.user_id(None)
             await self.storage.is_bot(None)
+        else:
+            # Needed for migration from storage v2 to v3
+            if not await self.storage.api_id():
+                while True:
+                    try:
+                        value = int(await ainput("Enter the api_id part of the API key: "))
+
+                        if value <= 0:
+                            print("Invalid value")
+                            continue
+
+                        confirm = (await ainput(f'Is "{value}" correct? (y/N): ')).lower()
+
+                        if confirm == "y":
+                            await self.storage.api_id(value)
+                            break
+                    except Exception as e:
+                        print(e)
 
     def load_plugins(self):
         if self.plugins:
diff --git a/pyrogram/connection/transport/tcp/tcp.py b/pyrogram/connection/transport/tcp/tcp.py
index 0b858c0265..5cd67937e5 100644
--- a/pyrogram/connection/transport/tcp/tcp.py
+++ b/pyrogram/connection/transport/tcp/tcp.py
@@ -47,9 +47,8 @@ def __init__(self, ipv6: bool, proxy: dict):
         self.lock = asyncio.Lock()
         self.loop = asyncio.get_event_loop()
 
-        if proxy.get("enabled", False):
-            hostname = proxy.get("hostname", None)
-            port = proxy.get("port", None)
+        if proxy:
+            hostname = proxy.get("hostname")
 
             try:
                 ip_address = ipaddress.ip_address(hostname)
@@ -62,14 +61,14 @@ def __init__(self, ipv6: bool, proxy: dict):
                     self.socket = socks.socksocket(socket.AF_INET)
 
             self.socket.set_proxy(
-                proxy_type=socks.SOCKS5,
+                proxy_type=getattr(socks, proxy.get("scheme").upper()),
                 addr=hostname,
-                port=port,
+                port=proxy.get("port", None),
                 username=proxy.get("username", None),
                 password=proxy.get("password", None)
             )
 
-            log.info(f"Using proxy {hostname}:{port}")
+            log.info(f"Using proxy {hostname}")
         else:
             self.socket = socks.socksocket(
                 socket.AF_INET6 if ipv6
diff --git a/pyrogram/enums/__init__.py b/pyrogram/enums/__init__.py
index 50c85f608e..7d553918a8 100644
--- a/pyrogram/enums/__init__.py
+++ b/pyrogram/enums/__init__.py
@@ -25,6 +25,7 @@
 from .message_media import MessageMedia
 from .message_service import MessageService
 from .messages_filter import MessagesFilter
+from .next_code_type import NextCodeType
 from .parse_mode import ParseMode
 from .poll_type import PollType
 from .sent_code_type import SentCodeType
diff --git a/pyrogram/enums/next_code_type.py b/pyrogram/enums/next_code_type.py
new file mode 100644
index 0000000000..eadaf191a1
--- /dev/null
+++ b/pyrogram/enums/next_code_type.py
@@ -0,0 +1,36 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  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 pyrogram import raw
+from .auto_name import AutoName
+
+
+class NextCodeType(AutoName):
+    """Next code type enumeration used in :obj:`~pyrogram.types.SentCode`."""
+
+    CALL = raw.types.auth.CodeTypeCall
+    "The code will be sent via a phone call. A synthesized voice will tell the user which verification code to input."
+
+    FLASH_CALL = raw.types.auth.CodeTypeFlashCall
+    "The code will be sent via a flash phone call, that will be closed immediately."
+
+    MISSED_CALL = raw.types.auth.CodeTypeMissedCall
+    "Missed call."
+
+    SMS = raw.types.auth.CodeTypeSms
+    "The code was sent via SMS."
diff --git a/pyrogram/enums/sent_code_type.py b/pyrogram/enums/sent_code_type.py
index 73ae724cf2..bee9de3fcb 100644
--- a/pyrogram/enums/sent_code_type.py
+++ b/pyrogram/enums/sent_code_type.py
@@ -33,7 +33,7 @@ class SentCodeType(AutoName):
     "The code will be sent via a flash phone call, that will be closed immediately."
 
     MISSED_CALL = raw.types.auth.SentCodeTypeMissedCall
-    "Missed call"
+    "Missed call."
 
     SMS = raw.types.auth.SentCodeTypeSms
     "The code was sent via SMS."
diff --git a/pyrogram/methods/auth/connect.py b/pyrogram/methods/auth/connect.py
index 191a0e9376..612e064bb4 100644
--- a/pyrogram/methods/auth/connect.py
+++ b/pyrogram/methods/auth/connect.py
@@ -37,7 +37,6 @@ async def connect(
         if self.is_connected:
             raise ConnectionError("Client is already connected")
 
-        self.load_config()
         await self.load_session()
 
         self.session = Session(
diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py
index b50bb6d79e..ff643859a7 100644
--- a/pyrogram/session/session.py
+++ b/pyrogram/session/session.py
@@ -113,7 +113,7 @@ async def start(self):
                         raw.functions.InvokeWithLayer(
                             layer=layer,
                             query=raw.functions.InitConnection(
-                                api_id=self.client.api_id,
+                                api_id=await self.client.storage.api_id(),
                                 app_version=self.client.app_version,
                                 device_model=self.client.device_model,
                                 system_version=self.client.system_version,
diff --git a/pyrogram/storage/file_storage.py b/pyrogram/storage/file_storage.py
index 8ba8910cea..986787cd95 100644
--- a/pyrogram/storage/file_storage.py
+++ b/pyrogram/storage/file_storage.py
@@ -16,8 +16,6 @@
 #  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 sqlite3
@@ -36,37 +34,6 @@ def __init__(self, name: str, workdir: Path):
 
         self.database = workdir / (self.name + self.FILE_EXTENSION)
 
-    def migrate_from_json(self, session_json: dict):
-        self.open()
-
-        self.dc_id(session_json["dc_id"])
-        self.test_mode(session_json["test_mode"])
-        self.auth_key(base64.b64decode("".join(session_json["auth_key"])))
-        self.user_id(session_json["user_id"])
-        self.date(session_json.get("date", 0))
-        self.is_bot(session_json.get("is_bot", False))
-
-        peers_by_id = session_json.get("peers_by_id", {})
-        peers_by_phone = session_json.get("peers_by_phone", {})
-
-        peers = {}
-
-        for k, v in peers_by_id.items():
-            if v is None:
-                type_ = "group"
-            elif k.startswith("-100"):
-                type_ = "channel"
-            else:
-                type_ = "user"
-
-            peers[int(k)] = [int(k), int(v) if v is not None else None, type_, None, None]
-
-        for k, v in peers_by_phone.items():
-            peers[v][4] = k
-
-        # noinspection PyTypeChecker
-        self.update_peers(peers.values())
-
     def update(self):
         version = self.version()
 
@@ -76,34 +43,18 @@ def update(self):
 
             version += 1
 
+        if version == 2:
+            with self.lock, self.conn:
+                self.conn.execute("ALTER TABLE sessions ADD api_id INTEGER")
+
+            version += 1
+
         self.version(version)
 
     async def open(self):
         path = self.database
         file_exists = path.is_file()
 
-        if file_exists:
-            try:
-                with open(str(path), encoding="utf-8") as f:
-                    session_json = json.load(f)
-            except ValueError:
-                pass
-            else:
-                log.warning("JSON session storage detected! Converting it into an SQLite session storage...")
-
-                path.rename(path.name + ".OLD")
-
-                log.warning(f'The old session file has been renamed to "{path.name}.OLD"')
-
-                self.migrate_from_json(session_json)
-
-                log.warning("Done! The session has been successfully converted from JSON to SQLite storage")
-
-                return
-
-        if Path(path.name + ".OLD").is_file():
-            log.warning(f'Old session file detected: "{path.name}.OLD". You can remove this file now')
-
         self.conn = sqlite3.connect(str(path), timeout=1, check_same_thread=False)
 
         if not file_exists:
diff --git a/pyrogram/storage/sqlite_storage.py b/pyrogram/storage/sqlite_storage.py
index 106896f601..f7fbb55bec 100644
--- a/pyrogram/storage/sqlite_storage.py
+++ b/pyrogram/storage/sqlite_storage.py
@@ -31,6 +31,7 @@
 CREATE TABLE sessions
 (
     dc_id     INTEGER PRIMARY KEY,
+    api_id    INTEGER,
     test_mode INTEGER,
     auth_key  BLOB,
     date      INTEGER NOT NULL,
@@ -90,7 +91,7 @@ def get_input_peer(peer_id: int, access_hash: int, peer_type: str):
 
 
 class SQLiteStorage(Storage):
-    VERSION = 2
+    VERSION = 3
     USERNAME_TTL = 8 * 60 * 60
 
     def __init__(self, name: str):
@@ -109,8 +110,8 @@ def create(self):
             )
 
             self.conn.execute(
-                "INSERT INTO sessions VALUES (?, ?, ?, ?, ?, ?)",
-                (2, None, None, 0, None, None)
+                "INSERT INTO sessions VALUES (?, ?, ?, ?, ?, ?, ?)",
+                (2, None, None, None, 0, None, None)
             )
 
     async def open(self):
@@ -195,6 +196,9 @@ def _accessor(self, value: Any = object):
     async def dc_id(self, value: int = object):
         return self._accessor(value)
 
+    async def api_id(self, value: int = object):
+        return self._accessor(value)
+
     async def test_mode(self, value: bool = object):
         return self._accessor(value)
 
diff --git a/pyrogram/storage/storage.py b/pyrogram/storage/storage.py
index 578dee1755..8daaae7e88 100644
--- a/pyrogram/storage/storage.py
+++ b/pyrogram/storage/storage.py
@@ -59,6 +59,9 @@ async def get_peer_by_phone_number(self, phone_number: str):
     async def dc_id(self, value: int = object):
         raise NotImplementedError
 
+    async def api_id(self, value: int = object):
+        raise NotImplementedError
+
     async def test_mode(self, value: bool = object):
         raise NotImplementedError
 
diff --git a/pyrogram/types/authorization/sent_code.py b/pyrogram/types/authorization/sent_code.py
index b0ac9eebc6..2b29bb3af4 100644
--- a/pyrogram/types/authorization/sent_code.py
+++ b/pyrogram/types/authorization/sent_code.py
@@ -31,7 +31,7 @@ class SentCode(Object):
             Confirmation code identifier useful for the next authorization steps (either
             :meth:`~pyrogram.Client.sign_in` or :meth:`~pyrogram.Client.sign_up`).
 
-        next_type (:obj:`~pyrogram.enums.SentCodeType`, *optional*):
+        next_type (:obj:`~pyrogram.enums.NextCodeType`, *optional*):
             Type of the next code to be sent with :meth:`~pyrogram.Client.resend_code`.
 
         timeout (``int``, *optional*):
@@ -42,7 +42,7 @@ def __init__(
         self, *,
         type: "enums.SentCodeType",
         phone_code_hash: str,
-        next_type: "enums.SentCodeType" = None,
+        next_type: "enums.NextCodeType" = None,
         timeout: int = None
     ):
         super().__init__()
@@ -57,6 +57,6 @@ def _parse(sent_code: raw.types.auth.SentCode) -> "SentCode":
         return SentCode(
             type=enums.SentCodeType(type(sent_code.type)),
             phone_code_hash=sent_code.phone_code_hash,
-            next_type=enums.SentCodeType(type(sent_code.next_type)) if sent_code.next_type else None,
+            next_type=enums.NextCodeType(type(sent_code.next_type)) if sent_code.next_type else None,
             timeout=sent_code.timeout
         )

From 6fa4cdff150b1997e7f539bad497eca73cabe7ab Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 24 Apr 2022 11:56:07 +0200
Subject: [PATCH 0807/1185] Fix user mentions for deleted accounts

---
 pyrogram/types/user_and_chats/user.py | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/pyrogram/types/user_and_chats/user.py b/pyrogram/types/user_and_chats/user.py
index ff32e97336..666c72675d 100644
--- a/pyrogram/types/user_and_chats/user.py
+++ b/pyrogram/types/user_and_chats/user.py
@@ -195,7 +195,11 @@ def __init__(
 
     @property
     def mention(self):
-        return Link(f"tg://user?id={self.id}", self.first_name, self._client.parse_mode)
+        return Link(
+            f"tg://user?id={self.id}",
+            self.first_name or "Deleted Account",
+            self._client.parse_mode
+        )
 
     @staticmethod
     def _parse(client, user: "raw.base.User") -> Optional["User"]:

From 9be3818486d9c2226984e9bb86807a396219d7a0 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 24 Apr 2022 11:56:07 +0200
Subject: [PATCH 0808/1185] Add new function compose

---
 compiler/docs/compiler.py             |  2 +-
 compiler/docs/template/methods.rst    |  7 ++-
 pyrogram/__init__.py                  |  2 +-
 pyrogram/methods/utilities/compose.py | 62 +++++++++++++++++++++++++++
 pyrogram/methods/utilities/run.py     |  2 +
 pyrogram/sync.py                      |  7 ++-
 6 files changed, 76 insertions(+), 6 deletions(-)
 create mode 100644 pyrogram/methods/utilities/compose.py

diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py
index 91bbad3810..cce4f76945 100644
--- a/compiler/docs/compiler.py
+++ b/compiler/docs/compiler.py
@@ -339,7 +339,7 @@ def get_title_list(s: str) -> list:
                     f2.write(title + "\n" + "=" * len(title) + "\n\n")
                     f2.write(".. automethod:: pyrogram.Client.{}()".format(method))
 
-            functions = ["idle"]
+            functions = ["idle", "compose"]
 
             for func in functions:
                 with open(root + "/{}.rst".format(func), "w") as f2:
diff --git a/compiler/docs/template/methods.rst b/compiler/docs/template/methods.rst
index 794e657e4c..1620350de8 100644
--- a/compiler/docs/template/methods.rst
+++ b/compiler/docs/template/methods.rst
@@ -1,8 +1,9 @@
 Available Methods
 =================
 
-This page is about Pyrogram methods. All the methods listed here are bound to a :class:`~pyrogram.Client` instance.
-Some other utility functions can instead be found in the main package directly.
+This page is about Pyrogram methods. All the methods listed here are bound to a :class:`~pyrogram.Client` instance,
+except for :meth:`~pyrogram.idle()` and :meth:`~pyrogram.compose()`, which are special functions that can be found in
+the main package directly.
 
 .. code-block:: python
 
@@ -40,11 +41,13 @@ Utilities
     :nosignatures:
 
     idle
+    compose
 
 .. toctree::
     :hidden:
 
     idle
+    compose
 
 .. currentmodule:: pyrogram.Client
 
diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index bcef9c1d98..1b8202ce62 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -37,6 +37,6 @@ class ContinuePropagation(StopAsyncIteration):
 
 from . import raw, types, filters, handlers, emoji, enums
 from .client import Client
-from .sync import idle
+from .sync import idle, compose
 
 crypto_executor = ThreadPoolExecutor(1, thread_name_prefix="CryptoWorker")
diff --git a/pyrogram/methods/utilities/compose.py b/pyrogram/methods/utilities/compose.py
new file mode 100644
index 0000000000..c8bdf76956
--- /dev/null
+++ b/pyrogram/methods/utilities/compose.py
@@ -0,0 +1,62 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  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 List
+
+import pyrogram
+from .idle import idle
+
+
+async def compose(clients: List["pyrogram.Client"]):
+    """Run multiple clients at once.
+
+    This method can be used to run multiple clients at once and can be found directly in the ``pyrogram`` package.
+
+    If you want to run a single client, you can use Client's bound method :meth:`~pyrogram.Client.run`.
+
+    Parameters:
+        clients (List of :obj:`~pyrogram.Client`):
+            A list of client objects to run.
+
+    Example:
+        .. code-block:: python
+
+            import asyncio
+            from pyrogram import Client, compose
+
+
+            async def main():
+                app1 = Client("account1")
+                app2 = Client("account2")
+                app3 = Client("account3")
+
+                ...
+
+                await compose([app1, app2, app3])
+
+
+            asyncio.run(main())
+
+    """
+    for c in clients:
+        await c.start()
+
+    await idle()
+
+    for c in clients:
+        await c.stop()
diff --git a/pyrogram/methods/utilities/run.py b/pyrogram/methods/utilities/run.py
index e3890355a9..ec1bece320 100644
--- a/pyrogram/methods/utilities/run.py
+++ b/pyrogram/methods/utilities/run.py
@@ -38,6 +38,8 @@ def run(
         operation. This is almost the same as :py:obj:`asyncio.run` except for the fact that Pyrogram's ``run`` uses the
         current event loop instead of a new one.
 
+        If you want to run multiple clients at once, see :meth:`pyrogram.compose`.
+
         Parameters:
             coroutine (``Coroutine``, *optional*):
                 Pass a coroutine to run it until it completes.
diff --git a/pyrogram/sync.py b/pyrogram/sync.py
index 41c23e3b80..94c82a3dd6 100644
--- a/pyrogram/sync.py
+++ b/pyrogram/sync.py
@@ -23,7 +23,7 @@
 
 from pyrogram import types
 from pyrogram.methods import Methods
-from pyrogram.methods.utilities import idle as idle_module
+from pyrogram.methods.utilities import idle as idle_module, compose as compose_module
 
 
 def async_to_sync(obj, name):
@@ -105,6 +105,9 @@ def wrap(source):
     if inspect.isclass(cls):
         wrap(cls)
 
-# Special case for idle, because it's not inside Methods
+# Special case for idle and compose, because they are not inside Methods
 async_to_sync(idle_module, "idle")
 idle = getattr(idle_module, "idle")
+
+async_to_sync(compose_module, "compose")
+compose = getattr(compose_module, "compose")

From 7fa091719c74a5a70a193287edb32db374612363 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 24 Apr 2022 11:56:07 +0200
Subject: [PATCH 0809/1185] Add documentation page about synchronous usage

---
 docs/source/index.rst              | 12 +++--
 docs/source/topics/synchronous.rst | 73 ++++++++++++++++++++++++++++++
 2 files changed, 81 insertions(+), 4 deletions(-)
 create mode 100644 docs/source/topics/synchronous.rst

diff --git a/docs/source/index.rst b/docs/source/index.rst
index 89a8ae890a..c3f5d35252 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -69,8 +69,11 @@ How the Documentation is Organized
 ----------------------------------
 
 Contents are organized into sections composed of self-contained topics which can be all accessed from the sidebar, or by
-following them in order using the :guilabel:`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.
+following them in order using the :guilabel:`Next` button at the end of each page.
+You can also switch to Dark or Light theme or leave on Auto (follows system preferences) by using the dedicated button
+in the top left corner.
+
+Here below you can, instead, find a list of the most relevant pages for a quick access.
 
 First Steps
 ^^^^^^^^^^^
@@ -144,11 +147,12 @@ Meta
     topics/use-filters
     topics/create-filters
     topics/more-on-updates
-    topics/smart-plugins
     topics/client-settings
+    topics/synchronous
+    topics/text-formatting
     topics/tgcrypto
+    topics/smart-plugins
     topics/storage-engines
-    topics/text-formatting
     topics/serializing
     topics/proxy
     topics/scheduling
diff --git a/docs/source/topics/synchronous.rst b/docs/source/topics/synchronous.rst
new file mode 100644
index 0000000000..e082f18c48
--- /dev/null
+++ b/docs/source/topics/synchronous.rst
@@ -0,0 +1,73 @@
+Synchronous Usage
+=================
+
+Pyrogram is an asynchronous framework and as such it is subject to the asynchronous rules. It can, however, run in
+synchronous mode (also known as non-asynchronous or sync/non-async for short). This mode exists mainly as a convenience
+way for invoking methods without the need of ``async``/``await`` keywords and the extra boilerplate, but **it's not the
+intended way to use the framework**.
+
+You can use Pyrogram in this synchronous mode when you want to write something short and contained without the
+async boilerplate or in case you want to combine Pyrogram with other libraries that are not async.
+
+.. warning::
+
+    You have to be very careful when using the framework in its synchronous, non-native form, especially when combined
+    with other non-async libraries because thread blocking operations that clog the asynchronous event loop underneath
+    will make the program run erratically.
+
+.. contents:: Contents
+    :backlinks: none
+    :depth: 1
+    :local:
+
+-----
+
+Synchronous Invocations
+-----------------------
+
+The following is a standard example of running asynchronous functions with Python's asyncio.
+Pyrogram is being used inside the main function with its asynchronous interface.
+
+.. code-block:: python
+
+    import asyncio
+    from pyrogram import Client
+
+
+    async def main():
+        app = Client("my_account")
+
+        async with app:
+            await app.send_message("me", "Hi!")
+
+
+    asyncio.run(main())
+
+To run Pyrogram synchronously, use the non-async context manager as shown in the following example.
+As you can see, the non-async example becomes less cluttered.
+
+.. code-block:: python
+
+    from pyrogram import Client
+
+    app = Client("my_account")
+
+    with app:
+        app.send_message("me", "Hi!")
+
+Synchronous handlers
+--------------------
+
+You can also have synchronous handlers; you only need to define the callback function without using ``async def`` and
+invoke API methods by not placing ``await`` in front of them. Mixing ``def`` and ``async def`` handlers together is also
+possible.
+
+.. code-block:: python
+
+    @app.on_message()
+    async def handler1(client, message):
+        await message.forward("me")
+
+    @app.on_edited_message()
+    def handler2(client, message):
+        message.forward("me")

From d14665ad960ae9ee68981a029253c53c3669e770 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 24 Apr 2022 11:56:07 +0200
Subject: [PATCH 0810/1185] Fix text-formatting.rst examples

---
 docs/source/topics/text-formatting.rst | 36 ++++++++++++++------------
 1 file changed, 20 insertions(+), 16 deletions(-)

diff --git a/docs/source/topics/text-formatting.rst b/docs/source/topics/text-formatting.rst
index df3589a948..8ad2b4db61 100644
--- a/docs/source/topics/text-formatting.rst
+++ b/docs/source/topics/text-formatting.rst
@@ -44,14 +44,10 @@ list of the basic styles currently supported by Pyrogram.
       fixed-width
         code block
 
-.. note::
-
-    User text mentions are only guaranteed to work if you have already met the user (in groups or private chats).
-
 Markdown Style
 --------------
 
-To strictly use this mode, pass "markdown" to the *parse_mode* parameter when using
+To strictly use this mode, pass :obj:`~pyrogram.enums.ParseMode.MARKDOWN` to the *parse_mode* parameter when using
 :meth:`~pyrogram.Client.send_message`. Use the following syntax in your message:
 
 .. code-block:: text
@@ -82,6 +78,8 @@ To strictly use this mode, pass "markdown" to the *parse_mode* parameter when us
 
 .. code-block:: python
 
+    from pyrogram import enums
+
     app.send_message(
         "me",
         (
@@ -97,14 +95,14 @@ To strictly use this mode, pass "markdown" to the *parse_mode* parameter when us
             "    print(i)"
             "```"
         ),
-        parse_mode="markdown"
+        parse_mode=enums.ParseMode.MARKDOWN
     )
 
 HTML Style
 ----------
 
-To strictly use this mode, pass "html" to the *parse_mode* parameter when using :meth:`~pyrogram.Client.send_message`.
-The following tags are currently supported:
+To strictly use this mode, pass :obj:`~pyrogram.enums.HTML` to the *parse_mode* parameter when using
+:meth:`~pyrogram.Client.send_message`. The following tags are currently supported:
 
 .. code-block:: text
 
@@ -134,6 +132,8 @@ The following tags are currently supported:
 
 .. code-block:: python
 
+    from pyrogram import enums
+
     app.send_message(
         "me",
         (
@@ -149,7 +149,7 @@ The following tags are currently supported:
             "    print(i)"
             "
" ), - parse_mode="html" + parse_mode=enums.ParseMode.HTML ) .. note:: @@ -186,12 +186,14 @@ Result: **bold**, *italic* If you don't like this behaviour you can always choose to only enable either Markdown or HTML in strict mode by passing -"markdown" or "html" as argument to the *parse_mode* parameter. +:obj:`~pyrogram.enums.MARKDOWN` or :obj:`~pyrogram.enums.HTML` as argument to the *parse_mode* parameter. + +.. code-block:: python -.. code-block:: + from pyrogram import enums - app.send_message("me", "**bold**, italic", parse_mode="markdown") - app.send_message("me", "**bold**, italic", parse_mode="html") + app.send_message("me", "**bold**, italic", parse_mode=enums.ParseMode.MARKDOWN) + app.send_message("me", "**bold**, italic", parse_mode=enums.ParseMode.HTML) Result: @@ -199,12 +201,14 @@ Result: \*\*bold**, *italic* -In case you want to completely turn off the style parser, simply pass ``None`` to *parse_mode*. The text will be sent -as-is. +In case you want to completely turn off the style parser, simply pass :obj:`~pyrogram.enums.DISABLED` to *parse_mode*. +The text will be sent as-is. .. code-block:: python - app.send_message("me", "**bold**, italic", parse_mode=None) + from pyrogram import enums + + app.send_message("me", "**bold**, italic", parse_mode=enums.ParseMode.DISABLED) Result: From 8af17c3ed5ed9ff760152eae059813cb120e1698 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0811/1185] Documentation improvements --- README.md | 2 +- compiler/docs/compiler.py | 3 +++ compiler/docs/template/toctree.txt | 2 ++ docs/Makefile | 3 +-- docs/source/conf.py | 10 +++++----- docs/source/index.rst | 17 +++-------------- docs/source/topics/serializing.rst | 4 +--- 7 files changed, 16 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index 455908676c..764526769f 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ If you'd like to support Pyrogram, you can consider: - **Ready**: Install Pyrogram with pip and start building your applications right away. - **Easy**: Makes the Telegram API simple and intuitive, while still allowing advanced usages. - **Elegant**: Low-level details are abstracted and re-presented in a more convenient way. -- **Fast**: Boosted up by [TgCrypto](https://github.com/pyrogram/tgcrypto), a high-performance crypto library written in pure C. +- **Fast**: Boosted up by [TgCrypto](https://github.com/pyrogram/tgcrypto), a high-performance cryptography library written in C. - **Type-hinted**: Types and methods are all type-hinted, enabling excellent editor support. - **Async**: Fully asynchronous (also usable synchronously if wanted, for convenience). - **Powerful**: Full access to Telegram's API to execute any official client action and more. diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index cce4f76945..dd857fc31f 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -21,6 +21,8 @@ import re import shutil +import pyrogram + HOME = "compiler/docs" DESTINATION = "docs/source/telegram" PYROGRAM_API_DEST = "docs/source/api" @@ -112,6 +114,7 @@ def build(path, level=0): toctree.format( title=k.title(), title_markup="=" * len(k), + layer=pyrogram.raw.all.layer, module=module, entities="\n ".join(entities) ) diff --git a/compiler/docs/template/toctree.txt b/compiler/docs/template/toctree.txt index 717276c40e..cc2ca9ac22 100644 --- a/compiler/docs/template/toctree.txt +++ b/compiler/docs/template/toctree.txt @@ -1,6 +1,8 @@ {title} {title_markup} +Layer {layer} + .. module:: {module} .. toctree:: diff --git a/docs/Makefile b/docs/Makefile index 8eacc12a4d..c94d48ccac 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -21,5 +21,4 @@ help: lhtml: # live html sphinx-autobuild --host $(shell ifconfig | grep "inet " | grep -v 127.0.0.1 | cut -d\ -f2) \ - --watch ../pyrogram \ - -b html "$(SOURCEDIR)" "$(BUILDDIR)/html" $(SPHINXOPTS) + --watch ../pyrogram -b html "$(SOURCEDIR)" "$(BUILDDIR)/html" $(SPHINXOPTS) diff --git a/docs/source/conf.py b/docs/source/conf.py index cb273c070f..3071cdc17b 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -49,7 +49,7 @@ source_suffix = ".rst" autodoc_member_order = "bysource" -templates_path = ["_resources/templates"] +templates_path = ["../resources/templates"] html_copy_source = False napoleon_use_rtype = False @@ -63,7 +63,7 @@ html_title = "Pyrogram Documentation" html_theme = "sphinx_rtd_theme" -html_static_path = ["_resources/static"] +html_static_path = ["../resources/static"] html_show_sourcelink = True html_show_copyright = False html_theme_options = { @@ -75,11 +75,11 @@ "style_external_links": True } -html_logo = "_resources/static/img/pyrogram.png" -html_favicon = "_resources/static/img/favicon.ico" +html_logo = "../resources/static/img/pyrogram.png" +html_favicon = "../resources/static/img/favicon.ico" latex_engine = "xelatex" -latex_logo = "_resources/static/img/pyrogram.png" +latex_logo = "../resources/static/img/pyrogram.png" latex_elements = { "pointsize": "12pt", diff --git a/docs/source/index.rst b/docs/source/index.rst index c3f5d35252..93605f064c 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -14,8 +14,8 @@ Welcome to Pyrogram Telegram MTProto API Framework for Python
- - Development + + Website @@ -54,17 +54,6 @@ If you'd like to support Pyrogram, you can consider: - `Become a LiberaPay patron `_. - `Become an OpenCollective backer `_. -Key Features ------------- - -- **Ready**: Install Pyrogram with pip and start building your applications right away. -- **Easy**: Makes the Telegram API simple and intuitive, while still allowing advanced usages. -- **Elegant**: Low-level details are abstracted and re-presented in a more convenient way. -- **Fast**: Boosted up by :doc:`TgCrypto `, a high-performance crypto library written in pure C. -- **Type-hinted**: Types and methods are all type-hinted, enabling excellent editor support. -- **Async**: Fully asynchronous (also usable synchronously if wanted, for convenience). -- **Powerful**: Full access to Telegram's API to execute any official client action and more. - How the Documentation is Organized ---------------------------------- @@ -172,7 +161,7 @@ Meta .. toctree:: :hidden: - :caption: Telegram API + :caption: Telegram Raw API telegram/functions/index telegram/types/index diff --git a/docs/source/topics/serializing.rst b/docs/source/topics/serializing.rst index e12e3f0511..6a8082b147 100644 --- a/docs/source/topics/serializing.rst +++ b/docs/source/topics/serializing.rst @@ -15,9 +15,7 @@ humans and another more compact for machines that is able to recover the origina For Humans - str(obj) --------------------- -If you want a nicely formatted, human readable JSON representation of any object in the API -- namely, any object from -:doc:`Pyrogram types <../api/types/index>`, :doc:`raw functions <../telegram/functions/index>` and -:doc:`raw types <../telegram/types/index>` -- you can use ``str(obj)``. +If you want a nicely formatted, human readable JSON representation of any object in the API you can use ``str(obj)``. .. code-block:: python From 296b866234ad0eb0ebccbc5d8e68958877302321 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0812/1185] Improve performance by adding a message cache --- pyrogram/client.py | 21 +++++++++++++++ .../bots_and_keyboards/callback_query.py | 10 +++++-- pyrogram/types/messages_and_media/message.py | 26 +++++++++++++------ 3 files changed, 47 insertions(+), 10 deletions(-) diff --git a/pyrogram/client.py b/pyrogram/client.py index 19ee051082..79168a93b4 100644 --- a/pyrogram/client.py +++ b/pyrogram/client.py @@ -270,6 +270,8 @@ def __init__( # Username used for mentioned bot commands, e.g.: /start@usernamebot self.username = None + self.message_cache = Cache(10000) + self.loop = asyncio.get_event_loop() def __enter__(self): @@ -989,3 +991,22 @@ def guess_mime_type(self, filename: str) -> Optional[str]: def guess_extension(self, mime_type: str) -> Optional[str]: return self.mimetypes.guess_extension(mime_type) + + +class Cache: + def __init__(self, capacity: int): + self.capacity = capacity + self.store = {} + + def __getitem__(self, key): + return self.store.get(key, None) + + def __setitem__(self, key, value): + if key in self.store: + del self.store[key] + + self.store[key] = value + + if len(self.store) > self.capacity: + for _ in range(self.capacity // 2 + 1): + del self.store[next(iter(self.store))] diff --git a/pyrogram/types/bots_and_keyboards/callback_query.py b/pyrogram/types/bots_and_keyboards/callback_query.py index 8c96d6cc61..dc4741b264 100644 --- a/pyrogram/types/bots_and_keyboards/callback_query.py +++ b/pyrogram/types/bots_and_keyboards/callback_query.py @@ -89,12 +89,18 @@ def __init__( self.matches = matches @staticmethod - async def _parse(client, callback_query, users) -> "CallbackQuery": + async def _parse(client: "pyrogram.Client", callback_query, users) -> "CallbackQuery": message = None inline_message_id = None if isinstance(callback_query, raw.types.UpdateBotCallbackQuery): - message = await client.get_messages(utils.get_peer_id(callback_query.peer), callback_query.msg_id) + chat_id = utils.get_peer_id(callback_query.peer) + message_id = callback_query.msg_id + + message = client.message_cache[(chat_id, message_id)] + + if not message: + message = await client.get_messages(chat_id, message_id) elif isinstance(callback_query, raw.types.UpdateInlineBotCallbackQuery): inline_message_id = b64encode( pack( diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index 0c639e359c..ec5eed51dd 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -445,7 +445,7 @@ def __init__( @staticmethod async def _parse( - client, + client: "pyrogram.Client", message: raw.base.Message, users: dict, chats: dict, @@ -592,6 +592,8 @@ async def _parse( except MessageIdsEmpty: pass + client.message_cache[(parsed_message.chat.id, parsed_message.id)] = parsed_message + return parsed_message if isinstance(message, raw.types.Message): @@ -802,18 +804,26 @@ async def _parse( ) if message.reply_to: + parsed_message.reply_to_message_id = message.reply_to.reply_to_msg_id + parsed_message.reply_to_top_message_id = message.reply_to.reply_to_top_id + if replies: try: - parsed_message.reply_to_message = await client.get_messages( - parsed_message.chat.id, - reply_to_message_ids=message.id, - replies=replies - 1 - ) + key = (parsed_message.chat.id, parsed_message.reply_to_message_id) + reply_to_message = client.message_cache[key] + + if not reply_to_message: + reply_to_message = await client.get_messages( + parsed_message.chat.id, + reply_to_message_ids=message.id, + replies=replies - 1 + ) + + parsed_message.reply_to_message = reply_to_message except MessageIdsEmpty: pass - parsed_message.reply_to_message_id = message.reply_to.reply_to_msg_id - parsed_message.reply_to_top_message_id = message.reply_to.reply_to_top_id + client.message_cache[(parsed_message.chat.id, parsed_message.id)] = parsed_message return parsed_message From bd11767e887601f20f69378fc02da73eb3fc50f5 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0813/1185] Use a shorter __license__ string --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 1b8202ce62..e4c8bcf356 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -17,7 +17,7 @@ # along with Pyrogram. If not, see . __version__ = "1.4.16" -__license__ = "GNU Lesser General Public License v3 or later (LGPLv3+)" +__license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " from concurrent.futures.thread import ThreadPoolExecutor From a34ee4b444af8a870f8c21147c374d7394ebddb9 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0814/1185] Update index.rst --- docs/source/index.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/source/index.rst b/docs/source/index.rst index 93605f064c..b7c5241f15 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -18,6 +18,10 @@ Welcome to Pyrogram Website • + + Development + + • Releases From 5c0806a8a94079809c54899170fd4c3bfb652325 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0815/1185] Add __repr__ to enumerations --- pyrogram/enums/auto_name.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyrogram/enums/auto_name.py b/pyrogram/enums/auto_name.py index b1d10d1077..b43fa74180 100644 --- a/pyrogram/enums/auto_name.py +++ b/pyrogram/enums/auto_name.py @@ -22,3 +22,6 @@ class AutoName(Enum): def _generate_next_value_(self, *args): return self.lower() + + def __repr__(self): + return f"pyrogram.enums.{self}" From 6eadb75086d12d30e795fc6a2e2ec8a96d38ed4c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0816/1185] Recursively bind when using Object.bind() --- pyrogram/types/object.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/pyrogram/types/object.py b/pyrogram/types/object.py index caf18373e1..8b6a5b99bf 100644 --- a/pyrogram/types/object.py +++ b/pyrogram/types/object.py @@ -29,7 +29,7 @@ def __init__(self, client: "pyrogram.Client" = None): self._client = client def bind(self, client: "pyrogram.Client"): - """Bind a Client instance to this Pyrogram Object + """Recursively bind a Client instance to this and to all nested Pyrogram objects. Parameters: client (:obj:`~pyrogram.types.Client`): @@ -38,6 +38,12 @@ def bind(self, client: "pyrogram.Client"): """ self._client = client + for i in self.__dict__: + o = getattr(self, i) + + if isinstance(o, Object): + o.bind(client) + @staticmethod def default(obj: "Object"): if isinstance(obj, bytes): From 4cb9dec35dc3722f893f3b8443ef988a617db4d5 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0817/1185] Remove remaining iter_* methods --- compiler/docs/compiler.py | 6 +- pyrogram/methods/chats/__init__.py | 2 - pyrogram/methods/chats/get_dialogs.py | 109 +++++++++--------- pyrogram/methods/chats/iter_dialogs.py | 106 ----------------- pyrogram/methods/users/__init__.py | 10 +- ...t_profile_photos.py => get_chat_photos.py} | 82 +++++++------ ...otos_count.py => get_chat_photos_count.py} | 8 +- pyrogram/methods/users/iter_profile_photos.py | 82 ------------- 8 files changed, 110 insertions(+), 295 deletions(-) delete mode 100644 pyrogram/methods/chats/iter_dialogs.py rename pyrogram/methods/users/{get_profile_photos.py => get_chat_photos.py} (64%) rename pyrogram/methods/users/{get_profile_photos_count.py => get_chat_photos_count.py} (92%) delete mode 100644 pyrogram/methods/users/iter_profile_photos.py diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index dd857fc31f..9d1b743460 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -213,7 +213,6 @@ def get_title_list(s: str) -> list: get_chat_members get_chat_members_count get_dialogs - iter_dialogs get_dialogs_count set_chat_username get_nearby_chats @@ -238,9 +237,8 @@ def get_title_list(s: str) -> list: Users get_me get_users - get_profile_photos - get_profile_photos_count - iter_profile_photos + get_chat_photos + get_chat_photos_count set_profile_photo delete_profile_photos set_username diff --git a/pyrogram/methods/chats/__init__.py b/pyrogram/methods/chats/__init__.py index 1b744a0569..a2bdde08a4 100644 --- a/pyrogram/methods/chats/__init__.py +++ b/pyrogram/methods/chats/__init__.py @@ -36,7 +36,6 @@ from .get_dialogs_count import GetDialogsCount from .get_nearby_chats import GetNearbyChats from .get_send_as_chats import GetSendAsChats -from .iter_dialogs import IterDialogs from .join_chat import JoinChat from .leave_chat import LeaveChat from .mark_chat_unread import MarkChatUnread @@ -76,7 +75,6 @@ class Chats( UnpinChatMessage, GetDialogs, GetChatMembersCount, - IterDialogs, SetChatUsername, SetChatPermissions, GetDialogsCount, diff --git a/pyrogram/methods/chats/get_dialogs.py b/pyrogram/methods/chats/get_dialogs.py index f888c3ef4c..2869a6a1ed 100644 --- a/pyrogram/methods/chats/get_dialogs.py +++ b/pyrogram/methods/chats/get_dialogs.py @@ -16,92 +16,87 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -import logging -from datetime import datetime -from typing import List +from typing import AsyncGenerator, Optional import pyrogram -from pyrogram import raw -from pyrogram import types -from pyrogram import utils - -log = logging.getLogger(__name__) +from pyrogram import types, raw, utils class GetDialogs: async def get_dialogs( self: "pyrogram.Client", - offset_date: datetime = datetime.fromtimestamp(0), - limit: int = 100, - pinned_only: bool = False - ) -> List["types.Dialog"]: - """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:`~pyrogram.Client.iter_dialogs`. + limit: int = 0 + ) -> Optional[AsyncGenerator["types.Dialog", None]]: + """Get a user's dialogs sequentially. Parameters: - offset_date (:py:obj:`~datetime.datetime`): - The offset date taken from the top message of a :obj:`~pyrogram.types.Dialog`. - Defaults to epoch. Valid for non-pinned dialogs only. - - limit (``str``, *optional*): + limit (``int``, *optional*): Limits the number of dialogs to be retrieved. - Defaults to 100. Valid for non-pinned dialogs only. - - pinned_only (``bool``, *optional*): - Pass True if you want to get only pinned dialogs. - Defaults to False. + By default, no limit is applied and all dialogs are returned. Returns: - List of :obj:`~pyrogram.types.Dialog`: On success, a list of dialogs is returned. + ``Generator``: A generator yielding :obj:`~pyrogram.types.Dialog` objects. Example: .. code-block:: python - # Get first 100 dialogs - await app.get_dialogs() - - # Get pinned dialogs - await app.get_dialogs(pinned_only=True) + # Iterate through all dialogs + async for dialog in app.get_dialogs(): + print(dialog.chat.first_name or dialog.chat.title) """ + current = 0 + total = limit or (1 << 31) - 1 + limit = min(100, total) - if pinned_only: - r = await self.invoke( - raw.functions.messages.GetPinnedDialogs(folder_id=0), - sleep_threshold=60 - ) - else: + offset_date = 0 + offset_id = 0 + offset_peer = raw.types.InputPeerEmpty() + + while True: r = await self.invoke( raw.functions.messages.GetDialogs( - offset_date=utils.datetime_to_timestamp(offset_date), - offset_id=0, - offset_peer=raw.types.InputPeerEmpty(), + offset_date=offset_date, + offset_id=offset_id, + offset_peer=offset_peer, limit=limit, - hash=0, - exclude_pinned=True + hash=0 ), sleep_threshold=60 ) - users = {i.id: i for i in r.users} - chats = {i.id: i for i in r.chats} + users = {i.id: i for i in r.users} + chats = {i.id: i for i in r.chats} + + messages = {} + + for message in r.messages: + if isinstance(message, raw.types.MessageEmpty): + continue + + chat_id = utils.get_peer_id(message.peer_id) + messages[chat_id] = await types.Message._parse(self, message, users, chats) + + dialogs = [] + + for dialog in r.dialogs: + if not isinstance(dialog, raw.types.Dialog): + continue - messages = {} + dialogs.append(types.Dialog._parse(self, dialog, messages, users, chats)) - for message in r.messages: - if isinstance(message, raw.types.MessageEmpty): - continue + if not dialogs: + return - chat_id = utils.get_peer_id(message.peer_id) - messages[chat_id] = await types.Message._parse(self, message, users, chats) + last = dialogs[-1] - parsed_dialogs = [] + offset_id = last.top_message.id + offset_date = utils.datetime_to_timestamp(last.top_message.date) + offset_peer = await self.resolve_peer(last.chat.id) - for dialog in r.dialogs: - if not isinstance(dialog, raw.types.Dialog): - continue + for dialog in dialogs: + yield dialog - parsed_dialogs.append(types.Dialog._parse(self, dialog, messages, users, chats)) + current += 1 - return types.List(parsed_dialogs) + if current >= total: + return diff --git a/pyrogram/methods/chats/iter_dialogs.py b/pyrogram/methods/chats/iter_dialogs.py deleted file mode 100644 index d0f037bea0..0000000000 --- a/pyrogram/methods/chats/iter_dialogs.py +++ /dev/null @@ -1,106 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-present Dan -# -# 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 AsyncGenerator, Optional - -import pyrogram -from pyrogram import types, raw, utils - - -class IterDialogs: - async def iter_dialogs( - self: "pyrogram.Client", - limit: int = 0 - ) -> Optional[AsyncGenerator["types.Dialog", None]]: - """Iterate through a user's dialogs sequentially. - - This convenience method does the same as repeatedly calling :meth:`~pyrogram.Client.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. - - Parameters: - limit (``int``, *optional*): - Limits the number of dialogs to be retrieved. - By default, no limit is applied and all dialogs are returned. - - Returns: - ``Generator``: A generator yielding :obj:`~pyrogram.types.Dialog` objects. - - Example: - .. code-block:: python - - # Iterate through all dialogs - async for dialog in app.iter_dialogs(): - print(dialog.chat.first_name or dialog.chat.title) - """ - current = 0 - total = limit or (1 << 31) - 1 - limit = min(100, total) - - offset_date = 0 - offset_id = 0 - offset_peer = raw.types.InputPeerEmpty() - - while True: - r = (await self.invoke( - raw.functions.messages.GetDialogs( - offset_date=offset_date, - offset_id=offset_id, - offset_peer=offset_peer, - limit=limit, - hash=0 - ), - sleep_threshold=60 - )) - - users = {i.id: i for i in r.users} - chats = {i.id: i for i in r.chats} - - messages = {} - - for message in r.messages: - if isinstance(message, raw.types.MessageEmpty): - continue - - chat_id = utils.get_peer_id(message.peer_id) - messages[chat_id] = await types.Message._parse(self, message, users, chats) - - dialogs = [] - - for dialog in r.dialogs: - if not isinstance(dialog, raw.types.Dialog): - continue - - dialogs.append(types.Dialog._parse(self, dialog, messages, users, chats)) - - if not dialogs: - return - - last = dialogs[-1] - - offset_id = last.top_message.id - offset_date = last.top_message.date - offset_peer = await self.resolve_peer(last.chat.id) - - for dialog in dialogs: - yield dialog - - current += 1 - - if current >= total: - return diff --git a/pyrogram/methods/users/__init__.py b/pyrogram/methods/users/__init__.py index 6cd81bc363..2ed719e8fc 100644 --- a/pyrogram/methods/users/__init__.py +++ b/pyrogram/methods/users/__init__.py @@ -18,12 +18,11 @@ from .block_user import BlockUser from .delete_profile_photos import DeleteProfilePhotos +from .get_chat_photos import GetChatPhotos +from .get_chat_photos_count import GetChatPhotosCount from .get_common_chats import GetCommonChats from .get_me import GetMe -from .get_profile_photos import GetProfilePhotos -from .get_profile_photos_count import GetProfilePhotosCount from .get_users import GetUsers -from .iter_profile_photos import IterProfilePhotos from .set_profile_photo import SetProfilePhoto from .set_username import SetUsername from .unblock_user import UnblockUser @@ -33,14 +32,13 @@ class Users( BlockUser, GetCommonChats, - GetProfilePhotos, + GetChatPhotos, SetProfilePhoto, DeleteProfilePhotos, GetUsers, GetMe, SetUsername, - GetProfilePhotosCount, - IterProfilePhotos, + GetChatPhotosCount, UnblockUser, UpdateProfile, ): diff --git a/pyrogram/methods/users/get_profile_photos.py b/pyrogram/methods/users/get_chat_photos.py similarity index 64% rename from pyrogram/methods/users/get_profile_photos.py rename to pyrogram/methods/users/get_chat_photos.py index ba20c89ce1..d3bc1f1feb 100644 --- a/pyrogram/methods/users/get_profile_photos.py +++ b/pyrogram/methods/users/get_chat_photos.py @@ -16,22 +16,19 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Union, List +from typing import Union, AsyncGenerator, Optional import pyrogram -from pyrogram import raw -from pyrogram import types -from pyrogram import utils +from pyrogram import types, raw, utils -class GetProfilePhotos: - async def get_profile_photos( +class GetChatPhotos: + async def get_chat_photos( self: "pyrogram.Client", chat_id: Union[int, str], - offset: int = 0, - limit: int = 100 - ) -> List["types.Photo"]: - """Get a list of profile pictures for a user or a chat. + limit: int = 0, + ) -> Optional[AsyncGenerator["types.Photo", None]]: + """Get a chat or a user profile photos sequentially. Parameters: chat_id (``int`` | ``str``): @@ -39,28 +36,18 @@ async def get_profile_photos( For your personal cloud (Saved Messages) you can simply use "me" or "self". For a contact that exists in your Telegram address book you can use his phone number (str). - offset (``int``, *optional*): - Sequential number of the first photo to be returned. - By default, all photos are returned. - limit (``int``, *optional*): - Limits the number of photos to be retrieved. - Values between 1—100 are accepted. Defaults to 100. + Limits the number of profile photos to be retrieved. + By default, no limit is applied and all profile photos are returned. Returns: - List of :obj:`~pyrogram.types.Photo`: On success, a list of profile photos is returned. + ``Generator``: A generator yielding :obj:`~pyrogram.types.Photo` objects. Example: .. code-block:: python - # Get the first 100 profile photos of a user - await app.get_profile_photos("me") - - # Get only the first profile photo of a user - await app.get_profile_photos("me", limit=1) - - # Get 3 profile photos of a user, skip the first 5 - await app.get_profile_photos("me", limit=3, offset=5) + async for photo in app.get_chat_photos("me"): + print(photo) """ peer_id = await self.resolve_peer(chat_id) @@ -105,15 +92,42 @@ async def get_profile_photos( else: photos = [] - return types.List(photos[offset:limit]) + current = 0 + + for photo in photos: + yield photo + + current += 1 + + if current >= limit: + return else: - r = await self.invoke( - raw.functions.photos.GetUserPhotos( - user_id=peer_id, - offset=offset, - max_id=0, - limit=limit + current = 0 + total = limit or (1 << 31) + limit = min(100, total) + offset = 0 + + while True: + r = await self.invoke( + raw.functions.photos.GetUserPhotos( + user_id=peer_id, + offset=offset, + max_id=0, + limit=limit + ) ) - ) - return types.List(types.Photo._parse(self, photo) for photo in r.photos) + photos = [types.Photo._parse(self, photo) for photo in r.photos] + + if not photos: + return + + offset += len(photos) + + for photo in photos: + yield photo + + current += 1 + + if current >= total: + return diff --git a/pyrogram/methods/users/get_profile_photos_count.py b/pyrogram/methods/users/get_chat_photos_count.py similarity index 92% rename from pyrogram/methods/users/get_profile_photos_count.py rename to pyrogram/methods/users/get_chat_photos_count.py index 4c0fe5d5f0..eca186a820 100644 --- a/pyrogram/methods/users/get_profile_photos_count.py +++ b/pyrogram/methods/users/get_chat_photos_count.py @@ -22,12 +22,12 @@ from pyrogram import raw -class GetProfilePhotosCount: - async def get_profile_photos_count( +class GetChatPhotosCount: + async def get_chat_photos_count( self: "pyrogram.Client", chat_id: Union[int, str] ) -> int: - """Get the total count of profile pictures for a user. + """Get the total count of photos for a chat. Parameters: chat_id (``int`` | ``str``): @@ -41,7 +41,7 @@ async def get_profile_photos_count( Example: .. code-block:: python - count = await app.get_profile_photos_count("me") + count = await app.get_chat_photos_count("me") print(count) """ diff --git a/pyrogram/methods/users/iter_profile_photos.py b/pyrogram/methods/users/iter_profile_photos.py deleted file mode 100644 index 6665bc9bda..0000000000 --- a/pyrogram/methods/users/iter_profile_photos.py +++ /dev/null @@ -1,82 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-present Dan -# -# 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 Union, AsyncGenerator, Optional - -import pyrogram -from pyrogram import types - - -class IterProfilePhotos: - async def iter_profile_photos( - self: "pyrogram.Client", - chat_id: Union[int, str], - offset: int = 0, - limit: int = 0, - ) -> Optional[AsyncGenerator["types.Photo", None]]: - """Iterate through a chat or a user profile photos sequentially. - - This convenience method does the same as repeatedly calling :meth:`~pyrogram.Client.get_profile_photos` in a - loop, thus saving you from the hassle of setting up boilerplate code. It is useful for getting all the profile - photos with a single call. - - Parameters: - chat_id (``int`` | ``str``): - Unique identifier (int) or username (str) of the target chat. - For your personal cloud (Saved Messages) you can simply use "me" or "self". - For a contact that exists in your Telegram address book you can use his phone number (str). - - limit (``int``, *optional*): - Limits the number of profile photos to be retrieved. - By default, no limit is applied and all profile photos are returned. - - offset (``int``, *optional*): - Sequential number of the first profile photo to be returned. - - Returns: - ``Generator``: A generator yielding :obj:`~pyrogram.types.Photo` objects. - - Example: - .. code-block:: python - - async for photo in app.iter_profile_photos("me"): - print(photo) - """ - current = 0 - total = limit or (1 << 31) - limit = min(100, total) - - while True: - photos = await self.get_profile_photos( - chat_id=chat_id, - offset=offset, - limit=limit - ) - - if not photos: - return - - offset += len(photos) - - for photo in photos: - yield photo - - current += 1 - - if current >= total: - return From 0d054fa9bc820c85c345a0b9016d1cc23a2e334e Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0818/1185] Update index.rst --- docs/source/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/index.rst b/docs/source/index.rst index b7c5241f15..5ee12b7ec2 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -15,7 +15,7 @@ Welcome to Pyrogram
- Website + Homepage From 01ca652f8c814c68b50d824685a37c9ce665f627 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0819/1185] Add support for in-memory downloads --- pyrogram/client.py | 242 +++++++++---------- pyrogram/methods/messages/download_media.py | 37 ++- pyrogram/types/messages_and_media/message.py | 7 + 3 files changed, 152 insertions(+), 134 deletions(-) diff --git a/pyrogram/client.py b/pyrogram/client.py index 79168a93b4..f727658de2 100644 --- a/pyrogram/client.py +++ b/pyrogram/client.py @@ -29,10 +29,10 @@ from concurrent.futures.thread import ThreadPoolExecutor from hashlib import sha256 from importlib import import_module -from io import StringIO +from io import StringIO, BytesIO from mimetypes import MimeTypes from pathlib import Path -from typing import Union, List, Optional, Callable +from typing import Union, List, Optional, Callable, BinaryIO import pyrogram from pyrogram import __version__, __license__ @@ -482,34 +482,6 @@ async def fetch_peers(self, peers: List[Union[raw.types.User, raw.types.Chat, ra return is_min - async def handle_download(self, packet): - temp_file_path = "" - final_file_path = "" - - try: - file_id, directory, file_name, file_size, progress, progress_args = packet - - temp_file_path = await self.get_file( - file_id=file_id, - file_size=file_size, - progress=progress, - progress_args=progress_args - ) - - if temp_file_path: - final_file_path = os.path.abspath(re.sub("\\\\", "/", os.path.join(directory, file_name))) - os.makedirs(directory, exist_ok=True) - shutil.move(temp_file_path, final_file_path) - except Exception as e: - log.error(e, exc_info=True) - - try: - os.remove(temp_file_path) - except OSError: - pass - else: - return final_file_path or None - async def handle_updates(self, updates): if isinstance(updates, (raw.types.Updates, raw.types.UpdatesCombined)): is_min = (await self.fetch_peers(updates.users)) or (await self.fetch_peers(updates.chats)) @@ -747,13 +719,41 @@ def load_plugins(self): else: log.warning(f'[{self.session_name}] No plugin loaded from "{root}"') + async def handle_download(self, packet): + file_id, directory, file_name, in_memory, file_size, progress, progress_args = packet + + file = await self.get_file( + file_id=file_id, + file_size=file_size, + in_memory=in_memory, + progress=progress, + progress_args=progress_args + ) + + if file and not in_memory: + file_path = os.path.abspath(re.sub("\\\\", "/", os.path.join(directory, file_name))) + os.makedirs(directory, exist_ok=True) + shutil.move(file.name, file_path) + + try: + file.close() + except FileNotFoundError: + pass + + return file_path + + if file and in_memory: + file.name = file_name + return file + async def get_file( self, file_id: FileId, file_size: int, + in_memory: bool, progress: Callable, progress_args: tuple = () - ) -> str: + ) -> Optional[BinaryIO]: dc_id = file_id.dc_id async with self.media_sessions_lock: @@ -838,7 +838,8 @@ async def get_file( limit = 1024 * 1024 offset = 0 - file_name = "" + + file = BytesIO() if in_memory else tempfile.NamedTemporaryFile("wb") try: r = await session.invoke( @@ -851,43 +852,40 @@ async def get_file( ) if isinstance(r, raw.types.upload.File): - with tempfile.NamedTemporaryFile("wb", delete=False) as f: - file_name = f.name - - while True: - chunk = r.bytes - - f.write(chunk) + while True: + chunk = r.bytes - offset += limit + file.write(chunk) - if progress: - func = functools.partial( - progress, - min(offset, file_size) - if file_size != 0 - else offset, - file_size, - *progress_args - ) - - if inspect.iscoroutinefunction(progress): - await func() - else: - await self.loop.run_in_executor(self.executor, func) + offset += limit - if len(chunk) < limit: - break - - r = await session.invoke( - raw.functions.upload.GetFile( - location=location, - offset=offset, - limit=limit - ), - sleep_threshold=30 + if progress: + func = functools.partial( + progress, + min(offset, file_size) + if file_size != 0 + else offset, + file_size, + *progress_args ) + if inspect.iscoroutinefunction(progress): + await func() + else: + await self.loop.run_in_executor(self.executor, func) + + if len(chunk) < limit: + break + + r = await session.invoke( + raw.functions.upload.GetFile( + location=location, + offset=offset, + limit=limit + ), + sleep_threshold=30 + ) + elif isinstance(r, raw.types.upload.FileCdnRedirect): async with self.media_sessions_lock: cdn_session = self.media_sessions.get(r.dc_id, None) @@ -903,88 +901,82 @@ async def get_file( self.media_sessions[r.dc_id] = cdn_session try: - with tempfile.NamedTemporaryFile("wb", delete=False) as f: - file_name = f.name - - while True: - r2 = await cdn_session.invoke( - raw.functions.upload.GetCdnFile( - file_token=r.file_token, - offset=offset, - limit=limit - ) + while True: + r2 = await cdn_session.invoke( + raw.functions.upload.GetCdnFile( + file_token=r.file_token, + offset=offset, + limit=limit ) + ) - if isinstance(r2, raw.types.upload.CdnFileReuploadNeeded): - try: - await session.invoke( - raw.functions.upload.ReuploadCdnFile( - file_token=r.file_token, - request_token=r2.request_token - ) + if isinstance(r2, raw.types.upload.CdnFileReuploadNeeded): + try: + await session.invoke( + raw.functions.upload.ReuploadCdnFile( + file_token=r.file_token, + request_token=r2.request_token ) - except VolumeLocNotFound: - break - else: - continue - - chunk = r2.bytes - - # https://core.telegram.org/cdn#decrypting-files - decrypted_chunk = aes.ctr256_decrypt( - chunk, - r.encryption_key, - bytearray( - r.encryption_iv[:-4] - + (offset // 16).to_bytes(4, "big") ) + except VolumeLocNotFound: + break + else: + continue + + chunk = r2.bytes + + # https://core.telegram.org/cdn#decrypting-files + decrypted_chunk = aes.ctr256_decrypt( + chunk, + r.encryption_key, + bytearray( + r.encryption_iv[:-4] + + (offset // 16).to_bytes(4, "big") ) + ) - hashes = await session.invoke( - raw.functions.upload.GetCdnFileHashes( - file_token=r.file_token, - offset=offset - ) + hashes = await session.invoke( + raw.functions.upload.GetCdnFileHashes( + file_token=r.file_token, + offset=offset ) + ) - # https://core.telegram.org/cdn#verifying-files - for i, h in enumerate(hashes): - cdn_chunk = decrypted_chunk[h.limit * i: h.limit * (i + 1)] - CDNFileHashMismatch.check(h.hash == sha256(cdn_chunk).digest()) + # https://core.telegram.org/cdn#verifying-files + for i, h in enumerate(hashes): + cdn_chunk = decrypted_chunk[h.limit * i: h.limit * (i + 1)] + CDNFileHashMismatch.check(h.hash == sha256(cdn_chunk).digest()) - f.write(decrypted_chunk) + file.write(decrypted_chunk) - offset += limit + offset += limit - if progress: - func = functools.partial( - progress, - min(offset, file_size) if file_size != 0 else offset, - file_size, - *progress_args - ) + if progress: + func = functools.partial( + progress, + min(offset, file_size) if file_size != 0 else offset, + file_size, + *progress_args + ) - if inspect.iscoroutinefunction(progress): - await func() - else: - await self.loop.run_in_executor(self.executor, func) + if inspect.iscoroutinefunction(progress): + await func() + else: + await self.loop.run_in_executor(self.executor, func) - if len(chunk) < limit: - break + if len(chunk) < limit: + break except Exception as e: raise e except Exception as e: if not isinstance(e, pyrogram.StopTransmission): log.error(e, exc_info=True) - try: - os.remove(file_name) - except OSError: - pass + file.close() - return "" + return None else: - return file_name + return file def guess_mime_type(self, filename: str) -> Optional[str]: return self.mimetypes.guess_type(filename)[0] diff --git a/pyrogram/methods/messages/download_media.py b/pyrogram/methods/messages/download_media.py index 8b587d2fae..d46bd50391 100644 --- a/pyrogram/methods/messages/download_media.py +++ b/pyrogram/methods/messages/download_media.py @@ -18,9 +18,8 @@ import asyncio import os -import time from datetime import datetime -from typing import Union, Optional, Callable +from typing import Union, Optional, Callable, BinaryIO import pyrogram from pyrogram import types @@ -34,10 +33,11 @@ async def download_media( self: "pyrogram.Client", message: Union["types.Message", str], file_name: str = DEFAULT_DOWNLOAD_DIR, + in_memory: bool = False, block: bool = True, progress: Callable = None, progress_args: tuple = () - ) -> Optional[str]: + ) -> Optional[Union[str, BinaryIO]]: """Download the media from a message. Parameters: @@ -51,6 +51,11 @@ async def download_media( You can also specify a path for downloading files in a custom location: paths that end with "/" are considered directories. All non-existent folders will be created automatically. + in_memory (``bool``, *optional*): + Pass True to download the media in-memory. + A binary file-like object with its attribute ".name" set will be returned. + Defaults to False. + block (``bool``, *optional*): Blocks the code execution until the file has been downloaded. Defaults to True. @@ -78,14 +83,17 @@ async def download_media( You can either keep ``*args`` or add every single extra argument in your function signature. Returns: - ``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:`~pyrogram.Client.stop_transmission`, None is - returned. + ``str`` | ``None`` | ``BinaryIO``: On success, the absolute path of the downloaded file is returned, + otherwise, in case the download failed or was deliberately stopped with + :meth:`~pyrogram.Client.stop_transmission`, None is returned. + Otherwise, in case ``in_memory=True``, a binary file-like object with its attribute ".name" set is returned. Raises: ValueError: if the message doesn't contain any downloadable media Example: + Download media to file + .. code-block:: python # Download from Message @@ -99,6 +107,15 @@ async def progress(current, total): print(f"{current * 100 / total:.1f}%") await app.download_media(message, progress=progress) + + Download media in-memory + + .. code-block:: python + + file = await app.download_media(message, in_memory=True) + + file_name = file.name + file_bytes = bytes(file.getbuffer()) """ available_media = ("audio", "document", "photo", "sticker", "animation", "video", "voice", "video_note", "new_chat_photo") @@ -125,7 +142,7 @@ async def progress(current, total): media_file_name = getattr(media, "file_name", "") file_size = getattr(media, "file_size", 0) mime_type = getattr(media, "mime_type", "") - date = getattr(media, "date", 0) + date = getattr(media, "date", None) directory, file_name = os.path.split(file_name) file_name = file_name or media_file_name or "" @@ -153,12 +170,14 @@ async def progress(current, total): file_name = "{}_{}_{}{}".format( FileType(file_id_obj.file_type).name.lower(), - datetime.fromtimestamp(date or time.time()).strftime("%Y-%m-%d_%H-%M-%S"), + (date or datetime.now()).strftime("%Y-%m-%d_%H-%M-%S"), self.rnd_id(), extension ) - downloader = self.handle_download((file_id_obj, directory, file_name, file_size, progress, progress_args)) + downloader = self.handle_download( + (file_id_obj, directory, file_name, in_memory, file_size, progress, progress_args) + ) if block: return await downloader diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index ec5eed51dd..b7b26805c4 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -3329,6 +3329,7 @@ async def retract_vote( async def download( self, file_name: str = "", + in_memory: bool = False, block: bool = True, progress: Callable = None, progress_args: tuple = () @@ -3353,6 +3354,11 @@ async def download( You can also specify a path for downloading files in a custom location: paths that end with "/" are considered directories. All non-existent folders will be created automatically. + in_memory (``bool``, *optional*): + Pass True to download the media in-memory. + A binary file-like object with its attribute ".name" set will be returned. + Defaults to False. + block (``bool``, *optional*): Blocks the code execution until the file has been downloaded. Defaults to True. @@ -3389,6 +3395,7 @@ async def download( return await self._client.download_media( message=self, file_name=file_name, + in_memory=in_memory, block=block, progress=progress, progress_args=progress_args, From b2c4d26ce66807901b7c08bfe2b74e587f8eb0a1 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0820/1185] Fix Message.download() docstrings --- pyrogram/types/messages_and_media/message.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index b7b26805c4..3a72fe33eb 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -3340,7 +3340,7 @@ async def download( .. code-block:: python - await lient.download_media(message) + await client.download_media(message) Example: .. code-block:: python From 3e33ef0c0d6df837b445e2d35bbdfda79599ac40 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0821/1185] Add support for media streams with the method stream_media --- compiler/docs/compiler.py | 1 + pyrogram/client.py | 72 ++++++++---------- pyrogram/methods/messages/__init__.py | 4 +- pyrogram/methods/messages/stream_media.py | 93 +++++++++++++++++++++++ 4 files changed, 130 insertions(+), 40 deletions(-) create mode 100644 pyrogram/methods/messages/stream_media.py diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index 9d1b743460..b5382438cb 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -187,6 +187,7 @@ def get_title_list(s: str) -> list: search_global search_global_count download_media + stream_media get_discussion_message get_discussion_replies get_discussion_replies_count diff --git a/pyrogram/client.py b/pyrogram/client.py index f727658de2..18e5253b1c 100644 --- a/pyrogram/client.py +++ b/pyrogram/client.py @@ -32,7 +32,7 @@ from io import StringIO, BytesIO from mimetypes import MimeTypes from pathlib import Path -from typing import Union, List, Optional, Callable, BinaryIO +from typing import Union, List, Optional, Callable, AsyncGenerator import pyrogram from pyrogram import __version__, __license__ @@ -722,13 +722,10 @@ def load_plugins(self): async def handle_download(self, packet): file_id, directory, file_name, in_memory, file_size, progress, progress_args = packet - file = await self.get_file( - file_id=file_id, - file_size=file_size, - in_memory=in_memory, - progress=progress, - progress_args=progress_args - ) + file = BytesIO() if in_memory else tempfile.NamedTemporaryFile("wb", delete=False) + + async for chunk in self.get_file(file_id, file_size, 0, 0, progress, progress_args): + file.write(chunk) if file and not in_memory: file_path = os.path.abspath(re.sub("\\\\", "/", os.path.join(directory, file_name))) @@ -749,11 +746,12 @@ async def handle_download(self, packet): async def get_file( self, file_id: FileId, - file_size: int, - in_memory: bool, - progress: Callable, + file_size: int = 0, + limit: int = 0, + offset: int = 0, + progress: Callable = None, progress_args: tuple = () - ) -> Optional[BinaryIO]: + ) -> Optional[AsyncGenerator[bytes, None]]: dc_id = file_id.dc_id async with self.media_sessions_lock: @@ -836,17 +834,17 @@ async def get_file( thumb_size=file_id.thumbnail_size ) - limit = 1024 * 1024 - offset = 0 - - file = BytesIO() if in_memory else tempfile.NamedTemporaryFile("wb") + current = 0 + total = abs(limit) or (1 << 31) - 1 + chunk_size = 1024 * 1024 + offset_bytes = abs(offset) * chunk_size try: r = await session.invoke( raw.functions.upload.GetFile( location=location, - offset=offset, - limit=limit + offset=offset_bytes, + limit=chunk_size ), sleep_threshold=30 ) @@ -855,16 +853,17 @@ async def get_file( while True: chunk = r.bytes - file.write(chunk) + yield chunk - offset += limit + current += 1 + offset_bytes += chunk_size if progress: func = functools.partial( progress, - min(offset, file_size) + min(offset_bytes, file_size) if file_size != 0 - else offset, + else offset_bytes, file_size, *progress_args ) @@ -874,14 +873,14 @@ async def get_file( else: await self.loop.run_in_executor(self.executor, func) - if len(chunk) < limit: + if len(chunk) < chunk_size or current >= total: break r = await session.invoke( raw.functions.upload.GetFile( location=location, - offset=offset, - limit=limit + offset=offset_bytes, + limit=chunk_size ), sleep_threshold=30 ) @@ -905,8 +904,8 @@ async def get_file( r2 = await cdn_session.invoke( raw.functions.upload.GetCdnFile( file_token=r.file_token, - offset=offset, - limit=limit + offset=offset_bytes, + limit=chunk_size ) ) @@ -931,14 +930,14 @@ async def get_file( r.encryption_key, bytearray( r.encryption_iv[:-4] - + (offset // 16).to_bytes(4, "big") + + (offset_bytes // 16).to_bytes(4, "big") ) ) hashes = await session.invoke( raw.functions.upload.GetCdnFileHashes( file_token=r.file_token, - offset=offset + offset=offset_bytes ) ) @@ -947,14 +946,15 @@ async def get_file( cdn_chunk = decrypted_chunk[h.limit * i: h.limit * (i + 1)] CDNFileHashMismatch.check(h.hash == sha256(cdn_chunk).digest()) - file.write(decrypted_chunk) + yield decrypted_chunk - offset += limit + current += 1 + offset_bytes += chunk_size if progress: func = functools.partial( progress, - min(offset, file_size) if file_size != 0 else offset, + min(offset_bytes, file_size) if file_size != 0 else offset_bytes, file_size, *progress_args ) @@ -964,7 +964,7 @@ async def get_file( else: await self.loop.run_in_executor(self.executor, func) - if len(chunk) < limit: + if len(chunk) < chunk_size or current >= total: break except Exception as e: raise e @@ -972,12 +972,6 @@ async def get_file( if not isinstance(e, pyrogram.StopTransmission): log.error(e, exc_info=True) - file.close() - - return None - else: - return file - def guess_mime_type(self, filename: str) -> Optional[str]: return self.mimetypes.guess_type(filename)[0] diff --git a/pyrogram/methods/messages/__init__.py b/pyrogram/methods/messages/__init__.py index 0bf34900d0..dafce11e4f 100644 --- a/pyrogram/methods/messages/__init__.py +++ b/pyrogram/methods/messages/__init__.py @@ -61,6 +61,7 @@ from .send_video_note import SendVideoNote from .send_voice import SendVoice from .stop_poll import StopPoll +from .stream_media import StreamMedia from .vote_poll import VotePoll @@ -110,6 +111,7 @@ class Messages( GetDiscussionMessage, SendReaction, GetDiscussionReplies, - GetDiscussionRepliesCount + GetDiscussionRepliesCount, + StreamMedia ): pass diff --git a/pyrogram/methods/messages/stream_media.py b/pyrogram/methods/messages/stream_media.py new file mode 100644 index 0000000000..0daaa55667 --- /dev/null +++ b/pyrogram/methods/messages/stream_media.py @@ -0,0 +1,93 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# 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 Union, Optional, BinaryIO + +import pyrogram +from pyrogram import types +from pyrogram.file_id import FileId + + +class StreamMedia: + async def stream_media( + self: "pyrogram.Client", + message: Union["types.Message", str], + limit: int = 0, + offset: int = 0 + ) -> Optional[Union[str, BinaryIO]]: + """Stream the media from a message chunk by chunk. + + The chunk size is 1 MiB (1024 * 1024 bytes). + + Parameters: + message (:obj:`~pyrogram.types.Message` | ``str``): + Pass a Message containing the media, the media itself (message.audio, message.video, ...) or a file id + as string. + + limit (``int``, *optional*): + Limit the amount of chunks to stream. + Defaults to 0 (stream the whole media). + + offset (``int``, *optional*): + How many chunks to skip before starting to stream. + Defaults to 0 (start from the beginning). + + Returns: + ``Generator``: A generator yielding bytes chunk by chunk + + Example: + .. code-block:: python + + # Stream the whole media + async for chunk in app.stream_media(message): + print(len(chunk)) + + # Stream the first 3 chunks only + async for chunk in app.stream_media(message, limit=3): + print(len(chunk)) + + # Stream the last 3 chunks only + import math + chunks = math.ceil(message.document.file_size / 1024 / 1024) + async for chunk in app.stream_media(message, offset=chunks - 3): + print(len(chunk)) + """ + available_media = ("audio", "document", "photo", "sticker", "animation", "video", "voice", "video_note", + "new_chat_photo") + + if isinstance(message, types.Message): + for kind in available_media: + media = getattr(message, kind, None) + + if media is not None: + break + else: + raise ValueError("This message doesn't contain any downloadable media") + else: + media = message + + if isinstance(media, str): + file_id_str = media + else: + file_id_str = media.file_id + + file_id_obj = FileId.decode(file_id_str) + file_size = getattr(media, "file_size", 0) + + async for chunk in self.get_file(file_id_obj, file_size, limit, offset): + yield chunk From 394a9adc03941f41d4edaa7ba70133f393bd98ed Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0822/1185] Fix type hints --- pyrogram/methods/chats/get_chat_members.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/methods/chats/get_chat_members.py b/pyrogram/methods/chats/get_chat_members.py index 065e262c7b..65688a3d71 100644 --- a/pyrogram/methods/chats/get_chat_members.py +++ b/pyrogram/methods/chats/get_chat_members.py @@ -17,7 +17,7 @@ # along with Pyrogram. If not, see . import logging -from typing import Union, List +from typing import Union, Optional, AsyncGenerator import pyrogram from pyrogram import raw, types, enums @@ -64,7 +64,7 @@ async def get_chat_members( query: str = "", limit: int = 0, filter: "enums.ChatMembersFilter" = enums.ChatMembersFilter.SEARCH - ) -> List["types.ChatMember"]: + ) -> Optional[AsyncGenerator["types.ChatMember", None]]: """Get the members list of a chat. A chat can be either a basic group, a supergroup or a channel. From 70d5f22e0deb524839588bcf7b2b7d800ecf25be Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0823/1185] Some fixes to the documentation --- docs/Makefile | 2 +- docs/source/intro/quickstart.rst | 2 +- docs/source/start/auth.rst | 4 +- docs/source/start/errors.rst | 5 +- docs/source/start/updates.rst | 2 +- docs/source/topics/advanced-usage.rst | 14 ++-- docs/source/topics/client-settings.rst | 16 +---- docs/source/topics/create-filters.rst | 52 ++------------- docs/source/topics/debugging.rst | 2 +- docs/source/topics/more-on-updates.rst | 38 +++++------ docs/source/topics/serializing.rst | 8 +-- docs/source/topics/smart-plugins.rst | 88 +++++--------------------- docs/source/topics/test-servers.rst | 4 +- docs/source/topics/text-formatting.rst | 12 ++-- docs/source/topics/use-filters.rst | 18 +++--- 15 files changed, 75 insertions(+), 192 deletions(-) diff --git a/docs/Makefile b/docs/Makefile index c94d48ccac..066560f8f7 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -21,4 +21,4 @@ help: lhtml: # live html sphinx-autobuild --host $(shell ifconfig | grep "inet " | grep -v 127.0.0.1 | cut -d\ -f2) \ - --watch ../pyrogram -b html "$(SOURCEDIR)" "$(BUILDDIR)/html" $(SPHINXOPTS) + --watch ../pyrogram --watch resources -b html "$(SOURCEDIR)" "$(BUILDDIR)/html" $(SPHINXOPTS) diff --git a/docs/source/intro/quickstart.rst b/docs/source/intro/quickstart.rst index 72f2dd86b4..cccbfbc76b 100644 --- a/docs/source/intro/quickstart.rst +++ b/docs/source/intro/quickstart.rst @@ -50,7 +50,7 @@ Enjoy the API That was just a quick overview. In the next few pages of the introduction, we'll take a much more in-depth look of what we have just done above. -If you are feeling eager to continue you can take a shortcut to :doc:`Calling Methods <../start/invoking>` and come back +If you are feeling eager to continue you can take a shortcut to :doc:`Invoking Methods <../start/invoking>` and come back later to learn some more details. .. _community: https://t.me/Pyrogram diff --git a/docs/source/start/auth.rst b/docs/source/start/auth.rst index 39a4face94..08c1875ac6 100644 --- a/docs/source/start/auth.rst +++ b/docs/source/start/auth.rst @@ -82,8 +82,8 @@ after the session name, which will be ``my_bot.session`` for the example below. .. note:: - The API key (api_id and api_hash) and the bot_token is not needed anymore after a successful authorization. - This means you can now simply use: + The API key (api_id and api_hash) and the bot_token are not required anymore after a successful authorization. + This means you can now simply use the following: .. code-block:: python diff --git a/docs/source/start/errors.rst b/docs/source/start/errors.rst index 0188107413..402fea8b7c 100644 --- a/docs/source/start/errors.rst +++ b/docs/source/start/errors.rst @@ -25,7 +25,7 @@ This error is raised every time a method call against Telegram's API was unsucce from pyrogram.errors import RPCError -.. note:: +.. warning:: Avoid catching this error everywhere, 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. @@ -81,9 +81,6 @@ In case Pyrogram does not know anything about a specific error yet, it raises a for example, an unknown error with error code ``400``, will be raised as a ``BadRequest``. This way you can catch the whole category of errors and be sure to also handle these unknown errors. -In case a whole class of errors is unknown (that is, an error code that is unknown), Pyrogram will raise a special -``520 UnknownError`` exception. - Errors with Values ------------------ diff --git a/docs/source/start/updates.rst b/docs/source/start/updates.rst index 450f0d853c..685128c21c 100644 --- a/docs/source/start/updates.rst +++ b/docs/source/start/updates.rst @@ -14,7 +14,7 @@ and how to handle new incoming messages or other events in Pyrogram. Defining Updates ---------------- -As hinted already, updates are events that happen in your Telegram account (incoming messages, new members join, +Updates are 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 has changed. These updates are handled by registering one or more callback functions in your app using :doc:`Handlers <../api/handlers>`. diff --git a/docs/source/topics/advanced-usage.rst b/docs/source/topics/advanced-usage.rst index c84528e4e1..86eb67c4db 100644 --- a/docs/source/topics/advanced-usage.rst +++ b/docs/source/topics/advanced-usage.rst @@ -50,8 +50,8 @@ Here's some examples: from pyrogram import Client from pyrogram.raw import functions - with Client("my_account") as app: - app.send( + async with Client("my_account") as app: + await app.send( functions.account.UpdateProfile( first_name="First Name", last_name="Last Name", about="New bio text" @@ -65,12 +65,12 @@ Here's some examples: from pyrogram import Client from pyrogram.raw import functions, types - with Client("my_account") as app: + async with Client("my_account") as app: # Set online status - app.send(functions.account.UpdateStatus(offline=False)) + await app.send(functions.account.UpdateStatus(offline=False)) # Set offline status - app.send(functions.account.UpdateStatus(offline=True)) + await app.send(functions.account.UpdateStatus(offline=True)) - Get chat info: @@ -79,8 +79,8 @@ Here's some examples: from pyrogram import Client from pyrogram.raw import functions, types - with Client("my_account") as app: - r = app.send( + async with Client("my_account") as app: + r = await app.send( functions.channels.GetFullChannel( channel=app.resolve_peer("username") ) diff --git a/docs/source/topics/client-settings.rst b/docs/source/topics/client-settings.rst index 1b93d99d7b..02dce71375 100644 --- a/docs/source/topics/client-settings.rst +++ b/docs/source/topics/client-settings.rst @@ -18,16 +18,7 @@ settings. By default you will see something like the following: Set Custom Values ----------------- -To set custom values, you can either make use of the ``config.ini`` file, this way: - -.. code-block:: ini - - [pyrogram] - app_version = 1.2.3 - device_model = PC - system_version = Linux - -Or, pass the arguments directly in the Client's constructor. +To set custom values, you can pass the arguments directly in the Client's constructor. .. code-block:: python @@ -47,11 +38,6 @@ English). With the following code we make Telegram know we want it to speak in Italian (it): -.. code-block:: ini - - [pyrogram] - lang_code = it - .. code-block:: python app = Client( diff --git a/docs/source/topics/create-filters.rst b/docs/source/topics/create-filters.rst index ae7bd5ecf7..f8c05af620 100644 --- a/docs/source/topics/create-filters.rst +++ b/docs/source/topics/create-filters.rst @@ -25,7 +25,7 @@ button: from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton - app.send_message( + await app.send_message( "username", # Change this to your username or id "Pyrogram custom filter test", reply_markup=InlineKeyboardMarkup( @@ -48,40 +48,20 @@ queries containing "pyrogram" as data: from pyrogram import filters - static_data_filter = filters.create(lambda _, __, query: query.data == "pyrogram") - -The first two arguments of the callback function are unused here and because of this we named them using underscores. - -The ``lambda`` operator in python is used to create small anonymous functions and is perfect for this example. The same -can be achieved with a normal function, but we don't really need it as it makes sense only inside the filter's scope: - -.. code-block:: python - - from pyrogram import filters - - def func(_, __, query): + async def func(_, __, query): return query.data == "pyrogram" static_data_filter = filters.create(func) -Asynchronous filters are also possible. Sadly, Python itself doesn't have an ``async lambda``, so we are left with -using a named function: - -.. code-block:: python - - from pyrogram import filters - - async def func(_, __, query): - return query.data == "pyrogram" - static_data_filter = filters.create(func) +The first two arguments of the callback function are unused here and because of this we named them using underscores. Finally, the filter usage remains the same: .. code-block:: python @app.on_callback_query(static_data_filter) - def pyrogram_data(_, query): + async def pyrogram_data(_, query): query.answer("it works!") Filters with Arguments @@ -94,18 +74,6 @@ via named arguments. This is how a dynamic custom filter looks like: -.. code-block:: python - - from pyrogram import filters - - def dynamic_data_filter(data): - return filters.create( - lambda flt, _, query: flt.data == query.data, - data=data # "data" kwarg is accessed with "flt.data" above - ) - -And its asynchronous variant: - .. code-block:: python from pyrogram import filters @@ -122,7 +90,7 @@ And finally its usage: .. code-block:: python @app.on_callback_query(dynamic_data_filter("pyrogram")) - def pyrogram_data(_, query): + async def pyrogram_data(_, query): query.answer("it works!") @@ -133,16 +101,6 @@ The missing piece we haven't covered yet is the second argument of a filter call argument. This is a reference to the :obj:`~pyrogram.Client` instance that is running the filter and it is useful in case you would like to make some API calls before deciding whether the filter should allow the update or not: -.. code-block:: python - - def func(_, client, query): - # r = client.some_api_method() - # check response "r" and decide to return True or False - ... - -Asynchronous filters making API calls work fine as well. Just remember that you need to put ``async`` in front of -function definitions and ``await`` in front of method calls: - .. code-block:: python async def func(_, client, query): diff --git a/docs/source/topics/debugging.rst b/docs/source/topics/debugging.rst index 6e6e6d5ed8..1c0ac069f9 100644 --- a/docs/source/topics/debugging.rst +++ b/docs/source/topics/debugging.rst @@ -27,7 +27,7 @@ Consider the following code: .. code-block:: python - me = app.get_users("me") + me = await app.get_users("me") print(me) # User This will show a JSON representation of the object returned by :meth:`~pyrogram.Client.get_users`, which is a diff --git a/docs/source/topics/more-on-updates.rst b/docs/source/topics/more-on-updates.rst index 81a46f3be4..18c1a68af3 100644 --- a/docs/source/topics/more-on-updates.rst +++ b/docs/source/topics/more-on-updates.rst @@ -26,12 +26,12 @@ For example, take these two handlers: .. code-block:: python @app.on_message(filters.text | filters.sticker) - def text_or_sticker(client, message): + async def text_or_sticker(client, message): print("Text or Sticker") @app.on_message(filters.text) - def just_text(client, message): + async def just_text(client, message): print("Just Text") Here, ``just_text`` is never executed because ``text_or_sticker``, which has been registered first, already handles @@ -40,7 +40,7 @@ texts (``filters.text`` is shared and conflicting). To enable it, register the h .. code-block:: python @app.on_message(filters.text, group=1) - def just_text(client, message): + async def just_text(client, message): print("Just Text") Or, if you want ``just_text`` to be executed *before* ``text_or_sticker`` (note ``-1``, which is less than ``0``): @@ -48,7 +48,7 @@ Or, if you want ``just_text`` to be executed *before* ``text_or_sticker`` (note .. code-block:: python @app.on_message(filters.text, group=-1) - def just_text(client, message): + async def just_text(client, message): print("Just Text") With :meth:`~pyrogram.Client.add_handler` (without decorators) the same can be achieved with: @@ -68,17 +68,17 @@ continue to propagate the same update to the next groups until all the handlers .. code-block:: python @app.on_message(filters.private) - def _(client, message): + async def _(client, message): print(0) @app.on_message(filters.private, group=1) - def _(client, message): + async def _(client, message): raise Exception("Unhandled exception!") # Simulate an unhandled exception @app.on_message(filters.private, group=2) - def _(client, message): + async def _(client, message): print(2) All these handlers will handle the same kind of messages, that are, messages sent or received in private chats. @@ -110,18 +110,18 @@ Example with ``stop_propagation()``: .. code-block:: python @app.on_message(filters.private) - def _(client, message): + async def _(client, message): print(0) @app.on_message(filters.private, group=1) - def _(client, message): + async def _(client, message): print(1) message.stop_propagation() @app.on_message(filters.private, group=2) - def _(client, message): + async def _(client, message): print(2) Example with ``raise StopPropagation``: @@ -131,18 +131,18 @@ Example with ``raise StopPropagation``: from pyrogram import StopPropagation @app.on_message(filters.private) - def _(client, message): + async def _(client, message): print(0) @app.on_message(filters.private, group=1) - def _(client, message): + async ef _(client, message): print(1) raise StopPropagation @app.on_message(filters.private, group=2) - def _(client, message): + async def _(client, message): print(2) Each handler is registered in a different group, but the handler in group number 2 will never be executed because the @@ -178,19 +178,19 @@ Example with ``continue_propagation()``: .. code-block:: python @app.on_message(filters.private) - def _(client, message): + async def _(client, message): print(0) message.continue_propagation() @app.on_message(filters.private) - def _(client, message): + async def _(client, message): print(1) message.continue_propagation() @app.on_message(filters.private) - def _(client, message): + async def _(client, message): print(2) Example with ``raise ContinuePropagation``: @@ -200,19 +200,19 @@ Example with ``raise ContinuePropagation``: from pyrogram import ContinuePropagation @app.on_message(filters.private) - def _(client, message): + async def _(client, message): print(0) raise ContinuePropagation @app.on_message(filters.private) - def _(client, message): + async def _(client, message): print(1) raise ContinuePropagation @app.on_message(filters.private) - def _(client, message): + async def _(client, message): print(2) Three handlers are registered in the same group, and all of them will be executed because the propagation was continued diff --git a/docs/source/topics/serializing.rst b/docs/source/topics/serializing.rst index 6a8082b147..3dc644f880 100644 --- a/docs/source/topics/serializing.rst +++ b/docs/source/topics/serializing.rst @@ -21,8 +21,8 @@ If you want a nicely formatted, human readable JSON representation of any object ... - with app: - r = app.get_chat("me") + async with app: + r = await app.get_chat("me") print(str(r)) .. tip:: @@ -44,8 +44,8 @@ as the process requires the package to be in scope. ... - with app: - r = app.get_chat("me") + async with app: + r = await app.get_chat("me") print(repr(r)) print(eval(repr(r)) == r) # True diff --git a/docs/source/topics/smart-plugins.rst b/docs/source/topics/smart-plugins.rst index a94e3212fe..c378c9d81f 100644 --- a/docs/source/topics/smart-plugins.rst +++ b/docs/source/topics/smart-plugins.rst @@ -33,7 +33,6 @@ after importing your modules, like this: .. code-block:: text myproject/ - config.ini handlers.py main.py @@ -41,12 +40,12 @@ after importing your modules, like this: .. code-block:: python - def echo(client, message): - message.reply(message.text) + async def echo(client, message): + await message.reply(message.text) - def echo_reversed(client, message): - message.reply(message.text[::-1]) + async def echo_reversed(client, message): + await message.reply(message.text[::-1]) - ``main.py`` @@ -84,7 +83,7 @@ Setting up your Pyrogram project to accommodate Smart Plugins is pretty straight #. Create a new folder to store all the plugins (e.g.: "plugins", "handlers", ...). #. Put your python files full of plugins inside. Organize them as you wish. -#. Enable plugins in your Client or via the *config.ini* file. +#. Enable plugins in your Client. .. note:: @@ -95,7 +94,6 @@ Setting up your Pyrogram project to accommodate Smart Plugins is pretty straight myproject/ plugins/ handlers.py - config.ini main.py - ``plugins/handlers.py`` @@ -106,31 +104,16 @@ Setting up your Pyrogram project to accommodate Smart Plugins is pretty straight @Client.on_message(filters.text & filters.private) - def echo(client, message): - message.reply(message.text) + async def echo(client, message): + await message.reply(message.text) @Client.on_message(filters.text & filters.private, group=1) - def echo_reversed(client, message): - message.reply(message.text[::-1]) - -- ``config.ini`` - - .. code-block:: ini - - [plugins] - root = plugins + async def echo_reversed(client, message): + await message.reply(message.text[::-1]) - ``main.py`` - .. code-block:: python - - from pyrogram import Client - - Client("my_account").run() - - Alternatively, without using the *config.ini* file: - .. code-block:: python from pyrogram import Client @@ -144,9 +127,9 @@ The first important thing to note is the new ``plugins`` folder. You can put *an each file can contain *any decorated function* (handlers) with one limitation: within a single module (file) you must use different names for each decorated function. -The second thing is telling Pyrogram where to look for your plugins: you can either use the *config.ini* file or -the Client parameter "plugins"; the *root* value must match the name of your plugins root folder. Your Pyrogram Client -instance will **automatically** scan the folder upon starting to search for valid handlers and register them for you. +The second thing is telling Pyrogram where to look for your plugins: you can use the Client parameter "plugins"; +the *root* value must match the name of your plugins root folder. Your Pyrogram Client instance will **automatically** +scan the folder upon starting to search for valid handlers and register them for you. Then you'll notice you can now use decorators. That's right, you can apply the usual decorators to your callback functions in a static way, i.e. **without having the Client instance around**: simply use ``@Client`` (Client class) @@ -166,7 +149,7 @@ found inside each module will be, instead, loaded in the order they are defined, 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`` -directives, either in the *config.ini* file or in the dictionary passed as Client argument. Here's how they work: +directives in the dictionary passed as Client argument. Here's how they work: - If both ``include`` and ``exclude`` are omitted, all plugins are loaded as described above. - If ``include`` is given, only the specified plugins will be loaded, in the order they are passed. @@ -208,15 +191,6 @@ 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): - Using *config.ini* file: - - .. code-block:: ini - - [plugins] - root = plugins - - Using *Client*'s parameter: - .. code-block:: python plugins = dict(root="plugins") @@ -225,18 +199,6 @@ also organized in subfolders: - Load only handlers defined inside *plugins2.py* and *plugins0.py*, in this order: - Using *config.ini* file: - - .. code-block:: ini - - [plugins] - root = plugins - include = - subfolder2.plugins2 - plugins0 - - Using *Client*'s parameter: - .. code-block:: python plugins = dict( @@ -251,16 +213,6 @@ also organized in subfolders: - Load everything except the handlers inside *plugins2.py*: - Using *config.ini* file: - - .. code-block:: ini - - [plugins] - root = plugins - exclude = subfolder2.plugins2 - - Using *Client*'s parameter: - .. code-block:: python plugins = dict( @@ -272,16 +224,6 @@ also organized in subfolders: - Load only *fn3*, *fn1* and *fn2* (in this order) from *plugins1.py*: - Using *config.ini* file: - - .. code-block:: ini - - [plugins] - root = plugins - include = subfolder1.plugins1 fn3 fn1 fn2 - - Using *Client*'s parameter: - .. code-block:: python plugins = dict( @@ -306,8 +248,8 @@ updates) will be modified in such a way that a special ``handlers`` attribute po .. code-block:: python @Client.on_message(filters.text & filters.private) - def echo(client, message): - message.reply(message.text) + async def echo(client, message): + await message.reply(message.text) print(echo) print(echo.handlers) diff --git a/docs/source/topics/test-servers.rst b/docs/source/topics/test-servers.rst index cba5e709f0..1ccfe286c8 100644 --- a/docs/source/topics/test-servers.rst +++ b/docs/source/topics/test-servers.rst @@ -9,8 +9,8 @@ Telegram's test servers without hassle. All you need to do is start a new sessio from pyrogram import Client - with Client("my_account_test", test_mode=True) as app: - print(app.get_me()) + async with Client("my_account_test", test_mode=True) as app: + print(await app.get_me()) .. note:: diff --git a/docs/source/topics/text-formatting.rst b/docs/source/topics/text-formatting.rst index 8ad2b4db61..4e11fb7410 100644 --- a/docs/source/topics/text-formatting.rst +++ b/docs/source/topics/text-formatting.rst @@ -80,7 +80,7 @@ To strictly use this mode, pass :obj:`~pyrogram.enums.ParseMode.MARKDOWN` to the from pyrogram import enums - app.send_message( + await app.send_message( "me", ( "**bold**, " @@ -134,7 +134,7 @@ To strictly use this mode, pass :obj:`~pyrogram.enums.HTML` to the *parse_mode* from pyrogram import enums - app.send_message( + await app.send_message( "me", ( "bold, " @@ -179,7 +179,7 @@ This means you can combine together both syntaxes in the same text: .. code-block:: python - app.send_message("me", "**bold**, italic") + await app.send_message("me", "**bold**, italic") Result: @@ -192,8 +192,8 @@ If you don't like this behaviour you can always choose to only enable either Mar from pyrogram import enums - app.send_message("me", "**bold**, italic", parse_mode=enums.ParseMode.MARKDOWN) - app.send_message("me", "**bold**, italic", parse_mode=enums.ParseMode.HTML) + await app.send_message("me", "**bold**, italic", parse_mode=enums.ParseMode.MARKDOWN) + await app.send_message("me", "**bold**, italic", parse_mode=enums.ParseMode.HTML) Result: @@ -208,7 +208,7 @@ The text will be sent as-is. from pyrogram import enums - app.send_message("me", "**bold**, italic", parse_mode=enums.ParseMode.DISABLED) + await app.send_message("me", "**bold**, italic", parse_mode=enums.ParseMode.DISABLED) Result: diff --git a/docs/source/topics/use-filters.rst b/docs/source/topics/use-filters.rst index 62e94d27c8..ab7296af85 100644 --- a/docs/source/topics/use-filters.rst +++ b/docs/source/topics/use-filters.rst @@ -28,7 +28,7 @@ Let's start right away with a simple example: @app.on_message(filters.sticker) - def my_handler(client, message): + async def my_handler(client, message): print(message) - or, without decorators. Here filters are passed as the second argument of the handler constructor; the first is the @@ -40,7 +40,7 @@ Let's start right away with a simple example: from pyrogram.handlers import MessageHandler - def my_handler(client, message): + async def my_handler(client, message): print(message) @@ -62,7 +62,7 @@ Here are some examples: .. code-block:: python @app.on_message(filters.text | filters.photo) - def my_handler(client, message): + async def my_handler(client, message): print(message) - Message is a **sticker** **and** is coming from a **channel or** a **private** chat. @@ -70,7 +70,7 @@ Here are some examples: .. code-block:: python @app.on_message(filters.sticker & (filters.channel | filters.private)) - def my_handler(client, message): + async def my_handler(client, message): print(message) Advanced Filters @@ -84,7 +84,7 @@ can also accept arguments: .. code-block:: python @app.on_message(filters.command(["start", "help"])) - def my_handler(client, message): + async def my_handler(client, message): print(message) - Message is a **text** message or a media **caption** matching the given **regex** pattern. @@ -92,7 +92,7 @@ can also accept arguments: .. code-block:: python @app.on_message(filters.regex("pyrogram")) - def my_handler(client, message): + async def my_handler(client, message): print(message) More handlers using different filters can also live together. @@ -100,15 +100,15 @@ More handlers using different filters can also live together. .. code-block:: python @app.on_message(filters.command("start")) - def start_command(client, message): + async def start_command(client, message): print("This is the /start command") @app.on_message(filters.command("help")) - def help_command(client, message): + async def help_command(client, message): print("This is the /help command") @app.on_message(filters.chat("PyrogramChat")) - def from_pyrogramchat(client, message): + async def from_pyrogramchat(client, message): print("New message in @PyrogramChat") From e3419f0f3d0b22af2f7a7b00ae77bd1db77a3b92 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0824/1185] Add InlineQueryResultContact and InlineQueryResultDocument --- pyrogram/types/inline_mode/__init__.py | 5 +- .../types/inline_mode/inline_query_result.py | 6 +- .../inline_query_result_contact.py | 114 ++++++++++++++ .../inline_query_result_document.py | 145 ++++++++++++++++++ 4 files changed, 268 insertions(+), 2 deletions(-) create mode 100644 pyrogram/types/inline_mode/inline_query_result_contact.py create mode 100644 pyrogram/types/inline_mode/inline_query_result_document.py diff --git a/pyrogram/types/inline_mode/__init__.py b/pyrogram/types/inline_mode/__init__.py index c1f5b87f87..25a568f539 100644 --- a/pyrogram/types/inline_mode/__init__.py +++ b/pyrogram/types/inline_mode/__init__.py @@ -24,8 +24,11 @@ from .inline_query_result_audio import InlineQueryResultAudio from .inline_query_result_photo import InlineQueryResultPhoto from .inline_query_result_video import InlineQueryResultVideo +from .inline_query_result_contact import InlineQueryResultContact +from .inline_query_result_document import InlineQueryResultDocument __all__ = [ "InlineQuery", "InlineQueryResult", "InlineQueryResultArticle", "InlineQueryResultPhoto", - "InlineQueryResultAnimation", "InlineQueryResultAudio", "InlineQueryResultVideo", "ChosenInlineResult" + "InlineQueryResultAnimation", "InlineQueryResultAudio", "InlineQueryResultVideo", "ChosenInlineResult", + "InlineQueryResultContact", "InlineQueryResultDocument" ] diff --git a/pyrogram/types/inline_mode/inline_query_result.py b/pyrogram/types/inline_mode/inline_query_result.py index 48d633c141..4ea2f7be64 100644 --- a/pyrogram/types/inline_mode/inline_query_result.py +++ b/pyrogram/types/inline_mode/inline_query_result.py @@ -49,8 +49,12 @@ class InlineQueryResult(Object): Pyrogram currently supports results of the following types: - :obj:`~pyrogram.types.InlineQueryResultArticle` - - :obj:`~pyrogram.types.InlineQueryResultPhoto` + - :obj:`~pyrogram.types.InlineQueryResultAudio` - :obj:`~pyrogram.types.InlineQueryResultAnimation` + - :obj:`~pyrogram.types.InlineQueryResultContact` + - :obj:`~pyrogram.types.InlineQueryResultDocument` + - :obj:`~pyrogram.types.InlineQueryResultPhoto` + - :obj:`~pyrogram.types.InlineQueryResultVideo` """ def __init__( diff --git a/pyrogram/types/inline_mode/inline_query_result_contact.py b/pyrogram/types/inline_mode/inline_query_result_contact.py new file mode 100644 index 0000000000..d55a624450 --- /dev/null +++ b/pyrogram/types/inline_mode/inline_query_result_contact.py @@ -0,0 +1,114 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# 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 import raw, types +from .inline_query_result import InlineQueryResult + + +class InlineQueryResultContact(InlineQueryResult): + """Contact with a phone number + + By default, this contact will be sent by the user. + Alternatively, you can use *input_message_content* to send a message with the specified content instead of the + contact. + + Parameters: + phone_number (``str``): + Contact's phone number. + + first_name (``str``): + Contact's first name. + + last_name (``str``, *optional*): + Contact's last name. + + vcard (``str``, *optional*): + Additional data about the contact in the form of a `vCard `_. + + id (``str``, *optional*): + Unique identifier for this result, 1-64 bytes. + Defaults to a randomly generated UUID4. + + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*): + Inline keyboard attached to the message. + + input_message_content (:obj:`~pyrogram.types.InputMessageContent`, *optional*): + Content of the message to be sent instead of the contact. + + thumb_url (``str``, *optional*): + Url of the thumbnail for the result. + + thumb_width (``int``, *optional*): + Thumbnail width. + + thumb_height (``int``, *optional*): + Thumbnail height. + """ + + def __init__( + self, + phone_number: str, + first_name: str, + last_name: str = "", + vcard: str = "", + id: str = None, + reply_markup: "types.InlineKeyboardMarkup" = None, + input_message_content: "types.InputMessageContent" = None, + thumb_url: str = None, + thumb_width: int = 0, + thumb_height: int = 0 + ): + super().__init__("contact", id, input_message_content, reply_markup) + + self.phone_number = phone_number + self.first_name = first_name + self.last_name = last_name + self.vcard = vcard + self.thumb_url = thumb_url + self.thumb_width = thumb_width + self.thumb_height = thumb_height + + async def write(self, client: "pyrogram.Client"): + return raw.types.InputBotInlineResult( + id=self.id, + type=self.type, + title=self.first_name, + send_message=( + await self.input_message_content.write(client, self.reply_markup) + if self.input_message_content + else raw.types.InputBotInlineMessageMediaContact( + phone_number=self.phone_number, + first_name=self.first_name, + last_name=self.last_name, + vcard=self.vcard, + reply_markup=await self.reply_markup.write(client) if self.reply_markup else None, + ) + ), + thumb=raw.types.InputWebDocument( + url=self.thumb_url, + size=0, + mime_type="image/jpg", + attributes=[ + raw.types.DocumentAttributeImageSize( + w=self.thumb_width, + h=self.thumb_height + ) + ] + ) if self.thumb_url else None + ) diff --git a/pyrogram/types/inline_mode/inline_query_result_document.py b/pyrogram/types/inline_mode/inline_query_result_document.py new file mode 100644 index 0000000000..eac7901b14 --- /dev/null +++ b/pyrogram/types/inline_mode/inline_query_result_document.py @@ -0,0 +1,145 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# 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 Optional, List + +import pyrogram +from pyrogram import raw, types, utils, enums +from .inline_query_result import InlineQueryResult + + +class InlineQueryResultDocument(InlineQueryResult): + """Link to a file. + + By default, this file will be sent by the user with an optional caption. + Alternatively, you can use *input_message_content* to send a message with the specified content instead of the file. + + Parameters: + document_url (``str``): + A valid URL for the file. + + title (``str``): + Title for the result. + + mime_type (``str``, *optional*): + Mime type of the content of the file, either “application/pdf” or “application/zip”. + Defaults to "application/zip". + + id (``str``, *optional*): + Unique identifier for this result, 1-64 bytes. + Defaults to a randomly generated UUID4. + + caption (``str``, *optional*): + Caption of the video to be sent, 0-1024 characters. + + parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + + caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): + List of special entities that appear in the caption, which can be specified instead of *parse_mode*. + + description (``str``, *optional*): + Short description of the result. + + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*): + Inline keyboard attached to the message. + + input_message_content (:obj:`~pyrogram.types.InputMessageContent`): + Content of the message to be sent instead of the file. + + thumb_url (``str``, *optional*): + Url of the thumbnail for the result. + + thumb_width (``int``, *optional*): + Thumbnail width. + + thumb_height (``int``, *optional*): + Thumbnail height. + """ + + def __init__( + self, + document_url: str, + title: str, + mime_type: str = "application/zip", + id: str = None, + caption: str = "", + parse_mode: Optional["enums.ParseMode"] = None, + caption_entities: List["types.MessageEntity"] = None, + description: str = "", + reply_markup: "types.InlineKeyboardMarkup" = None, + input_message_content: "types.InputMessageContent" = None, + thumb_url: str = None, + thumb_width: int = 0, + thumb_height: int = 0 + ): + super().__init__("file", id, input_message_content, reply_markup) + + self.document_url = document_url + self.title = title + self.mime_type = mime_type + self.caption = caption + self.parse_mode = parse_mode + self.caption_entities = caption_entities + self.description = description + self.thumb_url = thumb_url + self.thumb_width = thumb_width + self.thumb_height = thumb_height + + async def write(self, client: "pyrogram.Client"): + document = raw.types.InputWebDocument( + url=self.document_url, + size=0, + mime_type=self.mime_type, + attributes=[] + ) + + thumb = raw.types.InputWebDocument( + url=self.thumb_url, + size=0, + mime_type="image/jpeg", + attributes=[ + raw.types.DocumentAttributeImageSize( + w=self.thumb_width, + h=self.thumb_height + ) + ] + ) if self.thumb_url else None + + message, entities = (await utils.parse_text_entities( + client, self.caption, self.parse_mode, self.caption_entities + )).values() + + return raw.types.InputBotInlineResult( + id=self.id, + type=self.type, + title=self.title, + description=self.description, + thumb=thumb, + content=document, + send_message=( + await self.input_message_content.write(client, self.reply_markup) + if self.input_message_content + else raw.types.InputBotInlineMessageMediaAuto( + reply_markup=await self.reply_markup.write(client) if self.reply_markup else None, + message=message, + entities=entities + ) + ) + ) From ade31f8989649053c87c8cfb78d00f7f53850edb Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0825/1185] Update the session string format --- pyrogram/client.py | 67 ++++++++++++++++-------------- pyrogram/storage/memory_storage.py | 33 +++++++++++---- pyrogram/storage/storage.py | 31 +++++++------- 3 files changed, 78 insertions(+), 53 deletions(-) diff --git a/pyrogram/client.py b/pyrogram/client.py index 18e5253b1c..6b2ac9a1f2 100644 --- a/pyrogram/client.py +++ b/pyrogram/client.py @@ -49,7 +49,7 @@ from pyrogram.handlers.handler import Handler from pyrogram.methods import Methods from pyrogram.session import Auth, Session -from pyrogram.storage import Storage, FileStorage, MemoryStorage +from pyrogram.storage import FileStorage, MemoryStorage from pyrogram.types import User, TermsOfService from pyrogram.utils import ainput from .dispatcher import Dispatcher @@ -65,14 +65,8 @@ class Client(Methods): """Pyrogram Client, the main means for interacting with Telegram. Parameters: - session_name (``str``): - Pass a string of your choice to give a name to the client session, e.g.: "*my_account*". This name will be - used to save a file on disk that stores details needed to reconnect without asking again for credentials. - Alternatively, if you don't want a file to be saved on disk, pass the special name ``":memory:"`` to start - an in-memory session that will be discarded as soon as you stop the Client. In order to reconnect again - using a memory storage without having to login again, you can use - :meth:`~pyrogram.Client.export_session_string` before stopping the client to get a session string you can - pass here as argument. + name (``str``): + Pass a string of your choice to give a name to the client, e.g.: "my_account". api_id (``int`` | ``str``, *optional*): The *api_id* part of your Telegram API key, as integer. @@ -116,6 +110,17 @@ class Client(Methods): Pass your Bot API token to create a bot session, e.g.: "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11" Only applicable for new sessions. + session_string (``str``, *optional*): + Pass a session string to load the session in-memory. + Implies ``in_memory=True``. + + in_memory (``bool``, *optional*): + Pass True to start an in-memory session that will be discarded as soon as the client stops. + In order to reconnect again using an in-memory session without having to login again, you can use + :meth:`~pyrogram.Client.export_session_string` before stopping the client to get a session string you can + pass to the ``session_string`` parameter. + Defaults to False. + phone_number (``str``, *optional*): Pass your phone number as string (with your Country Code prefix included) to avoid entering it manually. Only applicable for new sessions. @@ -187,7 +192,7 @@ class Client(Methods): def __init__( self, - session_name: Union[str, Storage], + name: str, api_id: int = None, api_hash: str = None, app_version: str = APP_VERSION, @@ -198,6 +203,8 @@ def __init__( proxy: dict = None, test_mode: bool = False, bot_token: str = None, + session_string: str = None, + in_memory: bool = None, phone_number: str = None, phone_code: str = None, password: str = None, @@ -212,7 +219,7 @@ def __init__( ): super().__init__() - self.session_name = session_name + self.name = name self.api_id = api_id self.api_hash = api_hash self.app_version = app_version @@ -223,6 +230,8 @@ def __init__( self.proxy = proxy self.test_mode = test_mode self.bot_token = bot_token + self.session_string = session_string + self.in_memory = in_memory self.phone_number = phone_number self.phone_code = phone_code self.password = password @@ -237,16 +246,12 @@ def __init__( self.executor = ThreadPoolExecutor(self.workers, thread_name_prefix="Handler") - if isinstance(session_name, str): - if session_name == ":memory:" or len(session_name) >= MemoryStorage.SESSION_STRING_SIZE: - session_name = re.sub(r"[\n\s]+", "", session_name) - self.storage = MemoryStorage(session_name) - else: - self.storage = FileStorage(session_name, self.workdir) - elif isinstance(session_name, Storage): - self.storage = session_name + if self.session_string: + self.storage = MemoryStorage(self.name, self.session_string) + elif self.in_memory: + self.storage = MemoryStorage(self.name) else: - raise ValueError("Unknown storage engine") + self.storage = FileStorage(self.name, self.workdir) self.dispatcher = Dispatcher(self) @@ -638,7 +643,7 @@ def load_plugins(self): self.add_handler(handler, group) log.info('[{}] [LOAD] {}("{}") in group {} from "{}"'.format( - self.session_name, type(handler).__name__, name, group, module_path)) + self.name, type(handler).__name__, name, group, module_path)) count += 1 except Exception: @@ -651,11 +656,11 @@ def load_plugins(self): try: module = import_module(module_path) except ImportError: - log.warning(f'[{self.session_name}] [LOAD] Ignoring non-existent module "{module_path}"') + log.warning(f'[{self.name}] [LOAD] Ignoring non-existent module "{module_path}"') continue if "__path__" in dir(module): - log.warning(f'[{self.session_name}] [LOAD] Ignoring namespace "{module_path}"') + log.warning(f'[{self.name}] [LOAD] Ignoring namespace "{module_path}"') continue if handlers is None: @@ -670,13 +675,13 @@ def load_plugins(self): self.add_handler(handler, group) log.info('[{}] [LOAD] {}("{}") in group {} from "{}"'.format( - self.session_name, type(handler).__name__, name, group, module_path)) + self.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( - self.session_name, name, module_path)) + self.name, name, module_path)) if exclude: for path, handlers in exclude: @@ -686,11 +691,11 @@ def load_plugins(self): try: module = import_module(module_path) except ImportError: - log.warning(f'[{self.session_name}] [UNLOAD] Ignoring non-existent module "{module_path}"') + log.warning(f'[{self.name}] [UNLOAD] Ignoring non-existent module "{module_path}"') continue if "__path__" in dir(module): - log.warning(f'[{self.session_name}] [UNLOAD] Ignoring namespace "{module_path}"') + log.warning(f'[{self.name}] [UNLOAD] Ignoring namespace "{module_path}"') continue if handlers is None: @@ -705,19 +710,19 @@ def load_plugins(self): self.remove_handler(handler, group) log.info('[{}] [UNLOAD] {}("{}") from group {} in "{}"'.format( - self.session_name, type(handler).__name__, name, group, module_path)) + self.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( - self.session_name, name, module_path)) + self.name, name, module_path)) if count > 0: log.info('[{}] Successfully loaded {} plugin{} from "{}"'.format( - self.session_name, count, "s" if count > 1 else "", root)) + self.name, count, "s" if count > 1 else "", root)) else: - log.warning(f'[{self.session_name}] No plugin loaded from "{root}"') + log.warning(f'[{self.name}] No plugin loaded from "{root}"') async def handle_download(self, packet): file_id, directory, file_name, in_memory, file_size, progress, progress_args = packet diff --git a/pyrogram/storage/memory_storage.py b/pyrogram/storage/memory_storage.py index 1035356dc9..2c01f4474d 100644 --- a/pyrogram/storage/memory_storage.py +++ b/pyrogram/storage/memory_storage.py @@ -27,23 +27,42 @@ class MemoryStorage(SQLiteStorage): - def __init__(self, name: str): + def __init__(self, name: str, session_string: str = None): super().__init__(name) + self.session_string = session_string + async def open(self): self.conn = sqlite3.connect(":memory:", check_same_thread=False) self.create() - if self.name != ":memory:": - dc_id, test_mode, auth_key, user_id, is_bot = struct.unpack( - (self.SESSION_STRING_FORMAT if len(self.name) == MemoryStorage.SESSION_STRING_SIZE else - self.SESSION_STRING_FORMAT_64), - base64.urlsafe_b64decode( - self.name + "=" * (-len(self.name) % 4) + if self.session_string: + # Old format + if len(self.session_string) in [self.SESSION_STRING_SIZE, self.SESSION_STRING_SIZE_64]: + dc_id, test_mode, auth_key, user_id, is_bot = struct.unpack( + (self.OLD_SESSION_STRING_FORMAT + if len(self.session_string) == self.SESSION_STRING_SIZE else + self.OLD_SESSION_STRING_FORMAT_64), + base64.urlsafe_b64decode(self.session_string + "=" * (-len(self.session_string) % 4)) ) + + await self.dc_id(dc_id) + await self.test_mode(test_mode) + await self.auth_key(auth_key) + await self.user_id(user_id) + await self.is_bot(is_bot) + await self.date(0) + + log.warning("You are using an old session string format. Use export_session_string to update") + return + + dc_id, api_id, test_mode, auth_key, user_id, is_bot = struct.unpack( + self.SESSION_STRING_FORMAT, + base64.urlsafe_b64decode(self.session_string + "=" * (-len(self.session_string) % 4)) ) await self.dc_id(dc_id) + await self.api_id(api_id) await self.test_mode(test_mode) await self.auth_key(auth_key) await self.user_id(user_id) diff --git a/pyrogram/storage/storage.py b/pyrogram/storage/storage.py index 8daaae7e88..de397718fc 100644 --- a/pyrogram/storage/storage.py +++ b/pyrogram/storage/storage.py @@ -17,18 +17,19 @@ # along with Pyrogram. If not, see . import base64 +import lzma import struct from typing import List, Tuple -from pyrogram import utils - class Storage: - SESSION_STRING_FORMAT = ">B?256sI?" - SESSION_STRING_FORMAT_64 = ">B?256sQ?" + OLD_SESSION_STRING_FORMAT = ">B?256sI?" + OLD_SESSION_STRING_FORMAT_64 = ">B?256sQ?" SESSION_STRING_SIZE = 351 SESSION_STRING_SIZE_64 = 356 + SESSION_STRING_FORMAT = ">BI?256sQ?" + def __init__(self, name: str): self.name = name @@ -78,14 +79,14 @@ async def is_bot(self, value: bool = object): raise NotImplementedError async def export_session_string(self): - user_id = await self.user_id() - return base64.urlsafe_b64encode( - struct.pack( - self.SESSION_STRING_FORMAT if user_id < utils.MAX_USER_ID_OLD else self.SESSION_STRING_FORMAT_64, - await self.dc_id(), - await self.test_mode(), - await self.auth_key(), - user_id, - await self.is_bot() - ) - ).decode().rstrip("=") + packed = struct.pack( + self.SESSION_STRING_FORMAT, + await self.dc_id(), + await self.api_id(), + await self.test_mode(), + await self.auth_key(), + await self.user_id(), + await self.is_bot() + ) + + return base64.urlsafe_b64encode(packed).decode().rstrip("=") From ef78900fdb19a93fe11b36db34ca8e307bdba336 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0826/1185] Fixes to the documentation --- docs/source/start/auth.rst | 4 ++-- docs/source/topics/storage-engines.rst | 22 +++++++++++----------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/docs/source/start/auth.rst b/docs/source/start/auth.rst index 08c1875ac6..ba28ac69b7 100644 --- a/docs/source/start/auth.rst +++ b/docs/source/start/auth.rst @@ -16,7 +16,7 @@ User Authorization In order to use the API, Telegram requires that users be authorized via their phone numbers. Pyrogram automatically manages this process, all you need to do is create an instance of the -:class:`~pyrogram.Client` class by passing to it a ``session_name`` of your choice (e.g.: "my_account") and call +:class:`~pyrogram.Client` class by passing to it a ``name`` of your choice (e.g.: "my_account") and call the :meth:`~pyrogram.Client.run` method: .. code-block:: python @@ -57,7 +57,7 @@ Bots are a special kind of users that are authorized via their tokens (instead o the `Bot Father`_. Bot tokens replace the users' phone numbers only — you still need to :doc:`configure a Telegram API key <../start/setup>` with Pyrogram, even when using bots. -The authorization process is automatically managed. All you need to do is choose a ``session_name`` (can be anything, +The authorization process is automatically managed. All you need to do is choose a ``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 ``my_bot.session`` for the example below. diff --git a/docs/source/topics/storage-engines.rst b/docs/source/topics/storage-engines.rst index 8d693647d6..c83e7a5b7c 100644 --- a/docs/source/topics/storage-engines.rst +++ b/docs/source/topics/storage-engines.rst @@ -31,15 +31,15 @@ This is the most common storage engine. It is implemented by using **SQLite**, w The database will be saved to disk as a single portable file and is designed to efficiently store and retrieve data whenever they are needed. -To use this type of engine, simply pass any name of your choice to the ``session_name`` parameter of the +To use this type of engine, simply pass any name of your choice to the ``name`` parameter of the :obj:`~pyrogram.Client` constructor, as usual: .. code-block:: python from pyrogram import Client - with Client("my_account") as app: - print(app.get_me()) + async with Client("my_account") as app: + print(await app.get_me()) Once you successfully log in (either with a user or a bot identity), a session file will be created and saved to disk as ``my_account.session``. Any subsequent client restart will make Pyrogram search for a file named that way and the @@ -48,15 +48,15 @@ session database will be automatically loaded. Memory Storage ^^^^^^^^^^^^^^ -In case you don't want to have any session file saved to disk, you can use an in-memory storage by passing the special -session name "**:memory:**" to the ``session_name`` parameter of the :obj:`~pyrogram.Client` constructor: +In case you don't want to have any session file saved to disk, you can use an in-memory storage by passing True to the +``in_memory`` parameter of the :obj:`~pyrogram.Client` constructor: .. code-block:: python from pyrogram import Client - with Client(":memory:") as app: - print(app.get_me()) + async with Client("my_account", in_memory=True) as app: + print(await app.get_me()) This storage engine is still backed by SQLite, but the database exists purely in memory. This means that, once you stop a client, the entire database is discarded and the session details used for logging in again will be lost forever. @@ -71,8 +71,8 @@ In case you want to use an in-memory storage, but also want to keep access to th from pyrogram import Client - with Client(":memory:") as app: - print(app.export_session_string()) + async with Client("my_account", in_memory=True) as app: + print(await app.export_session_string()) ...and save the resulting string. You can use this string as session name the next time you want to login using the same session; the storage used will still be in-memory: @@ -83,8 +83,8 @@ using the same session; the storage used will still be in-memory: session_string = "...ZnUIFD8jsjXTb8g_vpxx48k1zkov9sapD-tzjz-S4WZv70M..." - with Client(session_string) as app: - print(app.get_me()) + async with Client("my_account", session_string=session_string) as app: + print(await app.get_me()) Session strings are useful when you want to run authorized Pyrogram clients on platforms where their ephemeral filesystems makes it harder for a file-based storage engine to properly work as intended. From 74f970e8637abfc81d3539237a6ebdb266079830 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0827/1185] Add more docstrings to stream_media --- pyrogram/methods/messages/stream_media.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pyrogram/methods/messages/stream_media.py b/pyrogram/methods/messages/stream_media.py index 0daaa55667..ea41b7bc37 100644 --- a/pyrogram/methods/messages/stream_media.py +++ b/pyrogram/methods/messages/stream_media.py @@ -32,7 +32,8 @@ async def stream_media( ) -> Optional[Union[str, BinaryIO]]: """Stream the media from a message chunk by chunk. - The chunk size is 1 MiB (1024 * 1024 bytes). + You can use this method to partially download a file into memory or to selectively download chunks of file. + The chunk maximum size is 1 MiB (1024 * 1024 bytes). Parameters: message (:obj:`~pyrogram.types.Message` | ``str``): From f6f6141b19c15c30cc2f83d5decab44f67b5818a Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0828/1185] Add approve/decline_all_chat_join_requests --- .../approve_all_chat_join_requests.py | 53 +++++++++++++++++++ .../decline_all_chat_join_requests.py | 53 +++++++++++++++++++ 2 files changed, 106 insertions(+) create mode 100644 pyrogram/methods/invite_links/approve_all_chat_join_requests.py create mode 100644 pyrogram/methods/invite_links/decline_all_chat_join_requests.py diff --git a/pyrogram/methods/invite_links/approve_all_chat_join_requests.py b/pyrogram/methods/invite_links/approve_all_chat_join_requests.py new file mode 100644 index 0000000000..ec2fc1bc6c --- /dev/null +++ b/pyrogram/methods/invite_links/approve_all_chat_join_requests.py @@ -0,0 +1,53 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# 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 Union + +import pyrogram +from pyrogram import raw + + +class ApproveAllChatJoinRequests: + async def approve_all_chat_join_requests( + self: "pyrogram.Client", + chat_id: Union[int, str], + invite_link: str = None + ) -> bool: + """Approve all pending join requests in a chat. + + Parameters: + chat_id (``int`` | ``str``): + Unique identifier for the target chat or username of the target channel/supergroup + (in the format @username). + + invite_link (``str``, *optional*): + Pass an invite link to approve only its join requests. + By default, all join requests are approved. + + Returns: + ``bool``: True on success. + """ + await self.invoke( + raw.functions.messages.HideAllChatJoinRequests( + peer=await self.resolve_peer(chat_id), + approved=True, + link=invite_link + ) + ) + + return True diff --git a/pyrogram/methods/invite_links/decline_all_chat_join_requests.py b/pyrogram/methods/invite_links/decline_all_chat_join_requests.py new file mode 100644 index 0000000000..620e26245e --- /dev/null +++ b/pyrogram/methods/invite_links/decline_all_chat_join_requests.py @@ -0,0 +1,53 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# 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 Union + +import pyrogram +from pyrogram import raw + + +class DeclineAllChatJoinRequests: + async def decline_all_chat_join_requests( + self: "pyrogram.Client", + chat_id: Union[int, str], + invite_link: str = None + ) -> bool: + """Decline all pending join requests in a chat. + + Parameters: + chat_id (``int`` | ``str``): + Unique identifier for the target chat or username of the target channel/supergroup + (in the format @username). + + invite_link (``str``, *optional*): + Pass an invite link to decline only its join requests. + By default, all join requests are declined. + + Returns: + ``bool``: True on success. + """ + await self.invoke( + raw.functions.messages.HideAllChatJoinRequests( + peer=await self.resolve_peer(chat_id), + approved=False, + link=invite_link + ) + ) + + return True From d48cef9a26d178d29161fc8e4e5a77fefda42fb4 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0829/1185] Add ChatJoiner and get_chat_join_requests Rename get_chat_invite_link_{members -> joiners} Rename get_chat_invite_link_{members -> joiners}_count --- compiler/docs/compiler.py | 8 +- pyrogram/methods/invite_links/__init__.py | 16 ++-- ...ers.py => get_chat_invite_link_joiners.py} | 18 ++-- ... => get_chat_invite_link_joiners_count.py} | 4 +- .../invite_links/get_chat_join_requests.py | 86 +++++++++++++++++++ pyrogram/types/user_and_chats/__init__.py | 4 +- pyrogram/types/user_and_chats/chat_joiner.py | 82 ++++++++++++++++++ 7 files changed, 196 insertions(+), 22 deletions(-) rename pyrogram/methods/invite_links/{get_chat_invite_link_members.py => get_chat_invite_link_joiners.py} (85%) rename pyrogram/methods/invite_links/{get_chat_invite_link_members_count.py => get_chat_invite_link_joiners_count.py} (95%) create mode 100644 pyrogram/methods/invite_links/get_chat_join_requests.py create mode 100644 pyrogram/types/user_and_chats/chat_joiner.py diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index b5382438cb..01acf6322c 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -256,14 +256,17 @@ def get_title_list(s: str) -> list: edit_chat_invite_link revoke_chat_invite_link delete_chat_invite_link - get_chat_invite_link_members - get_chat_invite_link_members_count + get_chat_invite_link_joiners + get_chat_invite_link_joiners_count get_chat_admin_invite_links get_chat_admin_invite_links_count get_chat_admins_with_invite_links + get_chat_join_requests delete_chat_admin_invite_links approve_chat_join_request + approve_all_chat_join_requests decline_chat_join_request + decline_all_chat_join_requests """, contacts=""" Contacts @@ -370,6 +373,7 @@ def get_title_list(s: str) -> list: ChatEventFilter ChatMemberUpdated ChatJoinRequest + ChatJoiner Dialog Restriction """, diff --git a/pyrogram/methods/invite_links/__init__.py b/pyrogram/methods/invite_links/__init__.py index c2183d9b55..67c1d14958 100644 --- a/pyrogram/methods/invite_links/__init__.py +++ b/pyrogram/methods/invite_links/__init__.py @@ -17,8 +17,10 @@ # along with Pyrogram. If not, see . +from .approve_all_chat_join_requests import ApproveAllChatJoinRequests from .approve_chat_join_request import ApproveChatJoinRequest from .create_chat_invite_link import CreateChatInviteLink +from .decline_all_chat_join_requests import DeclineAllChatJoinRequests from .decline_chat_join_request import DeclineChatJoinRequest from .delete_chat_admin_invite_links import DeleteChatAdminInviteLinks from .delete_chat_invite_link import DeleteChatInviteLink @@ -28,8 +30,9 @@ from .get_chat_admin_invite_links_count import GetChatAdminInviteLinksCount from .get_chat_admins_with_invite_links import GetChatAdminsWithInviteLinks from .get_chat_invite_link import GetChatInviteLink -from .get_chat_invite_link_members import GetChatInviteLinkMembers -from .get_chat_invite_link_members_count import GetChatInviteLinkMembersCount +from .get_chat_invite_link_joiners import GetChatInviteLinkJoiners +from .get_chat_invite_link_joiners_count import GetChatInviteLinkJoinersCount +from .get_chat_join_requests import GetChatJoinRequests from .revoke_chat_invite_link import RevokeChatInviteLink @@ -38,8 +41,8 @@ class InviteLinks( DeleteChatInviteLink, EditChatInviteLink, CreateChatInviteLink, - GetChatInviteLinkMembers, - GetChatInviteLinkMembersCount, + GetChatInviteLinkJoiners, + GetChatInviteLinkJoinersCount, GetChatAdminInviteLinks, ExportChatInviteLink, DeleteChatAdminInviteLinks, @@ -47,6 +50,9 @@ class InviteLinks( GetChatAdminsWithInviteLinks, GetChatInviteLink, ApproveChatJoinRequest, - DeclineChatJoinRequest + DeclineChatJoinRequest, + ApproveAllChatJoinRequests, + DeclineAllChatJoinRequests, + GetChatJoinRequests ): pass diff --git a/pyrogram/methods/invite_links/get_chat_invite_link_members.py b/pyrogram/methods/invite_links/get_chat_invite_link_joiners.py similarity index 85% rename from pyrogram/methods/invite_links/get_chat_invite_link_members.py rename to pyrogram/methods/invite_links/get_chat_invite_link_joiners.py index 28121cccb7..cfd6873433 100644 --- a/pyrogram/methods/invite_links/get_chat_invite_link_members.py +++ b/pyrogram/methods/invite_links/get_chat_invite_link_joiners.py @@ -23,13 +23,13 @@ from pyrogram import types -class GetChatInviteLinkMembers: - async def get_chat_invite_link_members( +class GetChatInviteLinkJoiners: + async def get_chat_invite_link_joiners( self: "pyrogram.Client", chat_id: Union[int, str], invite_link: str, limit: int = 0 - ) -> Optional[AsyncGenerator["types.ChatMember", None]]: + ) -> Optional[AsyncGenerator["types.ChatJoiner", None]]: """Get the members who joined the chat with the invite link. Parameters: @@ -45,10 +45,10 @@ async def get_chat_invite_link_members( By default, no limit is applied and all invite links are returned. Returns: - ``Generator``: A generator yielding :obj:`~pyrogram.types.ChatMember` objects. + ``Generator``: A generator yielding :obj:`~pyrogram.types.ChatJoiner` objects. Yields: - :obj:`~pyrogram.types.ChatMember` objects. + :obj:`~pyrogram.types.ChatJoiner` objects. """ current = 0 total = abs(limit) or (1 << 31) - 1 @@ -77,13 +77,7 @@ async def get_chat_invite_link_members( offset_user = await self.resolve_peer(r.importers[-1].user_id) for i in r.importers: - user = types.User._parse(self, users[i.user_id]) - - yield types.ChatMember( - user=user, - status="member", - joined_date=i.date - ) + yield types.ChatJoiner._parse(self, i, users) current += 1 diff --git a/pyrogram/methods/invite_links/get_chat_invite_link_members_count.py b/pyrogram/methods/invite_links/get_chat_invite_link_joiners_count.py similarity index 95% rename from pyrogram/methods/invite_links/get_chat_invite_link_members_count.py rename to pyrogram/methods/invite_links/get_chat_invite_link_joiners_count.py index d542895716..084fbae570 100644 --- a/pyrogram/methods/invite_links/get_chat_invite_link_members_count.py +++ b/pyrogram/methods/invite_links/get_chat_invite_link_joiners_count.py @@ -22,8 +22,8 @@ from pyrogram import raw -class GetChatInviteLinkMembersCount: - async def get_chat_invite_link_members_count( +class GetChatInviteLinkJoinersCount: + async def get_chat_invite_link_joiners_count( self: "pyrogram.Client", chat_id: Union[int, str], invite_link: str diff --git a/pyrogram/methods/invite_links/get_chat_join_requests.py b/pyrogram/methods/invite_links/get_chat_join_requests.py new file mode 100644 index 0000000000..9a9bc38793 --- /dev/null +++ b/pyrogram/methods/invite_links/get_chat_join_requests.py @@ -0,0 +1,86 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# 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 Union, Optional, AsyncGenerator + +import pyrogram +from pyrogram import raw +from pyrogram import types + + +class GetChatJoinRequests: + async def get_chat_join_requests( + self: "pyrogram.Client", + chat_id: Union[int, str], + limit: int = 0, + query: str = "" + ) -> Optional[AsyncGenerator["types.ChatJoiner", None]]: + """Get the pending join requests of a chat. + + Parameters: + chat_id (``int`` | ``str``): + Unique identifier for the target chat or username of the target channel/supergroup + (in the format @username). + + limit (``int``, *optional*): + Limits the number of invite links to be retrieved. + By default, no limit is applied and all invite links are returned. + + query (``str``, *optional*): + Query to search for a user. + + Returns: + ``Generator``: A generator yielding :obj:`~pyrogram.types.ChatJoiner` objects. + + Yields: + :obj:`~pyrogram.types.ChatJoiner` objects. + """ + current = 0 + total = abs(limit) or (1 << 31) - 1 + limit = min(100, total) + + offset_date = 0 + offset_user = raw.types.InputUserEmpty() + + while True: + r = await self.invoke( + raw.functions.messages.GetChatInviteImporters( + peer=await self.resolve_peer(chat_id), + limit=limit, + offset_date=offset_date, + offset_user=offset_user, + requested=True, + q=query + ) + ) + + if not r.importers: + break + + users = {i.id: i for i in r.users} + + offset_date = r.importers[-1].date + offset_user = await self.resolve_peer(r.importers[-1].user_id) + + for i in r.importers: + yield types.ChatJoiner._parse(self, i, users) + + current += 1 + + if current >= total: + return diff --git a/pyrogram/types/user_and_chats/__init__.py b/pyrogram/types/user_and_chats/__init__.py index 15ddf8c9e3..bf5082792d 100644 --- a/pyrogram/types/user_and_chats/__init__.py +++ b/pyrogram/types/user_and_chats/__init__.py @@ -22,6 +22,7 @@ from .chat_event_filter import ChatEventFilter from .chat_invite_link import ChatInviteLink from .chat_join_request import ChatJoinRequest +from .chat_joiner import ChatJoiner from .chat_member import ChatMember from .chat_member_updated import ChatMemberUpdated from .chat_permissions import ChatPermissions @@ -57,5 +58,6 @@ "ChatMemberUpdated", "VoiceChatScheduled", "ChatJoinRequest", - "ChatPrivileges" + "ChatPrivileges", + "ChatJoiner" ] diff --git a/pyrogram/types/user_and_chats/chat_joiner.py b/pyrogram/types/user_and_chats/chat_joiner.py new file mode 100644 index 0000000000..024f88ea26 --- /dev/null +++ b/pyrogram/types/user_and_chats/chat_joiner.py @@ -0,0 +1,82 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# 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 datetime import datetime +from typing import Dict + +import pyrogram +from pyrogram import raw, types, utils +from ..object import Object + + +class ChatJoiner(Object): + """Contains information about a joiner member of a chat. + + Parameters: + user (:obj:`~pyrogram.types.User`): + Information about the user. + + date (:py:obj:`~datetime.datetime`): + Date when the user joined. + + bio (``str``, *optional*): + Bio of the user. + + pending (``bool``, *optional*): + True in case the chat joiner has a pending request. + + approved_by (:obj:`~pyrogram.types.User`, *optional*): + Administrator who approved this chat joiner. + """ + + def __init__( + self, + *, + client: "pyrogram.Client", + user: "types.User", + date: datetime = None, + bio: str = None, + pending: bool = None, + approved_by: "types.User" = None, + ): + super().__init__(client) + + self.user = user + self.date = date + self.bio = bio + self.pending = pending + self.approved_by = approved_by + + @staticmethod + def _parse( + client: "pyrogram.Client", + joiner: "raw.base.ChatInviteImporter", + users: Dict[int, "raw.base.User"], + ) -> "ChatJoiner": + return ChatJoiner( + user=types.User._parse(client, users[joiner.user_id]), + date=utils.timestamp_to_datetime(joiner.date), + pending=joiner.requested, + bio=joiner.about, + approved_by=( + types.User._parse(client, users[joiner.approved_by]) + if joiner.approved_by + else None + ), + client=client + ) From 39694a29497aee87d6ee91155e9b7570b7849aa9 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0830/1185] Add speedups.rst --- docs/source/index.rst | 2 +- docs/source/topics/speedups.rst | 65 +++++++++++++++++++++++++++++++++ docs/source/topics/tgcrypto.rst | 29 --------------- pyrogram/crypto/aes.py | 2 +- 4 files changed, 67 insertions(+), 31 deletions(-) create mode 100644 docs/source/topics/speedups.rst delete mode 100644 docs/source/topics/tgcrypto.rst diff --git a/docs/source/index.rst b/docs/source/index.rst index 5ee12b7ec2..885f79dd8e 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -143,7 +143,7 @@ Meta topics/client-settings topics/synchronous topics/text-formatting - topics/tgcrypto + topics/speedups topics/smart-plugins topics/storage-engines topics/serializing diff --git a/docs/source/topics/speedups.rst b/docs/source/topics/speedups.rst new file mode 100644 index 0000000000..6340e1ad4f --- /dev/null +++ b/docs/source/topics/speedups.rst @@ -0,0 +1,65 @@ +Speedups +======== + +Pyrogram's speed can be boosted up by using TgCrypto and uvloop. + +.. contents:: Contents + :backlinks: none + :depth: 1 + :local: + +----- + +TgCrypto +-------- + +TgCrypto_ is a high-performance, easy-to-install cryptography library specifically written in C for Pyrogram as a Python +extension. It is a replacement for a slower Python-only alternative and implements the cryptographic algorithms Telegram +requires, namely: AES-256-IGE, AES-256-CTR and AES-256-CBC. + +Installation +^^^^^^^^^^^^ + +.. code-block:: bash + + $ pip3 install -U tgcrypto + +Usage +^^^^^ + +Pyrogram will automatically make use of TgCrypto when detected, all you need to do is to install it. + +uvloop +------ + +uvloop_ is a fast, drop-in replacement of the built-in asyncio event loop. uvloop is implemented in Cython and uses +libuv under the hood. It makes asyncio 2-4x faster. + +Installation +^^^^^^^^^^^^ + +.. code-block:: bash + + $ pip3 install -U uvloop + +Usage +^^^^^ + +Call ``uvloop.install()`` before calling ``asyncio.run()`` or ``app.run()`` + +.. code-block:: python + + import asyncio + import uvloop + + async def main(): + app = Client("my_account") + + async with app: + print(await app.get_me()) + + uvloop.install() + asyncio.run(main()) + +.. _TgCrypto: https://github.com/pyrogram/tgcrypto +.. _uvloop: https://github.com/MagicStack/uvloop diff --git a/docs/source/topics/tgcrypto.rst b/docs/source/topics/tgcrypto.rst deleted file mode 100644 index f6ca211d3e..0000000000 --- a/docs/source/topics/tgcrypto.rst +++ /dev/null @@ -1,29 +0,0 @@ -Fast Crypto -=========== - -Pyrogram's speed can be boosted up by TgCrypto_, a high-performance, easy-to-install cryptography library specifically -written in C for Pyrogram as a Python extension. - -TgCrypto is a replacement for a slower Python-only alternative and implements the cryptographic algorithms Telegram -requires, namely: AES-256-IGE, AES-256-CTR and AES-256-CBC. - -Installation ------------- - -.. code-block:: bash - - $ pip3 install -U tgcrypto - -.. note:: When TgCrypto is not detected in your system, Pyrogram will automatically fall back to a slower Python-only - implementation and will show you a warning. - -The reason about being an optional package is that TgCrypto requires extra system tools in order to be compiled. -The errors you receive when trying to install TgCrypto are system dependent, but also descriptive enough to understand -what you should do next: - -- **Windows**: Install `Visual C++ 2015 Build Tools `_. -- **macOS**: A pop-up will automatically ask you to install the command line developer tools. -- **Linux**: Install a proper C compiler (``gcc``, ``clang``) and the Python header files (``python3-dev``). -- **Termux**: Install ``clang`` package. - -.. _TgCrypto: https://github.com/pyrogram/tgcrypto \ No newline at end of file diff --git a/pyrogram/crypto/aes.py b/pyrogram/crypto/aes.py index 532cd5e8d0..bb937128df 100644 --- a/pyrogram/crypto/aes.py +++ b/pyrogram/crypto/aes.py @@ -54,7 +54,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.org/topics/tgcrypto" + "More info: https://docs.pyrogram.org/topics/speedups" ) From ed5fab6952de19ab79ebbfb3171b57f6d075eba4 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0831/1185] Update proxy example --- docs/source/topics/proxy.rst | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/docs/source/topics/proxy.rst b/docs/source/topics/proxy.rst index 74e054bc26..286743493d 100644 --- a/docs/source/topics/proxy.rst +++ b/docs/source/topics/proxy.rst @@ -19,17 +19,16 @@ you can omit ``username`` and ``password``. .. code-block:: python - from pyrogram import Client - - app = Client( - "my_account", - proxy=dict( - scheme="socks5", # "socks4", "socks5" and "http" are supported - hostname="11.22.33.44", - port=1234, - username="", - password="" - ) - ) + from pyrogram import Client + + proxy = { + "scheme": "socks5", # "socks4", "socks5" and "http" are supported + "hostname": "11.22.33.44", + "port": 1234, + "username": "username", + "password": "password" + } + + app = Client("my_account", proxy=proxy) app.run() From 4e1b54288b49afc0f01792c775efb7f4539b1f5d Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0832/1185] Fix Client.name usage --- 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 ff643859a7..ff195b96ec 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -364,7 +364,7 @@ async def invoke( if amount > sleep_threshold >= 0: raise - log.warning(f'[{self.client.session_name}] Waiting for {amount} seconds before continuing ' + log.warning(f'[{self.client.name}] Waiting for {amount} seconds before continuing ' f'(required by "{query_name}")') await asyncio.sleep(amount) From f6b8d78672093a02f2f408573a3532a71fa6033c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0833/1185] Fix documentation link --- docs/source/intro/install.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/intro/install.rst b/docs/source/intro/install.rst index 67a6b9432d..c45c384489 100644 --- a/docs/source/intro/install.rst +++ b/docs/source/intro/install.rst @@ -20,7 +20,7 @@ Install Pyrogram $ pip3 install -U pyrogram -- or, with :doc:`TgCrypto <../topics/tgcrypto>` as extra requirement (recommended): +- or, with :doc:`TgCrypto <../topics/speedups>` as extra requirement (recommended): .. code-block:: text From 6b0dca09dec5205c0a3eafdab07cee7a0980c56e Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0834/1185] Add WebAppInfo and field web_app to (Inline)KeyboardButton. --- compiler/docs/compiler.py | 1 + pyrogram/types/bots_and_keyboards/__init__.py | 2 + .../inline_keyboard_button.py | 22 +++++++++++ .../bots_and_keyboards/keyboard_button.py | 22 ++++++++++- .../types/bots_and_keyboards/web_app_info.py | 37 +++++++++++++++++++ 5 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 pyrogram/types/bots_and_keyboards/web_app_info.py diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index 01acf6322c..9b9d2322da 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -416,6 +416,7 @@ def get_title_list(s: str) -> list: CallbackQuery GameHighScore CallbackGame + WebAppInfo """, bot_commands=""" Bot commands diff --git a/pyrogram/types/bots_and_keyboards/__init__.py b/pyrogram/types/bots_and_keyboards/__init__.py index 37e6dfe831..44fd589452 100644 --- a/pyrogram/types/bots_and_keyboards/__init__.py +++ b/pyrogram/types/bots_and_keyboards/__init__.py @@ -35,6 +35,7 @@ from .login_url import LoginUrl from .reply_keyboard_markup import ReplyKeyboardMarkup from .reply_keyboard_remove import ReplyKeyboardRemove +from .web_app_info import WebAppInfo __all__ = [ "CallbackGame", @@ -56,4 +57,5 @@ "BotCommandScopeChatAdministrators", "BotCommandScopeChatMember", "BotCommandScopeDefault", + "WebAppInfo" ] diff --git a/pyrogram/types/bots_and_keyboards/inline_keyboard_button.py b/pyrogram/types/bots_and_keyboards/inline_keyboard_button.py index de6a04207c..a1d8a7adc8 100644 --- a/pyrogram/types/bots_and_keyboards/inline_keyboard_button.py +++ b/pyrogram/types/bots_and_keyboards/inline_keyboard_button.py @@ -39,6 +39,12 @@ class InlineKeyboardButton(Object): url (``str``, *optional*): HTTP url to be opened when button is pressed. + web_app (:obj:`~pyrogram.types.WebAppInfo`, *optional*): + Description of the `Web App `_ that will be launched when the user + presses the button. The Web App will be able to send an arbitrary message on behalf of the user using the + method :meth:`~pyrogram.Client.answer_web_app_query`. Available only in private chats between a user and the + bot. + login_url (:obj:`~pyrogram.types.LoginUrl`, *optional*): An HTTP URL used to automatically authorize the user. Can be used as a replacement for the `Telegram Login Widget `_. @@ -70,6 +76,7 @@ def __init__( text: str, callback_data: Union[str, bytes] = None, url: str = None, + web_app: "types.WebAppInfo" = None, login_url: "types.LoginUrl" = None, user_id: int = None, switch_inline_query: str = None, @@ -81,6 +88,7 @@ def __init__( self.text = str(text) self.callback_data = callback_data self.url = url + self.web_app = web_app self.login_url = login_url self.user_id = user_id self.switch_inline_query = switch_inline_query @@ -139,6 +147,14 @@ def read(b: "raw.base.KeyboardButton"): callback_game=types.CallbackGame() ) + if isinstance(b, raw.types.KeyboardButtonWebView): + return InlineKeyboardButton( + text=b.text, + web_app=types.WebAppInfo( + url=b.url + ) + ) + async def write(self, client: "pyrogram.Client"): if self.callback_data is not None: # Telegram only wants bytes, but we are allowed to pass strings too, for convenience. @@ -184,3 +200,9 @@ async def write(self, client: "pyrogram.Client"): return raw.types.KeyboardButtonGame( text=self.text ) + + if self.web_app is not None: + return raw.types.KeyboardButtonWebView( + text=self.text, + url=self.web_app.url + ) diff --git a/pyrogram/types/bots_and_keyboards/keyboard_button.py b/pyrogram/types/bots_and_keyboards/keyboard_button.py index 626df749c0..5c8d4b733c 100644 --- a/pyrogram/types/bots_and_keyboards/keyboard_button.py +++ b/pyrogram/types/bots_and_keyboards/keyboard_button.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from pyrogram import raw +from pyrogram import raw, types from ..object import Object @@ -37,19 +37,27 @@ class KeyboardButton(Object): request_location (``bool``, *optional*): If True, the user's current location will be sent when the button is pressed. Available in private chats only. + + web_app (:obj:`~pyrogram.types.WebAppInfo`, *optional*): + If specified, the described `Web App `_ will be launched when the + button is pressed. The Web App will be able to send a “web_app_data” service message. Available in private + chats only. + """ def __init__( self, text: str, request_contact: bool = None, - request_location: bool = None + request_location: bool = None, + web_app: "types.WebAppInfo" = None ): super().__init__() self.text = str(text) self.request_contact = request_contact self.request_location = request_location + self.web_app = web_app @staticmethod def read(b): @@ -68,10 +76,20 @@ def read(b): request_location=True ) + if isinstance(b, raw.types.KeyboardButtonSimpleWebView): + return KeyboardButton( + text=b.text, + web_app=types.WebAppInfo( + url=b.url + ) + ) + def write(self): if self.request_contact: return raw.types.KeyboardButtonRequestPhone(text=self.text) elif self.request_location: return raw.types.KeyboardButtonRequestGeoLocation(text=self.text) + elif self.web_app: + return raw.types.KeyboardButtonSimpleWebView(text=self.text, url=self.web_app.url) else: return raw.types.KeyboardButton(text=self.text) diff --git a/pyrogram/types/bots_and_keyboards/web_app_info.py b/pyrogram/types/bots_and_keyboards/web_app_info.py new file mode 100644 index 0000000000..1dbb0a781c --- /dev/null +++ b/pyrogram/types/bots_and_keyboards/web_app_info.py @@ -0,0 +1,37 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# 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 ..object import Object + + +class WebAppInfo(Object): + """Contains information about a `Web App `_. + + Parameters: + url (``str``): + An HTTPS URL of a Web App to be opened with additional data as specified in + `Initializing Web Apps `_. + """ + + def __init__( + self, *, + url: str, + ): + super().__init__() + + self.url = url From a3c7f5e991744858b122ef7209b5fb825d1a7c86 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0835/1185] Rename voice_* related fields to video_* --- pyrogram/enums/chat_event_action.py | 2 +- pyrogram/enums/message_service.py | 16 +++--- pyrogram/filters.py | 26 ++++----- pyrogram/types/messages_and_media/message.py | 56 +++++++++---------- pyrogram/types/user_and_chats/__init__.py | 16 +++--- ...oice_chat_ended.py => video_chat_ended.py} | 6 +- ...vited.py => video_chat_members_invited.py} | 6 +- ...t_scheduled.py => video_chat_scheduled.py} | 6 +- ..._chat_started.py => video_chat_started.py} | 2 +- 9 files changed, 68 insertions(+), 68 deletions(-) rename pyrogram/types/user_and_chats/{voice_chat_ended.py => video_chat_ended.py} (92%) rename pyrogram/types/user_and_chats/{voice_chat_members_invited.py => video_chat_members_invited.py} (92%) rename pyrogram/types/user_and_chats/{voice_chat_scheduled.py => video_chat_scheduled.py} (91%) rename pyrogram/types/user_and_chats/{voice_chat_started.py => video_chat_started.py} (96%) diff --git a/pyrogram/enums/chat_event_action.py b/pyrogram/enums/chat_event_action.py index 9b73de913c..7a5954720f 100644 --- a/pyrogram/enums/chat_event_action.py +++ b/pyrogram/enums/chat_event_action.py @@ -96,7 +96,7 @@ class ChatEventAction(AutoName): # MEMBER_VOLUME_CHANGED = auto() "" - # VOICE_CHAT_STARTED = auto() + # VIDEO_CHAT_STARTED = auto() "" POLL_STOPPED = auto() diff --git a/pyrogram/enums/message_service.py b/pyrogram/enums/message_service.py index b38e471793..7b23c730c2 100644 --- a/pyrogram/enums/message_service.py +++ b/pyrogram/enums/message_service.py @@ -57,14 +57,14 @@ class MessageService(AutoName): GAME_HIGH_SCORE = auto() "Game high score" - VOICE_CHAT_STARTED = auto() - "Voice chat started" + VIDEO_CHAT_STARTED = auto() + "Video chat started" - VOICE_CHAT_ENDED = auto() - "Voice chat ended" + VIDEO_CHAT_ENDED = auto() + "Video chat ended" - VOICE_CHAT_SCHEDULED = auto() - "Voice chat scheduled" + VIDEO_CHAT_SCHEDULED = auto() + "Video chat scheduled" - VOICE_CHAT_MEMBERS_INVITED = auto() - "Voice chat members invited" + VIDEO_CHAT_MEMBERS_INVITED = auto() + "Video chat members invited" diff --git a/pyrogram/filters.py b/pyrogram/filters.py index 078ed4fa7e..013adee4e2 100644 --- a/pyrogram/filters.py +++ b/pyrogram/filters.py @@ -636,34 +636,34 @@ async def via_bot_filter(_, __, m: Message): # endregion -# region voice_chat_started_filter -async def voice_chat_started_filter(_, __, m: Message): - return bool(m.voice_chat_started) +# region video_chat_started_filter +async def video_chat_started_filter(_, __, m: Message): + return bool(m.video_chat_started) -voice_chat_started = create(voice_chat_started_filter) +video_chat_started = create(video_chat_started_filter) """Filter messages for started voice chats""" # endregion -# region voice_chat_ended_filter -async def voice_chat_ended_filter(_, __, m: Message): - return bool(m.voice_chat_ended) +# region video_chat_ended_filter +async def video_chat_ended_filter(_, __, m: Message): + return bool(m.video_chat_ended) -voice_chat_ended = create(voice_chat_ended_filter) +video_chat_ended = create(video_chat_ended_filter) """Filter messages for ended voice chats""" # endregion -# region voice_chat_members_invited_filter -async def voice_chat_members_invited_filter(_, __, m: Message): - return bool(m.voice_chat_members_invited) +# region video_chat_members_invited_filter +async def video_chat_members_invited_filter(_, __, m: Message): + return bool(m.video_chat_members_invited) -voice_chat_members_invited = create(voice_chat_members_invited_filter) +video_chat_members_invited = create(video_chat_members_invited_filter) """Filter messages for voice chat invited members""" @@ -680,7 +680,7 @@ async def service_filter(_, __, m: Message): 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*, -*voice_chat_started*, *voice_chat_ended*, *voice_chat_members_invited*. +*video_chat_started*, *video_chat_ended*, *video_chat_members_invited*. """ diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index 3a72fe33eb..7a5341fdd8 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -273,16 +273,16 @@ class Message(Object, Update): E.g.: "/start 1 2 3" would produce ["start", "1", "2", "3"]. Only applicable when using :obj:`~pyrogram.filters.command`. - voice_chat_scheduled (:obj:`~pyrogram.types.VoiceChatScheduled`, *optional*): + video_chat_scheduled (:obj:`~pyrogram.types.VideoChatScheduled`, *optional*): Service message: voice chat scheduled. - voice_chat_started (:obj:`~pyrogram.types.VoiceChatStarted`, *optional*): + video_chat_started (:obj:`~pyrogram.types.VideoChatStarted`, *optional*): Service message: the voice chat started. - voice_chat_ended (:obj:`~pyrogram.types.VoiceChatEnded`, *optional*): + video_chat_ended (:obj:`~pyrogram.types.VideoChatEnded`, *optional*): Service message: the voice chat has ended. - voice_chat_members_invited (:obj:`~pyrogram.types.VoiceChatParticipantsInvited`, *optional*): + video_chat_members_invited (:obj:`~pyrogram.types.VoiceChatParticipantsInvited`, *optional*): Service message: new members were invited to the voice chat. reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): @@ -362,10 +362,10 @@ def __init__( outgoing: bool = None, matches: List[Match] = None, command: List[str] = None, - voice_chat_scheduled: "types.VoiceChatScheduled" = None, - voice_chat_started: "types.VoiceChatStarted" = None, - voice_chat_ended: "types.VoiceChatEnded" = None, - voice_chat_members_invited: "types.VoiceChatMembersInvited" = None, + video_chat_scheduled: "types.VideoChatScheduled" = None, + video_chat_started: "types.VideoChatStarted" = None, + video_chat_ended: "types.VideoChatEnded" = None, + video_chat_members_invited: "types.VideoChatMembersInvited" = None, reply_markup: Union[ "types.InlineKeyboardMarkup", "types.ReplyKeyboardMarkup", @@ -437,10 +437,10 @@ def __init__( self.matches = matches self.command = command self.reply_markup = reply_markup - self.voice_chat_scheduled = voice_chat_scheduled - self.voice_chat_started = voice_chat_started - self.voice_chat_ended = voice_chat_ended - self.voice_chat_members_invited = voice_chat_members_invited + self.video_chat_scheduled = video_chat_scheduled + self.video_chat_started = video_chat_started + self.video_chat_ended = video_chat_ended + self.video_chat_members_invited = video_chat_members_invited self.reactions = reactions @staticmethod @@ -487,10 +487,10 @@ async def _parse( group_chat_created = None channel_chat_created = None new_chat_photo = None - voice_chat_scheduled = None - voice_chat_started = None - voice_chat_ended = None - voice_chat_members_invited = None + video_chat_scheduled = None + video_chat_started = None + video_chat_ended = None + video_chat_members_invited = None service_type = None @@ -525,18 +525,18 @@ async def _parse( new_chat_photo = types.Photo._parse(client, action.photo) service_type = enums.MessageService.NEW_CHAT_PHOTO elif isinstance(action, raw.types.MessageActionGroupCallScheduled): - voice_chat_scheduled = types.VoiceChatScheduled._parse(action) - service_type = enums.MessageService.VOICE_CHAT_SCHEDULED + video_chat_scheduled = types.VideoChatScheduled._parse(action) + service_type = enums.MessageService.VIDEO_CHAT_SCHEDULED elif isinstance(action, raw.types.MessageActionGroupCall): if action.duration: - voice_chat_ended = types.VoiceChatEnded._parse(action) - service_type = enums.MessageService.VOICE_CHAT_ENDED + video_chat_ended = types.VideoChatEnded._parse(action) + service_type = enums.MessageService.VIDEO_CHAT_ENDED else: - voice_chat_started = types.VoiceChatStarted() - service_type = enums.MessageService.VOICE_CHAT_STARTED + video_chat_started = types.VideoChatStarted() + service_type = enums.MessageService.VIDEO_CHAT_STARTED elif isinstance(action, raw.types.MessageActionInviteToGroupCall): - voice_chat_members_invited = types.VoiceChatMembersInvited._parse(client, action, users) - service_type = enums.MessageService.VOICE_CHAT_MEMBERS_INVITED + video_chat_members_invited = types.VideoChatMembersInvited._parse(client, action, users) + service_type = enums.MessageService.VIDEO_CHAT_MEMBERS_INVITED from_user = types.User._parse(client, users.get(user_id, None)) sender_chat = types.Chat._parse(client, message, users, chats, is_chat=False) if not from_user else None @@ -557,10 +557,10 @@ async def _parse( migrate_from_chat_id=-migrate_from_chat_id if migrate_from_chat_id else None, group_chat_created=group_chat_created, channel_chat_created=channel_chat_created, - voice_chat_scheduled=voice_chat_scheduled, - voice_chat_started=voice_chat_started, - voice_chat_ended=voice_chat_ended, - voice_chat_members_invited=voice_chat_members_invited, + video_chat_scheduled=video_chat_scheduled, + video_chat_started=video_chat_started, + video_chat_ended=video_chat_ended, + video_chat_members_invited=video_chat_members_invited, client=client # TODO: supergroup_chat_created ) diff --git a/pyrogram/types/user_and_chats/__init__.py b/pyrogram/types/user_and_chats/__init__.py index bf5082792d..a9b633599f 100644 --- a/pyrogram/types/user_and_chats/__init__.py +++ b/pyrogram/types/user_and_chats/__init__.py @@ -33,10 +33,10 @@ from .invite_link_importer import InviteLinkImporter from .restriction import Restriction from .user import User -from .voice_chat_ended import VoiceChatEnded -from .voice_chat_members_invited import VoiceChatMembersInvited -from .voice_chat_scheduled import VoiceChatScheduled -from .voice_chat_started import VoiceChatStarted +from .video_chat_ended import VideoChatEnded +from .video_chat_members_invited import VideoChatMembersInvited +from .video_chat_scheduled import VideoChatScheduled +from .video_chat_started import VideoChatStarted __all__ = [ "Chat", @@ -52,11 +52,11 @@ "ChatInviteLink", "InviteLinkImporter", "ChatAdminWithInviteLinks", - "VoiceChatStarted", - "VoiceChatEnded", - "VoiceChatMembersInvited", + "VideoChatStarted", + "VideoChatEnded", + "VideoChatMembersInvited", "ChatMemberUpdated", - "VoiceChatScheduled", + "VideoChatScheduled", "ChatJoinRequest", "ChatPrivileges", "ChatJoiner" diff --git a/pyrogram/types/user_and_chats/voice_chat_ended.py b/pyrogram/types/user_and_chats/video_chat_ended.py similarity index 92% rename from pyrogram/types/user_and_chats/voice_chat_ended.py rename to pyrogram/types/user_and_chats/video_chat_ended.py index b6b05feaf3..8c9fac6a0a 100644 --- a/pyrogram/types/user_and_chats/voice_chat_ended.py +++ b/pyrogram/types/user_and_chats/video_chat_ended.py @@ -20,7 +20,7 @@ from ..object import Object -class VoiceChatEnded(Object): +class VideoChatEnded(Object): """A service message about a voice chat ended in the chat. Parameters: @@ -37,5 +37,5 @@ def __init__( self.duration = duration @staticmethod - def _parse(action: "raw.types.MessageActionGroupCall") -> "VoiceChatEnded": - return VoiceChatEnded(duration=action.duration) + def _parse(action: "raw.types.MessageActionGroupCall") -> "VideoChatEnded": + return VideoChatEnded(duration=action.duration) diff --git a/pyrogram/types/user_and_chats/voice_chat_members_invited.py b/pyrogram/types/user_and_chats/video_chat_members_invited.py similarity index 92% rename from pyrogram/types/user_and_chats/voice_chat_members_invited.py rename to pyrogram/types/user_and_chats/video_chat_members_invited.py index 0fd4249ba5..9f2e3d1ef4 100644 --- a/pyrogram/types/user_and_chats/voice_chat_members_invited.py +++ b/pyrogram/types/user_and_chats/video_chat_members_invited.py @@ -22,7 +22,7 @@ from ..object import Object -class VoiceChatMembersInvited(Object): +class VideoChatMembersInvited(Object): """A service message about new members invited to a voice chat. @@ -44,7 +44,7 @@ def _parse( client, action: "raw.types.MessageActionInviteToGroupCall", users: Dict[int, "raw.types.User"] - ) -> "VoiceChatMembersInvited": + ) -> "VideoChatMembersInvited": users = [types.User._parse(client, users[i]) for i in action.users] - return VoiceChatMembersInvited(users=users) + return VideoChatMembersInvited(users=users) diff --git a/pyrogram/types/user_and_chats/voice_chat_scheduled.py b/pyrogram/types/user_and_chats/video_chat_scheduled.py similarity index 91% rename from pyrogram/types/user_and_chats/voice_chat_scheduled.py rename to pyrogram/types/user_and_chats/video_chat_scheduled.py index 0bb00bc509..5bdd592515 100644 --- a/pyrogram/types/user_and_chats/voice_chat_scheduled.py +++ b/pyrogram/types/user_and_chats/video_chat_scheduled.py @@ -22,7 +22,7 @@ from ..object import Object -class VoiceChatScheduled(Object): +class VideoChatScheduled(Object): """A service message about a voice chat scheduled in the chat. Parameters: @@ -39,5 +39,5 @@ def __init__( self.start_date = start_date @staticmethod - def _parse(action: "raw.types.MessageActionGroupCallScheduled") -> "VoiceChatScheduled": - return VoiceChatScheduled(start_date=utils.timestamp_to_datetime(action.schedule_date)) + def _parse(action: "raw.types.MessageActionGroupCallScheduled") -> "VideoChatScheduled": + return VideoChatScheduled(start_date=utils.timestamp_to_datetime(action.schedule_date)) diff --git a/pyrogram/types/user_and_chats/voice_chat_started.py b/pyrogram/types/user_and_chats/video_chat_started.py similarity index 96% rename from pyrogram/types/user_and_chats/voice_chat_started.py rename to pyrogram/types/user_and_chats/video_chat_started.py index e260e784fb..ff48b39cd6 100644 --- a/pyrogram/types/user_and_chats/voice_chat_started.py +++ b/pyrogram/types/user_and_chats/video_chat_started.py @@ -19,7 +19,7 @@ from ..object import Object -class VoiceChatStarted(Object): +class VideoChatStarted(Object): """A service message about a voice chat started in the chat. Currently holds no information. From c54be38696201a7253c1ee21bbe188adb01ac978 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0836/1185] Rename can_manage_voice_chats to can_manage_video_chats --- pyrogram/methods/chats/promote_chat_member.py | 2 +- pyrogram/types/user_and_chats/chat_privileges.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pyrogram/methods/chats/promote_chat_member.py b/pyrogram/methods/chats/promote_chat_member.py index e6f2171e2e..f8a2779b3b 100644 --- a/pyrogram/methods/chats/promote_chat_member.py +++ b/pyrogram/methods/chats/promote_chat_member.py @@ -86,7 +86,7 @@ async def promote_chat_member( invite_users=privileges.can_invite_users, pin_messages=privileges.can_pin_messages, add_admins=privileges.can_promote_members, - manage_call=privileges.can_manage_voice_chats, + manage_call=privileges.can_manage_video_chats, other=privileges.can_manage_chat ), rank=rank or "" diff --git a/pyrogram/types/user_and_chats/chat_privileges.py b/pyrogram/types/user_and_chats/chat_privileges.py index 403bb9572b..8a420da5b7 100644 --- a/pyrogram/types/user_and_chats/chat_privileges.py +++ b/pyrogram/types/user_and_chats/chat_privileges.py @@ -32,7 +32,7 @@ class ChatPrivileges(Object): can_delete_messages (``bool``, *optional*): True, if the administrator can delete messages of other users. - can_manage_voice_chats (``bool``, *optional*): + can_manage_video_chats (``bool``, *optional*): Groups and supergroups only. True, if the administrator can manage voice chats (also called group calls). @@ -71,7 +71,7 @@ def __init__( *, can_manage_chat: bool = True, can_delete_messages: bool = False, - can_manage_voice_chats: bool = False, # Groups and supergroups only + can_manage_video_chats: bool = False, # Groups and supergroups only can_restrict_members: bool = False, can_promote_members: bool = False, can_change_info: bool = False, @@ -85,7 +85,7 @@ def __init__( self.can_manage_chat: bool = can_manage_chat self.can_delete_messages: bool = can_delete_messages - self.can_manage_voice_chats: bool = can_manage_voice_chats + self.can_manage_video_chats: bool = can_manage_video_chats self.can_restrict_members: bool = can_restrict_members self.can_promote_members: bool = can_promote_members self.can_change_info: bool = can_change_info @@ -100,7 +100,7 @@ def _parse(admin_rights: "raw.base.ChatAdminRights") -> "ChatPrivileges": return ChatPrivileges( can_manage_chat=admin_rights.other, can_delete_messages=admin_rights.delete_messages, - can_manage_voice_chats=admin_rights.manage_call, + can_manage_video_chats=admin_rights.manage_call, can_restrict_members=admin_rights.ban_users, can_promote_members=admin_rights.add_admins, can_change_info=admin_rights.change_info, From 7654dc82e84b3347872c0479e2d60529f9a23776 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0837/1185] Add methods {get,set}_bot_default_privileges.py --- compiler/docs/compiler.py | 2 + pyrogram/methods/bots/__init__.py | 6 +- .../bots/get_bot_default_privileges.py | 57 +++++++++++++ .../bots/set_bot_default_privileges.py | 79 +++++++++++++++++++ 4 files changed, 143 insertions(+), 1 deletion(-) create mode 100644 pyrogram/methods/bots/get_bot_default_privileges.py create mode 100644 pyrogram/methods/bots/set_bot_default_privileges.py diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index 9b9d2322da..c5f8dea5fe 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -295,6 +295,8 @@ def get_title_list(s: str) -> list: set_bot_commands get_bot_commands delete_bot_commands + set_bot_default_privileges + get_bot_default_privileges """, authorization=""" Authorization diff --git a/pyrogram/methods/bots/__init__.py b/pyrogram/methods/bots/__init__.py index 54854b8e3d..7ff79e8403 100644 --- a/pyrogram/methods/bots/__init__.py +++ b/pyrogram/methods/bots/__init__.py @@ -20,12 +20,14 @@ from .answer_inline_query import AnswerInlineQuery from .delete_bot_commands import DeleteBotCommands from .get_bot_commands import GetBotCommands +from .get_bot_default_privileges import GetBotDefaultPrivileges from .get_game_high_scores import GetGameHighScores from .get_inline_bot_results import GetInlineBotResults from .request_callback_answer import RequestCallbackAnswer from .send_game import SendGame from .send_inline_bot_result import SendInlineBotResult from .set_bot_commands import SetBotCommands +from .set_bot_default_privileges import SetBotDefaultPrivileges from .set_game_score import SetGameScore @@ -40,6 +42,8 @@ class Bots( GetGameHighScores, SetBotCommands, GetBotCommands, - DeleteBotCommands + DeleteBotCommands, + SetBotDefaultPrivileges, + GetBotDefaultPrivileges ): pass diff --git a/pyrogram/methods/bots/get_bot_default_privileges.py b/pyrogram/methods/bots/get_bot_default_privileges.py new file mode 100644 index 0000000000..e1f70fa1e8 --- /dev/null +++ b/pyrogram/methods/bots/get_bot_default_privileges.py @@ -0,0 +1,57 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# 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 Optional + +import pyrogram +from pyrogram import raw +from pyrogram import types + + +class GetBotDefaultPrivileges: + async def get_bot_default_privileges( + self: "pyrogram.Client", + for_channels: bool = None + ) -> Optional["types.ChatPrivileges"]: + """Get the current default privileges of the bot. + + Parameters: + for_channels (``bool``, *optional*): + Pass True to get default privileges of the bot in channels. Otherwise, default privileges of the bot + for groups and supergroups will be returned. + + Returns: + ``bool``: On success, True is returned. + + Example: + .. code-block:: python + + privileges = await app.get_bot_default_privileges() + """ + + bot_info = await self.invoke( + raw.functions.users.GetFullUser( + id=raw.types.InputUserSelf() + ) + ) + + field = "bot_broadcast_admin_rights" if for_channels else "bot_group_admin_rights" + + admin_rights = getattr(bot_info.full_user, field) + + return types.ChatPrivileges._parse(admin_rights) if admin_rights else None diff --git a/pyrogram/methods/bots/set_bot_default_privileges.py b/pyrogram/methods/bots/set_bot_default_privileges.py new file mode 100644 index 0000000000..52e480266b --- /dev/null +++ b/pyrogram/methods/bots/set_bot_default_privileges.py @@ -0,0 +1,79 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# 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 import raw +from pyrogram import types + + +class SetBotDefaultPrivileges: + async def set_bot_default_privileges( + self: "pyrogram.Client", + privileges: "types.ChatPrivileges" = None, + for_channels: bool = None + ) -> bool: + """Change the default privileges requested by the bot when it's added as an administrator to groups or channels. + + These privileges will be suggested to users, but they are are free to modify the list before adding the bot. + + Parameters: + privileges (:obj:`~pyrogram.types.ChatPrivileges`): + New default privileges. None to clear. + Defaults to None. + + for_channels (``bool``, *optional*): + Pass True to change the default privileges of the bot in channels. Otherwise, the default privileges of + the bot for groups and supergroups will be changed. + + Returns: + ``bool``: On success, True is returned. + + Example: + .. code-block:: python + + from pyrogram.types import ChatPrivileges + + await app.set_bot_default_privileges( + ChatPrivileges( + can_delete_messages=True, + can_restrict_members=True + ) + ) + """ + + function = ( + raw.functions.bots.SetBotBroadcastDefaultAdminRights + if for_channels + else raw.functions.bots.SetBotGroupDefaultAdminRights + ) + + admin_rights = raw.types.ChatAdminRights( + change_info=privileges.can_change_info, + post_messages=privileges.can_post_messages, + edit_messages=privileges.can_edit_messages, + delete_messages=privileges.can_delete_messages, + ban_users=privileges.can_restrict_members, + invite_users=privileges.can_invite_users, + pin_messages=privileges.can_pin_messages, + add_admins=privileges.can_promote_members, + anonymous=privileges.is_anonymous, + manage_call=privileges.can_manage_video_chats, + other=privileges.can_manage_chat + ) if privileges else raw.types.ChatAdminRights() + + return await self.invoke(function(admin_rights=admin_rights)) From 76546b0a1354d34f26192bc1f4320d3bf311db3c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0838/1185] Add MenuButton related classes --- compiler/docs/compiler.py | 4 ++ pyrogram/types/bots_and_keyboards/__init__.py | 10 +++- .../types/bots_and_keyboards/menu_button.py | 44 ++++++++++++++++ .../menu_button_commands.py | 32 ++++++++++++ .../bots_and_keyboards/menu_button_default.py | 32 ++++++++++++ .../bots_and_keyboards/menu_button_web_app.py | 51 +++++++++++++++++++ 6 files changed, 172 insertions(+), 1 deletion(-) create mode 100644 pyrogram/types/bots_and_keyboards/menu_button.py create mode 100644 pyrogram/types/bots_and_keyboards/menu_button_commands.py create mode 100644 pyrogram/types/bots_and_keyboards/menu_button_default.py create mode 100644 pyrogram/types/bots_and_keyboards/menu_button_web_app.py diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index c5f8dea5fe..e9007b7bdd 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -419,6 +419,10 @@ def get_title_list(s: str) -> list: GameHighScore CallbackGame WebAppInfo + MenuButton + MenuButtonCommands + MenuButtonWebApp + MenuButtonDefault """, bot_commands=""" Bot commands diff --git a/pyrogram/types/bots_and_keyboards/__init__.py b/pyrogram/types/bots_and_keyboards/__init__.py index 44fd589452..58f98ced31 100644 --- a/pyrogram/types/bots_and_keyboards/__init__.py +++ b/pyrogram/types/bots_and_keyboards/__init__.py @@ -33,6 +33,10 @@ from .inline_keyboard_markup import InlineKeyboardMarkup from .keyboard_button import KeyboardButton from .login_url import LoginUrl +from .menu_button import MenuButton +from .menu_button_commands import MenuButtonCommands +from .menu_button_default import MenuButtonDefault +from .menu_button_web_app import MenuButtonWebApp from .reply_keyboard_markup import ReplyKeyboardMarkup from .reply_keyboard_remove import ReplyKeyboardRemove from .web_app_info import WebAppInfo @@ -57,5 +61,9 @@ "BotCommandScopeChatAdministrators", "BotCommandScopeChatMember", "BotCommandScopeDefault", - "WebAppInfo" + "WebAppInfo", + "MenuButton", + "MenuButtonCommands", + "MenuButtonWebApp", + "MenuButtonDefault" ] diff --git a/pyrogram/types/bots_and_keyboards/menu_button.py b/pyrogram/types/bots_and_keyboards/menu_button.py new file mode 100644 index 0000000000..e61e7baa29 --- /dev/null +++ b/pyrogram/types/bots_and_keyboards/menu_button.py @@ -0,0 +1,44 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# 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 import raw +from ..object import Object + + +class MenuButton(Object): + """Describes the bot's menu button in a private chat. + + It should be one of: + + - :obj:`~pyrogram.types.MenuButtonCommands` + - :obj:`~pyrogram.types.MenuButtonWebApp` + - :obj:`~pyrogram.types.MenuButtonDefault` + + If a menu button other than :obj:`~pyrogram.types.MenuButtonDefault` is set for a private chat, then it is applied + in the chat. Otherwise the default menu button is applied. By default, the menu button opens the list of bot + commands. + """ + + def __init__(self, type: str): + super().__init__() + + self.type = type + + async def write(self, client: "pyrogram.Client") -> "raw.base.BotMenuButton": + raise NotImplementedError diff --git a/pyrogram/types/bots_and_keyboards/menu_button_commands.py b/pyrogram/types/bots_and_keyboards/menu_button_commands.py new file mode 100644 index 0000000000..b2ef77c9de --- /dev/null +++ b/pyrogram/types/bots_and_keyboards/menu_button_commands.py @@ -0,0 +1,32 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# 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 import raw +from .menu_button import MenuButton + + +class MenuButtonCommands(MenuButton): + """A menu button, which opens the bot's list of commands. + """ + + def __init__(self): + super().__init__("commands") + + async def write(self, client: "pyrogram.Client") -> "raw.types.BotMenuButtonCommands": + return raw.types.BotMenuButtonCommands() diff --git a/pyrogram/types/bots_and_keyboards/menu_button_default.py b/pyrogram/types/bots_and_keyboards/menu_button_default.py new file mode 100644 index 0000000000..a00e67633e --- /dev/null +++ b/pyrogram/types/bots_and_keyboards/menu_button_default.py @@ -0,0 +1,32 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# 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 import raw +from .menu_button import MenuButton + + +class MenuButtonDefault(MenuButton): + """Describes that no specific value for the menu button was set. + """ + + def __init__(self): + super().__init__("default") + + async def write(self, client: "pyrogram.Client") -> "raw.types.BotMenuButtonDefault": + return raw.types.BotMenuButtonDefault() diff --git a/pyrogram/types/bots_and_keyboards/menu_button_web_app.py b/pyrogram/types/bots_and_keyboards/menu_button_web_app.py new file mode 100644 index 0000000000..109088bbcf --- /dev/null +++ b/pyrogram/types/bots_and_keyboards/menu_button_web_app.py @@ -0,0 +1,51 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# 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 import raw, types +from .menu_button import MenuButton + + +class MenuButtonWebApp(MenuButton): + """A menu button, which launches a `Web App `_. + + Parameters: + text (``str``): + Text on the button + + web_app (:obj:`~pyrogram.types.WebAppInfo`): + Description of the Web App that will be launched when the user presses the button. + The Web App will be able to send an arbitrary message on behalf of the user using the method + :meth:`~pyrogram.Client.answer_web_app_query`. + """ + + def __init__( + self, + text: str, + web_app: "types.WebAppInfo" + ): + super().__init__("web_app") + + self.text = text + self.web_app = web_app + + async def write(self, client: "pyrogram.Client") -> "raw.types.BotMenuButton": + return raw.types.BotMenuButton( + text=self.text, + url=self.web_app.url + ) From 173888f7c92f1e4afb90d41b9615d5ef3c520efa Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0839/1185] Fix renamed classes in the documentation --- compiler/docs/compiler.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index e9007b7bdd..68be1e0777 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -401,10 +401,10 @@ def get_title_list(s: str) -> list: PollOption Dice Reaction - VoiceChatScheduled - VoiceChatStarted - VoiceChatEnded - VoiceChatMembersInvited + VideoChatScheduled + VideoChatStarted + VideoChatEnded + VideoChatMembersInvited """, bot_keyboards=""" Bot keyboards From fd0044c2ecb75b5ac7d84cbdbb22cbf69726bc80 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0840/1185] Add {get,set}_chat_menu_button --- compiler/docs/compiler.py | 2 + pyrogram/methods/bots/__init__.py | 6 +- pyrogram/methods/bots/get_chat_menu_button.py | 63 +++++++++++++++++++ pyrogram/methods/bots/set_chat_menu_button.py | 53 ++++++++++++++++ 4 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 pyrogram/methods/bots/get_chat_menu_button.py create mode 100644 pyrogram/methods/bots/set_chat_menu_button.py diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index 68be1e0777..be5c14bba4 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -297,6 +297,8 @@ def get_title_list(s: str) -> list: delete_bot_commands set_bot_default_privileges get_bot_default_privileges + set_chat_menu_button + get_chat_menu_button """, authorization=""" Authorization diff --git a/pyrogram/methods/bots/__init__.py b/pyrogram/methods/bots/__init__.py index 7ff79e8403..de1abe1c2e 100644 --- a/pyrogram/methods/bots/__init__.py +++ b/pyrogram/methods/bots/__init__.py @@ -21,6 +21,7 @@ from .delete_bot_commands import DeleteBotCommands from .get_bot_commands import GetBotCommands from .get_bot_default_privileges import GetBotDefaultPrivileges +from .get_chat_menu_button import GetChatMenuButton from .get_game_high_scores import GetGameHighScores from .get_inline_bot_results import GetInlineBotResults from .request_callback_answer import RequestCallbackAnswer @@ -28,6 +29,7 @@ from .send_inline_bot_result import SendInlineBotResult from .set_bot_commands import SetBotCommands from .set_bot_default_privileges import SetBotDefaultPrivileges +from .set_chat_menu_button import SetChatMenuButton from .set_game_score import SetGameScore @@ -44,6 +46,8 @@ class Bots( GetBotCommands, DeleteBotCommands, SetBotDefaultPrivileges, - GetBotDefaultPrivileges + GetBotDefaultPrivileges, + SetChatMenuButton, + GetChatMenuButton ): pass diff --git a/pyrogram/methods/bots/get_chat_menu_button.py b/pyrogram/methods/bots/get_chat_menu_button.py new file mode 100644 index 0000000000..ec1c7ed238 --- /dev/null +++ b/pyrogram/methods/bots/get_chat_menu_button.py @@ -0,0 +1,63 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# 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 Union + +import pyrogram +from pyrogram import raw +from pyrogram import types + + +class GetChatMenuButton: + async def get_chat_menu_button( + self: "pyrogram.Client", + chat_id: Union[int, str] = None, + ) -> "types.MenuButton": + """Get the current value of the bot's menu button in a private chat, or the default menu button. + + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + If not specified, default bot's menu button will be returned. + """ + + if chat_id: + r = await self.invoke( + raw.functions.bots.GetBotMenuButton( + user_id=await self.resolve_peer(chat_id), + ) + ) + else: + r = (await self.invoke( + raw.functions.users.GetFullUser( + id=raw.types.InputUserSelf() + ) + )).full_user.bot_info.menu_button + + if isinstance(r, raw.types.BotMenuButtonCommands): + return types.MenuButtonCommands() + + if isinstance(r, raw.types.BotMenuButtonDefault): + return types.MenuButtonDefault() + + if isinstance(r, raw.types.BotMenuButton): + return types.MenuButtonWebApp( + text=r.text, + web_app=types.WebAppInfo( + url=r.url + ) + ) diff --git a/pyrogram/methods/bots/set_chat_menu_button.py b/pyrogram/methods/bots/set_chat_menu_button.py new file mode 100644 index 0000000000..1305c495c4 --- /dev/null +++ b/pyrogram/methods/bots/set_chat_menu_button.py @@ -0,0 +1,53 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# 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 Union + +import pyrogram +from pyrogram import raw +from pyrogram import types + + +class SetChatMenuButton: + async def set_chat_menu_button( + self: "pyrogram.Client", + chat_id: Union[int, str] = None, + menu_button: "types.MenuButton" = None + ) -> bool: + """Change the bot's menu button in a private chat, or the default menu button. + + chat_id (``int`` | ``str``, *optional*): + Unique identifier (int) or username (str) of the target chat. + If not specified, default bot's menu button will be changed. + + menu_button (:obj:`~pyrogram.types.MenuButton`, *optional*): + The new bot's menu button. + Defaults to :obj:`~pyrogram.types.MenuButtonDefault`. + """ + + await self.invoke( + raw.functions.bots.SetBotMenuButton( + user_id=await self.resolve_peer(chat_id or "me"), + button=( + (await menu_button.write(self)) if menu_button + else (await types.MenuButtonDefault().write(self)) + ) + ) + ) + + return True From b7b7e8ec696e5562945e6f337d05341005210c20 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0841/1185] Use GitHub buttons with dark theme support --- docs/source/support.rst | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/docs/source/support.rst b/docs/source/support.rst index 32dfdb6b71..8efa4bc19f 100644 --- a/docs/source/support.rst +++ b/docs/source/support.rst @@ -7,14 +7,16 @@ Support Pyrogram
Fork + href="https://github.com/pyrogram/pyrogram" + data-color-scheme="no-preference: light; light: light; dark: dark;" + data-icon="octicon-star" data-size="large" data-show-count="true" + aria-label="Star pyrogram/pyrogram on GitHub">Star Star + href="https://github.com/pyrogram/pyrogram/fork" + data-color-scheme="no-preference: light; light: light; dark: dark;" + data-icon="octicon-repo-forked" data-size="large" + data-show-count="true" aria-label="Fork pyrogram/pyrogram on GitHub">Fork

@@ -34,10 +36,9 @@ GitHub Sponsor - Sponsor + data-color-scheme="no-preference: light; light: light; dark: dark;" + data-icon="octicon-heart" data-size="large" + aria-label="Sponsor @delivrance on GitHub">Sponsor ----- From ccadabca4a6a6e6ba2da12d88b51573da855fac7 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0842/1185] Update documentation --- docs/source/index.rst | 4 ++-- docs/source/topics/speedups.rst | 25 ++++++++++++++++++++++++- docs/source/topics/synchronous.rst | 15 +++++++++++++++ 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/docs/source/index.rst b/docs/source/index.rst index 885f79dd8e..d4699d5430 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -141,9 +141,9 @@ Meta topics/create-filters topics/more-on-updates topics/client-settings - topics/synchronous - topics/text-formatting topics/speedups + topics/text-formatting + topics/synchronous topics/smart-plugins topics/storage-engines topics/serializing diff --git a/docs/source/topics/speedups.rst b/docs/source/topics/speedups.rst index 6340e1ad4f..821b26f4df 100644 --- a/docs/source/topics/speedups.rst +++ b/docs/source/topics/speedups.rst @@ -45,21 +45,44 @@ Installation Usage ^^^^^ -Call ``uvloop.install()`` before calling ``asyncio.run()`` or ``app.run()`` +Call ``uvloop.install()`` before calling ``asyncio.run()`` or ``app.run()``. .. code-block:: python import asyncio import uvloop + from pyrogram import Client + + async def main(): app = Client("my_account") async with app: print(await app.get_me()) + uvloop.install() asyncio.run(main()) +The ``uvloop.install()`` call also needs to be placed before creating a Client instance. + +.. code-block:: python + + import uvloop + from pyrogram import Client + + uvloop.install() + + app = Client("my_account") + + + @app.on_message() + async def hello(client, message): + print(await client.get_me()) + + + app.run() + .. _TgCrypto: https://github.com/pyrogram/tgcrypto .. _uvloop: https://github.com/MagicStack/uvloop diff --git a/docs/source/topics/synchronous.rst b/docs/source/topics/synchronous.rst index e082f18c48..a6e12383d1 100644 --- a/docs/source/topics/synchronous.rst +++ b/docs/source/topics/synchronous.rst @@ -71,3 +71,18 @@ possible. @app.on_edited_message() def handler2(client, message): message.forward("me") + +uvloop usage +------------ + +When using Pyrogram in its synchronous mode combined with uvloop, you need to call ``uvloop.install()`` before importing +Pyrogram. + +.. code-block:: python + + import uvloop + uvloop.install() + + from pyrogram import Client + + ... \ No newline at end of file From 59ccc4de8880ce540849d4a18ae5334b80392b6b Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0843/1185] Documentation fixes --- pyrogram/filters.py | 4 ++-- pyrogram/methods/bots/get_chat_menu_button.py | 7 ++++--- pyrogram/methods/bots/set_chat_menu_button.py | 15 ++++++++------- .../types/user_and_chats/chat_event_filter.py | 10 +++++----- pyrogram/types/user_and_chats/chat_privileges.py | 2 +- 5 files changed, 20 insertions(+), 18 deletions(-) diff --git a/pyrogram/filters.py b/pyrogram/filters.py index 013adee4e2..76bff856ff 100644 --- a/pyrogram/filters.py +++ b/pyrogram/filters.py @@ -642,7 +642,7 @@ async def video_chat_started_filter(_, __, m: Message): video_chat_started = create(video_chat_started_filter) -"""Filter messages for started voice chats""" +"""Filter messages for started video chats""" # endregion @@ -653,7 +653,7 @@ async def video_chat_ended_filter(_, __, m: Message): video_chat_ended = create(video_chat_ended_filter) -"""Filter messages for ended voice chats""" +"""Filter messages for ended video chats""" # endregion diff --git a/pyrogram/methods/bots/get_chat_menu_button.py b/pyrogram/methods/bots/get_chat_menu_button.py index ec1c7ed238..8fb61b0173 100644 --- a/pyrogram/methods/bots/get_chat_menu_button.py +++ b/pyrogram/methods/bots/get_chat_menu_button.py @@ -30,9 +30,10 @@ async def get_chat_menu_button( ) -> "types.MenuButton": """Get the current value of the bot's menu button in a private chat, or the default menu button. - chat_id (``int`` | ``str``): - Unique identifier (int) or username (str) of the target chat. - If not specified, default bot's menu button will be returned. + Parameters: + chat_id (``int`` | ``str``): + Unique identifier (int) or username (str) of the target chat. + If not specified, default bot's menu button will be returned. """ if chat_id: diff --git a/pyrogram/methods/bots/set_chat_menu_button.py b/pyrogram/methods/bots/set_chat_menu_button.py index 1305c495c4..87e26c036c 100644 --- a/pyrogram/methods/bots/set_chat_menu_button.py +++ b/pyrogram/methods/bots/set_chat_menu_button.py @@ -31,13 +31,14 @@ async def set_chat_menu_button( ) -> bool: """Change the bot's menu button in a private chat, or the default menu button. - chat_id (``int`` | ``str``, *optional*): - Unique identifier (int) or username (str) of the target chat. - If not specified, default bot's menu button will be changed. - - menu_button (:obj:`~pyrogram.types.MenuButton`, *optional*): - The new bot's menu button. - Defaults to :obj:`~pyrogram.types.MenuButtonDefault`. + Parameters: + chat_id (``int`` | ``str``, *optional*): + Unique identifier (int) or username (str) of the target chat. + If not specified, default bot's menu button will be changed. + + menu_button (:obj:`~pyrogram.types.MenuButton`, *optional*): + The new bot's menu button. + Defaults to :obj:`~pyrogram.types.MenuButtonDefault`. """ await self.invoke( diff --git a/pyrogram/types/user_and_chats/chat_event_filter.py b/pyrogram/types/user_and_chats/chat_event_filter.py index 7edc3a07eb..92298ea3be 100644 --- a/pyrogram/types/user_and_chats/chat_event_filter.py +++ b/pyrogram/types/user_and_chats/chat_event_filter.py @@ -66,8 +66,8 @@ class ChatEventFilter(Object): True, if members leaving events should be returned. Defaults to False. - voice_chats (``bool``, *optional*): - True, if voice chats events should be returned. + video_chats (``bool``, *optional*): + True, if video chats events should be returned. Defaults to False. """ @@ -83,7 +83,7 @@ def __init__( edited_messages: bool = False, pinned_messages: bool = False, leaving_members: bool = False, - voice_chats: bool = False + video_chats: bool = False ): super().__init__() @@ -97,7 +97,7 @@ def __init__( self.edited_messages = edited_messages self.pinned_messages = pinned_messages self.leaving_members = leaving_members - self.voice_chats = voice_chats + self.video_chats = video_chats def write(self) -> "raw.base.ChannelAdminLogEventsFilter": join = False @@ -152,7 +152,7 @@ def write(self) -> "raw.base.ChannelAdminLogEventsFilter": if self.leaving_members: leave = True - if self.voice_chats: + if self.video_chats: group_call = True return raw.types.ChannelAdminLogEventsFilter( diff --git a/pyrogram/types/user_and_chats/chat_privileges.py b/pyrogram/types/user_and_chats/chat_privileges.py index 8a420da5b7..09bb341dfc 100644 --- a/pyrogram/types/user_and_chats/chat_privileges.py +++ b/pyrogram/types/user_and_chats/chat_privileges.py @@ -34,7 +34,7 @@ class ChatPrivileges(Object): can_manage_video_chats (``bool``, *optional*): Groups and supergroups only. - True, if the administrator can manage voice chats (also called group calls). + True, if the administrator can manage video chats (also called group calls). can_restrict_members (``bool``, *optional*): True, if the administrator can restrict, ban or unban chat members. From 515531774ba72de3dc9c47fedf52e5e1b56eed7b Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0844/1185] Small documentation fix --- docs/source/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/index.rst b/docs/source/index.rst index d4699d5430..d96223cb13 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -75,7 +75,7 @@ First Steps :columns: 1 - :doc:`Quick Start `: Overview to get you started quickly. - - :doc:`Calling Methods `: How to call Pyrogram's methods. + - :doc:`Invoking Methods `: How to call Pyrogram's methods. - :doc:`Handling Updates `: How to handle Telegram updates. - :doc:`Error Handling `: How to handle API errors correctly. From b645a75b93201cd1fd203b99145a0fcc73ffc534 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0845/1185] Allow negative offsets in stream_media --- pyrogram/methods/messages/stream_media.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/pyrogram/methods/messages/stream_media.py b/pyrogram/methods/messages/stream_media.py index ea41b7bc37..91ffefd7ee 100644 --- a/pyrogram/methods/messages/stream_media.py +++ b/pyrogram/methods/messages/stream_media.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 math from typing import Union, Optional, BinaryIO import pyrogram @@ -62,10 +63,12 @@ async def stream_media( async for chunk in app.stream_media(message, limit=3): print(len(chunk)) - # Stream the last 3 chunks only - import math - chunks = math.ceil(message.document.file_size / 1024 / 1024) - async for chunk in app.stream_media(message, offset=chunks - 3): + # Stream the rest of the media by skipping the first 3 chunks + async for chunk in app.stream_media(message, offset=3): + print(len(chunk)) + + # Stream the last 3 chunks only (negative offset) + async for chunk in app.stream_media(message, offset=-3): print(len(chunk)) """ available_media = ("audio", "document", "photo", "sticker", "animation", "video", "voice", "video_note", @@ -90,5 +93,12 @@ async def stream_media( file_id_obj = FileId.decode(file_id_str) file_size = getattr(media, "file_size", 0) + if offset < 0: + if file_size == 0: + raise ValueError("Negative offsets are not supported for file ids, pass a Message object instead") + + chunks = math.ceil(file_size / 1024 / 1024) + offset += chunks + async for chunk in self.get_file(file_id_obj, file_size, limit, offset): yield chunk From c44643faadbd8c13f4e70903079f705d3bd2784a Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0846/1185] Add the class WebAppData --- compiler/docs/compiler.py | 1 + pyrogram/enums/message_service.py | 3 ++ pyrogram/types/messages_and_media/__init__.py | 3 +- pyrogram/types/messages_and_media/message.py | 10 ++++ .../types/messages_and_media/web_app_data.py | 51 +++++++++++++++++++ 5 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 pyrogram/types/messages_and_media/web_app_data.py diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index be5c14bba4..a626649d04 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -407,6 +407,7 @@ def get_title_list(s: str) -> list: VideoChatStarted VideoChatEnded VideoChatMembersInvited + WebAppData """, bot_keyboards=""" Bot keyboards diff --git a/pyrogram/enums/message_service.py b/pyrogram/enums/message_service.py index 7b23c730c2..636e6db575 100644 --- a/pyrogram/enums/message_service.py +++ b/pyrogram/enums/message_service.py @@ -68,3 +68,6 @@ class MessageService(AutoName): VIDEO_CHAT_MEMBERS_INVITED = auto() "Video chat members invited" + + WEB_APP_DATA = auto() + "Web app data" diff --git a/pyrogram/types/messages_and_media/__init__.py b/pyrogram/types/messages_and_media/__init__.py index cbed4f0f9d..e5ade1ea7f 100644 --- a/pyrogram/types/messages_and_media/__init__.py +++ b/pyrogram/types/messages_and_media/__init__.py @@ -37,9 +37,10 @@ from .video_note import VideoNote from .voice import Voice from .webpage import WebPage +from .web_app_data import WebAppData __all__ = [ "Animation", "Audio", "Contact", "Document", "Game", "Location", "Message", "MessageEntity", "Photo", "Thumbnail", "StrippedThumbnail", "Poll", "PollOption", "Sticker", "Venue", "Video", "VideoNote", "Voice", "WebPage", "Dice", - "Reaction" + "Reaction", "WebAppData" ] diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index 7a5341fdd8..526839c951 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -285,6 +285,9 @@ class Message(Object, Update): video_chat_members_invited (:obj:`~pyrogram.types.VoiceChatParticipantsInvited`, *optional*): Service message: new members were invited to the voice chat. + web_app_data (:obj:`~pyrogram.types.WebAppData`, *optional*): + Service message: web app data sent to the bot. + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. @@ -366,6 +369,7 @@ def __init__( video_chat_started: "types.VideoChatStarted" = None, video_chat_ended: "types.VideoChatEnded" = None, video_chat_members_invited: "types.VideoChatMembersInvited" = None, + web_app_data: "types.WebAppData" = None, reply_markup: Union[ "types.InlineKeyboardMarkup", "types.ReplyKeyboardMarkup", @@ -441,6 +445,7 @@ def __init__( self.video_chat_started = video_chat_started self.video_chat_ended = video_chat_ended self.video_chat_members_invited = video_chat_members_invited + self.web_app_data = web_app_data self.reactions = reactions @staticmethod @@ -491,6 +496,7 @@ async def _parse( video_chat_started = None video_chat_ended = None video_chat_members_invited = None + web_app_data = None service_type = None @@ -537,6 +543,9 @@ async def _parse( elif isinstance(action, raw.types.MessageActionInviteToGroupCall): video_chat_members_invited = types.VideoChatMembersInvited._parse(client, action, users) service_type = enums.MessageService.VIDEO_CHAT_MEMBERS_INVITED + elif isinstance(action, raw.types.MessageActionWebViewDataSentMe): + web_app_data = types.WebAppData._parse(action) + service_type = enums.MessageService.WEB_APP_DATA from_user = types.User._parse(client, users.get(user_id, None)) sender_chat = types.Chat._parse(client, message, users, chats, is_chat=False) if not from_user else None @@ -561,6 +570,7 @@ async def _parse( video_chat_started=video_chat_started, video_chat_ended=video_chat_ended, video_chat_members_invited=video_chat_members_invited, + web_app_data=web_app_data, client=client # TODO: supergroup_chat_created ) diff --git a/pyrogram/types/messages_and_media/web_app_data.py b/pyrogram/types/messages_and_media/web_app_data.py new file mode 100644 index 0000000000..b9a471fd89 --- /dev/null +++ b/pyrogram/types/messages_and_media/web_app_data.py @@ -0,0 +1,51 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# 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 pyrogram import raw +from ..object import Object + + +class WebAppData(Object): + """Contains data sent from a `Web App `_ to the bot. + + Parameters: + data (``str``): + The data. + + button_text (``str``): + Text of the *web_app* keyboard button, from which the Web App was opened. + + """ + + def __init__( + self, + *, + data: str, + button_text: str, + ): + super().__init__() + + self.data = data + self.button_text = button_text + + @staticmethod + def _parse(action: "raw.types.MessageActionWebViewDataSentMe"): + return WebAppData( + data=action.data, + button_text=action.text + ) From 663594876d3d5f30087270489ca5c9c50308d20a Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0847/1185] Rename webpage.py to web_page.py --- pyrogram/types/messages_and_media/__init__.py | 2 +- pyrogram/types/messages_and_media/{webpage.py => web_page.py} | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename pyrogram/types/messages_and_media/{webpage.py => web_page.py} (100%) diff --git a/pyrogram/types/messages_and_media/__init__.py b/pyrogram/types/messages_and_media/__init__.py index e5ade1ea7f..3a18b2a5bf 100644 --- a/pyrogram/types/messages_and_media/__init__.py +++ b/pyrogram/types/messages_and_media/__init__.py @@ -36,8 +36,8 @@ from .video import Video from .video_note import VideoNote from .voice import Voice -from .webpage import WebPage from .web_app_data import WebAppData +from .web_page import WebPage __all__ = [ "Animation", "Audio", "Contact", "Document", "Game", "Location", "Message", "MessageEntity", "Photo", "Thumbnail", diff --git a/pyrogram/types/messages_and_media/webpage.py b/pyrogram/types/messages_and_media/web_page.py similarity index 100% rename from pyrogram/types/messages_and_media/webpage.py rename to pyrogram/types/messages_and_media/web_page.py From 4e6c1690d26eb493475f8c75de93eb536ffc4640 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0848/1185] Add pack_inline_message_id util function --- pyrogram/utils.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/pyrogram/utils.py b/pyrogram/utils.py index b3f04e6018..c855ae62e0 100644 --- a/pyrogram/utils.py +++ b/pyrogram/utils.py @@ -146,6 +146,26 @@ def parse_deleted_messages(client, update) -> List["types.Message"]: return types.List(parsed_messages) +def pack_inline_message_id(msg_id: "raw.base.InputBotInlineMessageID"): + if isinstance(msg_id, raw.types.InputBotInlineMessageID): + inline_message_id_packed = struct.pack( + " Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0849/1185] Use pack_inline_message_id util function in CallbackQuery --- pyrogram/types/bots_and_keyboards/callback_query.py | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/pyrogram/types/bots_and_keyboards/callback_query.py b/pyrogram/types/bots_and_keyboards/callback_query.py index dc4741b264..efdd14ca4d 100644 --- a/pyrogram/types/bots_and_keyboards/callback_query.py +++ b/pyrogram/types/bots_and_keyboards/callback_query.py @@ -16,8 +16,6 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from base64 import b64encode -from struct import pack from typing import Union, List, Match, Optional import pyrogram @@ -102,15 +100,7 @@ async def _parse(client: "pyrogram.Client", callback_query, users) -> "CallbackQ if not message: message = await client.get_messages(chat_id, message_id) elif isinstance(callback_query, raw.types.UpdateInlineBotCallbackQuery): - inline_message_id = b64encode( - pack( - " +# +# 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 pyrogram import raw, utils +from ..object import Object + + +class SentWebAppMessage(Object): + """Contains information about an inline message sent by a `Web App `_ on behalf of a user. + + Parameters: + inline_message_id (``str``): + Identifier of the sent inline message. + Available only if there is an inline keyboard attached to the message. + """ + + def __init__( + self, *, + inline_message_id: str, + ): + super().__init__() + + self.inline_message_id = inline_message_id + + @staticmethod + def _parse(obj: "raw.types.WebViewMessageSent"): + return SentWebAppMessage(inline_message_id=utils.pack_inline_message_id(obj.msg_id)) From 43f9b57567edffc1c86d41c269570abf2ed6ebcf Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0851/1185] Add the method answer_web_app_query --- compiler/docs/compiler.py | 1 + pyrogram/methods/bots/__init__.py | 4 +- pyrogram/methods/bots/answer_web_app_query.py | 51 +++++++++++++++++++ 3 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 pyrogram/methods/bots/answer_web_app_query.py diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index e1667a8519..3948df6933 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -299,6 +299,7 @@ def get_title_list(s: str) -> list: get_bot_default_privileges set_chat_menu_button get_chat_menu_button + answer_web_app_query """, authorization=""" Authorization diff --git a/pyrogram/methods/bots/__init__.py b/pyrogram/methods/bots/__init__.py index de1abe1c2e..da52fcfb00 100644 --- a/pyrogram/methods/bots/__init__.py +++ b/pyrogram/methods/bots/__init__.py @@ -18,6 +18,7 @@ from .answer_callback_query import AnswerCallbackQuery from .answer_inline_query import AnswerInlineQuery +from .answer_web_app_query import AnswerWebAppQuery from .delete_bot_commands import DeleteBotCommands from .get_bot_commands import GetBotCommands from .get_bot_default_privileges import GetBotDefaultPrivileges @@ -48,6 +49,7 @@ class Bots( SetBotDefaultPrivileges, GetBotDefaultPrivileges, SetChatMenuButton, - GetChatMenuButton + GetChatMenuButton, + AnswerWebAppQuery ): pass diff --git a/pyrogram/methods/bots/answer_web_app_query.py b/pyrogram/methods/bots/answer_web_app_query.py new file mode 100644 index 0000000000..f887e28098 --- /dev/null +++ b/pyrogram/methods/bots/answer_web_app_query.py @@ -0,0 +1,51 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# 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 import raw +from pyrogram import types + + +class AnswerWebAppQuery: + async def answer_web_app_query( + self: "pyrogram.Client", + web_app_query_id: str, + result: "types.InlineQueryResult" + ) -> "types.SentWebAppMessage": + """Set the result of an interaction with a `Web App `_ and send a + corresponding message on behalf of the user to the chat from which the query originated. + + Parameters: + web_app_query_id (``str``): + Unique identifier for the answered query. + + result (:obj:`~pyrogram.types.InlineQueryResult`): + A list of results for the inline query. + + Returns: + :obj:`~pyrogram.types.SentWebAppMessage`: On success the sent web app message is returned. + """ + + r = await self.invoke( + raw.functions.messages.SendWebViewResultMessage( + bot_query_id=web_app_query_id, + result=await result.write(self) + ) + ) + + return types.SentWebAppMessage._parse(r) From 6087c2a9748d5216be217e1a6ed0ea967793ef87 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0852/1185] Add missing entry in __init__.py --- pyrogram/types/bots_and_keyboards/__init__.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyrogram/types/bots_and_keyboards/__init__.py b/pyrogram/types/bots_and_keyboards/__init__.py index 58f98ced31..6f05a3b486 100644 --- a/pyrogram/types/bots_and_keyboards/__init__.py +++ b/pyrogram/types/bots_and_keyboards/__init__.py @@ -39,6 +39,7 @@ from .menu_button_web_app import MenuButtonWebApp from .reply_keyboard_markup import ReplyKeyboardMarkup from .reply_keyboard_remove import ReplyKeyboardRemove +from .sent_web_app_message import SentWebAppMessage from .web_app_info import WebAppInfo __all__ = [ @@ -65,5 +66,6 @@ "MenuButton", "MenuButtonCommands", "MenuButtonWebApp", - "MenuButtonDefault" + "MenuButtonDefault", + "SentWebAppMessage" ] From 2ad53ec00ba9fea4a2873ef4ea85b33138264447 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0853/1185] Update unpack_inline_message_id --- pyrogram/utils.py | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/pyrogram/utils.py b/pyrogram/utils.py index c855ae62e0..18230ed4f5 100644 --- a/pyrogram/utils.py +++ b/pyrogram/utils.py @@ -166,15 +166,27 @@ def pack_inline_message_id(msg_id: "raw.base.InputBotInlineMessageID"): return base64.urlsafe_b64encode(inline_message_id_packed).decode().rstrip("=") -def unpack_inline_message_id(inline_message_id: str) -> "raw.types.InputBotInlineMessageID": - r = inline_message_id + "=" * (-len(inline_message_id) % 4) - r = struct.unpack(" "raw.base.InputBotInlineMessageID": + padded = inline_message_id + "=" * (-len(inline_message_id) % 4) + decoded = base64.urlsafe_b64decode(padded) + + if len(decoded) == 20: + unpacked = struct.unpack(" Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0854/1185] Update setup.py --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 66ede5066d..160c0fda64 100644 --- a/setup.py +++ b/setup.py @@ -140,12 +140,12 @@ def run(self): download_url="https://github.com/pyrogram/pyrogram/releases/latest", author="Dan", author_email="dan@pyrogram.org", - license="LGPLv3+", + license="LGPLv3", classifiers=[ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "Natural Language :: English", - "License :: OSI Approved :: GNU Lesser General Public License v3 or later (LGPLv3+)", + "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)", "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", From 1ebc7041464302ec2bf37cd701ffc712c27982f2 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0855/1185] Documentation cleanup --- compiler/docs/compiler.py | 3 --- compiler/docs/template/toctree.txt | 2 -- compiler/errors/compiler.py | 2 -- 3 files changed, 7 deletions(-) diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index 3948df6933..a03d2ee316 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -21,8 +21,6 @@ import re import shutil -import pyrogram - HOME = "compiler/docs" DESTINATION = "docs/source/telegram" PYROGRAM_API_DEST = "docs/source/api" @@ -114,7 +112,6 @@ def build(path, level=0): toctree.format( title=k.title(), title_markup="=" * len(k), - layer=pyrogram.raw.all.layer, module=module, entities="\n ".join(entities) ) diff --git a/compiler/docs/template/toctree.txt b/compiler/docs/template/toctree.txt index cc2ca9ac22..717276c40e 100644 --- a/compiler/docs/template/toctree.txt +++ b/compiler/docs/template/toctree.txt @@ -1,8 +1,6 @@ {title} {title_markup} -Layer {layer} - .. module:: {module} .. toctree:: diff --git a/compiler/errors/compiler.py b/compiler/errors/compiler.py index d2c1010456..0c4ef399b6 100644 --- a/compiler/errors/compiler.py +++ b/compiler/errors/compiler.py @@ -133,8 +133,6 @@ def start(): with open("{}/all.py".format(DEST), "w", encoding="utf-8") as f: f.write(re.sub("{count}", str(count), content)) - print("Compiling Errors: [100%]") - if "__main__" == __name__: HOME = "." From 109c9d4a0af2961377a7ff44b71ac18426f7076b Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0856/1185] Migrate setup.py commands to a Makefile --- Makefile | 53 ++++++++++++++++++++++ dev-requirements.txt | 3 +- docs/Makefile | 24 ---------- docs/make.bat | 36 --------------- docs/requirements.txt | 4 +- setup.py | 103 ++---------------------------------------- 6 files changed, 59 insertions(+), 164 deletions(-) create mode 100644 Makefile delete mode 100644 docs/Makefile delete mode 100644 docs/make.bat diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000..0d19832945 --- /dev/null +++ b/Makefile @@ -0,0 +1,53 @@ +VENV := venv +PYTHON := $(VENV)/bin/python + +RM := rm -rf + +.PHONY: venv build docs + +venv: + $(RM) $(VENV) + python3 -m venv $(VENV) + $(PYTHON) -m pip install -U pip wheel setuptools + $(PYTHON) -m pip install -U -r requirements.txt -r dev-requirements.txt -r docs/requirements.txt + @echo "Created venv with $$($(PYTHON) --version)" + +clean-build: + $(RM) *.egg-info build dist + +clean-docs: + $(RM) docs/build + $(RM) docs/source/api/bound-methods docs/source/api/methods docs/source/api/types docs/source/telegram + +clean-api: + $(RM) pyrogram/errors/exceptions pyrogram/raw/all.py pyrogram/raw/base pyrogram/raw/functions pyrogram/raw/types + +clean: + make clean-build + make clean-docs + make clean-api + +api: + cd compiler/api && ../../$(PYTHON) compiler.py + cd compiler/errors && ../../$(PYTHON) compiler.py + +docs-live: + make clean-docs + cd compiler/docs && ../../$(PYTHON) compiler.py + $(RM) docs/source/telegram + $(VENV)/bin/sphinx-autobuild \ + --host $(shell ifconfig | grep "inet " | grep -v 127.0.0.1 | cut -d\ -f2) \ + --watch pyrogram --watch docs/resources \ + -b html "docs/source" "docs/build/html" -j auto + +docs: + make clean-docs + cd compiler/docs && ../../$(PYTHON) compiler.py + $(VENV)/bin/sphinx-build \ + -b html "docs/source" "docs/build/html" -j auto + +build: + make clean-build + make clean-api + $(PYTHON) setup.py sdist + $(PYTHON) setup.py bdist_wheel \ No newline at end of file diff --git a/dev-requirements.txt b/dev-requirements.txt index 17f8c0348c..d8968085b3 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -2,4 +2,5 @@ pytest pytest-asyncio -pytest-cov \ No newline at end of file +pytest-cov +twine \ No newline at end of file diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index 066560f8f7..0000000000 --- a/docs/Makefile +++ /dev/null @@ -1,24 +0,0 @@ -# Minimal makefile for Sphinx documentation -# - -# You can set these variables from the command line. -SPHINXOPTS = -j $(shell nproc --all) -SPHINXBUILD = sphinx-build -SPHINXPROJ = Pyrogram -SOURCEDIR = source -BUILDDIR = build - -# Put it first so that "make" without argument is like "make help". -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -.PHONY: help Makefile - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -lhtml: # live html - sphinx-autobuild --host $(shell ifconfig | grep "inet " | grep -v 127.0.0.1 | cut -d\ -f2) \ - --watch ../pyrogram --watch resources -b html "$(SOURCEDIR)" "$(BUILDDIR)/html" $(SPHINXOPTS) diff --git a/docs/make.bat b/docs/make.bat deleted file mode 100644 index fea543e258..0000000000 --- a/docs/make.bat +++ /dev/null @@ -1,36 +0,0 @@ -@ECHO OFF - -pushd %~dp0 - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set SOURCEDIR=source -set BUILDDIR=build -set SPHINXPROJ=Pyrogram - -if "%1" == "" goto help - -%SPHINXBUILD% >NUL 2>NUL -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.http://sphinx-doc.org/ - exit /b 1 -) - -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% -goto end - -:help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% - -:end -popd diff --git a/docs/requirements.txt b/docs/requirements.txt index f83e673d74..7283c8ba69 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,6 +1,4 @@ sphinx sphinx_rtd_theme==1.0.0 sphinx_copybutton -pypandoc -requests -sphinx-autobuild +sphinx-autobuild \ No newline at end of file diff --git a/setup.py b/setup.py index 160c0fda64..d1bd9c90dd 100644 --- a/setup.py +++ b/setup.py @@ -16,15 +16,12 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -import os import re -import shutil from sys import argv -from setuptools import setup, find_packages, Command +from setuptools import setup, find_packages from compiler.api import compiler as api_compiler -from compiler.docs import compiler as docs_compiler from compiler.errors import compiler as errors_compiler with open("requirements.txt", encoding="utf-8") as r: @@ -36,96 +33,6 @@ with open("README.md", encoding="utf-8") as f: readme = f.read() - -class Clean(Command): - DIST = ["./build", "./dist", "./Pyrogram.egg-info"] - API = [ - "pyrogram/errors/exceptions", "pyrogram/raw/functions", "pyrogram/raw/types", "pyrogram/raw/base", - "pyrogram/raw/all.py" - ] - DOCS = [ - "docs/source/telegram", "docs/build", "docs/source/api/methods", "docs/source/api/types", - "docs/source/api/bound-methods" - ] - - ALL = DIST + API + DOCS - - description = "Clean generated files" - - user_options = [ - ("dist", None, "Clean distribution files"), - ("api", None, "Clean generated API files"), - ("docs", None, "Clean generated docs files"), - ("all", None, "Clean all generated files"), - ] - - def __init__(self, dist, **kw): - super().__init__(dist, **kw) - - self.dist = None - self.api = None - self.docs = None - self.all = None - - def initialize_options(self): - pass - - def finalize_options(self): - pass - - def run(self): - paths = set() - - if self.dist: - paths.update(Clean.DIST) - - if self.api: - paths.update(Clean.API) - - if self.docs: - paths.update(Clean.DOCS) - - if self.all or not paths: - paths.update(Clean.ALL) - - for path in sorted(list(paths)): - try: - shutil.rmtree(path) if os.path.isdir(path) else os.remove(path) - except OSError: - print("skipping {}".format(path)) - else: - print("removing {}".format(path)) - - -class Generate(Command): - description = "Generate Pyrogram files" - - user_options = [ - ("api", None, "Generate API files"), - ("docs", None, "Generate docs files") - ] - - def __init__(self, dist, **kw): - super().__init__(dist, **kw) - - self.api = None - self.docs = None - - def initialize_options(self): - pass - - def finalize_options(self): - pass - - def run(self): - if self.api: - errors_compiler.start() - api_compiler.start() - - if self.docs: - docs_compiler.start() - - if len(argv) > 1 and argv[1] in ["bdist_wheel", "install", "develop"]: api_compiler.start() errors_compiler.start() @@ -172,14 +79,10 @@ def run(self): "Documentation": "https://docs.pyrogram.org", }, python_requires="~=3.6", - package_data = { + package_data={ "pyrogram": ["py.typed"], }, packages=find_packages(exclude=["compiler*", "tests*"]), zip_safe=False, - install_requires=requires, - cmdclass={ - "clean": Clean, - "generate": Generate - } + install_requires=requires ) From 077687b85d4ff5759f16668aa996c6bf940dca43 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0857/1185] Add missing fields to InlineQueryResultArticle --- .../inline_query_result_article.py | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/pyrogram/types/inline_mode/inline_query_result_article.py b/pyrogram/types/inline_mode/inline_query_result_article.py index 73260dd9cc..096273f15b 100644 --- a/pyrogram/types/inline_mode/inline_query_result_article.py +++ b/pyrogram/types/inline_mode/inline_query_result_article.py @@ -43,11 +43,17 @@ class InlineQueryResultArticle(InlineQueryResult): description (``str``, *optional*): Short description of the result. - thumb_url (``str``, *optional*): - URL of the thumbnail for the result. - reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*): Inline keyboard attached to the message. + + thumb_url (``str``, *optional*): + Url of the thumbnail for the result. + + thumb_width (``int``, *optional*): + Thumbnail width. + + thumb_height (``int``, *optional*): + Thumbnail height """ def __init__( @@ -55,10 +61,12 @@ def __init__( title: str, input_message_content: "types.InputMessageContent", id: str = None, - reply_markup: "types.InlineKeyboardMarkup" = None, url: str = None, description: str = None, - thumb_url: str = None + reply_markup: "types.InlineKeyboardMarkup" = None, + thumb_url: str = None, + thumb_width: int = 0, + thumb_height: int = 0 ): super().__init__("article", id, input_message_content, reply_markup) @@ -66,6 +74,8 @@ def __init__( self.url = url self.description = description self.thumb_url = thumb_url + self.thumb_width = thumb_width + self.thumb_height = thumb_height async def write(self, client: "pyrogram.Client"): return raw.types.InputBotInlineResult( @@ -79,6 +89,11 @@ async def write(self, client: "pyrogram.Client"): url=self.thumb_url, size=0, mime_type="image/jpeg", - attributes=[] + attributes=[ + raw.types.DocumentAttributeImageSize( + w=self.thumb_width, + h=self.thumb_height + ) + ] ) if self.thumb_url else None ) From 5108b78ef5823e76ca96edcf1c30fa0c1ab08bc6 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0858/1185] Add missing fields to InlineQueryResultPhoto --- .../inline_mode/inline_query_result_photo.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/pyrogram/types/inline_mode/inline_query_result_photo.py b/pyrogram/types/inline_mode/inline_query_result_photo.py index 110b604661..9e03548954 100644 --- a/pyrogram/types/inline_mode/inline_query_result_photo.py +++ b/pyrogram/types/inline_mode/inline_query_result_photo.py @@ -39,6 +39,12 @@ class InlineQueryResultPhoto(InlineQueryResult): URL of the thumbnail for the photo. Defaults to the value passed in *photo_url*. + photo_width (``int``, *optional*): + Width of the photo. + + photo_height (``int``, *optional*): + Height of the photo + id (``str``, *optional*): Unique identifier for this result, 1-64 bytes. Defaults to a randomly generated UUID4. @@ -70,6 +76,8 @@ def __init__( self, photo_url: str, thumb_url: str = None, + photo_width: int = 0, + photo_height: int = 0, id: str = None, title: str = None, description: str = None, @@ -83,6 +91,8 @@ def __init__( self.photo_url = photo_url self.thumb_url = thumb_url + self.photo_width = photo_width + self.photo_height = photo_height self.title = title self.description = description self.caption = caption @@ -96,7 +106,12 @@ async def write(self, client: "pyrogram.Client"): url=self.photo_url, size=0, mime_type="image/jpeg", - attributes=[] + attributes=[ + raw.types.DocumentAttributeImageSize( + w=self.photo_width, + h=self.photo_height + ) + ] ) if self.thumb_url is None: From 4367dbc46551a4ec256bef89a8d35ae4f744b7a2 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0859/1185] Add missing fields to InlineQueryResultAnimation --- .../inline_query_result_animation.py | 37 +++++++++++++++---- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/pyrogram/types/inline_mode/inline_query_result_animation.py b/pyrogram/types/inline_mode/inline_query_result_animation.py index 4a170a1834..71d1edf40a 100644 --- a/pyrogram/types/inline_mode/inline_query_result_animation.py +++ b/pyrogram/types/inline_mode/inline_query_result_animation.py @@ -35,10 +35,23 @@ class InlineQueryResultAnimation(InlineQueryResult): A valid URL for the animated GIF file. File size must not exceed 1 MB. + animation_width (``int``, *optional*) + Width of the animation. + + animation_height (``int``, *optional*) + Height of the animation. + + animation_duration (``int``, *optional*) + Duration of the animation in seconds. + thumb_url (``str``, *optional*): URL of the static thumbnail for the result (jpeg or gif) Defaults to the value passed in *animation_url*. + thumb_mime_type (``str``, *optional*) + MIME type of the thumbnail, must be one of "image/jpeg", "image/gif", or "video/mp4". + Defaults to "image/jpeg". + id (``str``, *optional*): Unique identifier for this result, 1-64 bytes. Defaults to a randomly generated UUID4. @@ -46,11 +59,8 @@ class InlineQueryResultAnimation(InlineQueryResult): title (``str``, *optional*): Title for the result. - description (``str``, *optional*): - Short description of the result. - caption (``str``, *optional*): - Caption of the photo to be sent, 0-1024 characters. + Caption of the animation to be sent, 0-1024 characters. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. @@ -69,7 +79,11 @@ class InlineQueryResultAnimation(InlineQueryResult): def __init__( self, animation_url: str, + animation_width: int = 0, + animation_height: int = 0, + animation_duration: int = 0, thumb_url: str = None, + thumb_mime_type: str = "image/jpeg", id: str = None, title: str = None, description: str = None, @@ -82,7 +96,11 @@ def __init__( super().__init__("gif", id, input_message_content, reply_markup) self.animation_url = animation_url + self.animation_width = animation_width + self.animation_height = animation_height + self.animation_duration = animation_duration self.thumb_url = thumb_url + self.thumb_mime_type = thumb_mime_type self.title = title self.description = description self.caption = caption @@ -96,7 +114,13 @@ async def write(self, client: "pyrogram.Client"): url=self.animation_url, size=0, mime_type="image/gif", - attributes=[] + attributes=[ + raw.types.DocumentAttributeVideo( + w=self.animation_width, + h=self.animation_height, + duration=self.animation_duration + ) + ] ) if self.thumb_url is None: @@ -105,7 +129,7 @@ async def write(self, client: "pyrogram.Client"): thumb = raw.types.InputWebDocument( url=self.thumb_url, size=0, - mime_type="image/gif", + mime_type=self.thumb_mime_type, attributes=[] ) @@ -117,7 +141,6 @@ async def write(self, client: "pyrogram.Client"): id=self.id, type=self.type, title=self.title, - description=self.description, thumb=thumb, content=animation, send_message=( From 9c28ccdf60311d9709a3dfc8fa506b98faec0858 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0860/1185] Add InlineQueryResultVoice --- pyrogram/types/inline_mode/__init__.py | 3 +- .../inline_mode/inline_query_result_voice.py | 114 ++++++++++++++++++ 2 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 pyrogram/types/inline_mode/inline_query_result_voice.py diff --git a/pyrogram/types/inline_mode/__init__.py b/pyrogram/types/inline_mode/__init__.py index 25a568f539..daff01174f 100644 --- a/pyrogram/types/inline_mode/__init__.py +++ b/pyrogram/types/inline_mode/__init__.py @@ -26,9 +26,10 @@ from .inline_query_result_video import InlineQueryResultVideo from .inline_query_result_contact import InlineQueryResultContact from .inline_query_result_document import InlineQueryResultDocument +from .inline_query_result_voice import InlineQueryResultVoice __all__ = [ "InlineQuery", "InlineQueryResult", "InlineQueryResultArticle", "InlineQueryResultPhoto", "InlineQueryResultAnimation", "InlineQueryResultAudio", "InlineQueryResultVideo", "ChosenInlineResult", - "InlineQueryResultContact", "InlineQueryResultDocument" + "InlineQueryResultContact", "InlineQueryResultDocument", "InlineQueryResultVoice" ] diff --git a/pyrogram/types/inline_mode/inline_query_result_voice.py b/pyrogram/types/inline_mode/inline_query_result_voice.py new file mode 100644 index 0000000000..31b422f8d3 --- /dev/null +++ b/pyrogram/types/inline_mode/inline_query_result_voice.py @@ -0,0 +1,114 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# 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 List, Optional + +import pyrogram +from pyrogram import raw, types, utils, enums +from .inline_query_result import InlineQueryResult + + +class InlineQueryResultVoice(InlineQueryResult): + """Link to a voice recording in an .OGG container encoded with OPUS. + + By default, this voice recording will be sent by the user. + Alternatively, you can use *input_message_content* to send a message with the specified content instead of the + voice message. + + Parameters: + voice_url (``str``): + A valid URL for the voice recording. + + title (``str``): + Title for the result. + + id (``str``, *optional*): + Unique identifier for this result, 1-64 bytes. + Defaults to a randomly generated UUID4. + + voice_duration (``int``, *optional*): + Recording duration in seconds. + + caption (``str``, *optional*): + Caption of the audio to be sent, 0-1024 characters. + + parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + + caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): + List of special entities that appear in the caption, which can be specified instead of *parse_mode*. + + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*): + Inline keyboard attached to the message. + + input_message_content (:obj:`~pyrogram.types.InputMessageContent`, *optional*): + Content of the message to be sent instead of the audio. + """ + + def __init__( + self, + voice_url: str, + title: str, + id: str = None, + voice_duration: int = 0, + caption: str = "", + parse_mode: Optional["enums.ParseMode"] = None, + caption_entities: List["types.MessageEntity"] = None, + reply_markup: "types.InlineKeyboardMarkup" = None, + input_message_content: "types.InputMessageContent" = None + ): + super().__init__("voice", id, input_message_content, reply_markup) + + self.voice_url = voice_url + self.title = title + self.voice_duration = voice_duration + self.caption = caption + self.parse_mode = parse_mode + self.caption_entities = caption_entities + + async def write(self, client: "pyrogram.Client"): + audio = raw.types.InputWebDocument( + url=self.voice_url, + size=0, + mime_type="audio/mpeg", + attributes=[raw.types.DocumentAttributeAudio( + duration=self.voice_duration, + title=self.title, + )] + ) + + message, entities = (await utils.parse_text_entities( + client, self.caption, self.parse_mode, self.caption_entities + )).values() + + return raw.types.InputBotInlineResult( + id=self.id, + type=self.type, + title=self.title, + content=audio, + send_message=( + await self.input_message_content.write(client, self.reply_markup) + if self.input_message_content + else raw.types.InputBotInlineMessageMediaAuto( + reply_markup=await self.reply_markup.write(client) if self.reply_markup else None, + message=message, + entities=entities + ) + ) + ) From 13e26ca64a4dee11801027f85ef2a4a7f6066211 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0861/1185] Add InlineQueryResultLocation --- pyrogram/types/inline_mode/__init__.py | 7 +- .../inline_query_result_location.py | 122 ++++++++++++++++++ 2 files changed, 126 insertions(+), 3 deletions(-) create mode 100644 pyrogram/types/inline_mode/inline_query_result_location.py diff --git a/pyrogram/types/inline_mode/__init__.py b/pyrogram/types/inline_mode/__init__.py index daff01174f..0d84dcd31f 100644 --- a/pyrogram/types/inline_mode/__init__.py +++ b/pyrogram/types/inline_mode/__init__.py @@ -22,14 +22,15 @@ from .inline_query_result_animation import InlineQueryResultAnimation from .inline_query_result_article import InlineQueryResultArticle from .inline_query_result_audio import InlineQueryResultAudio -from .inline_query_result_photo import InlineQueryResultPhoto -from .inline_query_result_video import InlineQueryResultVideo from .inline_query_result_contact import InlineQueryResultContact from .inline_query_result_document import InlineQueryResultDocument +from .inline_query_result_location import InlineQueryResultLocation +from .inline_query_result_photo import InlineQueryResultPhoto +from .inline_query_result_video import InlineQueryResultVideo from .inline_query_result_voice import InlineQueryResultVoice __all__ = [ "InlineQuery", "InlineQueryResult", "InlineQueryResultArticle", "InlineQueryResultPhoto", "InlineQueryResultAnimation", "InlineQueryResultAudio", "InlineQueryResultVideo", "ChosenInlineResult", - "InlineQueryResultContact", "InlineQueryResultDocument", "InlineQueryResultVoice" + "InlineQueryResultContact", "InlineQueryResultDocument", "InlineQueryResultVoice", "InlineQueryResultLocation" ] diff --git a/pyrogram/types/inline_mode/inline_query_result_location.py b/pyrogram/types/inline_mode/inline_query_result_location.py new file mode 100644 index 0000000000..236f39a624 --- /dev/null +++ b/pyrogram/types/inline_mode/inline_query_result_location.py @@ -0,0 +1,122 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# 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 import raw, types +from .inline_query_result import InlineQueryResult + + +class InlineQueryResultLocation(InlineQueryResult): + """A location on a map. + + By default, the location will be sent by the user. Alternatively, you can use *input_message_content* to send a + message with the specified content instead of the location. + + Parameters: + title (``str``): + Title for the result. + + latitude (``float``): + Location latitude in degrees. + + longitude (``float``): + Location longitude in degrees. + + id (``str``, *optional*): + Unique identifier for this result, 1-64 bytes. + Defaults to a randomly generated UUID4. + + horizontal_accuracy (``float``, *optional*) + The radius of uncertainty for the location, measured in meters; 0-1500. + + live_period (``int``, *optional*): + Period in seconds for which the location can be updated, should be between 60 and 86400. + + heading (``int``, *optional*): + For live locations, a direction in which the user is moving, in degrees. + Must be between 1 and 360 if specified. + + proximity_alert_radius (``int``, *optional*): + For live locations, a maximum distance for proximity alerts about approaching another chat member, + in meters. Must be between 1 and 100000 if specified. + + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*): + Inline keyboard attached to the message. + + input_message_content (:obj:`~pyrogram.types.InputMessageContent`): + Content of the message to be sent instead of the file. + + thumb_url (``str``, *optional*): + Url of the thumbnail for the result. + + thumb_width (``int``, *optional*): + Thumbnail width. + + thumb_height (``int``, *optional*): + Thumbnail height. + """ + + def __init__( + self, + title: str, + latitude: float, + longitude: float, + horizontal_accuracy: float = None, + live_period: int = None, + heading: int = None, + proximity_alert_radius: int = None, + id: str = None, + reply_markup: "types.InlineKeyboardMarkup" = None, + input_message_content: "types.InputMessageContent" = None, + thumb_url: str = None, + thumb_width: int = 0, + thumb_height: int = 0 + ): + super().__init__("location", id, input_message_content, reply_markup) + + self.title = title + self.latitude = latitude + self.longitude = longitude + self.horizontal_accuracy = horizontal_accuracy + self.live_period = live_period + self.heading = heading + self.proximity_alert_radius = proximity_alert_radius + self.thumb_url = thumb_url + self.thumb_width = thumb_width + self.thumb_height = thumb_height + + async def write(self, client: "pyrogram.Client"): + return raw.types.InputBotInlineResult( + id=self.id, + type=self.type, + title=self.title, + send_message=( + await self.input_message_content.write(client, self.reply_markup) + if self.input_message_content + else raw.types.InputBotInlineMessageMediaGeo( + geo_point=raw.types.InputGeoPoint( + lat=self.latitude, + long=self.longitude + ), + heading=self.heading, + period=self.live_period, + proximity_notification_radius=self.proximity_alert_radius, + reply_markup=await self.reply_markup.write(client) if self.reply_markup else None + ) + ) + ) From d209074e44a325795f5d78babc8728c0fd912ace Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0862/1185] Add InlineQueryResultVenue --- pyrogram/types/inline_mode/__init__.py | 4 +- .../inline_mode/inline_query_result_venue.py | 131 ++++++++++++++++++ 2 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 pyrogram/types/inline_mode/inline_query_result_venue.py diff --git a/pyrogram/types/inline_mode/__init__.py b/pyrogram/types/inline_mode/__init__.py index 0d84dcd31f..8646b8e47e 100644 --- a/pyrogram/types/inline_mode/__init__.py +++ b/pyrogram/types/inline_mode/__init__.py @@ -26,11 +26,13 @@ from .inline_query_result_document import InlineQueryResultDocument from .inline_query_result_location import InlineQueryResultLocation from .inline_query_result_photo import InlineQueryResultPhoto +from .inline_query_result_venue import InlineQueryResultVenue from .inline_query_result_video import InlineQueryResultVideo from .inline_query_result_voice import InlineQueryResultVoice __all__ = [ "InlineQuery", "InlineQueryResult", "InlineQueryResultArticle", "InlineQueryResultPhoto", "InlineQueryResultAnimation", "InlineQueryResultAudio", "InlineQueryResultVideo", "ChosenInlineResult", - "InlineQueryResultContact", "InlineQueryResultDocument", "InlineQueryResultVoice", "InlineQueryResultLocation" + "InlineQueryResultContact", "InlineQueryResultDocument", "InlineQueryResultVoice", "InlineQueryResultLocation", + "InlineQueryResultVenue" ] diff --git a/pyrogram/types/inline_mode/inline_query_result_venue.py b/pyrogram/types/inline_mode/inline_query_result_venue.py new file mode 100644 index 0000000000..b3b513a55d --- /dev/null +++ b/pyrogram/types/inline_mode/inline_query_result_venue.py @@ -0,0 +1,131 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# 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 import raw, types +from .inline_query_result import InlineQueryResult + + +class InlineQueryResultVenue(InlineQueryResult): + """A venue. + + By default, the venue will be sent by the user. Alternatively, you can use *input_message_content* to send a message + with the specified content instead of the venue. + + Parameters: + title (``str``): + Title for the result. + + address (``str``): + Address of the venue. + + latitude (``float``): + Location latitude in degrees. + + longitude (``float``): + Location longitude in degrees. + + id (``str``, *optional*): + Unique identifier for this result, 1-64 bytes. + Defaults to a randomly generated UUID4. + + foursquare_id (``str``, *optional*): + Foursquare identifier of the venue if known. + + foursquare_type (``str``, *optional*): + Foursquare type of the venue, if known. + + google_place_id (``str``, *optional*): + Google Places identifier of the venue. + + google_place_type (``str``, *optional*): + Google Places type of the venue. + + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*): + Inline keyboard attached to the message. + + input_message_content (:obj:`~pyrogram.types.InputMessageContent`): + Content of the message to be sent instead of the file. + + thumb_url (``str``, *optional*): + Url of the thumbnail for the result. + + thumb_width (``int``, *optional*): + Thumbnail width. + + thumb_height (``int``, *optional*): + Thumbnail height. + """ + + def __init__( + self, + title: str, + address: str, + latitude: float, + longitude: float, + id: str = None, + foursquare_id: str = None, + foursquare_type: str = None, + google_place_id: str = None, + google_place_type: str = None, + reply_markup: "types.InlineKeyboardMarkup" = None, + input_message_content: "types.InputMessageContent" = None, + thumb_url: str = None, + thumb_width: int = 0, + thumb_height: int = 0 + ): + super().__init__("venue", id, input_message_content, reply_markup) + + self.title = title + self.address = address + self.latitude = latitude + self.longitude = longitude + self.foursquare_id = foursquare_id + self.foursquare_type = foursquare_type + self.google_place_id = google_place_id + self.google_place_type = google_place_type + self.thumb_url = thumb_url + self.thumb_width = thumb_width + self.thumb_height = thumb_height + + async def write(self, client: "pyrogram.Client"): + return raw.types.InputBotInlineResult( + id=self.id, + type=self.type, + title=self.title, + send_message=( + await self.input_message_content.write(client, self.reply_markup) + if self.input_message_content + else raw.types.InputBotInlineMessageMediaVenue( + geo_point=raw.types.InputGeoPoint( + lat=self.latitude, + long=self.longitude + ), + title=self.title, + address=self.address, + provider=( + "foursquare" if self.foursquare_id or self.foursquare_type + else "google" if self.google_place_id or self.google_place_type + else "" + ), + venue_id=self.foursquare_id or self.google_place_id or "", + venue_type=self.foursquare_type or self.google_place_type or "", + reply_markup=await self.reply_markup.write(client) if self.reply_markup else None + ) + ) + ) From c0dc882f2c38ea08fa4c1eb6382e37ad7c27a9ee Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0863/1185] Add InlineQueryResultCachedPhoto --- pyrogram/types/inline_mode/__init__.py | 3 +- .../inline_query_result_cached_photo.py | 111 ++++++++++++++++++ 2 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 pyrogram/types/inline_mode/inline_query_result_cached_photo.py diff --git a/pyrogram/types/inline_mode/__init__.py b/pyrogram/types/inline_mode/__init__.py index 8646b8e47e..c6f5192f09 100644 --- a/pyrogram/types/inline_mode/__init__.py +++ b/pyrogram/types/inline_mode/__init__.py @@ -22,6 +22,7 @@ from .inline_query_result_animation import InlineQueryResultAnimation from .inline_query_result_article import InlineQueryResultArticle from .inline_query_result_audio import InlineQueryResultAudio +from .inline_query_result_cached_photo import InlineQueryResultCachedPhoto from .inline_query_result_contact import InlineQueryResultContact from .inline_query_result_document import InlineQueryResultDocument from .inline_query_result_location import InlineQueryResultLocation @@ -34,5 +35,5 @@ "InlineQuery", "InlineQueryResult", "InlineQueryResultArticle", "InlineQueryResultPhoto", "InlineQueryResultAnimation", "InlineQueryResultAudio", "InlineQueryResultVideo", "ChosenInlineResult", "InlineQueryResultContact", "InlineQueryResultDocument", "InlineQueryResultVoice", "InlineQueryResultLocation", - "InlineQueryResultVenue" + "InlineQueryResultVenue", "InlineQueryResultCachedPhoto" ] diff --git a/pyrogram/types/inline_mode/inline_query_result_cached_photo.py b/pyrogram/types/inline_mode/inline_query_result_cached_photo.py new file mode 100644 index 0000000000..843acab7c7 --- /dev/null +++ b/pyrogram/types/inline_mode/inline_query_result_cached_photo.py @@ -0,0 +1,111 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# 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 Optional, List + +import pyrogram +from pyrogram import raw, types, utils, enums +from .inline_query_result import InlineQueryResult +from ...file_id import FileId + + +class InlineQueryResultCachedPhoto(InlineQueryResult): + """A link to a photo stored on the Telegram servers. + + By default, this photo will be sent by the user with an optional caption. Alternatively, you can use + *input_message_content* to send a message with the specified content instead of the photo. + + Parameters: + photo_file_id (``str``): + A valid file identifier of the photo. + + id (``str``, *optional*): + Unique identifier for this result, 1-64 bytes. + Defaults to a randomly generated UUID4. + + title (``str``, *optional*): + Title for the result. + + description (``str``, *optional*): + Short description of the result. + + caption (``str``, *optional*): + Caption of the photo to be sent, 0-1024 characters. + + parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + + caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): + List of special entities that appear in the caption, which can be specified instead of *parse_mode*. + + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*): + An InlineKeyboardMarkup object. + + input_message_content (:obj:`~pyrogram.types.InputMessageContent`): + Content of the message to be sent instead of the photo. + """ + + def __init__( + self, + photo_file_id: str, + id: str = None, + title: str = None, + description: str = None, + caption: str = "", + parse_mode: Optional["enums.ParseMode"] = None, + caption_entities: List["types.MessageEntity"] = None, + reply_markup: "types.InlineKeyboardMarkup" = None, + input_message_content: "types.InputMessageContent" = None + ): + super().__init__("photo", id, input_message_content, reply_markup) + + self.photo_file_id = photo_file_id + self.title = title + self.description = description + self.caption = caption + self.parse_mode = parse_mode + self.caption_entities = caption_entities + self.reply_markup = reply_markup + self.input_message_content = input_message_content + + async def write(self, client: "pyrogram.Client"): + message, entities = (await utils.parse_text_entities( + client, self.caption, self.parse_mode, self.caption_entities + )).values() + + file_id = FileId.decode(self.photo_file_id) + + return raw.types.InputBotInlineResultPhoto( + id=self.id, + type=self.type, + photo=raw.types.InputPhoto( + id=file_id.media_id, + access_hash=file_id.access_hash, + file_reference=file_id.file_reference, + ), + send_message=( + await self.input_message_content.write(client, self.reply_markup) + if self.input_message_content + else raw.types.InputBotInlineMessageMediaAuto( + reply_markup=await self.reply_markup.write(client) if self.reply_markup else None, + message=message, + entities=entities + ) + ) + ) From d87810ceb03403351cc6b30a5226c11a3fce0f6b Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0864/1185] Fix docstring indentation --- pyrogram/types/inline_mode/inline_query_result_cached_photo.py | 2 +- pyrogram/types/inline_mode/inline_query_result_photo.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/types/inline_mode/inline_query_result_cached_photo.py b/pyrogram/types/inline_mode/inline_query_result_cached_photo.py index 843acab7c7..2e01d344ec 100644 --- a/pyrogram/types/inline_mode/inline_query_result_cached_photo.py +++ b/pyrogram/types/inline_mode/inline_query_result_cached_photo.py @@ -52,7 +52,7 @@ class InlineQueryResultCachedPhoto(InlineQueryResult): You can combine both syntaxes together. caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): - List of special entities that appear in the caption, which can be specified instead of *parse_mode*. + List of special entities that appear in the caption, which can be specified instead of *parse_mode*. reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*): An InlineKeyboardMarkup object. diff --git a/pyrogram/types/inline_mode/inline_query_result_photo.py b/pyrogram/types/inline_mode/inline_query_result_photo.py index 9e03548954..d75ccac2a5 100644 --- a/pyrogram/types/inline_mode/inline_query_result_photo.py +++ b/pyrogram/types/inline_mode/inline_query_result_photo.py @@ -63,7 +63,7 @@ class InlineQueryResultPhoto(InlineQueryResult): You can combine both syntaxes together. caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): - List of special entities that appear in the caption, which can be specified instead of *parse_mode*. + List of special entities that appear in the caption, which can be specified instead of *parse_mode*. reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*): An InlineKeyboardMarkup object. From 0b0af2da5b30f4044d102e21114eb26ab28edb54 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0865/1185] Add InlineQueryResultCachedAnimation --- pyrogram/types/inline_mode/__init__.py | 3 +- .../inline_query_result_cached_animation.py | 108 ++++++++++++++++++ 2 files changed, 110 insertions(+), 1 deletion(-) create mode 100644 pyrogram/types/inline_mode/inline_query_result_cached_animation.py diff --git a/pyrogram/types/inline_mode/__init__.py b/pyrogram/types/inline_mode/__init__.py index c6f5192f09..0bf60051ea 100644 --- a/pyrogram/types/inline_mode/__init__.py +++ b/pyrogram/types/inline_mode/__init__.py @@ -22,6 +22,7 @@ from .inline_query_result_animation import InlineQueryResultAnimation from .inline_query_result_article import InlineQueryResultArticle from .inline_query_result_audio import InlineQueryResultAudio +from .inline_query_result_cached_animation import InlineQueryResultCachedAnimation from .inline_query_result_cached_photo import InlineQueryResultCachedPhoto from .inline_query_result_contact import InlineQueryResultContact from .inline_query_result_document import InlineQueryResultDocument @@ -35,5 +36,5 @@ "InlineQuery", "InlineQueryResult", "InlineQueryResultArticle", "InlineQueryResultPhoto", "InlineQueryResultAnimation", "InlineQueryResultAudio", "InlineQueryResultVideo", "ChosenInlineResult", "InlineQueryResultContact", "InlineQueryResultDocument", "InlineQueryResultVoice", "InlineQueryResultLocation", - "InlineQueryResultVenue", "InlineQueryResultCachedPhoto" + "InlineQueryResultVenue", "InlineQueryResultCachedPhoto", "InlineQueryResultCachedAnimation" ] diff --git a/pyrogram/types/inline_mode/inline_query_result_cached_animation.py b/pyrogram/types/inline_mode/inline_query_result_cached_animation.py new file mode 100644 index 0000000000..63e58ca027 --- /dev/null +++ b/pyrogram/types/inline_mode/inline_query_result_cached_animation.py @@ -0,0 +1,108 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# 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 Optional, List + +import pyrogram +from pyrogram import raw, types, utils, enums +from .inline_query_result import InlineQueryResult +from ...file_id import FileId + + +class InlineQueryResultCachedAnimation(InlineQueryResult): + """A link to an animation file stored on the Telegram servers. + + By default, this animation file will be sent by the user with an optional caption. + Alternatively, you can use *input_message_content* to send a message with specified content instead of the + animation. + + Parameters: + animation_file_id (``str``): + A valid file identifier for the animation file. + + id (``str``, *optional*): + Unique identifier for this result, 1-64 bytes. + Defaults to a randomly generated UUID4. + + title (``str``, *optional*): + Title for the result. + + caption (``str``, *optional*): + Caption of the photo to be sent, 0-1024 characters. + + parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + + caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): + List of special entities that appear in the caption, which can be specified instead of *parse_mode*. + + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*): + An InlineKeyboardMarkup object. + + input_message_content (:obj:`~pyrogram.types.InputMessageContent`): + Content of the message to be sent instead of the photo. + """ + + def __init__( + self, + animation_file_id: str, + id: str = None, + title: str = None, + caption: str = "", + parse_mode: Optional["enums.ParseMode"] = None, + caption_entities: List["types.MessageEntity"] = None, + reply_markup: "types.InlineKeyboardMarkup" = None, + input_message_content: "types.InputMessageContent" = None + ): + super().__init__("gif", id, input_message_content, reply_markup) + + self.animation_file_id = animation_file_id + self.title = title + self.caption = caption + self.parse_mode = parse_mode + self.caption_entities = caption_entities + self.reply_markup = reply_markup + self.input_message_content = input_message_content + + async def write(self, client: "pyrogram.Client"): + message, entities = (await utils.parse_text_entities( + client, self.caption, self.parse_mode, self.caption_entities + )).values() + + file_id = FileId.decode(self.animation_file_id) + + return raw.types.InputBotInlineResultDocument( + id=self.id, + type=self.type, + title=self.title, + document=raw.types.InputDocument( + id=file_id.media_id, + access_hash=file_id.access_hash, + file_reference=file_id.file_reference, + ), + send_message=( + await self.input_message_content.write(client, self.reply_markup) + if self.input_message_content + else raw.types.InputBotInlineMessageMediaAuto( + reply_markup=await self.reply_markup.write(client) if self.reply_markup else None, + message=message, + entities=entities + ) + ) + ) From a9cadf302207575fc41e1d4c5254b30a83c7e20b Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0866/1185] Add InlineQueryResultCachedSticker --- pyrogram/types/inline_mode/__init__.py | 4 +- .../inline_query_result_cached_sticker.py | 78 +++++++++++++++++++ 2 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 pyrogram/types/inline_mode/inline_query_result_cached_sticker.py diff --git a/pyrogram/types/inline_mode/__init__.py b/pyrogram/types/inline_mode/__init__.py index 0bf60051ea..7ed21c169b 100644 --- a/pyrogram/types/inline_mode/__init__.py +++ b/pyrogram/types/inline_mode/__init__.py @@ -24,6 +24,7 @@ from .inline_query_result_audio import InlineQueryResultAudio from .inline_query_result_cached_animation import InlineQueryResultCachedAnimation from .inline_query_result_cached_photo import InlineQueryResultCachedPhoto +from .inline_query_result_cached_sticker import InlineQueryResultCachedSticker from .inline_query_result_contact import InlineQueryResultContact from .inline_query_result_document import InlineQueryResultDocument from .inline_query_result_location import InlineQueryResultLocation @@ -36,5 +37,6 @@ "InlineQuery", "InlineQueryResult", "InlineQueryResultArticle", "InlineQueryResultPhoto", "InlineQueryResultAnimation", "InlineQueryResultAudio", "InlineQueryResultVideo", "ChosenInlineResult", "InlineQueryResultContact", "InlineQueryResultDocument", "InlineQueryResultVoice", "InlineQueryResultLocation", - "InlineQueryResultVenue", "InlineQueryResultCachedPhoto", "InlineQueryResultCachedAnimation" + "InlineQueryResultVenue", "InlineQueryResultCachedPhoto", "InlineQueryResultCachedAnimation", + "InlineQueryResultCachedSticker" ] diff --git a/pyrogram/types/inline_mode/inline_query_result_cached_sticker.py b/pyrogram/types/inline_mode/inline_query_result_cached_sticker.py new file mode 100644 index 0000000000..06d012fbe6 --- /dev/null +++ b/pyrogram/types/inline_mode/inline_query_result_cached_sticker.py @@ -0,0 +1,78 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# 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 import raw, types +from .inline_query_result import InlineQueryResult +from ...file_id import FileId + + +class InlineQueryResultCachedSticker(InlineQueryResult): + """A link to a sticker stored on the Telegram servers + + By default, this sticker will be sent by the user. Alternatively, you can use *input_message_content* to send a + message with the specified content instead of the sticker. + + Parameters: + sticker_file_id (``str``): + A valid file identifier of the sticker. + + id (``str``, *optional*): + Unique identifier for this result, 1-64 bytes. + Defaults to a randomly generated UUID4. + + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*): + An InlineKeyboardMarkup object. + + input_message_content (:obj:`~pyrogram.types.InputMessageContent`): + Content of the message to be sent instead of the photo. + """ + + def __init__( + self, + sticker_file_id: str, + id: str = None, + reply_markup: "types.InlineKeyboardMarkup" = None, + input_message_content: "types.InputMessageContent" = None + ): + super().__init__("sticker", id, input_message_content, reply_markup) + + self.sticker_file_id = sticker_file_id + self.reply_markup = reply_markup + self.input_message_content = input_message_content + + async def write(self, client: "pyrogram.Client"): + file_id = FileId.decode(self.sticker_file_id) + + return raw.types.InputBotInlineResultDocument( + id=self.id, + type=self.type, + document=raw.types.InputDocument( + id=file_id.media_id, + access_hash=file_id.access_hash, + file_reference=file_id.file_reference, + ), + send_message=( + await self.input_message_content.write(client, self.reply_markup) + if self.input_message_content + else raw.types.InputBotInlineMessageMediaAuto( + reply_markup=await self.reply_markup.write(client) if self.reply_markup else None, + message="", + ) + ) + ) From 65a213b2227613ecfdc78ca89a2fac9bc0e3b3e7 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0867/1185] Add InlineQueryResultCachedDocument --- pyrogram/types/inline_mode/__init__.py | 3 +- .../inline_query_result_cached_document.py | 112 ++++++++++++++++++ 2 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 pyrogram/types/inline_mode/inline_query_result_cached_document.py diff --git a/pyrogram/types/inline_mode/__init__.py b/pyrogram/types/inline_mode/__init__.py index 7ed21c169b..ac98fb66e3 100644 --- a/pyrogram/types/inline_mode/__init__.py +++ b/pyrogram/types/inline_mode/__init__.py @@ -23,6 +23,7 @@ from .inline_query_result_article import InlineQueryResultArticle from .inline_query_result_audio import InlineQueryResultAudio from .inline_query_result_cached_animation import InlineQueryResultCachedAnimation +from .inline_query_result_cached_document import InlineQueryResultCachedDocument from .inline_query_result_cached_photo import InlineQueryResultCachedPhoto from .inline_query_result_cached_sticker import InlineQueryResultCachedSticker from .inline_query_result_contact import InlineQueryResultContact @@ -38,5 +39,5 @@ "InlineQueryResultAnimation", "InlineQueryResultAudio", "InlineQueryResultVideo", "ChosenInlineResult", "InlineQueryResultContact", "InlineQueryResultDocument", "InlineQueryResultVoice", "InlineQueryResultLocation", "InlineQueryResultVenue", "InlineQueryResultCachedPhoto", "InlineQueryResultCachedAnimation", - "InlineQueryResultCachedSticker" + "InlineQueryResultCachedSticker", "InlineQueryResultCachedDocument" ] diff --git a/pyrogram/types/inline_mode/inline_query_result_cached_document.py b/pyrogram/types/inline_mode/inline_query_result_cached_document.py new file mode 100644 index 0000000000..5a50c18e4a --- /dev/null +++ b/pyrogram/types/inline_mode/inline_query_result_cached_document.py @@ -0,0 +1,112 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# 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 Optional, List + +import pyrogram +from pyrogram import raw, types, utils, enums +from .inline_query_result import InlineQueryResult +from ...file_id import FileId + + +class InlineQueryResultCachedDocument(InlineQueryResult): + """A link to a file stored on the Telegram servers. + + By default, this file will be sent by the user with an optional caption. Alternatively, you can use + *input_message_content* to send a message with the specified content instead of the file. + + Parameters: + document_file_id (``str``): + A valid file identifier for the file. + + title (``str``): + Title for the result. + + id (``str``, *optional*): + Unique identifier for this result, 1-64 bytes. + Defaults to a randomly generated UUID4. + + description (``str``, *optional*): + Short description of the result. + + caption (``str``, *optional*): + Caption of the photo to be sent, 0-1024 characters. + + parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + + caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): + List of special entities that appear in the caption, which can be specified instead of *parse_mode*. + + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*): + An InlineKeyboardMarkup object. + + input_message_content (:obj:`~pyrogram.types.InputMessageContent`): + Content of the message to be sent instead of the photo. + """ + + def __init__( + self, + document_file_id: str, + title: str, + id: str = None, + description: str = None, + caption: str = "", + parse_mode: Optional["enums.ParseMode"] = None, + caption_entities: List["types.MessageEntity"] = None, + reply_markup: "types.InlineKeyboardMarkup" = None, + input_message_content: "types.InputMessageContent" = None + ): + super().__init__("file", id, input_message_content, reply_markup) + + self.document_file_id = document_file_id + self.title = title + self.description = description + self.caption = caption + self.parse_mode = parse_mode + self.caption_entities = caption_entities + self.reply_markup = reply_markup + self.input_message_content = input_message_content + + async def write(self, client: "pyrogram.Client"): + message, entities = (await utils.parse_text_entities( + client, self.caption, self.parse_mode, self.caption_entities + )).values() + + file_id = FileId.decode(self.document_file_id) + + return raw.types.InputBotInlineResultDocument( + id=self.id, + type=self.type, + title=self.title, + document=raw.types.InputDocument( + id=file_id.media_id, + access_hash=file_id.access_hash, + file_reference=file_id.file_reference, + ), + send_message=( + await self.input_message_content.write(client, self.reply_markup) + if self.input_message_content + else raw.types.InputBotInlineMessageMediaAuto( + reply_markup=await self.reply_markup.write(client) if self.reply_markup else None, + message=message, + entities=entities + ) + ) + ) From c4948eac27e6e58ba8a7ba9befb79fa69ef6c7eb Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0868/1185] Add InlineQueryResultCachedVideo --- pyrogram/types/inline_mode/__init__.py | 3 +- .../inline_query_result_cached_video.py | 114 ++++++++++++++++++ 2 files changed, 116 insertions(+), 1 deletion(-) create mode 100644 pyrogram/types/inline_mode/inline_query_result_cached_video.py diff --git a/pyrogram/types/inline_mode/__init__.py b/pyrogram/types/inline_mode/__init__.py index ac98fb66e3..b8db4d68f8 100644 --- a/pyrogram/types/inline_mode/__init__.py +++ b/pyrogram/types/inline_mode/__init__.py @@ -26,6 +26,7 @@ from .inline_query_result_cached_document import InlineQueryResultCachedDocument from .inline_query_result_cached_photo import InlineQueryResultCachedPhoto from .inline_query_result_cached_sticker import InlineQueryResultCachedSticker +from .inline_query_result_cached_video import InlineQueryResultCachedVideo from .inline_query_result_contact import InlineQueryResultContact from .inline_query_result_document import InlineQueryResultDocument from .inline_query_result_location import InlineQueryResultLocation @@ -39,5 +40,5 @@ "InlineQueryResultAnimation", "InlineQueryResultAudio", "InlineQueryResultVideo", "ChosenInlineResult", "InlineQueryResultContact", "InlineQueryResultDocument", "InlineQueryResultVoice", "InlineQueryResultLocation", "InlineQueryResultVenue", "InlineQueryResultCachedPhoto", "InlineQueryResultCachedAnimation", - "InlineQueryResultCachedSticker", "InlineQueryResultCachedDocument" + "InlineQueryResultCachedSticker", "InlineQueryResultCachedDocument", "InlineQueryResultCachedVideo" ] diff --git a/pyrogram/types/inline_mode/inline_query_result_cached_video.py b/pyrogram/types/inline_mode/inline_query_result_cached_video.py new file mode 100644 index 0000000000..00ea32ecdb --- /dev/null +++ b/pyrogram/types/inline_mode/inline_query_result_cached_video.py @@ -0,0 +1,114 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# 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 Optional, List + +import pyrogram +from pyrogram import raw, types, utils, enums +from .inline_query_result import InlineQueryResult +from ...file_id import FileId + + +class InlineQueryResultCachedVideo(InlineQueryResult): + """A link to a video file stored on the Telegram servers. + + By default, this video file will be sent by the user with an optional caption. + Alternatively, you can use *input_message_content* to send a message with the specified content instead of the + video. + + Parameters: + video_file_id (``str``): + A valid file identifier for the video file. + + title (``str``): + Title for the result. + + id (``str``, *optional*): + Unique identifier for this result, 1-64 bytes. + Defaults to a randomly generated UUID4. + + description (``str``, *optional*): + Short description of the result. + + caption (``str``, *optional*): + Caption of the photo to be sent, 0-1024 characters. + + parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + + caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): + List of special entities that appear in the caption, which can be specified instead of *parse_mode*. + + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*): + An InlineKeyboardMarkup object. + + input_message_content (:obj:`~pyrogram.types.InputMessageContent`): + Content of the message to be sent instead of the photo. + """ + + def __init__( + self, + video_file_id: str, + title: str, + id: str = None, + description: str = None, + caption: str = "", + parse_mode: Optional["enums.ParseMode"] = None, + caption_entities: List["types.MessageEntity"] = None, + reply_markup: "types.InlineKeyboardMarkup" = None, + input_message_content: "types.InputMessageContent" = None + ): + super().__init__("video", id, input_message_content, reply_markup) + + self.video_file_id = video_file_id + self.title = title + self.description = description + self.caption = caption + self.parse_mode = parse_mode + self.caption_entities = caption_entities + self.reply_markup = reply_markup + self.input_message_content = input_message_content + + async def write(self, client: "pyrogram.Client"): + message, entities = (await utils.parse_text_entities( + client, self.caption, self.parse_mode, self.caption_entities + )).values() + + file_id = FileId.decode(self.video_file_id) + + return raw.types.InputBotInlineResultDocument( + id=self.id, + type=self.type, + title=self.title, + description=self.description, + document=raw.types.InputDocument( + id=file_id.media_id, + access_hash=file_id.access_hash, + file_reference=file_id.file_reference, + ), + send_message=( + await self.input_message_content.write(client, self.reply_markup) + if self.input_message_content + else raw.types.InputBotInlineMessageMediaAuto( + reply_markup=await self.reply_markup.write(client) if self.reply_markup else None, + message=message, + entities=entities + ) + ) + ) From b2643e91975ad940eabdd8ba4b02e66c16ddd9c4 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0869/1185] Use "description" in InlineQueryResultCachedDocument --- .../types/inline_mode/inline_query_result_cached_document.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyrogram/types/inline_mode/inline_query_result_cached_document.py b/pyrogram/types/inline_mode/inline_query_result_cached_document.py index 5a50c18e4a..2ab190e7ff 100644 --- a/pyrogram/types/inline_mode/inline_query_result_cached_document.py +++ b/pyrogram/types/inline_mode/inline_query_result_cached_document.py @@ -95,6 +95,7 @@ async def write(self, client: "pyrogram.Client"): id=self.id, type=self.type, title=self.title, + description=self.description, document=raw.types.InputDocument( id=file_id.media_id, access_hash=file_id.access_hash, From 8e8972d5acfe39c0bc8222de0abc74cf7464cdad Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0870/1185] Add InlineQueryResultCachedVoice --- pyrogram/types/inline_mode/__init__.py | 4 +- .../inline_query_result_cached_voice.py | 108 ++++++++++++++++++ 2 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 pyrogram/types/inline_mode/inline_query_result_cached_voice.py diff --git a/pyrogram/types/inline_mode/__init__.py b/pyrogram/types/inline_mode/__init__.py index b8db4d68f8..20e6f10c2b 100644 --- a/pyrogram/types/inline_mode/__init__.py +++ b/pyrogram/types/inline_mode/__init__.py @@ -27,6 +27,7 @@ from .inline_query_result_cached_photo import InlineQueryResultCachedPhoto from .inline_query_result_cached_sticker import InlineQueryResultCachedSticker from .inline_query_result_cached_video import InlineQueryResultCachedVideo +from .inline_query_result_cached_voice import InlineQueryResultCachedVoice from .inline_query_result_contact import InlineQueryResultContact from .inline_query_result_document import InlineQueryResultDocument from .inline_query_result_location import InlineQueryResultLocation @@ -40,5 +41,6 @@ "InlineQueryResultAnimation", "InlineQueryResultAudio", "InlineQueryResultVideo", "ChosenInlineResult", "InlineQueryResultContact", "InlineQueryResultDocument", "InlineQueryResultVoice", "InlineQueryResultLocation", "InlineQueryResultVenue", "InlineQueryResultCachedPhoto", "InlineQueryResultCachedAnimation", - "InlineQueryResultCachedSticker", "InlineQueryResultCachedDocument", "InlineQueryResultCachedVideo" + "InlineQueryResultCachedSticker", "InlineQueryResultCachedDocument", "InlineQueryResultCachedVideo", + "InlineQueryResultCachedVoice" ] diff --git a/pyrogram/types/inline_mode/inline_query_result_cached_voice.py b/pyrogram/types/inline_mode/inline_query_result_cached_voice.py new file mode 100644 index 0000000000..cc2bd76855 --- /dev/null +++ b/pyrogram/types/inline_mode/inline_query_result_cached_voice.py @@ -0,0 +1,108 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# 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 Optional, List + +import pyrogram +from pyrogram import raw, types, utils, enums +from .inline_query_result import InlineQueryResult +from ...file_id import FileId + + +class InlineQueryResultCachedVoice(InlineQueryResult): + """A link to a voice message stored on the Telegram servers. + + By default, this voice message will be sent by the user. + Alternatively, you can use *input_message_content* to send a message with the specified content instead of the voice + message. + + Parameters: + voice_file_id (``str``): + A valid file identifier for the voice message. + + id (``str``, *optional*): + Unique identifier for this result, 1-64 bytes. + Defaults to a randomly generated UUID4. + + title (``str``, *optional*): + Title for the result. + + caption (``str``, *optional*): + Caption of the photo to be sent, 0-1024 characters. + + parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + + caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): + List of special entities that appear in the caption, which can be specified instead of *parse_mode*. + + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*): + An InlineKeyboardMarkup object. + + input_message_content (:obj:`~pyrogram.types.InputMessageContent`): + Content of the message to be sent instead of the photo. + """ + + def __init__( + self, + voice_file_id: str, + id: str = None, + title: str = None, + caption: str = "", + parse_mode: Optional["enums.ParseMode"] = None, + caption_entities: List["types.MessageEntity"] = None, + reply_markup: "types.InlineKeyboardMarkup" = None, + input_message_content: "types.InputMessageContent" = None + ): + super().__init__("voice", id, input_message_content, reply_markup) + + self.voice_file_id = voice_file_id + self.title = title + self.caption = caption + self.parse_mode = parse_mode + self.caption_entities = caption_entities + self.reply_markup = reply_markup + self.input_message_content = input_message_content + + async def write(self, client: "pyrogram.Client"): + message, entities = (await utils.parse_text_entities( + client, self.caption, self.parse_mode, self.caption_entities + )).values() + + file_id = FileId.decode(self.voice_file_id) + + return raw.types.InputBotInlineResultDocument( + id=self.id, + type=self.type, + title=self.title, + document=raw.types.InputDocument( + id=file_id.media_id, + access_hash=file_id.access_hash, + file_reference=file_id.file_reference, + ), + send_message=( + await self.input_message_content.write(client, self.reply_markup) + if self.input_message_content + else raw.types.InputBotInlineMessageMediaAuto( + reply_markup=await self.reply_markup.write(client) if self.reply_markup else None, + message=message, + entities=entities + ) + ) + ) From 703ec1676c4732465f02d53c873b74b8ebea38b5 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0871/1185] Add InlineQueryResultCachedAudio --- pyrogram/types/inline_mode/__init__.py | 3 +- .../inline_query_result_cached_audio.py | 101 ++++++++++++++++++ 2 files changed, 103 insertions(+), 1 deletion(-) create mode 100644 pyrogram/types/inline_mode/inline_query_result_cached_audio.py diff --git a/pyrogram/types/inline_mode/__init__.py b/pyrogram/types/inline_mode/__init__.py index 20e6f10c2b..f7323abf95 100644 --- a/pyrogram/types/inline_mode/__init__.py +++ b/pyrogram/types/inline_mode/__init__.py @@ -35,6 +35,7 @@ from .inline_query_result_venue import InlineQueryResultVenue from .inline_query_result_video import InlineQueryResultVideo from .inline_query_result_voice import InlineQueryResultVoice +from .inline_query_result_cached_audio import InlineQueryResultCachedAudio __all__ = [ "InlineQuery", "InlineQueryResult", "InlineQueryResultArticle", "InlineQueryResultPhoto", @@ -42,5 +43,5 @@ "InlineQueryResultContact", "InlineQueryResultDocument", "InlineQueryResultVoice", "InlineQueryResultLocation", "InlineQueryResultVenue", "InlineQueryResultCachedPhoto", "InlineQueryResultCachedAnimation", "InlineQueryResultCachedSticker", "InlineQueryResultCachedDocument", "InlineQueryResultCachedVideo", - "InlineQueryResultCachedVoice" + "InlineQueryResultCachedVoice", "InlineQueryResultCachedAudio" ] diff --git a/pyrogram/types/inline_mode/inline_query_result_cached_audio.py b/pyrogram/types/inline_mode/inline_query_result_cached_audio.py new file mode 100644 index 0000000000..9535f63343 --- /dev/null +++ b/pyrogram/types/inline_mode/inline_query_result_cached_audio.py @@ -0,0 +1,101 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# 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 Optional, List + +import pyrogram +from pyrogram import raw, types, utils, enums +from .inline_query_result import InlineQueryResult +from ...file_id import FileId + + +class InlineQueryResultCachedAudio(InlineQueryResult): + """A link to an MP3 audio file stored on the Telegram servers + + By default, this audio file will be sent by the user. Alternatively, you can use *input_message_content* to send a + message with the specified content instead of the audio. + + Parameters: + audio_file_id (``str``): + A valid file identifier for the audio file. + + id (``str``, *optional*): + Unique identifier for this result, 1-64 bytes. + Defaults to a randomly generated UUID4. + + caption (``str``, *optional*): + Caption of the photo to be sent, 0-1024 characters. + + parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + + caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): + List of special entities that appear in the caption, which can be specified instead of *parse_mode*. + + reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup`, *optional*): + An InlineKeyboardMarkup object. + + input_message_content (:obj:`~pyrogram.types.InputMessageContent`): + Content of the message to be sent instead of the photo. + """ + + def __init__( + self, + audio_file_id: str, + id: str = None, + caption: str = "", + parse_mode: Optional["enums.ParseMode"] = None, + caption_entities: List["types.MessageEntity"] = None, + reply_markup: "types.InlineKeyboardMarkup" = None, + input_message_content: "types.InputMessageContent" = None + ): + super().__init__("audio", id, input_message_content, reply_markup) + + self.audio_file_id = audio_file_id + self.caption = caption + self.parse_mode = parse_mode + self.caption_entities = caption_entities + self.reply_markup = reply_markup + self.input_message_content = input_message_content + + async def write(self, client: "pyrogram.Client"): + message, entities = (await utils.parse_text_entities( + client, self.caption, self.parse_mode, self.caption_entities + )).values() + + file_id = FileId.decode(self.audio_file_id) + + return raw.types.InputBotInlineResultDocument( + id=self.id, + type=self.type, + document=raw.types.InputDocument( + id=file_id.media_id, + access_hash=file_id.access_hash, + file_reference=file_id.file_reference, + ), + send_message=( + await self.input_message_content.write(client, self.reply_markup) + if self.input_message_content + else raw.types.InputBotInlineMessageMediaAuto( + reply_markup=await self.reply_markup.write(client) if self.reply_markup else None, + message=message, + entities=entities + ) + ) + ) From c2333c05758a6338c255597478030ddf50bd1795 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0872/1185] Add inline query results to the documentation --- compiler/docs/compiler.py | 16 ++++++++-- .../types/inline_mode/inline_query_result.py | 30 ++++++------------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index a03d2ee316..6c2276b778 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -452,11 +452,23 @@ def get_title_list(s: str) -> list: Inline Mode InlineQuery InlineQueryResult + InlineQueryResultCachedAudio + InlineQueryResultCachedDocument + InlineQueryResultCachedAnimation + InlineQueryResultCachedPhoto + InlineQueryResultCachedSticker + InlineQueryResultCachedVideo + InlineQueryResultCachedVoice InlineQueryResultArticle - InlineQueryResultPhoto - InlineQueryResultAnimation InlineQueryResultAudio + InlineQueryResultContact + InlineQueryResultDocument + InlineQueryResultAnimation + InlineQueryResultLocation + InlineQueryResultPhoto + InlineQueryResultVenue InlineQueryResultVideo + InlineQueryResultVoice ChosenInlineResult """, input_message_content=""" diff --git a/pyrogram/types/inline_mode/inline_query_result.py b/pyrogram/types/inline_mode/inline_query_result.py index 4ea2f7be64..8548e023c7 100644 --- a/pyrogram/types/inline_mode/inline_query_result.py +++ b/pyrogram/types/inline_mode/inline_query_result.py @@ -22,39 +22,27 @@ from pyrogram import types from ..object import Object -"""- :obj:`~pyrogram.types.InlineQueryResultCachedAudio` + +class InlineQueryResult(Object): + """One result of an inline query. + + - :obj:`~pyrogram.types.InlineQueryResultCachedAudio` - :obj:`~pyrogram.types.InlineQueryResultCachedDocument` - - :obj:`~pyrogram.types.InlineQueryResultCachedGif` - - :obj:`~pyrogram.types.InlineQueryResultCachedMpeg4Gif` + - :obj:`~pyrogram.types.InlineQueryResultCachedAnimation` - :obj:`~pyrogram.types.InlineQueryResultCachedPhoto` - :obj:`~pyrogram.types.InlineQueryResultCachedSticker` - :obj:`~pyrogram.types.InlineQueryResultCachedVideo` - :obj:`~pyrogram.types.InlineQueryResultCachedVoice` + - :obj:`~pyrogram.types.InlineQueryResultArticle` - :obj:`~pyrogram.types.InlineQueryResultAudio` - :obj:`~pyrogram.types.InlineQueryResultContact` - - :obj:`~pyrogram.types.InlineQueryResultGame` - :obj:`~pyrogram.types.InlineQueryResultDocument` - - :obj:`~pyrogram.types.InlineQueryResultGif` + - :obj:`~pyrogram.types.InlineQueryResultAnimation` - :obj:`~pyrogram.types.InlineQueryResultLocation` - - :obj:`~pyrogram.types.InlineQueryResultMpeg4Gif` - :obj:`~pyrogram.types.InlineQueryResultPhoto` - :obj:`~pyrogram.types.InlineQueryResultVenue` - :obj:`~pyrogram.types.InlineQueryResultVideo` - - :obj:`~pyrogram.types.InlineQueryResultVoice`""" - - -class InlineQueryResult(Object): - """One result of an inline query. - - Pyrogram currently supports results of the following types: - - - :obj:`~pyrogram.types.InlineQueryResultArticle` - - :obj:`~pyrogram.types.InlineQueryResultAudio` - - :obj:`~pyrogram.types.InlineQueryResultAnimation` - - :obj:`~pyrogram.types.InlineQueryResultContact` - - :obj:`~pyrogram.types.InlineQueryResultDocument` - - :obj:`~pyrogram.types.InlineQueryResultPhoto` - - :obj:`~pyrogram.types.InlineQueryResultVideo` + - :obj:`~pyrogram.types.InlineQueryResultVoice` """ def __init__( From 20c6b959d5cd316bf3a030e26046e2f3f5e660d8 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0873/1185] Update Object.bind docstring --- pyrogram/types/object.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/types/object.py b/pyrogram/types/object.py index 8b6a5b99bf..9e2c684b3e 100644 --- a/pyrogram/types/object.py +++ b/pyrogram/types/object.py @@ -29,7 +29,7 @@ def __init__(self, client: "pyrogram.Client" = None): self._client = client def bind(self, client: "pyrogram.Client"): - """Recursively bind a Client instance to this and to all nested Pyrogram objects. + """Bind a Client instance to this and to all nested Pyrogram objects. Parameters: client (:obj:`~pyrogram.types.Client`): From 57a489747062e571eb33f95e8df5660f6cf274f8 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0874/1185] Skip attributes with leading underscore in Object.__eq__ --- pyrogram/types/object.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pyrogram/types/object.py b/pyrogram/types/object.py index 9e2c684b3e..28bee9a7fc 100644 --- a/pyrogram/types/object.py +++ b/pyrogram/types/object.py @@ -88,6 +88,9 @@ def __repr__(self) -> str: def __eq__(self, other: "Object") -> bool: for attr in self.__dict__: try: + if attr.startswith("_"): + continue + if getattr(self, attr) != getattr(other, attr): return False except AttributeError: From 1ae719c2529d28cf7efb29782c873882c64f759c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0875/1185] Rework send_poll: add missing parameters --- pyrogram/methods/messages/send_poll.py | 77 ++++++++++--- pyrogram/types/messages_and_media/message.py | 111 +++++++++++++------ 2 files changed, 137 insertions(+), 51 deletions(-) diff --git a/pyrogram/methods/messages/send_poll.py b/pyrogram/methods/messages/send_poll.py index 1305d2d680..3f6f35d240 100644 --- a/pyrogram/methods/messages/send_poll.py +++ b/pyrogram/methods/messages/send_poll.py @@ -31,13 +31,19 @@ async def send_poll( question: str, options: List[str], is_anonymous: bool = True, - allows_multiple_answers: bool = None, type: "enums.PollType" = enums.PollType.REGULAR, + allows_multiple_answers: bool = None, correct_option_id: int = None, + explanation: str = None, + explanation_parse_mode: "enums.ParseMode" = None, + explanation_entities: List["types.MessageEntity"] = None, + open_period: int = None, + close_date: datetime = None, + is_closed: bool = None, disable_notification: bool = None, + protect_content: bool = None, reply_to_message_id: int = None, schedule_date: datetime = None, - protect_content: bool = None, reply_markup: Union[ "types.InlineKeyboardMarkup", "types.ReplyKeyboardMarkup", @@ -69,25 +75,49 @@ async def send_poll( allows_multiple_answers (``bool``, *optional*): True, if the poll allows multiple answers, ignored for polls in quiz mode. - Defaults to False + Defaults to False. correct_option_id (``int``, *optional*): - 0-based identifier of the correct answer option (the index of the correct option) - Required for polls in quiz mode. + 0-based identifier of the correct answer option, required for polls in quiz mode. + + explanation (``str``, *optional*): + Text that is shown when a user chooses an incorrect answer or taps on the lamp icon in a quiz-style + poll, 0-200 characters with at most 2 line feeds after entities parsing. + + explanation_parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + + explanation_entities (List of :obj:`~pyrogram.types.MessageEntity`): + List of special entities that appear in the poll explanation, which can be specified instead of + *parse_mode*. + + open_period (``int``, *optional*): + Amount of time in seconds the poll will be active after creation, 5-600. + Can't be used together with *close_date*. + + close_date (:py:obj:`~datetime.datetime`, *optional*): + Point in time when the poll will be automatically closed. + Must be at least 5 and no more than 600 seconds in the future. + Can't be used together with *open_period*. + + is_closed (``bool``, *optional*): + Pass True, if the poll needs to be immediately closed. + This can be useful for poll preview. disable_notification (``bool``, *optional*): Sends the message silently. Users will receive a notification with no sound. + protect_content (``bool``, *optional*): + Protects the contents of the sent message from forwarding and saving. + reply_to_message_id (``int``, *optional*): If the message is a reply, ID of the original message. schedule_date (:py:obj:`~datetime.datetime`, *optional*): Date when the message will be automatically sent. - protect_content (``bool``, *optional*): - Protects the contents of the sent message from forwarding and saving. - reply_markup (:obj:`~pyrogram.types.InlineKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardMarkup` | :obj:`~pyrogram.types.ReplyKeyboardRemove` | :obj:`~pyrogram.types.ForceReply`, *optional*): Additional interface options. An object for an inline keyboard, custom reply keyboard, instructions to remove reply keyboard or to force a reply from the user. @@ -100,25 +130,40 @@ async def send_poll( await app.send_poll(chat_id, "Is this a poll question?", ["Yes", "No", "Maybe"]) """ + + message, entities = (await utils.parse_text_entities( + self, explanation, explanation_parse_mode, explanation_entities + )).values() + + # For some reason passing None or [] as solution_entities will lead to INPUT_CONSTRUCTOR_INVALID_00 + # Add a dummy message entity with no length as workaround + solution = message or None + solution_entities = entities or ([raw.types.MessageEntityBold(offset=0, length=0)] if solution else None) + r = await self.invoke( raw.functions.messages.SendMedia( peer=await self.resolve_peer(chat_id), media=raw.types.InputMediaPoll( poll=raw.types.Poll( - id=0, + id=self.rnd_id(), question=question, answers=[ - raw.types.PollAnswer(text=o, option=bytes([i])) - for i, o in enumerate(options) + raw.types.PollAnswer(text=text, option=bytes([i])) + for i, text in enumerate(options) ], - multiple_choice=allows_multiple_answers or None, - public_voters=not is_anonymous or None, - quiz=type == enums.PollType.QUIZ or None + closed=is_closed, + public_voters=not is_anonymous, + multiple_choice=allows_multiple_answers, + quiz=type == enums.PollType.QUIZ or False, + close_period=open_period, + close_date=utils.datetime_to_timestamp(close_date) ), - correct_answers=None if correct_option_id is None else [bytes([correct_option_id])] + correct_answers=[bytes([correct_option_id])] if correct_option_id is not None else None, + solution=solution, + solution_entities=solution_entities ), message="", - silent=disable_notification or None, + silent=disable_notification, reply_to_msg_id=reply_to_message_id, random_id=self.rnd_id(), schedule_date=utils.datetime_to_timestamp(schedule_date), diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index 526839c951..78817f4992 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -951,7 +951,7 @@ async def reply_text( RPCError: In case of a Telegram RPC error. """ if quote is None: - quote = self.chat.type != "private" + quote = self.chat.type != enums.ChatType.PRIVATE if reply_to_message_id is None and quote: reply_to_message_id = self.id @@ -1088,7 +1088,7 @@ async def reply_animation( RPCError: In case of a Telegram RPC error. """ if quote is None: - quote = self.chat.type != "private" + quote = self.chat.type != enums.ChatType.PRIVATE if reply_to_message_id is None and quote: reply_to_message_id = self.id @@ -1227,7 +1227,7 @@ async def reply_audio( RPCError: In case of a Telegram RPC error. """ if quote is None: - quote = self.chat.type != "private" + quote = self.chat.type != enums.ChatType.PRIVATE if reply_to_message_id is None and quote: reply_to_message_id = self.id @@ -1319,7 +1319,7 @@ async def reply_cached_media( RPCError: In case of a Telegram RPC error. """ if quote is None: - quote = self.chat.type != "private" + quote = self.chat.type != enums.ChatType.PRIVATE if reply_to_message_id is None and quote: reply_to_message_id = self.id @@ -1441,7 +1441,7 @@ async def reply_contact( RPCError: In case of a Telegram RPC error. """ if quote is None: - quote = self.chat.type != "private" + quote = self.chat.type != enums.ChatType.PRIVATE if reply_to_message_id is None and quote: reply_to_message_id = self.id @@ -1577,7 +1577,7 @@ async def reply_document( RPCError: In case of a Telegram RPC error. """ if quote is None: - quote = self.chat.type != "private" + quote = self.chat.type != enums.ChatType.PRIVATE if reply_to_message_id is None and quote: reply_to_message_id = self.id @@ -1655,7 +1655,7 @@ async def reply_game( RPCError: In case of a Telegram RPC error. """ if quote is None: - quote = self.chat.type != "private" + quote = self.chat.type != enums.ChatType.PRIVATE if reply_to_message_id is None and quote: reply_to_message_id = self.id @@ -1719,7 +1719,7 @@ async def reply_inline_bot_result( RPCError: In case of a Telegram RPC error. """ if quote is None: - quote = self.chat.type != "private" + quote = self.chat.type != enums.ChatType.PRIVATE if reply_to_message_id is None and quote: reply_to_message_id = self.id @@ -1793,7 +1793,7 @@ async def reply_location( RPCError: In case of a Telegram RPC error. """ if quote is None: - quote = self.chat.type != "private" + quote = self.chat.type != enums.ChatType.PRIVATE if reply_to_message_id is None and quote: reply_to_message_id = self.id @@ -1856,7 +1856,7 @@ async def reply_media_group( RPCError: In case of a Telegram RPC error. """ if quote is None: - quote = self.chat.type != "private" + quote = self.chat.type != enums.ChatType.PRIVATE if reply_to_message_id is None and quote: reply_to_message_id = self.id @@ -1972,7 +1972,7 @@ async def reply_photo( RPCError: In case of a Telegram RPC error. """ if quote is None: - quote = self.chat.type != "private" + quote = self.chat.type != enums.ChatType.PRIVATE if reply_to_message_id is None and quote: reply_to_message_id = self.id @@ -1995,12 +1995,19 @@ async def reply_poll( self, question: str, options: List[str], - quote: bool = None, is_anonymous: bool = True, + type: "enums.PollType" = enums.PollType.REGULAR, allows_multiple_answers: bool = None, - type: str = "regular", correct_option_id: int = None, + explanation: str = None, + explanation_parse_mode: "enums.ParseMode" = None, + explanation_entities: List["types.MessageEntity"] = None, + open_period: int = None, + close_date: datetime = None, + is_closed: bool = None, + quote: bool = None, disable_notification: bool = None, + protect_content: bool = None, reply_to_message_id: int = None, schedule_date: datetime = None, reply_markup: Union[ @@ -2029,39 +2036,66 @@ async def reply_poll( Parameters: question (``str``): - The poll question, as string. + Poll question, 1-255 characters. options (List of ``str``): - The poll options, as list of strings (2 to 10 options are allowed). + List of answer options, 2-10 strings 1-100 characters each. - quote (``bool``, *optional*): - If ``True``, the message will be sent as a reply to this message. - If *reply_to_message_id* is passed, this parameter will be ignored. - Defaults to ``True`` in group chats and ``False`` in private chats. - is_anonymous (``bool``, *optional*): True, if the poll needs to be anonymous. Defaults to True. - type (``str``, *optional*): - Poll type, "quiz" or "regular". - Defaults to "regular" + type (:obj`~pyrogram.enums.PollType`, *optional*): + Poll type, :obj:`~pyrogram.enums.PollType.QUIZ` or :obj:`~pyrogram.enums.PollType.REGULAR`. + Defaults to :obj:`~pyrogram.enums.PollType.REGULAR`. allows_multiple_answers (``bool``, *optional*): True, if the poll allows multiple answers, ignored for polls in quiz mode. - Defaults to False - + Defaults to False. + correct_option_id (``int``, *optional*): - 0-based identifier of the correct answer option (the index of the correct option) - Required for polls in quiz mode. + 0-based identifier of the correct answer option, required for polls in quiz mode. + + explanation (``str``, *optional*): + Text that is shown when a user chooses an incorrect answer or taps on the lamp icon in a quiz-style + poll, 0-200 characters with at most 2 line feeds after entities parsing. + + explanation_parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): + By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. + + explanation_entities (List of :obj:`~pyrogram.types.MessageEntity`): + List of special entities that appear in the poll explanation, which can be specified instead of + *parse_mode*. + + open_period (``int``, *optional*): + Amount of time in seconds the poll will be active after creation, 5-600. + Can't be used together with *close_date*. + + close_date (:py:obj:`~datetime.datetime`, *optional*): + Point in time when the poll will be automatically closed. + Must be at least 5 and no more than 600 seconds in the future. + Can't be used together with *open_period*. + + is_closed (``bool``, *optional*): + Pass True, if the poll needs to be immediately closed. + This can be useful for poll preview. + + quote (``bool``, *optional*): + If ``True``, the message will be sent as a reply to this message. + If *reply_to_message_id* is passed, this parameter will be ignored. + Defaults to ``True`` in group chats and ``False`` in private chats. disable_notification (``bool``, *optional*): Sends the message silently. Users will receive a notification with no sound. + protect_content (``bool``, *optional*): + Protects the contents of the sent message from forwarding and saving. + reply_to_message_id (``int``, *optional*): If the message is a reply, ID of the original message. - + schedule_date (:py:obj:`~datetime.datetime`, *optional*): Date when the message will be automatically sent. @@ -2076,7 +2110,7 @@ async def reply_poll( RPCError: In case of a Telegram RPC error. """ if quote is None: - quote = self.chat.type != "private" + quote = self.chat.type != enums.ChatType.PRIVATE if reply_to_message_id is None and quote: reply_to_message_id = self.id @@ -2086,10 +2120,17 @@ async def reply_poll( question=question, options=options, is_anonymous=is_anonymous, - allows_multiple_answers=allows_multiple_answers, type=type, + allows_multiple_answers=allows_multiple_answers, correct_option_id=correct_option_id, + explanation=explanation, + explanation_parse_mode=explanation_parse_mode, + explanation_entities=explanation_entities, + open_period=open_period, + close_date=close_date, + is_closed=is_closed, disable_notification=disable_notification, + protect_content=protect_content, reply_to_message_id=reply_to_message_id, schedule_date=schedule_date, reply_markup=reply_markup @@ -2180,7 +2221,7 @@ async def reply_sticker( RPCError: In case of a Telegram RPC error. """ if quote is None: - quote = self.chat.type != "private" + quote = self.chat.type != enums.ChatType.PRIVATE if reply_to_message_id is None and quote: reply_to_message_id = self.id @@ -2275,7 +2316,7 @@ async def reply_venue( RPCError: In case of a Telegram RPC error. """ if quote is None: - quote = self.chat.type != "private" + quote = self.chat.type != enums.ChatType.PRIVATE if reply_to_message_id is None and quote: reply_to_message_id = self.id @@ -2420,7 +2461,7 @@ async def reply_video( RPCError: In case of a Telegram RPC error. """ if quote is None: - quote = self.chat.type != "private" + quote = self.chat.type != enums.ChatType.PRIVATE if reply_to_message_id is None and quote: reply_to_message_id = self.id @@ -2544,7 +2585,7 @@ async def reply_video_note( RPCError: In case of a Telegram RPC error. """ if quote is None: - quote = self.chat.type != "private" + quote = self.chat.type != enums.ChatType.PRIVATE if reply_to_message_id is None and quote: reply_to_message_id = self.id @@ -2664,7 +2705,7 @@ async def reply_voice( RPCError: In case of a Telegram RPC error. """ if quote is None: - quote = self.chat.type != "private" + quote = self.chat.type != enums.ChatType.PRIVATE if reply_to_message_id is None and quote: reply_to_message_id = self.id From 264a206a139ab6fdaf166df9eb36c0cdfabb4faf Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0876/1185] Rename enum MessageMedia to MessageMediaType --- docs/source/api/enums/MessageMedia.rst | 8 ----- docs/source/api/enums/MessageMediaType.rst | 8 +++++ docs/source/api/enums/index.rst | 4 +-- pyrogram/enums/__init__.py | 2 +- ...message_media.py => message_media_type.py} | 4 +-- pyrogram/types/messages_and_media/message.py | 32 +++++++++---------- 6 files changed, 29 insertions(+), 29 deletions(-) delete mode 100644 docs/source/api/enums/MessageMedia.rst create mode 100644 docs/source/api/enums/MessageMediaType.rst rename pyrogram/enums/{message_media.py => message_media_type.py} (92%) diff --git a/docs/source/api/enums/MessageMedia.rst b/docs/source/api/enums/MessageMedia.rst deleted file mode 100644 index f42693f0c1..0000000000 --- a/docs/source/api/enums/MessageMedia.rst +++ /dev/null @@ -1,8 +0,0 @@ -MessageMedia -============ - -.. autoclass:: pyrogram.enums.MessageMedia() - :members: - -.. raw:: html - :file: ./cleanup.html \ No newline at end of file diff --git a/docs/source/api/enums/MessageMediaType.rst b/docs/source/api/enums/MessageMediaType.rst new file mode 100644 index 0000000000..04e439d20e --- /dev/null +++ b/docs/source/api/enums/MessageMediaType.rst @@ -0,0 +1,8 @@ +MessageMediaType +================ + +.. autoclass:: pyrogram.enums.MessageMediaType() + :members: + +.. raw:: html + :file: ./cleanup.html \ No newline at end of file diff --git a/docs/source/api/enums/index.rst b/docs/source/api/enums/index.rst index 574b4dd7ce..12b1fac180 100644 --- a/docs/source/api/enums/index.rst +++ b/docs/source/api/enums/index.rst @@ -19,7 +19,7 @@ to apply only a valid value among the expected ones. ChatMembersFilter ChatType MessageEntityType - MessageMedia + MessageMediaType MessageService MessagesFilter ParseMode @@ -37,7 +37,7 @@ to apply only a valid value among the expected ones. ChatMembersFilter ChatType MessageEntityType - MessageMedia + MessageMediaType MessageService MessagesFilter ParseMode diff --git a/pyrogram/enums/__init__.py b/pyrogram/enums/__init__.py index 7d553918a8..acdc5233a1 100644 --- a/pyrogram/enums/__init__.py +++ b/pyrogram/enums/__init__.py @@ -22,7 +22,7 @@ from .chat_members_filter import ChatMembersFilter from .chat_type import ChatType from .message_entity_type import MessageEntityType -from .message_media import MessageMedia +from .message_media_type import MessageMediaType from .message_service import MessageService from .messages_filter import MessagesFilter from .next_code_type import NextCodeType diff --git a/pyrogram/enums/message_media.py b/pyrogram/enums/message_media_type.py similarity index 92% rename from pyrogram/enums/message_media.py rename to pyrogram/enums/message_media_type.py index b7dfd03bc6..5887811405 100644 --- a/pyrogram/enums/message_media.py +++ b/pyrogram/enums/message_media_type.py @@ -21,8 +21,8 @@ from .auto_name import AutoName -class MessageMedia(AutoName): - """Message media enumeration used in :obj:`~pyrogram.types.Message`.""" +class MessageMediaType(AutoName): + """Message media type enumeration used in :obj:`~pyrogram.types.Message`.""" AUDIO = auto() "Audio media" diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index 78817f4992..9f0cf8a388 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -118,7 +118,7 @@ class Message(Object, Update): This field will contain the enumeration type of the service message. You can use ``service = getattr(message, message.service.value)`` to access the service message. - media (:obj:`~pyrogram.enums.MessageMedia`, *optional*): + media (:obj:`~pyrogram.enums.MessageMediaType`, *optional*): The message is a media message. This field will contain the enumeration type of the media message. You can use ``media = getattr(message, message.media.value)`` to access the media message. @@ -657,19 +657,19 @@ async def _parse( if media: if isinstance(media, raw.types.MessageMediaPhoto): photo = types.Photo._parse(client, media.photo, media.ttl_seconds) - media_type = enums.MessageMedia.PHOTO + media_type = enums.MessageMediaType.PHOTO elif isinstance(media, raw.types.MessageMediaGeo): location = types.Location._parse(client, media.geo) - media_type = enums.MessageMedia.LOCATION + media_type = enums.MessageMediaType.LOCATION elif isinstance(media, raw.types.MessageMediaContact): contact = types.Contact._parse(client, media) - media_type = enums.MessageMedia.CONTACT + media_type = enums.MessageMediaType.CONTACT elif isinstance(media, raw.types.MessageMediaVenue): venue = types.Venue._parse(client, media) - media_type = enums.MessageMedia.VENUE + media_type = enums.MessageMediaType.VENUE elif isinstance(media, raw.types.MessageMediaGame): game = types.Game._parse(client, message) - media_type = enums.MessageMedia.GAME + media_type = enums.MessageMediaType.GAME elif isinstance(media, raw.types.MessageMediaDocument): doc = media.document @@ -687,14 +687,14 @@ async def _parse( if audio_attributes.voice: voice = types.Voice._parse(client, doc, audio_attributes) - media_type = enums.MessageMedia.VOICE + media_type = enums.MessageMediaType.VOICE else: audio = types.Audio._parse(client, doc, audio_attributes, file_name) - media_type = enums.MessageMedia.AUDIO + media_type = enums.MessageMediaType.AUDIO elif raw.types.DocumentAttributeAnimated in attributes: video_attributes = attributes.get(raw.types.DocumentAttributeVideo, None) animation = types.Animation._parse(client, doc, video_attributes, file_name) - media_type = enums.MessageMedia.ANIMATION + media_type = enums.MessageMediaType.ANIMATION elif raw.types.DocumentAttributeSticker in attributes: sticker = await types.Sticker._parse( client, doc, @@ -702,31 +702,31 @@ async def _parse( attributes[raw.types.DocumentAttributeSticker], file_name ) - media_type = enums.MessageMedia.STICKER + media_type = enums.MessageMediaType.STICKER elif raw.types.DocumentAttributeVideo in attributes: video_attributes = attributes[raw.types.DocumentAttributeVideo] if video_attributes.round_message: video_note = types.VideoNote._parse(client, doc, video_attributes) - media_type = enums.MessageMedia.VIDEO_NOTE + media_type = enums.MessageMediaType.VIDEO_NOTE else: video = types.Video._parse(client, doc, video_attributes, file_name, media.ttl_seconds) - media_type = enums.MessageMedia.VIDEO + media_type = enums.MessageMediaType.VIDEO else: document = types.Document._parse(client, doc, file_name) - media_type = enums.MessageMedia.DOCUMENT + media_type = enums.MessageMediaType.DOCUMENT elif isinstance(media, raw.types.MessageMediaWebPage): if isinstance(media.webpage, raw.types.WebPage): web_page = types.WebPage._parse(client, media.webpage) - media_type = enums.MessageMedia.WEB_PAGE + media_type = enums.MessageMediaType.WEB_PAGE else: media = None elif isinstance(media, raw.types.MessageMediaPoll): poll = types.Poll._parse(client, media) - media_type = enums.MessageMedia.POLL + media_type = enums.MessageMediaType.POLL elif isinstance(media, raw.types.MessageMediaDice): dice = types.Dice._parse(client, media) - media_type = enums.MessageMedia.DICE + media_type = enums.MessageMediaType.DICE else: media = None From be37e3b46c5a917695476684503a2779469dc235 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0877/1185] Rename enum MessageService to MessageServiceType --- docs/source/api/enums/MessageService.rst | 8 ---- docs/source/api/enums/MessageServiceType.rst | 8 ++++ docs/source/api/enums/index.rst | 4 +- pyrogram/enums/__init__.py | 2 +- ...age_service.py => message_service_type.py} | 4 +- pyrogram/types/messages_and_media/message.py | 40 +++++++++---------- 6 files changed, 33 insertions(+), 33 deletions(-) delete mode 100644 docs/source/api/enums/MessageService.rst create mode 100644 docs/source/api/enums/MessageServiceType.rst rename pyrogram/enums/{message_service.py => message_service_type.py} (93%) diff --git a/docs/source/api/enums/MessageService.rst b/docs/source/api/enums/MessageService.rst deleted file mode 100644 index 7b7ee4e212..0000000000 --- a/docs/source/api/enums/MessageService.rst +++ /dev/null @@ -1,8 +0,0 @@ -MessageService -============== - -.. autoclass:: pyrogram.enums.MessageService() - :members: - -.. raw:: html - :file: ./cleanup.html \ No newline at end of file diff --git a/docs/source/api/enums/MessageServiceType.rst b/docs/source/api/enums/MessageServiceType.rst new file mode 100644 index 0000000000..2de56818d8 --- /dev/null +++ b/docs/source/api/enums/MessageServiceType.rst @@ -0,0 +1,8 @@ +MessageServiceType +================== + +.. autoclass:: pyrogram.enums.MessageServiceType() + :members: + +.. raw:: html + :file: ./cleanup.html \ No newline at end of file diff --git a/docs/source/api/enums/index.rst b/docs/source/api/enums/index.rst index 12b1fac180..bd9f8b1da7 100644 --- a/docs/source/api/enums/index.rst +++ b/docs/source/api/enums/index.rst @@ -20,7 +20,7 @@ to apply only a valid value among the expected ones. ChatType MessageEntityType MessageMediaType - MessageService + MessageServiceType MessagesFilter ParseMode PollType @@ -38,7 +38,7 @@ to apply only a valid value among the expected ones. ChatType MessageEntityType MessageMediaType - MessageService + MessageServiceType MessagesFilter ParseMode PollType diff --git a/pyrogram/enums/__init__.py b/pyrogram/enums/__init__.py index acdc5233a1..f847b3cf57 100644 --- a/pyrogram/enums/__init__.py +++ b/pyrogram/enums/__init__.py @@ -23,7 +23,7 @@ from .chat_type import ChatType from .message_entity_type import MessageEntityType from .message_media_type import MessageMediaType -from .message_service import MessageService +from .message_service_type import MessageServiceType from .messages_filter import MessagesFilter from .next_code_type import NextCodeType from .parse_mode import ParseMode diff --git a/pyrogram/enums/message_service.py b/pyrogram/enums/message_service_type.py similarity index 93% rename from pyrogram/enums/message_service.py rename to pyrogram/enums/message_service_type.py index 636e6db575..8a60e29e6e 100644 --- a/pyrogram/enums/message_service.py +++ b/pyrogram/enums/message_service_type.py @@ -21,8 +21,8 @@ from .auto_name import AutoName -class MessageService(AutoName): - """Message service enumeration used in :obj:`~pyrogram.types.Message`.""" +class MessageServiceType(AutoName): + """Message service type enumeration used in :obj:`~pyrogram.types.Message`.""" NEW_CHAT_MEMBERS = auto() "New members join" diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index 9f0cf8a388..68f92c3cdc 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -113,7 +113,7 @@ class Message(Object, Update): The message is empty. A message can be empty in case it was deleted or you tried to retrieve a message that doesn't exist yet. - service (:obj:`~pyrogram.enums.MessageService`, *optional*): + service (:obj:`~pyrogram.enums.MessageServiceType`, *optional*): The message is a service message. This field will contain the enumeration type of the service message. You can use ``service = getattr(message, message.service.value)`` to access the service message. @@ -321,10 +321,10 @@ def __init__( reply_to_message: "Message" = None, mentioned: bool = None, empty: bool = None, - service: "enums.MessageService" = None, + service: "enums.MessageServiceType" = None, scheduled: bool = None, from_scheduled: bool = None, - media: str = None, + media: "enums.MessageMediaType" = None, edit_date: datetime = None, media_group_id: str = None, author_signature: str = None, @@ -502,50 +502,50 @@ async def _parse( if isinstance(action, raw.types.MessageActionChatAddUser): new_chat_members = [types.User._parse(client, users[i]) for i in action.users] - service_type = enums.MessageService.NEW_CHAT_MEMBERS + service_type = enums.MessageServiceType.NEW_CHAT_MEMBERS elif isinstance(action, raw.types.MessageActionChatJoinedByLink): new_chat_members = [types.User._parse(client, users[utils.get_raw_peer_id(message.from_id)])] - service_type = enums.MessageService.NEW_CHAT_MEMBERS + service_type = enums.MessageServiceType.NEW_CHAT_MEMBERS elif isinstance(action, raw.types.MessageActionChatDeleteUser): left_chat_member = types.User._parse(client, users[action.user_id]) - service_type = enums.MessageService.LEFT_CHAT_MEMBERS + service_type = enums.MessageServiceType.LEFT_CHAT_MEMBERS elif isinstance(action, raw.types.MessageActionChatEditTitle): new_chat_title = action.title - service_type = enums.MessageService.NEW_CHAT_TITLE + service_type = enums.MessageServiceType.NEW_CHAT_TITLE elif isinstance(action, raw.types.MessageActionChatDeletePhoto): delete_chat_photo = True - service_type = enums.MessageService.DELETE_CHAT_PHOTO + service_type = enums.MessageServiceType.DELETE_CHAT_PHOTO elif isinstance(action, raw.types.MessageActionChatMigrateTo): migrate_to_chat_id = action.channel_id - service_type = enums.MessageService.MIGRATE_TO_CHAT_ID + service_type = enums.MessageServiceType.MIGRATE_TO_CHAT_ID elif isinstance(action, raw.types.MessageActionChannelMigrateFrom): migrate_from_chat_id = action.chat_id - service_type = enums.MessageService.MIGRATE_FROM_CHAT_ID + service_type = enums.MessageServiceType.MIGRATE_FROM_CHAT_ID elif isinstance(action, raw.types.MessageActionChatCreate): group_chat_created = True - service_type = enums.MessageService.GROUP_CHAT_CREATED + service_type = enums.MessageServiceType.GROUP_CHAT_CREATED elif isinstance(action, raw.types.MessageActionChannelCreate): channel_chat_created = True - service_type = enums.MessageService.CHANNEL_CHAT_CREATED + service_type = enums.MessageServiceType.CHANNEL_CHAT_CREATED elif isinstance(action, raw.types.MessageActionChatEditPhoto): new_chat_photo = types.Photo._parse(client, action.photo) - service_type = enums.MessageService.NEW_CHAT_PHOTO + service_type = enums.MessageServiceType.NEW_CHAT_PHOTO elif isinstance(action, raw.types.MessageActionGroupCallScheduled): video_chat_scheduled = types.VideoChatScheduled._parse(action) - service_type = enums.MessageService.VIDEO_CHAT_SCHEDULED + service_type = enums.MessageServiceType.VIDEO_CHAT_SCHEDULED elif isinstance(action, raw.types.MessageActionGroupCall): if action.duration: video_chat_ended = types.VideoChatEnded._parse(action) - service_type = enums.MessageService.VIDEO_CHAT_ENDED + service_type = enums.MessageServiceType.VIDEO_CHAT_ENDED else: video_chat_started = types.VideoChatStarted() - service_type = enums.MessageService.VIDEO_CHAT_STARTED + service_type = enums.MessageServiceType.VIDEO_CHAT_STARTED elif isinstance(action, raw.types.MessageActionInviteToGroupCall): video_chat_members_invited = types.VideoChatMembersInvited._parse(client, action, users) - service_type = enums.MessageService.VIDEO_CHAT_MEMBERS_INVITED + service_type = enums.MessageServiceType.VIDEO_CHAT_MEMBERS_INVITED elif isinstance(action, raw.types.MessageActionWebViewDataSentMe): web_app_data = types.WebAppData._parse(action) - service_type = enums.MessageService.WEB_APP_DATA + service_type = enums.MessageServiceType.WEB_APP_DATA from_user = types.User._parse(client, users.get(user_id, None)) sender_chat = types.Chat._parse(client, message, users, chats, is_chat=False) if not from_user else None @@ -583,7 +583,7 @@ async def _parse( replies=0 ) - parsed_message.service = enums.MessageService.PINNED_MESSAGE + parsed_message.service = enums.MessageServiceType.PINNED_MESSAGE except MessageIdsEmpty: pass @@ -598,7 +598,7 @@ async def _parse( replies=0 ) - parsed_message.service = enums.MessageService.GAME_HIGH_SCORE + parsed_message.service = enums.MessageServiceType.GAME_HIGH_SCORE except MessageIdsEmpty: pass From 61e737b2e831734c8f17c0687d2998fe5825335b Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0878/1185] Update minimum required Python version to 3.7 --- setup.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.py b/setup.py index d1bd9c90dd..2cb7d7f677 100644 --- a/setup.py +++ b/setup.py @@ -56,11 +56,11 @@ "Operating System :: OS Independent", "Programming Language :: Python", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", "Programming Language :: Python :: Implementation", "Programming Language :: Python :: Implementation :: CPython", "Programming Language :: Python :: Implementation :: PyPy", @@ -78,7 +78,7 @@ "Source": "https://github.com/pyrogram/pyrogram", "Documentation": "https://docs.pyrogram.org", }, - python_requires="~=3.6", + python_requires="~=3.7", package_data={ "pyrogram": ["py.typed"], }, From 822e09ae13ab54eddd3bc675c8eee2c10ab8e3e8 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0879/1185] Update Client's docstrings --- pyrogram/client.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/pyrogram/client.py b/pyrogram/client.py index 6b2ac9a1f2..4cf0740f60 100644 --- a/pyrogram/client.py +++ b/pyrogram/client.py @@ -146,15 +146,14 @@ class Client(Methods): Your Smart Plugins settings as dict, e.g.: *dict(root="plugins")*. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): - The parse mode, can be any of: *"combined"*, for the default combined mode. *"markdown"* or *"md"* - to force Markdown-only styles. *"html"* to force HTML-only styles. *None* to disable the parser - completely. + Set the global parse mode of the client. By default, texts are parsed using both Markdown and HTML styles. + You can combine both syntaxes together. no_updates (``bool``, *optional*): - Pass True to completely disable incoming updates for the current session. - When updates are disabled your client can't receive any new message. + Pass True to disable incoming updates. + When updates are disabled the client can't receive messages or other updates. Useful for batch programs that don't need to deal with updates. - Defaults to False (updates enabled and always received). + Defaults to False (updates enabled and received). takeout (``bool``, *optional*): Pass True to let the client use a takeout session instead of a normal one, implies *no_updates=True*. From e80ffd275c3403aef213d0f6f2f189be16812684 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0880/1185] Documentation fixes --- docs/source/intro/quickstart.rst | 2 +- docs/source/topics/storage-engines.rst | 4 ++-- docs/source/topics/synchronous.rst | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/source/intro/quickstart.rst b/docs/source/intro/quickstart.rst index cccbfbc76b..29c355e7c7 100644 --- a/docs/source/intro/quickstart.rst +++ b/docs/source/intro/quickstart.rst @@ -50,7 +50,7 @@ Enjoy the API That was just a quick overview. In the next few pages of the introduction, we'll take a much more in-depth look of what we have just done above. -If you are feeling eager to continue you can take a shortcut to :doc:`Invoking Methods <../start/invoking>` and come back +If you are feeling eager to continue you can take a shortcut to :doc:`../start/invoking` and come back later to learn some more details. .. _community: https://t.me/Pyrogram diff --git a/docs/source/topics/storage-engines.rst b/docs/source/topics/storage-engines.rst index c83e7a5b7c..34147917ee 100644 --- a/docs/source/topics/storage-engines.rst +++ b/docs/source/topics/storage-engines.rst @@ -74,8 +74,8 @@ In case you want to use an in-memory storage, but also want to keep access to th async with Client("my_account", in_memory=True) as app: print(await app.export_session_string()) -...and save the resulting string. You can use this string as session name the next time you want to login -using the same session; the storage used will still be in-memory: +...and save the resulting string. You can use this string by passing it as Client argument the next time you want to +login using the same session; the storage used will still be in-memory: .. code-block:: python diff --git a/docs/source/topics/synchronous.rst b/docs/source/topics/synchronous.rst index a6e12383d1..0a677b0e50 100644 --- a/docs/source/topics/synchronous.rst +++ b/docs/source/topics/synchronous.rst @@ -1,7 +1,7 @@ Synchronous Usage ================= -Pyrogram is an asynchronous framework and as such it is subject to the asynchronous rules. It can, however, run in +Pyrogram is an asynchronous framework and as such is subject to the asynchronous rules. It can, however, run in synchronous mode (also known as non-asynchronous or sync/non-async for short). This mode exists mainly as a convenience way for invoking methods without the need of ``async``/``await`` keywords and the extra boilerplate, but **it's not the intended way to use the framework**. From 0dc112ea8cb0a978e39a390308cdfc9654c36a6d Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 11:56:07 +0200 Subject: [PATCH 0881/1185] Update Pyrogram to v2.0.0 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index e4c8bcf356..761c790a38 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "1.4.16" +__version__ = "2.0.0" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From c799703965a54f8e64e6677edafd543ba728f92e Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 13:11:54 +0200 Subject: [PATCH 0882/1185] Fix plugins loading --- pyrogram/client.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pyrogram/client.py b/pyrogram/client.py index 4cf0740f60..74897e5f08 100644 --- a/pyrogram/client.py +++ b/pyrogram/client.py @@ -614,7 +614,7 @@ def load_plugins(self): plugins = self.plugins.copy() for option in ["include", "exclude"]: - if plugins[option]: + if plugins.get(option, []): plugins[option] = [ (i.split()[0], i.split()[1:] or None) for i in self.plugins[option] @@ -622,10 +622,10 @@ def load_plugins(self): else: return - if plugins.get("enabled", False): + if plugins.get("enabled", True): root = plugins["root"] - include = plugins["include"] - exclude = plugins["exclude"] + include = plugins.get("include", []) + exclude = plugins.get("exclude", []) count = 0 From e188da7afcfd4f7c6f63b0d0404f89cb467fb18e Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 13:12:17 +0200 Subject: [PATCH 0883/1185] Update Pyrogram to v2.0.1 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 761c790a38..9ae1f3c7d4 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.0" +__version__ = "2.0.1" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From ba34bf84f98a665305b6ece47dfdd0e45b2bd429 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 13:23:07 +0200 Subject: [PATCH 0884/1185] Show an error message for invalid parse modes --- pyrogram/parser/parser.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyrogram/parser/parser.py b/pyrogram/parser/parser.py index 35a15d0c68..16701b39ee 100644 --- a/pyrogram/parser/parser.py +++ b/pyrogram/parser/parser.py @@ -51,6 +51,8 @@ async def parse(self, text: str, mode: Optional[enums.ParseMode] = None): if mode == enums.ParseMode.DISABLED: return {"message": text, "entities": []} + raise ValueError(f'Invalid parse mode "{mode}"') + @staticmethod def unparse(text: str, entities: list, is_html: bool): if is_html: From e43bfd276a861117c9de4032dbdf8b8f9c289300 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 14:30:00 +0200 Subject: [PATCH 0885/1185] Update Pyrogram to v2.0.2 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 9ae1f3c7d4..aa6646cd0a 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.1" +__version__ = "2.0.2" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From 1b9dac8ad3da7d92ddb93fa786057a333485735f Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 14:50:00 +0200 Subject: [PATCH 0886/1185] Fix GitHub actions --- .github/workflows/python.yml | 9 +++++---- tests/filters/__init__.py | 8 +++++--- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 89ed8532ce..9d25f33f7b 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -8,8 +8,8 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - python-version: [3.6, 3.7, 3.8, 3.9, "3.10"] + os: [ubuntu-latest, macos-latest] + python-version: ["3.7", "3.8", "3.9", "3.10"] steps: - uses: actions/checkout@v2 @@ -26,8 +26,9 @@ jobs: - name: Generate API run: | - python setup.py generate --api + make venv + make api - name: Run tests run: | - tox + tox \ No newline at end of file diff --git a/tests/filters/__init__.py b/tests/filters/__init__.py index e93649f521..ebc674c470 100644 --- a/tests/filters/__init__.py +++ b/tests/filters/__init__.py @@ -17,9 +17,11 @@ # along with Pyrogram. If not, see . class Client: - @staticmethod - async def get_me(): - return User("username") + def __init__(self): + self.username = "username" + + async def get_me(self): + return User(self.username) class User: From 6437d2862eed2ecde54b388b96f21c9b2ca2cb2c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 16:38:30 +0200 Subject: [PATCH 0887/1185] Revert "Add pyproject.toml" This reverts commit 4f6ce8bec17af1ab76ca7832d3d4167c028ec483. --- pyproject.toml | 3 --- 1 file changed, 3 deletions(-) delete mode 100644 pyproject.toml diff --git a/pyproject.toml b/pyproject.toml deleted file mode 100644 index 07de284aa5..0000000000 --- a/pyproject.toml +++ /dev/null @@ -1,3 +0,0 @@ -[build-system] -requires = ["setuptools", "wheel"] -build-backend = "setuptools.build_meta" \ No newline at end of file From 3c08d02c200d90dd82c993bbff901fc438b114f8 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 16:40:55 +0200 Subject: [PATCH 0888/1185] Update Pyrogram to v2.0.3 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index aa6646cd0a..aeb9129dc9 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.2" +__version__ = "2.0.3" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From 240659f6163e653a69671ced9805d15a5e1ed521 Mon Sep 17 00:00:00 2001 From: lordcodes <83734728+LORD-ME-CODE@users.noreply.github.com> Date: Sun, 24 Apr 2022 18:06:45 +0300 Subject: [PATCH 0889/1185] Close the downloaded file before moving it (#964) * download media on windows fix mmmmmmmm * Style fixes Co-authored-by: Dan <14043624+delivrance@users.noreply.github.com> --- pyrogram/client.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/pyrogram/client.py b/pyrogram/client.py index 74897e5f08..40fe1cfeb3 100644 --- a/pyrogram/client.py +++ b/pyrogram/client.py @@ -734,13 +734,9 @@ async def handle_download(self, packet): if file and not in_memory: file_path = os.path.abspath(re.sub("\\\\", "/", os.path.join(directory, file_name))) os.makedirs(directory, exist_ok=True) + file.close() shutil.move(file.name, file_path) - try: - file.close() - except FileNotFoundError: - pass - return file_path if file and in_memory: From aecdd492eb855d012842d6da8c89e25fcf323ff7 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 17:07:26 +0200 Subject: [PATCH 0890/1185] Update Pyrogram to v2.0.4 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index aeb9129dc9..4bc6369a07 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.3" +__version__ = "2.0.4" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From 88527567989fabc54f8fed8e5d266ec3ba20c834 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 18:13:18 +0200 Subject: [PATCH 0891/1185] Fix zero-datetime not working in some systems --- pyrogram/methods/chats/ban_chat_member.py | 2 +- pyrogram/methods/chats/restrict_chat_member.py | 2 +- pyrogram/methods/invite_links/edit_chat_invite_link.py | 2 +- pyrogram/methods/messages/get_chat_history.py | 4 ++-- pyrogram/types/user_and_chats/chat.py | 4 ++-- pyrogram/utils.py | 6 +++++- 6 files changed, 12 insertions(+), 8 deletions(-) diff --git a/pyrogram/methods/chats/ban_chat_member.py b/pyrogram/methods/chats/ban_chat_member.py index 8e1bca1eda..16a1308247 100644 --- a/pyrogram/methods/chats/ban_chat_member.py +++ b/pyrogram/methods/chats/ban_chat_member.py @@ -29,7 +29,7 @@ async def ban_chat_member( self: "pyrogram.Client", chat_id: Union[int, str], user_id: Union[int, str], - until_date: datetime = datetime.fromtimestamp(0) + until_date: datetime = utils.zero_datetime() ) -> Union["types.Message", bool]: """Ban 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 diff --git a/pyrogram/methods/chats/restrict_chat_member.py b/pyrogram/methods/chats/restrict_chat_member.py index 4ebb5f1877..717ba60bd2 100644 --- a/pyrogram/methods/chats/restrict_chat_member.py +++ b/pyrogram/methods/chats/restrict_chat_member.py @@ -30,7 +30,7 @@ async def restrict_chat_member( chat_id: Union[int, str], user_id: Union[int, str], permissions: "types.ChatPermissions", - until_date: datetime = datetime.fromtimestamp(0) + until_date: datetime = utils.zero_datetime() ) -> "types.Chat": """Restrict a user in a supergroup. diff --git a/pyrogram/methods/invite_links/edit_chat_invite_link.py b/pyrogram/methods/invite_links/edit_chat_invite_link.py index 29f658ec8a..9951684787 100644 --- a/pyrogram/methods/invite_links/edit_chat_invite_link.py +++ b/pyrogram/methods/invite_links/edit_chat_invite_link.py @@ -51,7 +51,7 @@ async def edit_chat_invite_link( expire_date (:py:obj:`~datetime.datetime`, *optional*): Point in time when the link will expire. - Defaults to None (no change), pass ``datetime.fromtimestamp(0)`` to set no expiration date. + Defaults to None (no change), pass None to set no expiration date. member_limit (``int``, *optional*): Maximum number of users that can be members of the chat simultaneously after joining the chat via this diff --git a/pyrogram/methods/messages/get_chat_history.py b/pyrogram/methods/messages/get_chat_history.py index f21a081f19..a7fcc491e9 100644 --- a/pyrogram/methods/messages/get_chat_history.py +++ b/pyrogram/methods/messages/get_chat_history.py @@ -30,7 +30,7 @@ async def get_chunk( limit: int = 0, offset: int = 0, from_message_id: int = 0, - from_date: datetime = datetime.fromtimestamp(0) + from_date: datetime = utils.zero_datetime() ): messages = await client.invoke( raw.functions.messages.GetHistory( @@ -56,7 +56,7 @@ async def get_chat_history( limit: int = 0, offset: int = 0, offset_id: int = 0, - offset_date: datetime = datetime.fromtimestamp(0) + offset_date: datetime = utils.zero_datetime() ) -> Optional[AsyncGenerator["types.Message", None]]: """Get messages from a chat history. diff --git a/pyrogram/types/user_and_chats/chat.py b/pyrogram/types/user_and_chats/chat.py index e36107bf61..7e4442a132 100644 --- a/pyrogram/types/user_and_chats/chat.py +++ b/pyrogram/types/user_and_chats/chat.py @@ -514,7 +514,7 @@ async def set_photo(self, photo: str) -> bool: async def ban_member( self, user_id: Union[int, str], - until_date: datetime = datetime.fromtimestamp(0) + until_date: datetime = utils.zero_datetime() ) -> Union["types.Message", bool]: """Bound method *ban_member* of :obj:`~pyrogram.types.Chat`. @@ -602,7 +602,7 @@ async def restrict_member( self, user_id: Union[int, str], permissions: "types.ChatPermissions", - until_date: datetime = datetime.fromtimestamp(0), + until_date: datetime = utils.zero_datetime(), ) -> "types.Chat": """Bound method *unban_member* of :obj:`~pyrogram.types.Chat`. diff --git a/pyrogram/utils.py b/pyrogram/utils.py index 18230ed4f5..b9f3fdd84b 100644 --- a/pyrogram/utils.py +++ b/pyrogram/utils.py @@ -23,7 +23,7 @@ import os import struct from concurrent.futures.thread import ThreadPoolExecutor -from datetime import datetime +from datetime import datetime, timezone from getpass import getpass from typing import Union, List, Dict, Optional @@ -345,6 +345,10 @@ async def parse_text_entities( } +def zero_datetime() -> datetime: + return datetime.fromtimestamp(0, timezone.utc) + + def timestamp_to_datetime(ts: Optional[int]) -> Optional[datetime]: return datetime.fromtimestamp(ts) if ts else None From f9d42320a7e14d66603181a661f7026dad071a77 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 18:13:45 +0200 Subject: [PATCH 0892/1185] Update Pyrogram to v2.0.5 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 4bc6369a07..8c3a4f5a03 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.4" +__version__ = "2.0.5" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From 27c9338970b00e6106ea011835f65a6190c6f754 Mon Sep 17 00:00:00 2001 From: Alisson Lauffer Date: Sun, 24 Apr 2022 13:36:47 -0300 Subject: [PATCH 0893/1185] Fix Message.react() (#965) --- pyrogram/types/messages_and_media/message.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index 68f92c3cdc..ab5e202f2a 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -3319,7 +3319,7 @@ async def react(self, emoji: str = "") -> bool: await client.send_reaction( chat_id=chat_id, - message_id=message.message_id, + message_id=message.id, emoji="🔥" ) @@ -3342,7 +3342,7 @@ async def react(self, emoji: str = "") -> bool: return await self._client.send_reaction( chat_id=self.chat.id, - message_id=self.message_id, + message_id=self.id, emoji=emoji ) From 5239392480356a28ff4acf53ee57d4f83e7d8147 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 18:37:13 +0200 Subject: [PATCH 0894/1185] Update Pyrogram to v2.0.6 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 8c3a4f5a03..2876b5020b 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.5" +__version__ = "2.0.6" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From 228b45b17915d75c5545cf0b5916dc8d0284f602 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 21:19:36 +0200 Subject: [PATCH 0895/1185] Update docstrings --- pyrogram/client.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/pyrogram/client.py b/pyrogram/client.py index 40fe1cfeb3..b0c493d5c9 100644 --- a/pyrogram/client.py +++ b/pyrogram/client.py @@ -66,14 +66,14 @@ class Client(Methods): Parameters: name (``str``): - Pass a string of your choice to give a name to the client, e.g.: "my_account". + A name for the client, e.g.: "my_account". api_id (``int`` | ``str``, *optional*): - The *api_id* part of your Telegram API key, as integer. - E.g.: 12345. + The *api_id* part of the Telegram API key, as integer or string. + E.g.: 12345 or "12345". api_hash (``str``, *optional*): - The *api_hash* part of your Telegram API key, as string. + The *api_hash* part of the Telegram API key, as string. E.g.: "0123456789abcdef0123456789abcdef". app_version (``str``, *optional*): @@ -99,7 +99,7 @@ class Client(Methods): proxy (``dict``, *optional*): The Proxy settings as dict. E.g.: *dict(scheme="socks5", hostname="11.22.33.44", port=1234, username="user", password="pass")*. - The *username* and *password* can be omitted if your proxy doesn't require authorization. + The *username* and *password* can be omitted if the proxy doesn't require authorization. test_mode (``bool``, *optional*): Enable or disable login to the test servers. @@ -107,7 +107,7 @@ class Client(Methods): Defaults to False. bot_token (``str``, *optional*): - Pass your Bot API token to create a bot session, e.g.: "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11" + Pass the Bot API token to create a bot session, e.g.: "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11" Only applicable for new sessions. session_string (``str``, *optional*): @@ -122,7 +122,7 @@ class Client(Methods): Defaults to False. phone_number (``str``, *optional*): - Pass your phone number as string (with your Country Code prefix included) to avoid entering it manually. + Pass the phone number as string (with the Country Code prefix included) to avoid entering it manually. Only applicable for new sessions. phone_code (``str``, *optional*): @@ -130,7 +130,7 @@ class Client(Methods): Only applicable for new sessions. password (``str``, *optional*): - Pass your Two-Step Verification password as string (if you have one) to avoid entering it manually. + Pass the Two-Step Verification password as string (if required) to avoid entering it manually. Only applicable for new sessions. workers (``int``, *optional*): @@ -138,12 +138,12 @@ class Client(Methods): Defaults to ``min(32, os.cpu_count() + 4)``. workdir (``str``, *optional*): - Define a custom working directory. The working directory is the location in your filesystem where Pyrogram - will store your session files. + Define a custom working directory. + The working directory is the location in the filesystem where Pyrogram will store the session files. Defaults to the parent directory of the main script. plugins (``dict``, *optional*): - Your Smart Plugins settings as dict, e.g.: *dict(root="plugins")*. + Smart Plugins settings as dict, e.g.: *dict(root="plugins")*. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): Set the global parse mode of the client. By default, texts are parsed using both Markdown and HTML styles. @@ -157,7 +157,7 @@ class Client(Methods): takeout (``bool``, *optional*): Pass True to let the client use a takeout session instead of a normal one, implies *no_updates=True*. - Useful for exporting your Telegram data. Methods invoked inside a takeout session (such as get_history, + Useful for exporting Telegram data. Methods invoked inside a takeout session (such as get_chat_history, download_media, ...) are less prone to throw FloodWait exceptions. Only available for users, bots will ignore this parameter. Defaults to False (normal session). From 6e3dbbde5a8f2d90096e5e4890b66901ebd6f6e3 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 21:20:13 +0200 Subject: [PATCH 0896/1185] Allow passing the api_id as string --- pyrogram/client.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/client.py b/pyrogram/client.py index b0c493d5c9..2bc1dbfd21 100644 --- a/pyrogram/client.py +++ b/pyrogram/client.py @@ -192,7 +192,7 @@ class Client(Methods): def __init__( self, name: str, - api_id: int = None, + api_id: Union[int, str] = None, api_hash: str = None, app_version: str = APP_VERSION, device_model: str = DEVICE_MODEL, @@ -219,7 +219,7 @@ def __init__( super().__init__() self.name = name - self.api_id = api_id + self.api_id = int(api_id) if api_id else None self.api_hash = api_hash self.app_version = app_version self.device_model = device_model From a93e21831f79941e59ec59a21f6c49bddd442385 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 24 Apr 2022 21:20:35 +0200 Subject: [PATCH 0897/1185] Update Pyrogram to v2.0.7 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 2876b5020b..b0b5cf9ece 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.6" +__version__ = "2.0.7" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From 8a2416665b5c6d9b5ce20ccfb117726d29ac7e00 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 25 Apr 2022 08:30:26 +0200 Subject: [PATCH 0898/1185] Fix HTML and Markdown unparsing --- pyrogram/parser/html.py | 21 +++++++++++++-------- pyrogram/parser/markdown.py | 19 ++++++++++--------- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/pyrogram/parser/html.py b/pyrogram/parser/html.py index c5f3c1e12d..8a7063e58a 100644 --- a/pyrogram/parser/html.py +++ b/pyrogram/parser/html.py @@ -24,6 +24,7 @@ import pyrogram from pyrogram import raw +from pyrogram.enums import MessageEntityType from pyrogram.errors import PeerIdInvalid from . import utils @@ -155,17 +156,21 @@ def unparse(text: str, entities: list): start = entity.offset end = start + entity.length - if entity_type in ("bold", "italic", "underline", "strikethrough"): - start_tag = f"<{entity_type[0]}>" - end_tag = f"" - elif entity_type in ("code", "pre", "blockquote", "spoiler"): - start_tag = f"<{entity_type}>" - end_tag = f"" - elif entity_type == "text_link": + if entity_type in (MessageEntityType.BOLD, MessageEntityType.ITALIC, MessageEntityType.UNDERLINE, + MessageEntityType.STRIKETHROUGH): + name = entity_type.name[0].lower() + start_tag = f"<{name}>" + end_tag = f"" + elif entity_type in (MessageEntityType.CODE, MessageEntityType.PRE, MessageEntityType.BLOCKQUOTE, + MessageEntityType.SPOILER): + name = entity_type.name.lower() + start_tag = f"<{name}>" + end_tag = f"" + elif entity_type == MessageEntityType.TEXT_LINK: url = entity.url start_tag = f'' end_tag = "" - elif entity_type == "text_mention": + elif entity_type == MessageEntityType.TEXT_MENTION: user = entity.user start_tag = f'' end_tag = "" diff --git a/pyrogram/parser/markdown.py b/pyrogram/parser/markdown.py index 8b24d06302..364793406a 100644 --- a/pyrogram/parser/markdown.py +++ b/pyrogram/parser/markdown.py @@ -21,6 +21,7 @@ from typing import Optional import pyrogram +from pyrogram.enums import MessageEntityType from . import utils from .html import HTML @@ -119,25 +120,25 @@ def unparse(text: str, entities: list): start = entity.offset end = start + entity.length - if entity_type == "bold": + if entity_type == MessageEntityType.BOLD: start_tag = end_tag = BOLD_DELIM - elif entity_type == "italic": + elif entity_type == MessageEntityType.ITALIC: start_tag = end_tag = ITALIC_DELIM - elif entity_type == "underline": + elif entity_type == MessageEntityType.UNDERLINE: start_tag = end_tag = UNDERLINE_DELIM - elif entity_type == "strikethrough": + elif entity_type == MessageEntityType.STRIKETHROUGH: start_tag = end_tag = STRIKE_DELIM - elif entity_type == "code": + elif entity_type == MessageEntityType.CODE: start_tag = end_tag = CODE_DELIM - elif entity_type in ("pre", "blockquote"): + elif entity_type in (MessageEntityType.PRE, MessageEntityType.BLOCKQUOTE): start_tag = end_tag = PRE_DELIM - elif entity_type == "spoiler": + elif entity_type == MessageEntityType.SPOILER: start_tag = end_tag = SPOILER_DELIM - elif entity_type == "text_link": + elif entity_type == MessageEntityType.TEXT_LINK: url = entity.url start_tag = "[" end_tag = f"]({url})" - elif entity_type == "text_mention": + elif entity_type == MessageEntityType.TEXT_MENTION: user = entity.user start_tag = "[" end_tag = f"](tg://user?id={user.id})" From 7bedf30d308d7a871b2ab43fd0a6aab12cf9f294 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 25 Apr 2022 08:30:56 +0200 Subject: [PATCH 0899/1185] Update Pyrogram to v2.0.8 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index b0b5cf9ece..33eb712cf8 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.7" +__version__ = "2.0.8" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From b309caccd7fe722567f635f8c7b1abffe71608f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=AE=AE=E0=AE=A9=E0=AF=8B=E0=AE=9C=E0=AF=8D=E0=AE=95?= =?UTF-8?q?=E0=AF=81=E0=AE=AE=E0=AE=BE=E0=AE=B0=E0=AF=8D=20=E0=AE=AA?= =?UTF-8?q?=E0=AE=B4=E0=AE=A9=E0=AE=BF=E0=AE=9A=E0=AF=8D=E0=AE=9A=E0=AE=BE?= =?UTF-8?q?=E0=AE=AE=E0=AE=BF?= Date: Mon, 25 Apr 2022 20:20:15 +0530 Subject: [PATCH 0900/1185] Make timestamp_to_datetime timezone aware (#966) * timezone aware object * Update utils.py Co-authored-by: Dan <14043624+delivrance@users.noreply.github.com> --- pyrogram/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/utils.py b/pyrogram/utils.py index b9f3fdd84b..ce19e3bd5f 100644 --- a/pyrogram/utils.py +++ b/pyrogram/utils.py @@ -350,7 +350,7 @@ def zero_datetime() -> datetime: def timestamp_to_datetime(ts: Optional[int]) -> Optional[datetime]: - return datetime.fromtimestamp(ts) if ts else None + return datetime.fromtimestamp(ts, timezone.utc) if ts else None def datetime_to_timestamp(dt: Optional[datetime]) -> Optional[int]: From 5f47c8c4990317352ab89f80af60909b8928b17a Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 25 Apr 2022 16:52:04 +0200 Subject: [PATCH 0901/1185] Fix some examples --- pyrogram/methods/chats/get_chat_members.py | 12 ++++++------ pyrogram/methods/messages/read_chat_history.py | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pyrogram/methods/chats/get_chat_members.py b/pyrogram/methods/chats/get_chat_members.py index 65688a3d71..3e11872cb1 100644 --- a/pyrogram/methods/chats/get_chat_members.py +++ b/pyrogram/methods/chats/get_chat_members.py @@ -101,14 +101,14 @@ async def get_chat_members( print(member) # Get administrators - administrators = list(await app.get_chat_members( - chat_id, - filter=enums.ChatMembersFilter.ADMINISTRATORS)) + administrators = [] + async for m in app.get_chat_members(chat_id, filter=enums.ChatMembersFilter.ADMINISTRATORS): + administrators.append(m) # Get bots - bots = list(await app.get_chat_members( - chat_id, - filter=enums.ChatMembersFilter.BOTS)) + bots = [] + async for m in app.get_chat_members(chat_id, filter=enums.ChatMembersFilter.BOTS): + bots.append(m) """ peer = await self.resolve_peer(chat_id) diff --git a/pyrogram/methods/messages/read_chat_history.py b/pyrogram/methods/messages/read_chat_history.py index 8a58fa264c..b3cc3bfc4c 100644 --- a/pyrogram/methods/messages/read_chat_history.py +++ b/pyrogram/methods/messages/read_chat_history.py @@ -47,10 +47,10 @@ async def read_chat_history( .. code-block:: python # Mark the whole chat as read - await app.read_history(chat_id) + await app.read_chat_history(chat_id) # Mark messages as read only up to the given message id - await app.read_history(chat_id, 12345) + await app.read_chat_history(chat_id, 12345) """ peer = await self.resolve_peer(chat_id) From 318996f81120148e0136132daff98e00c9aeb7aa Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 25 Apr 2022 16:52:56 +0200 Subject: [PATCH 0902/1185] Update Pyrogram to v2.0.9 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 33eb712cf8..c7de8533f7 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.8" +__version__ = "2.0.9" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From c71d36ea49f43cd30a4d67f6c46f5da996a823d4 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 25 Apr 2022 17:19:38 +0200 Subject: [PATCH 0903/1185] Fix Chat.get_members() --- pyrogram/types/user_and_chats/chat.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyrogram/types/user_and_chats/chat.py b/pyrogram/types/user_and_chats/chat.py index 7e4442a132..b726d96e84 100644 --- a/pyrogram/types/user_and_chats/chat.py +++ b/pyrogram/types/user_and_chats/chat.py @@ -17,7 +17,7 @@ # along with Pyrogram. If not, see . from datetime import datetime -from typing import Union, List +from typing import Union, List, Optional, AsyncGenerator import pyrogram from pyrogram import raw, enums @@ -791,12 +791,12 @@ async def get_member( user_id=user_id ) - async def get_members( + def get_members( self, query: str = "", limit: int = 0, filter: "enums.ChatMembersFilter" = enums.ChatMembersFilter.SEARCH - ) -> List["types.ChatMember"]: + ) -> Optional[AsyncGenerator["types.ChatMember", None]]: """Bound method *get_members* of :obj:`~pyrogram.types.Chat`. Use as a shortcut for: From 43e08d4143049d6bc4189c3387af1ee5b2347442 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 25 Apr 2022 17:20:05 +0200 Subject: [PATCH 0904/1185] Update Pyrogram to v2.0.10 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index c7de8533f7..38b64a4822 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.9" +__version__ = "2.0.10" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From 7992549386f138092a97329150c577b065663cb6 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 25 Apr 2022 17:27:36 +0200 Subject: [PATCH 0905/1185] Fix on_edited_message not being correctly registered in plugins --- pyrogram/methods/decorators/on_edited_message.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/methods/decorators/on_edited_message.py b/pyrogram/methods/decorators/on_edited_message.py index 02ebdec3b8..a8c86bb6d2 100644 --- a/pyrogram/methods/decorators/on_edited_message.py +++ b/pyrogram/methods/decorators/on_edited_message.py @@ -51,7 +51,7 @@ def decorator(func: Callable) -> Callable: func.handlers.append( ( - pyrogram.handlers.MessageHandler(func, self), + pyrogram.handlers.EditedMessageHandler(func, self), group if filters is None else filters ) ) From 1c0ddc9a8f59b5bbd5bd68b964e8d12a0161431e Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 25 Apr 2022 17:27:54 +0200 Subject: [PATCH 0906/1185] Update Pyrogram to v2.0.11 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 38b64a4822..e5eee02b93 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.10" +__version__ = "2.0.11" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From 9deec03b873ac103bad9d53c3b79a56587ba84c4 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 25 Apr 2022 19:40:39 +0200 Subject: [PATCH 0907/1185] Revert "Make timestamp_to_datetime timezone aware (#966)" This reverts commit b309caccd7fe722567f635f8c7b1abffe71608f3. --- pyrogram/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/utils.py b/pyrogram/utils.py index ce19e3bd5f..b9f3fdd84b 100644 --- a/pyrogram/utils.py +++ b/pyrogram/utils.py @@ -350,7 +350,7 @@ def zero_datetime() -> datetime: def timestamp_to_datetime(ts: Optional[int]) -> Optional[datetime]: - return datetime.fromtimestamp(ts, timezone.utc) if ts else None + return datetime.fromtimestamp(ts) if ts else None def datetime_to_timestamp(dt: Optional[datetime]) -> Optional[int]: From bee5136598ee518f39184944be669852faf3c87f Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 25 Apr 2022 19:41:06 +0200 Subject: [PATCH 0908/1185] Update Pyrogram to v2.0.12 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index e5eee02b93..8c264744c9 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.11" +__version__ = "2.0.12" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From 23b03c6b1944bf29d73b720eee12bba370926154 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 25 Apr 2022 21:02:19 +0200 Subject: [PATCH 0909/1185] Fix parsing of text mentions --- pyrogram/types/messages_and_media/message_entity.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/pyrogram/types/messages_and_media/message_entity.py b/pyrogram/types/messages_and_media/message_entity.py index dbd6a98264..8a880a1fad 100644 --- a/pyrogram/types/messages_and_media/message_entity.py +++ b/pyrogram/types/messages_and_media/message_entity.py @@ -71,12 +71,21 @@ def __init__( @staticmethod def _parse(client, entity: "raw.base.MessageEntity", users: dict) -> Optional["MessageEntity"]: + # Special case for InputMessageEntityMentionName -> MessageEntityType.TEXT_MENTION + # This happens in case of UpdateShortSentMessage inside send_message() where entities are parsed from the input + if isinstance(entity, raw.types.InputMessageEntityMentionName): + entity_type = enums.MessageEntityType.TEXT_MENTION + user_id = entity.user_id.user_id + else: + entity_type = enums.MessageEntityType(entity.__class__) + user_id = getattr(entity, "user_id", None) + return MessageEntity( - type=enums.MessageEntityType(entity.__class__), + type=entity_type, offset=entity.offset, length=entity.length, url=getattr(entity, "url", None), - user=types.User._parse(client, users.get(getattr(entity, "user_id", None), None)), + user=types.User._parse(client, users.get(user_id, None)), language=getattr(entity, "language", None), client=client ) From a21dd9f1313ae1b28b7bcafaac53a34fd6a6cdfe Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 25 Apr 2022 21:02:44 +0200 Subject: [PATCH 0910/1185] Update Pyrogram to v2.0.13 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 8c264744c9..cb25265ed0 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.12" +__version__ = "2.0.13" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From 2f82e753d681596233e7cab653a7c592a00b4a0b Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 27 Apr 2022 09:04:20 +0200 Subject: [PATCH 0911/1185] Keep namespaces in constructors names --- compiler/docs/compiler.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index 6c2276b778..a1cc115eae 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -65,13 +65,19 @@ def build(path, level=0): if level: full_path = base + "/" + full_path + namespace = path.split("/")[-1] + if namespace in ["base", "types", "functions"]: + namespace = "" + + full_name = f"{(namespace + '.') if namespace else ''}{name}" + os.makedirs(os.path.dirname(DESTINATION + "/" + full_path), exist_ok=True) with open(DESTINATION + "/" + full_path, "w", encoding="utf-8") as f: f.write( page_template.format( - title=name, - title_markup="=" * len(name), + title=full_name, + title_markup="=" * len(full_name), full_class_path="pyrogram.raw.{}".format( ".".join(full_path.split("/")[:-1]) + "." + name ) @@ -90,7 +96,7 @@ def build(path, level=0): entities = [] for i in v: - entities.append(snek(i).replace("_", "-")) + entities.append(f'{i} <{snek(i).replace("_", "-")}>') if k != base: inner_path = base + "/" + k + "/index" + ".rst" From 4b6f29bb7605798259229959c4ee9bc94a920aeb Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 27 Apr 2022 09:10:48 +0200 Subject: [PATCH 0912/1185] Update examples --- docs/source/topics/advanced-usage.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/source/topics/advanced-usage.rst b/docs/source/topics/advanced-usage.rst index 86eb67c4db..df99042d1b 100644 --- a/docs/source/topics/advanced-usage.rst +++ b/docs/source/topics/advanced-usage.rst @@ -51,7 +51,7 @@ Here's some examples: from pyrogram.raw import functions async with Client("my_account") as app: - await app.send( + await app.invoke( functions.account.UpdateProfile( first_name="First Name", last_name="Last Name", about="New bio text" @@ -67,10 +67,10 @@ Here's some examples: async with Client("my_account") as app: # Set online status - await app.send(functions.account.UpdateStatus(offline=False)) + await app.invoke(functions.account.UpdateStatus(offline=False)) # Set offline status - await app.send(functions.account.UpdateStatus(offline=True)) + await app.invoke(functions.account.UpdateStatus(offline=True)) - Get chat info: @@ -80,7 +80,7 @@ Here's some examples: from pyrogram.raw import functions, types async with Client("my_account") as app: - r = await app.send( + r = await app.invoke( functions.channels.GetFullChannel( channel=app.resolve_peer("username") ) From 045fe0bf210e997ea9820a761797c0ff4067f184 Mon Sep 17 00:00:00 2001 From: Hitalo <40531911+HitaloSama@users.noreply.github.com> Date: Wed, 27 Apr 2022 04:13:47 -0300 Subject: [PATCH 0913/1185] Remove wrong Client.parse_mode assignment (#973) --- pyrogram/client.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyrogram/client.py b/pyrogram/client.py index 2bc1dbfd21..5904747a91 100644 --- a/pyrogram/client.py +++ b/pyrogram/client.py @@ -257,7 +257,6 @@ def __init__( self.rnd_id = MsgId self.parser = Parser(self) - self.parse_mode = enums.ParseMode.DEFAULT self.session = None From 32b3452e76f81147a959ac00cff547c39184cb39 Mon Sep 17 00:00:00 2001 From: Stark Programmer <88478059+StarkBotsIndustries@users.noreply.github.com> Date: Wed, 27 Apr 2022 12:47:14 +0530 Subject: [PATCH 0914/1185] Fix self-destruct media if file_id and ttl_seconds are passed (#971) --- pyrogram/methods/messages/send_photo.py | 2 +- pyrogram/methods/messages/send_video.py | 2 +- pyrogram/utils.py | 9 ++++++--- 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/pyrogram/methods/messages/send_photo.py b/pyrogram/methods/messages/send_photo.py index 85d6fe4b7c..836ce1b1d4 100644 --- a/pyrogram/methods/messages/send_photo.py +++ b/pyrogram/methods/messages/send_photo.py @@ -155,7 +155,7 @@ async def send_photo( ttl_seconds=ttl_seconds ) else: - media = utils.get_input_media_from_file_id(photo, FileType.PHOTO) + media = utils.get_input_media_from_file_id(photo, FileType.PHOTO, ttl_seconds=ttl_seconds) else: file = await self.save_file(photo, progress=progress, progress_args=progress_args) media = raw.types.InputMediaUploadedPhoto( diff --git a/pyrogram/methods/messages/send_video.py b/pyrogram/methods/messages/send_video.py index 299f1b477c..669b42ec05 100644 --- a/pyrogram/methods/messages/send_video.py +++ b/pyrogram/methods/messages/send_video.py @@ -200,7 +200,7 @@ async def progress(current, total): ttl_seconds=ttl_seconds ) else: - media = utils.get_input_media_from_file_id(video, FileType.VIDEO) + media = utils.get_input_media_from_file_id(video, FileType.VIDEO, ttl_seconds=ttl_seconds) else: thumb = await self.save_file(thumb) file = await self.save_file(video, progress=progress, progress_args=progress_args) diff --git a/pyrogram/utils.py b/pyrogram/utils.py index b9f3fdd84b..d5862d1a67 100644 --- a/pyrogram/utils.py +++ b/pyrogram/utils.py @@ -42,7 +42,8 @@ async def ainput(prompt: str = "", *, hide: bool = False): def get_input_media_from_file_id( file_id: str, - expected_file_type: FileType = None + expected_file_type: FileType = None, + ttl_seconds: int = None ) -> Union["raw.types.InputMediaPhoto", "raw.types.InputMediaDocument"]: try: decoded = FileId.decode(file_id) @@ -64,7 +65,8 @@ def get_input_media_from_file_id( id=decoded.media_id, access_hash=decoded.access_hash, file_reference=decoded.file_reference - ) + ), + ttl_seconds=ttl_seconds ) if file_type in DOCUMENT_TYPES: @@ -73,7 +75,8 @@ def get_input_media_from_file_id( id=decoded.media_id, access_hash=decoded.access_hash, file_reference=decoded.file_reference - ) + ), + ttl_seconds=ttl_seconds ) raise ValueError(f"Unknown file id: {file_id}") From 295060d190e98245ee16d68f887ba2aed1cf9cbf Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 27 Apr 2022 09:56:48 +0200 Subject: [PATCH 0915/1185] Remove "0x" prefix from IDs in the documentation --- compiler/api/compiler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/api/compiler.py b/compiler/api/compiler.py index 5736c68d49..7cbce6f396 100644 --- a/compiler/api/compiler.py +++ b/compiler/api/compiler.py @@ -378,7 +378,7 @@ def start(format: bool = False): else: docstring += f"Telegram API method.\n\n" - docstring += f" Details:\n - Layer: ``{layer}``\n - ID: ``{c.id}``\n\n" + docstring += f" Details:\n - Layer: ``{layer}``\n - ID: ``{c.id[2:].upper()}``\n\n" if docstring_args: docstring += " Parameters:\n " + "\n ".join(docstring_args) From 5a7675597f843183d47e26d12a636fa0664580a2 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 27 Apr 2022 10:42:44 +0200 Subject: [PATCH 0916/1185] Update Pyrogram to v2.0.14 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index cb25265ed0..9580260818 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.13" +__version__ = "2.0.14" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From ebb7bb2958392711760a945d2add815490469e94 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 27 Apr 2022 21:37:05 +0200 Subject: [PATCH 0917/1185] Fix enumeration docstring --- pyrogram/enums/messages_filter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/enums/messages_filter.py b/pyrogram/enums/messages_filter.py index b9c84fc939..67bfac63ee 100644 --- a/pyrogram/enums/messages_filter.py +++ b/pyrogram/enums/messages_filter.py @@ -21,7 +21,7 @@ class MessagesFilter(AutoName): - """Messages filter enumeration used in used in :meth:`~pyrogram.Client.search_messages` and used in :meth:`~pyrogram.Client.search_global`""" + """Messages filter enumeration used in :meth:`~pyrogram.Client.search_messages` and :meth:`~pyrogram.Client.search_global`""" EMPTY = raw.types.InputMessagesFilterEmpty "Empty filter (any kind of messages)" From a762cbc23787f3c745e920d1a890a486ea05ee34 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 29 Apr 2022 11:02:58 +0200 Subject: [PATCH 0918/1185] Remove unused import --- pyrogram/storage/storage.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyrogram/storage/storage.py b/pyrogram/storage/storage.py index de397718fc..0689b6826c 100644 --- a/pyrogram/storage/storage.py +++ b/pyrogram/storage/storage.py @@ -17,7 +17,6 @@ # along with Pyrogram. If not, see . import base64 -import lzma import struct from typing import List, Tuple From bbe90fc6d376f7b6be4ed35f677828296b5c7e07 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 29 Apr 2022 11:03:28 +0200 Subject: [PATCH 0919/1185] Update Pyrogram to v2.0.15 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 9580260818..19fdc0cf9a 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.14" +__version__ = "2.0.15" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From 795ffc027af6ffdc58b63eeb3fa8bd592349a2d9 Mon Sep 17 00:00:00 2001 From: Evgen Fil Date: Fri, 29 Apr 2022 15:40:19 +0500 Subject: [PATCH 0920/1185] Allow non-interactive migration from storage v2 to v3 (#979) --- pyrogram/client.py | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/pyrogram/client.py b/pyrogram/client.py index 5904747a91..c12de07508 100644 --- a/pyrogram/client.py +++ b/pyrogram/client.py @@ -592,21 +592,24 @@ async def load_session(self): else: # Needed for migration from storage v2 to v3 if not await self.storage.api_id(): - while True: - try: - value = int(await ainput("Enter the api_id part of the API key: ")) + if self.api_id: + await self.storage.api_id(self.api_id) + else: + while True: + try: + value = int(await ainput("Enter the api_id part of the API key: ")) - if value <= 0: - print("Invalid value") - continue + if value <= 0: + print("Invalid value") + continue - confirm = (await ainput(f'Is "{value}" correct? (y/N): ')).lower() + confirm = (await ainput(f'Is "{value}" correct? (y/N): ')).lower() - if confirm == "y": - await self.storage.api_id(value) - break - except Exception as e: - print(e) + if confirm == "y": + await self.storage.api_id(value) + break + except Exception as e: + print(e) def load_plugins(self): if self.plugins: From 97b6c32c7ff707fd2721338581e7dad5072f745e Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 29 Apr 2022 12:41:06 +0200 Subject: [PATCH 0921/1185] Update Pyrogram to v2.0.16 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 19fdc0cf9a..164d8c60df 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.15" +__version__ = "2.0.16" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From 956e5c1a4f6a7657b325bf873be4666aad1ee25f Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 4 May 2022 09:04:25 +0200 Subject: [PATCH 0922/1185] Clean up on download's stop_transmission and return None --- pyrogram/__init__.py | 2 +- pyrogram/client.py | 39 ++++++++++++++++++++++----------------- 2 files changed, 23 insertions(+), 18 deletions(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 164d8c60df..7dccf1046a 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -23,7 +23,7 @@ from concurrent.futures.thread import ThreadPoolExecutor -class StopTransmission(StopAsyncIteration): +class StopTransmission(Exception): pass diff --git a/pyrogram/client.py b/pyrogram/client.py index c12de07508..c1d04046b2 100644 --- a/pyrogram/client.py +++ b/pyrogram/client.py @@ -727,23 +727,27 @@ def load_plugins(self): async def handle_download(self, packet): file_id, directory, file_name, in_memory, file_size, progress, progress_args = packet - file = BytesIO() if in_memory else tempfile.NamedTemporaryFile("wb", delete=False) - async for chunk in self.get_file(file_id, file_size, 0, 0, progress, progress_args): - file.write(chunk) - - if file and not in_memory: - file_path = os.path.abspath(re.sub("\\\\", "/", os.path.join(directory, file_name))) - os.makedirs(directory, exist_ok=True) - file.close() - shutil.move(file.name, file_path) - - return file_path - - if file and in_memory: - file.name = file_name - return file + try: + async for chunk in self.get_file(file_id, file_size, 0, 0, progress, progress_args): + file.write(chunk) + except pyrogram.StopTransmission: + if not in_memory: + file.close() + os.remove(file.name) + + return None + else: + if in_memory: + file.name = file_name + return file + else: + file_path = os.path.abspath(re.sub("\\\\", "/", os.path.join(directory, file_name))) + os.makedirs(directory, exist_ok=True) + file.close() + shutil.move(file.name, file_path) + return file_path async def get_file( self, @@ -970,9 +974,10 @@ async def get_file( break except Exception as e: raise e + except pyrogram.StopTransmission: + raise except Exception as e: - if not isinstance(e, pyrogram.StopTransmission): - log.error(e, exc_info=True) + log.error(e, exc_info=True) def guess_mime_type(self, filename: str) -> Optional[str]: return self.mimetypes.guess_type(filename)[0] From ec43196df78592ddec6bd85db70315e250857146 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 4 May 2022 09:05:05 +0200 Subject: [PATCH 0923/1185] Update Pyrogram to v2.0.17 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 7dccf1046a..ff0c79d077 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.16" +__version__ = "2.0.17" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From 97a4ddea270325b17f7ce37f0c65ac9edda99acf Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 6 May 2022 21:57:33 +0200 Subject: [PATCH 0924/1185] Update error messages --- compiler/errors/source/400_BAD_REQUEST.tsv | 24 +++++++++++----------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/compiler/errors/source/400_BAD_REQUEST.tsv b/compiler/errors/source/400_BAD_REQUEST.tsv index f3b42fb397..990d875ea8 100644 --- a/compiler/errors/source/400_BAD_REQUEST.tsv +++ b/compiler/errors/source/400_BAD_REQUEST.tsv @@ -4,7 +4,7 @@ ACCESS_TOKEN_EXPIRED The bot token has expired ACCESS_TOKEN_INVALID The bot access token is invalid ADMINS_TOO_MUCH The chat has too many administrators ADMIN_RANK_EMOJI_NOT_ALLOWED Emoji are not allowed in custom administrator titles -ADMIN_RANK_INVALID The custom administrator title is invalid or is longer than 16 characters +ADMIN_RANK_INVALID The custom administrator title is invalid or too long ALBUM_PHOTOS_TOO_MANY Too many photos were included in the album API_ID_INVALID The api_id/api_hash combination is invalid API_ID_PUBLISHED_FLOOD You are using an API key that is limited on the server side because it was published somewhere @@ -35,7 +35,7 @@ BOT_SCORE_NOT_MODIFIED The bot score was not modified BROADCAST_ID_INVALID The channel is invalid BROADCAST_PUBLIC_VOTERS_FORBIDDEN Polls with public voters cannot be sent in channels BROADCAST_REQUIRED The request can only be used with a channel -BUTTON_DATA_INVALID The button callback data contains invalid data or exceeds 64 bytes +BUTTON_DATA_INVALID The button callback data is invalid or too large BUTTON_TYPE_INVALID The type of one of the buttons you provided is invalid BUTTON_URL_INVALID The button url is invalid CALL_ALREADY_ACCEPTED The call is already accepted @@ -49,7 +49,7 @@ CHANNEL_ADD_INVALID Internal error. CHANNEL_BANNED The channel is banned CHANNEL_INVALID The channel parameter is invalid CHANNEL_PRIVATE The channel/supergroup is not accessible -CHANNEL_TOO_LARGE The channel is too large to be deleted; this error is issued when trying to delete channels with more than 1000 members (subject to change) +CHANNEL_TOO_LARGE The channel is too large CHAT_ABOUT_NOT_MODIFIED The chat about text was not modified because you tried to edit it using the same content CHAT_ABOUT_TOO_LONG The chat about text is too long CHAT_ADMIN_REQUIRED The method requires chat admin privileges @@ -109,13 +109,13 @@ FIELD_NAME_EMPTY The field with the name FIELD_NAME is missing FIELD_NAME_INVALID The field with the name FIELD_NAME is invalid FILE_ID_INVALID The file id is invalid FILE_MIGRATE_X The file is in Data Center No. {value} -FILE_PARTS_INVALID Invalid number of parts. The value is not between 1 and 4000 +FILE_PARTS_INVALID Invalid number of parts. FILE_PART_EMPTY The file part sent is empty -FILE_PART_INVALID The file part number is invalid. The value is not between 0 and 3999 +FILE_PART_INVALID The file part number is invalid. FILE_PART_LENGTH_INVALID The length of a file part is invalid FILE_PART_SIZE_CHANGED The part size is different from the size of one of the previous parts in the same file -FILE_PART_SIZE_INVALID 512 KB cannot be evenly divided by part_size -FILE_PART_TOO_BIG The size limit (512 KB) for the content of the file part has been exceeded +FILE_PART_SIZE_INVALID The file part size is invalid +FILE_PART_TOO_BIG The size limit for the content of the file part has been exceeded FILE_PART_X_MISSING Part {value} of the file is missing from storage FILE_REFERENCE_EMPTY The file id contains an empty file reference, you must obtain a valid one by fetching the message from the origin context FILE_REFERENCE_EXPIRED The file id contains an expired file reference, you must obtain a valid one by fetching the message from the origin context @@ -162,7 +162,7 @@ LOCATION_INVALID The file location is invalid MAX_ID_INVALID The max_id parameter is invalid MAX_QTS_INVALID The provided QTS is invalid MD5_CHECKSUM_INVALID The file's checksum did not match the md5_checksum parameter -MEDIA_CAPTION_TOO_LONG The media caption is longer than 1024 characters +MEDIA_CAPTION_TOO_LONG The media caption is too long MEDIA_EMPTY The media you tried to send is invalid MEDIA_INVALID The media is invalid MEDIA_NEW_INVALID The new media to edit the message with is invalid @@ -176,7 +176,7 @@ MESSAGE_IDS_EMPTY The requested message doesn't exist or you provided no message MESSAGE_ID_INVALID The message id is invalid MESSAGE_NOT_MODIFIED The message was not modified because you tried to edit it using the same content MESSAGE_POLL_CLOSED You can't interact with a closed poll -MESSAGE_TOO_LONG The message text is over 4096 characters +MESSAGE_TOO_LONG The message text is too long METHOD_INVALID The API method is invalid and cannot be used MSG_ID_INVALID The message ID used in the peer was invalid MSG_WAIT_FAILED A waiting call returned an error @@ -225,7 +225,7 @@ PHOTO_FILE_MISSING Profile photo file missing PHOTO_ID_INVALID The photo id is invalid PHOTO_INVALID The photo is invalid PHOTO_INVALID_DIMENSIONS The photo dimensions are invalid -PHOTO_SAVE_FILE_INVALID The photo you tried to send cannot be saved by Telegram. A reason may be that it exceeds 10 MB. Try resizing it locally +PHOTO_SAVE_FILE_INVALID The photo you tried to send cannot be saved by Telegram PHOTO_THUMB_URL_EMPTY The photo thumb URL is empty PHOTO_THUMB_URL_INVALID The photo thumb URL is invalid PINNED_DIALOGS_TOO_MUCH Too many pinned dialogs @@ -268,9 +268,9 @@ SCHEDULE_BOT_NOT_ALLOWED Bots are not allowed to schedule messages SCHEDULE_DATE_INVALID Invalid schedule date provided SCHEDULE_DATE_TOO_LATE The date you tried to schedule is too far in the future (more than one year) SCHEDULE_STATUS_PRIVATE You cannot schedule a message until the person comes online if their privacy does not show this information -SCHEDULE_TOO_MUCH You cannot schedule more than 100 messages in this chat +SCHEDULE_TOO_MUCH You tried to schedule too many messages in this chat SEARCH_QUERY_EMPTY The search query is empty -SECONDS_INVALID The seconds interval is invalid, for slow mode try with 0 (off), 10, 30, 60 (1m), 300 (5m), 900 (15m) or 3600 (1h) +SECONDS_INVALID The seconds interval is invalid SEND_MESSAGE_MEDIA_INVALID The message media is invalid SEND_MESSAGE_TYPE_INVALID The message type is invalid SESSION_TOO_FRESH_X You can't do this action because the current session was logged-in recently From ce49fc38a05c193144a02c43cffa880f30ed7c70 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 6 May 2022 22:00:20 +0200 Subject: [PATCH 0925/1185] Fix promote_chat_member when adding bots as admins --- pyrogram/methods/chats/promote_chat_member.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/pyrogram/methods/chats/promote_chat_member.py b/pyrogram/methods/chats/promote_chat_member.py index f8a2779b3b..46ffe44681 100644 --- a/pyrogram/methods/chats/promote_chat_member.py +++ b/pyrogram/methods/chats/promote_chat_member.py @@ -19,7 +19,7 @@ from typing import Union import pyrogram -from pyrogram import raw, types +from pyrogram import raw, types, errors class PromoteChatMember: @@ -61,12 +61,15 @@ async def promote_chat_member( if privileges is None: privileges = types.ChatPrivileges() - raw_chat_member = (await self.invoke( - raw.functions.channels.GetParticipant( - channel=chat_id, - participant=user_id - ) - )).participant + try: + raw_chat_member = (await self.invoke( + raw.functions.channels.GetParticipant( + channel=chat_id, + participant=user_id + ) + )).participant + except errors.RPCError: + raw_chat_member = None rank = None if isinstance(raw_chat_member, raw.types.ChannelParticipantAdmin): From a320a9e7ffbe15f41325bca6aef45f18b93d4557 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 6 May 2022 22:00:41 +0200 Subject: [PATCH 0926/1185] Update Pyrogram to v2.0.18 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index ff0c79d077..599f5ec41b 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.17" +__version__ = "2.0.18" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From f1e4a0ce74ae7ad5bbdd71def5e3f746e72e7249 Mon Sep 17 00:00:00 2001 From: leonardotty <81367045+leonardotty@users.noreply.github.com> Date: Fri, 6 May 2022 22:06:11 +0200 Subject: [PATCH 0927/1185] Update maximum caption length (#989) Co-authored-by: leonardotty Co-authored-by: Dan <14043624+delivrance@users.noreply.github.com> --- pyrogram/methods/messages/copy_media_group.py | 2 +- pyrogram/methods/messages/copy_message.py | 2 +- pyrogram/methods/messages/send_animation.py | 2 +- pyrogram/methods/messages/send_audio.py | 2 +- pyrogram/methods/messages/send_cached_media.py | 2 +- pyrogram/methods/messages/send_document.py | 2 +- pyrogram/methods/messages/send_photo.py | 2 +- pyrogram/methods/messages/send_video.py | 2 +- pyrogram/methods/messages/send_voice.py | 2 +- .../inline_query_result_animation.py | 2 +- .../inline_mode/inline_query_result_audio.py | 2 +- .../inline_query_result_cached_animation.py | 2 +- .../inline_query_result_cached_audio.py | 2 +- .../inline_query_result_cached_document.py | 2 +- .../inline_query_result_cached_photo.py | 2 +- .../inline_query_result_cached_video.py | 2 +- .../inline_query_result_cached_voice.py | 2 +- .../inline_query_result_document.py | 2 +- .../inline_mode/inline_query_result_photo.py | 2 +- .../inline_mode/inline_query_result_video.py | 2 +- .../inline_mode/inline_query_result_voice.py | 2 +- .../types/input_media/input_media_animation.py | 2 +- .../types/input_media/input_media_audio.py | 2 +- .../types/input_media/input_media_document.py | 2 +- .../types/input_media/input_media_photo.py | 2 +- .../types/input_media/input_media_video.py | 2 +- pyrogram/types/messages_and_media/message.py | 18 +++++++++--------- 27 files changed, 35 insertions(+), 35 deletions(-) diff --git a/pyrogram/methods/messages/copy_media_group.py b/pyrogram/methods/messages/copy_media_group.py index de47465d27..981a4ec2cd 100644 --- a/pyrogram/methods/messages/copy_media_group.py +++ b/pyrogram/methods/messages/copy_media_group.py @@ -51,7 +51,7 @@ async def copy_media_group( Message identifier in the chat specified in *from_chat_id*. captions (``str`` | List of ``str`` , *optional*): - New caption for media, 0-1024 characters after entities parsing for each media. + New caption for media, 0-4096 characters after entities parsing for each media. If not specified, the original caption is kept. Pass "" (empty string) to remove the caption. diff --git a/pyrogram/methods/messages/copy_message.py b/pyrogram/methods/messages/copy_message.py index b114a9e0af..c94145e6ec 100644 --- a/pyrogram/methods/messages/copy_message.py +++ b/pyrogram/methods/messages/copy_message.py @@ -66,7 +66,7 @@ async def copy_message( Message identifier in the chat specified in *from_chat_id*. caption (``string``, *optional*): - New caption for media, 0-1024 characters after entities parsing. + New caption for media, 0-4096 characters after entities parsing. If not specified, the original caption is kept. Pass "" (empty string) to remove the caption. diff --git a/pyrogram/methods/messages/send_animation.py b/pyrogram/methods/messages/send_animation.py index efa9cb1671..74b892133f 100644 --- a/pyrogram/methods/messages/send_animation.py +++ b/pyrogram/methods/messages/send_animation.py @@ -73,7 +73,7 @@ async def send_animation( pass a binary file-like object with its attribute ".name" set for in-memory uploads. caption (``str``, *optional*): - Animation caption, 0-1024 characters. + Animation caption, 0-4096 characters. unsave (``bool``, *optional*): By default, the server will save into your own collection any new animation you send. diff --git a/pyrogram/methods/messages/send_audio.py b/pyrogram/methods/messages/send_audio.py index f4552832fe..e0e2493b77 100644 --- a/pyrogram/methods/messages/send_audio.py +++ b/pyrogram/methods/messages/send_audio.py @@ -74,7 +74,7 @@ async def send_audio( pass a binary file-like object with its attribute ".name" set for in-memory uploads. caption (``str``, *optional*): - Audio caption, 0-1024 characters. + Audio caption, 0-4096 characters. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. diff --git a/pyrogram/methods/messages/send_cached_media.py b/pyrogram/methods/messages/send_cached_media.py index f0e04d7232..85040885d1 100644 --- a/pyrogram/methods/messages/send_cached_media.py +++ b/pyrogram/methods/messages/send_cached_media.py @@ -61,7 +61,7 @@ async def send_cached_media( Pass a file_id as string to send a media that exists on the Telegram servers. caption (``str``, *optional*): - Media caption, 0-1024 characters. + Media caption, 0-4096 characters. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. diff --git a/pyrogram/methods/messages/send_document.py b/pyrogram/methods/messages/send_document.py index 99c90fbb25..9c96a03b89 100644 --- a/pyrogram/methods/messages/send_document.py +++ b/pyrogram/methods/messages/send_document.py @@ -76,7 +76,7 @@ async def send_document( Thumbnails can't be reused and can be only uploaded as a new file. caption (``str``, *optional*): - Document caption, 0-1024 characters. + Document caption, 0-4096 characters. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. diff --git a/pyrogram/methods/messages/send_photo.py b/pyrogram/methods/messages/send_photo.py index 836ce1b1d4..3cd32ef26c 100644 --- a/pyrogram/methods/messages/send_photo.py +++ b/pyrogram/methods/messages/send_photo.py @@ -67,7 +67,7 @@ async def send_photo( pass a binary file-like object with its attribute ".name" set for in-memory uploads. caption (``str``, *optional*): - Photo caption, 0-1024 characters. + Photo caption, 0-4096 characters. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. diff --git a/pyrogram/methods/messages/send_video.py b/pyrogram/methods/messages/send_video.py index 669b42ec05..de4150f003 100644 --- a/pyrogram/methods/messages/send_video.py +++ b/pyrogram/methods/messages/send_video.py @@ -74,7 +74,7 @@ async def send_video( pass a binary file-like object with its attribute ".name" set for in-memory uploads. caption (``str``, *optional*): - Video caption, 0-1024 characters. + Video caption, 0-4096 characters. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. diff --git a/pyrogram/methods/messages/send_voice.py b/pyrogram/methods/messages/send_voice.py index 5947ecc23d..e12a0992de 100644 --- a/pyrogram/methods/messages/send_voice.py +++ b/pyrogram/methods/messages/send_voice.py @@ -68,7 +68,7 @@ async def send_voice( pass a binary file-like object with its attribute ".name" set for in-memory uploads. caption (``str``, *optional*): - Voice message caption, 0-1024 characters. + Voice message caption, 0-4096 characters. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. diff --git a/pyrogram/types/inline_mode/inline_query_result_animation.py b/pyrogram/types/inline_mode/inline_query_result_animation.py index 71d1edf40a..4a70b02c0d 100644 --- a/pyrogram/types/inline_mode/inline_query_result_animation.py +++ b/pyrogram/types/inline_mode/inline_query_result_animation.py @@ -60,7 +60,7 @@ class InlineQueryResultAnimation(InlineQueryResult): Title for the result. caption (``str``, *optional*): - Caption of the animation to be sent, 0-1024 characters. + Caption of the animation to be sent, 0-4096 characters. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. diff --git a/pyrogram/types/inline_mode/inline_query_result_audio.py b/pyrogram/types/inline_mode/inline_query_result_audio.py index a39021000f..685b53d46e 100644 --- a/pyrogram/types/inline_mode/inline_query_result_audio.py +++ b/pyrogram/types/inline_mode/inline_query_result_audio.py @@ -48,7 +48,7 @@ class InlineQueryResultAudio(InlineQueryResult): Audio duration in seconds. caption (``str``, *optional*): - Caption of the audio to be sent, 0-1024 characters. + Caption of the audio to be sent, 0-4096 characters. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. diff --git a/pyrogram/types/inline_mode/inline_query_result_cached_animation.py b/pyrogram/types/inline_mode/inline_query_result_cached_animation.py index 63e58ca027..9233fb704f 100644 --- a/pyrogram/types/inline_mode/inline_query_result_cached_animation.py +++ b/pyrogram/types/inline_mode/inline_query_result_cached_animation.py @@ -43,7 +43,7 @@ class InlineQueryResultCachedAnimation(InlineQueryResult): Title for the result. caption (``str``, *optional*): - Caption of the photo to be sent, 0-1024 characters. + Caption of the photo to be sent, 0-4096 characters. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. diff --git a/pyrogram/types/inline_mode/inline_query_result_cached_audio.py b/pyrogram/types/inline_mode/inline_query_result_cached_audio.py index 9535f63343..e31cde8684 100644 --- a/pyrogram/types/inline_mode/inline_query_result_cached_audio.py +++ b/pyrogram/types/inline_mode/inline_query_result_cached_audio.py @@ -39,7 +39,7 @@ class InlineQueryResultCachedAudio(InlineQueryResult): Defaults to a randomly generated UUID4. caption (``str``, *optional*): - Caption of the photo to be sent, 0-1024 characters. + Caption of the photo to be sent, 0-4096 characters. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. diff --git a/pyrogram/types/inline_mode/inline_query_result_cached_document.py b/pyrogram/types/inline_mode/inline_query_result_cached_document.py index 2ab190e7ff..acd7c9dc53 100644 --- a/pyrogram/types/inline_mode/inline_query_result_cached_document.py +++ b/pyrogram/types/inline_mode/inline_query_result_cached_document.py @@ -45,7 +45,7 @@ class InlineQueryResultCachedDocument(InlineQueryResult): Short description of the result. caption (``str``, *optional*): - Caption of the photo to be sent, 0-1024 characters. + Caption of the photo to be sent, 0-4096 characters. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. diff --git a/pyrogram/types/inline_mode/inline_query_result_cached_photo.py b/pyrogram/types/inline_mode/inline_query_result_cached_photo.py index 2e01d344ec..142085ac37 100644 --- a/pyrogram/types/inline_mode/inline_query_result_cached_photo.py +++ b/pyrogram/types/inline_mode/inline_query_result_cached_photo.py @@ -45,7 +45,7 @@ class InlineQueryResultCachedPhoto(InlineQueryResult): Short description of the result. caption (``str``, *optional*): - Caption of the photo to be sent, 0-1024 characters. + Caption of the photo to be sent, 0-4096 characters. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. diff --git a/pyrogram/types/inline_mode/inline_query_result_cached_video.py b/pyrogram/types/inline_mode/inline_query_result_cached_video.py index 00ea32ecdb..999386357a 100644 --- a/pyrogram/types/inline_mode/inline_query_result_cached_video.py +++ b/pyrogram/types/inline_mode/inline_query_result_cached_video.py @@ -46,7 +46,7 @@ class InlineQueryResultCachedVideo(InlineQueryResult): Short description of the result. caption (``str``, *optional*): - Caption of the photo to be sent, 0-1024 characters. + Caption of the photo to be sent, 0-4096 characters. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. diff --git a/pyrogram/types/inline_mode/inline_query_result_cached_voice.py b/pyrogram/types/inline_mode/inline_query_result_cached_voice.py index cc2bd76855..8ca7de4690 100644 --- a/pyrogram/types/inline_mode/inline_query_result_cached_voice.py +++ b/pyrogram/types/inline_mode/inline_query_result_cached_voice.py @@ -43,7 +43,7 @@ class InlineQueryResultCachedVoice(InlineQueryResult): Title for the result. caption (``str``, *optional*): - Caption of the photo to be sent, 0-1024 characters. + Caption of the photo to be sent, 0-4096 characters. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. diff --git a/pyrogram/types/inline_mode/inline_query_result_document.py b/pyrogram/types/inline_mode/inline_query_result_document.py index eac7901b14..517c7523b5 100644 --- a/pyrogram/types/inline_mode/inline_query_result_document.py +++ b/pyrogram/types/inline_mode/inline_query_result_document.py @@ -45,7 +45,7 @@ class InlineQueryResultDocument(InlineQueryResult): Defaults to a randomly generated UUID4. caption (``str``, *optional*): - Caption of the video to be sent, 0-1024 characters. + Caption of the video to be sent, 0-4096 characters. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. diff --git a/pyrogram/types/inline_mode/inline_query_result_photo.py b/pyrogram/types/inline_mode/inline_query_result_photo.py index d75ccac2a5..434b57b363 100644 --- a/pyrogram/types/inline_mode/inline_query_result_photo.py +++ b/pyrogram/types/inline_mode/inline_query_result_photo.py @@ -56,7 +56,7 @@ class InlineQueryResultPhoto(InlineQueryResult): Short description of the result. caption (``str``, *optional*): - Caption of the photo to be sent, 0-1024 characters. + Caption of the photo to be sent, 0-4096 characters. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. diff --git a/pyrogram/types/inline_mode/inline_query_result_video.py b/pyrogram/types/inline_mode/inline_query_result_video.py index 5f71111f4f..82a9368105 100644 --- a/pyrogram/types/inline_mode/inline_query_result_video.py +++ b/pyrogram/types/inline_mode/inline_query_result_video.py @@ -61,7 +61,7 @@ class InlineQueryResultVideo(InlineQueryResult): Short description of the result. caption (``str``, *optional*): - Caption of the video to be sent, 0-1024 characters. + Caption of the video to be sent, 0-4096 characters. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. diff --git a/pyrogram/types/inline_mode/inline_query_result_voice.py b/pyrogram/types/inline_mode/inline_query_result_voice.py index 31b422f8d3..7a8d18ed71 100644 --- a/pyrogram/types/inline_mode/inline_query_result_voice.py +++ b/pyrogram/types/inline_mode/inline_query_result_voice.py @@ -45,7 +45,7 @@ class InlineQueryResultVoice(InlineQueryResult): Recording duration in seconds. caption (``str``, *optional*): - Caption of the audio to be sent, 0-1024 characters. + Caption of the audio to be sent, 0-4096 characters. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. diff --git a/pyrogram/types/input_media/input_media_animation.py b/pyrogram/types/input_media/input_media_animation.py index 04aa940edc..39cfb5b45d 100644 --- a/pyrogram/types/input_media/input_media_animation.py +++ b/pyrogram/types/input_media/input_media_animation.py @@ -41,7 +41,7 @@ class InputMediaAnimation(InputMedia): Thumbnails can't be reused and can be only uploaded as a new file. caption (``str``, *optional*): - Caption of the animation to be sent, 0-1024 characters. + Caption of the animation to be sent, 0-4096 characters. If not specified, the original caption is kept. Pass "" (empty string) to remove the caption. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): diff --git a/pyrogram/types/input_media/input_media_audio.py b/pyrogram/types/input_media/input_media_audio.py index b4bb7575be..6ab4fe0ef3 100644 --- a/pyrogram/types/input_media/input_media_audio.py +++ b/pyrogram/types/input_media/input_media_audio.py @@ -43,7 +43,7 @@ class InputMediaAudio(InputMedia): Thumbnails can't be reused and can be only uploaded as a new file. caption (``str``, *optional*): - Caption of the audio to be sent, 0-1024 characters. + Caption of the audio to be sent, 0-4096 characters. If not specified, the original caption is kept. Pass "" (empty string) to remove the caption. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): diff --git a/pyrogram/types/input_media/input_media_document.py b/pyrogram/types/input_media/input_media_document.py index 91dfc7d673..3e5a0f5788 100644 --- a/pyrogram/types/input_media/input_media_document.py +++ b/pyrogram/types/input_media/input_media_document.py @@ -41,7 +41,7 @@ class InputMediaDocument(InputMedia): Thumbnails can't be reused and can be only uploaded as a new file. caption (``str``, *optional*): - Caption of the document to be sent, 0-1024 characters. + Caption of the document to be sent, 0-4096 characters. If not specified, the original caption is kept. Pass "" (empty string) to remove the caption. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): diff --git a/pyrogram/types/input_media/input_media_photo.py b/pyrogram/types/input_media/input_media_photo.py index ce8b41a218..178ee3d420 100644 --- a/pyrogram/types/input_media/input_media_photo.py +++ b/pyrogram/types/input_media/input_media_photo.py @@ -36,7 +36,7 @@ class InputMediaPhoto(InputMedia): pass an HTTP URL as a string for Telegram to get a photo from the Internet. caption (``str``, *optional*): - Caption of the photo to be sent, 0-1024 characters. + Caption of the photo to be sent, 0-4096 characters. If not specified, the original caption is kept. Pass "" (empty string) to remove the caption. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): diff --git a/pyrogram/types/input_media/input_media_video.py b/pyrogram/types/input_media/input_media_video.py index bad4e3ef3a..130129ab60 100644 --- a/pyrogram/types/input_media/input_media_video.py +++ b/pyrogram/types/input_media/input_media_video.py @@ -42,7 +42,7 @@ class InputMediaVideo(InputMedia): Thumbnails can't be reused and can be only uploaded as a new file. caption (``str``, *optional*): - Caption of the video to be sent, 0-1024 characters. + Caption of the video to be sent, 0-4096 characters. If not specified, the original caption is kept. Pass "" (empty string) to remove the caption. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index ab5e202f2a..1ffdeafdb3 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -177,7 +177,7 @@ class Message(Object, Update): Message is a video note, information about the video message. caption (``str``, *optional*): - Caption for the audio, document, photo, video or voice, 0-1024 characters. + Caption for the audio, document, photo, video or voice, 0-4096 characters. If the message contains caption entities (bold, italic, ...) you can access *caption.markdown* or *caption.html* to get the marked up caption text. In case there is no caption entity, the fields will contain the same text as *caption*. @@ -1022,7 +1022,7 @@ async def reply_animation( Defaults to ``True`` in group chats and ``False`` in private chats. caption (``str``, *optional*): - Animation caption, 0-1024 characters. + Animation caption, 0-4096 characters. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. @@ -1161,7 +1161,7 @@ async def reply_audio( Defaults to ``True`` in group chats and ``False`` in private chats. caption (``str``, *optional*): - Audio caption, 0-1024 characters. + Audio caption, 0-4096 characters. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. @@ -1292,7 +1292,7 @@ async def reply_cached_media( Defaults to ``True`` in group chats and ``False`` in private chats. caption (``bool``, *optional*): - Media caption, 0-1024 characters. + Media caption, 0-4096 characters. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. @@ -1514,7 +1514,7 @@ async def reply_document( Thumbnails can't be reused and can be only uploaded as a new file. caption (``str``, *optional*): - Document caption, 0-1024 characters. + Document caption, 0-4096 characters. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. @@ -1916,7 +1916,7 @@ async def reply_photo( Defaults to ``True`` in group chats and ``False`` in private chats. caption (``str``, *optional*): - Photo caption, 0-1024 characters. + Photo caption, 0-4096 characters. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. @@ -2387,7 +2387,7 @@ async def reply_video( Defaults to ``True`` in group chats and ``False`` in private chats. caption (``str``, *optional*): - Video caption, 0-1024 characters. + Video caption, 0-4096 characters. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. @@ -2651,7 +2651,7 @@ async def reply_voice( Defaults to ``True`` in group chats and ``False`` in private chats. caption (``str``, *optional*): - Voice message caption, 0-1024 characters. + Voice message caption, 0-4096 characters. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. @@ -3007,7 +3007,7 @@ async def copy( For a contact that exists in your Telegram address book you can use his phone number (str). caption (``string``, *optional*): - New caption for media, 0-1024 characters after entities parsing. + New caption for media, 0-4096 characters after entities parsing. If not specified, the original caption is kept. Pass "" (empty string) to remove the caption. From 0bc340081fefcaead193a046a804af8eca683787 Mon Sep 17 00:00:00 2001 From: Moshe <62907797+moshe-coh@users.noreply.github.com> Date: Fri, 6 May 2022 23:08:23 +0300 Subject: [PATCH 0928/1185] Fix wrong enum usage (#988) --- pyrogram/types/user_and_chats/chat_event.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/types/user_and_chats/chat_event.py b/pyrogram/types/user_and_chats/chat_event.py index a5292c6418..88ff42f343 100644 --- a/pyrogram/types/user_and_chats/chat_event.py +++ b/pyrogram/types/user_and_chats/chat_event.py @@ -369,7 +369,7 @@ async def _parse( elif isinstance(action, raw.types.ChannelAdminLogEventActionParticipantToggleAdmin): old_administrator_privileges = types.ChatMember._parse(client, action.prev_participant, users, chats) new_administrator_privileges = types.ChatMember._parse(client, action.new_participant, users, chats) - action = enums.ChatEventAction.ADMIN_RIGHTS_CHANGED + action = enums.ChatEventAction.ADMINISTRATOR_PRIVILEGES_CHANGED elif isinstance(action, raw.types.ChannelAdminLogEventActionParticipantToggleBan): old_member_permissions = types.ChatMember._parse(client, action.prev_participant, users, chats) From e708f8dabfd39e37c4d4e820a67abb654c38b03e Mon Sep 17 00:00:00 2001 From: Stark Programmer <88478059+StarkBotsIndustries@users.noreply.github.com> Date: Sat, 7 May 2022 01:38:52 +0530 Subject: [PATCH 0929/1185] Add missing parameters to Chat.set_photo (#980) --- pyrogram/methods/chats/set_chat_photo.py | 4 +-- pyrogram/types/user_and_chats/chat.py | 41 ++++++++++++++++++++---- 2 files changed, 37 insertions(+), 8 deletions(-) diff --git a/pyrogram/methods/chats/set_chat_photo.py b/pyrogram/methods/chats/set_chat_photo.py index dd44bb872c..615eca253e 100644 --- a/pyrogram/methods/chats/set_chat_photo.py +++ b/pyrogram/methods/chats/set_chat_photo.py @@ -70,14 +70,14 @@ async def set_chat_photo( # Set chat photo using a local file await app.set_chat_photo(chat_id, photo="photo.jpg") - # Set chat photo using an exiting Photo file_id + # Set chat photo using an existing Photo file_id await app.set_chat_photo(chat_id, photo=photo.file_id) # Set chat video using a local file await app.set_chat_photo(chat_id, video="video.mp4") - # Set chat photo using an exiting Video file_id + # Set chat photo using an existing Video file_id await app.set_chat_photo(chat_id, video=video.file_id) """ peer = await self.resolve_peer(chat_id) diff --git a/pyrogram/types/user_and_chats/chat.py b/pyrogram/types/user_and_chats/chat.py index b726d96e84..dc345bbc63 100644 --- a/pyrogram/types/user_and_chats/chat.py +++ b/pyrogram/types/user_and_chats/chat.py @@ -17,7 +17,7 @@ # along with Pyrogram. If not, see . from datetime import datetime -from typing import Union, List, Optional, AsyncGenerator +from typing import Union, List, Optional, AsyncGenerator, BinaryIO import pyrogram from pyrogram import raw, enums @@ -477,7 +477,13 @@ async def set_description(self, description: str) -> bool: description=description ) - async def set_photo(self, photo: str) -> bool: + async def set_photo( + self, + *, + photo: Union[str, BinaryIO] = None, + video: Union[str, BinaryIO] = None, + video_start_ts: float = None, + ) -> bool: """Bound method *set_photo* of :obj:`~pyrogram.types.Chat`. Use as a shortcut for: @@ -492,11 +498,32 @@ async def set_photo(self, photo: str) -> bool: Example: .. code-block:: python - await chat.set_photo("photo.png") + # Set chat photo using a local file + await chat.set_photo(photo="photo.jpg") + + # Set chat photo using an existing Photo file_id + await chat.set_photo(photo=photo.file_id) + + + # Set chat video using a local file + await chat.set_photo(video="video.mp4") + + # Set chat photo using an existing Video file_id + await chat.set_photo(video=video.file_id) Parameters: - photo (``str``): - New chat photo. You can pass a :obj:`~pyrogram.types.Photo` id or a file path to upload a new photo. + photo (``str`` | ``BinaryIO``, *optional*): + New chat photo. You can pass a :obj:`~pyrogram.types.Photo` file_id, a file path to upload a new photo + from your local machine or a binary file-like object with its attribute + ".name" set for in-memory uploads. + + video (``str`` | ``BinaryIO``, *optional*): + New chat video. You can pass a :obj:`~pyrogram.types.Video` file_id, a file path to upload a new video + from your local machine or a binary file-like object with its attribute + ".name" set for in-memory uploads. + + video_start_ts (``float``, *optional*): + The timestamp in seconds of the video frame to use as photo profile preview. Returns: ``bool``: True on success. @@ -508,7 +535,9 @@ async def set_photo(self, photo: str) -> bool: return await self._client.set_chat_photo( chat_id=self.id, - photo=photo + photo=photo, + video=video, + video_start_ts=video_start_ts ) async def ban_member( From 4916b02d3e39c6202e824a83430c1327bbc52133 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 6 May 2022 22:09:31 +0200 Subject: [PATCH 0930/1185] Update Pyrogram to v2.0.19 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 599f5ec41b..0be47e5f44 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.18" +__version__ = "2.0.19" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From 6cf849c3eacd58d7befd3b357952121d5c80b7ba Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 7 May 2022 12:01:14 +0200 Subject: [PATCH 0931/1185] Revert "Update maximum caption length (#989)" This reverts commit f1e4a0ce74ae7ad5bbdd71def5e3f746e72e7249. --- pyrogram/methods/messages/copy_media_group.py | 2 +- pyrogram/methods/messages/copy_message.py | 2 +- pyrogram/methods/messages/send_animation.py | 2 +- pyrogram/methods/messages/send_audio.py | 2 +- pyrogram/methods/messages/send_cached_media.py | 2 +- pyrogram/methods/messages/send_document.py | 2 +- pyrogram/methods/messages/send_photo.py | 2 +- pyrogram/methods/messages/send_video.py | 2 +- pyrogram/methods/messages/send_voice.py | 2 +- .../inline_query_result_animation.py | 2 +- .../inline_mode/inline_query_result_audio.py | 2 +- .../inline_query_result_cached_animation.py | 2 +- .../inline_query_result_cached_audio.py | 2 +- .../inline_query_result_cached_document.py | 2 +- .../inline_query_result_cached_photo.py | 2 +- .../inline_query_result_cached_video.py | 2 +- .../inline_query_result_cached_voice.py | 2 +- .../inline_query_result_document.py | 2 +- .../inline_mode/inline_query_result_photo.py | 2 +- .../inline_mode/inline_query_result_video.py | 2 +- .../inline_mode/inline_query_result_voice.py | 2 +- .../types/input_media/input_media_animation.py | 2 +- .../types/input_media/input_media_audio.py | 2 +- .../types/input_media/input_media_document.py | 2 +- .../types/input_media/input_media_photo.py | 2 +- .../types/input_media/input_media_video.py | 2 +- pyrogram/types/messages_and_media/message.py | 18 +++++++++--------- 27 files changed, 35 insertions(+), 35 deletions(-) diff --git a/pyrogram/methods/messages/copy_media_group.py b/pyrogram/methods/messages/copy_media_group.py index 981a4ec2cd..de47465d27 100644 --- a/pyrogram/methods/messages/copy_media_group.py +++ b/pyrogram/methods/messages/copy_media_group.py @@ -51,7 +51,7 @@ async def copy_media_group( Message identifier in the chat specified in *from_chat_id*. captions (``str`` | List of ``str`` , *optional*): - New caption for media, 0-4096 characters after entities parsing for each media. + New caption for media, 0-1024 characters after entities parsing for each media. If not specified, the original caption is kept. Pass "" (empty string) to remove the caption. diff --git a/pyrogram/methods/messages/copy_message.py b/pyrogram/methods/messages/copy_message.py index c94145e6ec..b114a9e0af 100644 --- a/pyrogram/methods/messages/copy_message.py +++ b/pyrogram/methods/messages/copy_message.py @@ -66,7 +66,7 @@ async def copy_message( Message identifier in the chat specified in *from_chat_id*. caption (``string``, *optional*): - New caption for media, 0-4096 characters after entities parsing. + New caption for media, 0-1024 characters after entities parsing. If not specified, the original caption is kept. Pass "" (empty string) to remove the caption. diff --git a/pyrogram/methods/messages/send_animation.py b/pyrogram/methods/messages/send_animation.py index 74b892133f..efa9cb1671 100644 --- a/pyrogram/methods/messages/send_animation.py +++ b/pyrogram/methods/messages/send_animation.py @@ -73,7 +73,7 @@ async def send_animation( pass a binary file-like object with its attribute ".name" set for in-memory uploads. caption (``str``, *optional*): - Animation caption, 0-4096 characters. + Animation caption, 0-1024 characters. unsave (``bool``, *optional*): By default, the server will save into your own collection any new animation you send. diff --git a/pyrogram/methods/messages/send_audio.py b/pyrogram/methods/messages/send_audio.py index e0e2493b77..f4552832fe 100644 --- a/pyrogram/methods/messages/send_audio.py +++ b/pyrogram/methods/messages/send_audio.py @@ -74,7 +74,7 @@ async def send_audio( pass a binary file-like object with its attribute ".name" set for in-memory uploads. caption (``str``, *optional*): - Audio caption, 0-4096 characters. + Audio caption, 0-1024 characters. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. diff --git a/pyrogram/methods/messages/send_cached_media.py b/pyrogram/methods/messages/send_cached_media.py index 85040885d1..f0e04d7232 100644 --- a/pyrogram/methods/messages/send_cached_media.py +++ b/pyrogram/methods/messages/send_cached_media.py @@ -61,7 +61,7 @@ async def send_cached_media( Pass a file_id as string to send a media that exists on the Telegram servers. caption (``str``, *optional*): - Media caption, 0-4096 characters. + Media caption, 0-1024 characters. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. diff --git a/pyrogram/methods/messages/send_document.py b/pyrogram/methods/messages/send_document.py index 9c96a03b89..99c90fbb25 100644 --- a/pyrogram/methods/messages/send_document.py +++ b/pyrogram/methods/messages/send_document.py @@ -76,7 +76,7 @@ async def send_document( Thumbnails can't be reused and can be only uploaded as a new file. caption (``str``, *optional*): - Document caption, 0-4096 characters. + Document caption, 0-1024 characters. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. diff --git a/pyrogram/methods/messages/send_photo.py b/pyrogram/methods/messages/send_photo.py index 3cd32ef26c..836ce1b1d4 100644 --- a/pyrogram/methods/messages/send_photo.py +++ b/pyrogram/methods/messages/send_photo.py @@ -67,7 +67,7 @@ async def send_photo( pass a binary file-like object with its attribute ".name" set for in-memory uploads. caption (``str``, *optional*): - Photo caption, 0-4096 characters. + Photo caption, 0-1024 characters. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. diff --git a/pyrogram/methods/messages/send_video.py b/pyrogram/methods/messages/send_video.py index de4150f003..669b42ec05 100644 --- a/pyrogram/methods/messages/send_video.py +++ b/pyrogram/methods/messages/send_video.py @@ -74,7 +74,7 @@ async def send_video( pass a binary file-like object with its attribute ".name" set for in-memory uploads. caption (``str``, *optional*): - Video caption, 0-4096 characters. + Video caption, 0-1024 characters. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. diff --git a/pyrogram/methods/messages/send_voice.py b/pyrogram/methods/messages/send_voice.py index e12a0992de..5947ecc23d 100644 --- a/pyrogram/methods/messages/send_voice.py +++ b/pyrogram/methods/messages/send_voice.py @@ -68,7 +68,7 @@ async def send_voice( pass a binary file-like object with its attribute ".name" set for in-memory uploads. caption (``str``, *optional*): - Voice message caption, 0-4096 characters. + Voice message caption, 0-1024 characters. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. diff --git a/pyrogram/types/inline_mode/inline_query_result_animation.py b/pyrogram/types/inline_mode/inline_query_result_animation.py index 4a70b02c0d..71d1edf40a 100644 --- a/pyrogram/types/inline_mode/inline_query_result_animation.py +++ b/pyrogram/types/inline_mode/inline_query_result_animation.py @@ -60,7 +60,7 @@ class InlineQueryResultAnimation(InlineQueryResult): Title for the result. caption (``str``, *optional*): - Caption of the animation to be sent, 0-4096 characters. + Caption of the animation to be sent, 0-1024 characters. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. diff --git a/pyrogram/types/inline_mode/inline_query_result_audio.py b/pyrogram/types/inline_mode/inline_query_result_audio.py index 685b53d46e..a39021000f 100644 --- a/pyrogram/types/inline_mode/inline_query_result_audio.py +++ b/pyrogram/types/inline_mode/inline_query_result_audio.py @@ -48,7 +48,7 @@ class InlineQueryResultAudio(InlineQueryResult): Audio duration in seconds. caption (``str``, *optional*): - Caption of the audio to be sent, 0-4096 characters. + Caption of the audio to be sent, 0-1024 characters. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. diff --git a/pyrogram/types/inline_mode/inline_query_result_cached_animation.py b/pyrogram/types/inline_mode/inline_query_result_cached_animation.py index 9233fb704f..63e58ca027 100644 --- a/pyrogram/types/inline_mode/inline_query_result_cached_animation.py +++ b/pyrogram/types/inline_mode/inline_query_result_cached_animation.py @@ -43,7 +43,7 @@ class InlineQueryResultCachedAnimation(InlineQueryResult): Title for the result. caption (``str``, *optional*): - Caption of the photo to be sent, 0-4096 characters. + Caption of the photo to be sent, 0-1024 characters. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. diff --git a/pyrogram/types/inline_mode/inline_query_result_cached_audio.py b/pyrogram/types/inline_mode/inline_query_result_cached_audio.py index e31cde8684..9535f63343 100644 --- a/pyrogram/types/inline_mode/inline_query_result_cached_audio.py +++ b/pyrogram/types/inline_mode/inline_query_result_cached_audio.py @@ -39,7 +39,7 @@ class InlineQueryResultCachedAudio(InlineQueryResult): Defaults to a randomly generated UUID4. caption (``str``, *optional*): - Caption of the photo to be sent, 0-4096 characters. + Caption of the photo to be sent, 0-1024 characters. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. diff --git a/pyrogram/types/inline_mode/inline_query_result_cached_document.py b/pyrogram/types/inline_mode/inline_query_result_cached_document.py index acd7c9dc53..2ab190e7ff 100644 --- a/pyrogram/types/inline_mode/inline_query_result_cached_document.py +++ b/pyrogram/types/inline_mode/inline_query_result_cached_document.py @@ -45,7 +45,7 @@ class InlineQueryResultCachedDocument(InlineQueryResult): Short description of the result. caption (``str``, *optional*): - Caption of the photo to be sent, 0-4096 characters. + Caption of the photo to be sent, 0-1024 characters. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. diff --git a/pyrogram/types/inline_mode/inline_query_result_cached_photo.py b/pyrogram/types/inline_mode/inline_query_result_cached_photo.py index 142085ac37..2e01d344ec 100644 --- a/pyrogram/types/inline_mode/inline_query_result_cached_photo.py +++ b/pyrogram/types/inline_mode/inline_query_result_cached_photo.py @@ -45,7 +45,7 @@ class InlineQueryResultCachedPhoto(InlineQueryResult): Short description of the result. caption (``str``, *optional*): - Caption of the photo to be sent, 0-4096 characters. + Caption of the photo to be sent, 0-1024 characters. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. diff --git a/pyrogram/types/inline_mode/inline_query_result_cached_video.py b/pyrogram/types/inline_mode/inline_query_result_cached_video.py index 999386357a..00ea32ecdb 100644 --- a/pyrogram/types/inline_mode/inline_query_result_cached_video.py +++ b/pyrogram/types/inline_mode/inline_query_result_cached_video.py @@ -46,7 +46,7 @@ class InlineQueryResultCachedVideo(InlineQueryResult): Short description of the result. caption (``str``, *optional*): - Caption of the photo to be sent, 0-4096 characters. + Caption of the photo to be sent, 0-1024 characters. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. diff --git a/pyrogram/types/inline_mode/inline_query_result_cached_voice.py b/pyrogram/types/inline_mode/inline_query_result_cached_voice.py index 8ca7de4690..cc2bd76855 100644 --- a/pyrogram/types/inline_mode/inline_query_result_cached_voice.py +++ b/pyrogram/types/inline_mode/inline_query_result_cached_voice.py @@ -43,7 +43,7 @@ class InlineQueryResultCachedVoice(InlineQueryResult): Title for the result. caption (``str``, *optional*): - Caption of the photo to be sent, 0-4096 characters. + Caption of the photo to be sent, 0-1024 characters. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. diff --git a/pyrogram/types/inline_mode/inline_query_result_document.py b/pyrogram/types/inline_mode/inline_query_result_document.py index 517c7523b5..eac7901b14 100644 --- a/pyrogram/types/inline_mode/inline_query_result_document.py +++ b/pyrogram/types/inline_mode/inline_query_result_document.py @@ -45,7 +45,7 @@ class InlineQueryResultDocument(InlineQueryResult): Defaults to a randomly generated UUID4. caption (``str``, *optional*): - Caption of the video to be sent, 0-4096 characters. + Caption of the video to be sent, 0-1024 characters. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. diff --git a/pyrogram/types/inline_mode/inline_query_result_photo.py b/pyrogram/types/inline_mode/inline_query_result_photo.py index 434b57b363..d75ccac2a5 100644 --- a/pyrogram/types/inline_mode/inline_query_result_photo.py +++ b/pyrogram/types/inline_mode/inline_query_result_photo.py @@ -56,7 +56,7 @@ class InlineQueryResultPhoto(InlineQueryResult): Short description of the result. caption (``str``, *optional*): - Caption of the photo to be sent, 0-4096 characters. + Caption of the photo to be sent, 0-1024 characters. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. diff --git a/pyrogram/types/inline_mode/inline_query_result_video.py b/pyrogram/types/inline_mode/inline_query_result_video.py index 82a9368105..5f71111f4f 100644 --- a/pyrogram/types/inline_mode/inline_query_result_video.py +++ b/pyrogram/types/inline_mode/inline_query_result_video.py @@ -61,7 +61,7 @@ class InlineQueryResultVideo(InlineQueryResult): Short description of the result. caption (``str``, *optional*): - Caption of the video to be sent, 0-4096 characters. + Caption of the video to be sent, 0-1024 characters. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. diff --git a/pyrogram/types/inline_mode/inline_query_result_voice.py b/pyrogram/types/inline_mode/inline_query_result_voice.py index 7a8d18ed71..31b422f8d3 100644 --- a/pyrogram/types/inline_mode/inline_query_result_voice.py +++ b/pyrogram/types/inline_mode/inline_query_result_voice.py @@ -45,7 +45,7 @@ class InlineQueryResultVoice(InlineQueryResult): Recording duration in seconds. caption (``str``, *optional*): - Caption of the audio to be sent, 0-4096 characters. + Caption of the audio to be sent, 0-1024 characters. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. diff --git a/pyrogram/types/input_media/input_media_animation.py b/pyrogram/types/input_media/input_media_animation.py index 39cfb5b45d..04aa940edc 100644 --- a/pyrogram/types/input_media/input_media_animation.py +++ b/pyrogram/types/input_media/input_media_animation.py @@ -41,7 +41,7 @@ class InputMediaAnimation(InputMedia): Thumbnails can't be reused and can be only uploaded as a new file. caption (``str``, *optional*): - Caption of the animation to be sent, 0-4096 characters. + Caption of the animation to be sent, 0-1024 characters. If not specified, the original caption is kept. Pass "" (empty string) to remove the caption. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): diff --git a/pyrogram/types/input_media/input_media_audio.py b/pyrogram/types/input_media/input_media_audio.py index 6ab4fe0ef3..b4bb7575be 100644 --- a/pyrogram/types/input_media/input_media_audio.py +++ b/pyrogram/types/input_media/input_media_audio.py @@ -43,7 +43,7 @@ class InputMediaAudio(InputMedia): Thumbnails can't be reused and can be only uploaded as a new file. caption (``str``, *optional*): - Caption of the audio to be sent, 0-4096 characters. + Caption of the audio to be sent, 0-1024 characters. If not specified, the original caption is kept. Pass "" (empty string) to remove the caption. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): diff --git a/pyrogram/types/input_media/input_media_document.py b/pyrogram/types/input_media/input_media_document.py index 3e5a0f5788..91dfc7d673 100644 --- a/pyrogram/types/input_media/input_media_document.py +++ b/pyrogram/types/input_media/input_media_document.py @@ -41,7 +41,7 @@ class InputMediaDocument(InputMedia): Thumbnails can't be reused and can be only uploaded as a new file. caption (``str``, *optional*): - Caption of the document to be sent, 0-4096 characters. + Caption of the document to be sent, 0-1024 characters. If not specified, the original caption is kept. Pass "" (empty string) to remove the caption. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): diff --git a/pyrogram/types/input_media/input_media_photo.py b/pyrogram/types/input_media/input_media_photo.py index 178ee3d420..ce8b41a218 100644 --- a/pyrogram/types/input_media/input_media_photo.py +++ b/pyrogram/types/input_media/input_media_photo.py @@ -36,7 +36,7 @@ class InputMediaPhoto(InputMedia): pass an HTTP URL as a string for Telegram to get a photo from the Internet. caption (``str``, *optional*): - Caption of the photo to be sent, 0-4096 characters. + Caption of the photo to be sent, 0-1024 characters. If not specified, the original caption is kept. Pass "" (empty string) to remove the caption. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): diff --git a/pyrogram/types/input_media/input_media_video.py b/pyrogram/types/input_media/input_media_video.py index 130129ab60..bad4e3ef3a 100644 --- a/pyrogram/types/input_media/input_media_video.py +++ b/pyrogram/types/input_media/input_media_video.py @@ -42,7 +42,7 @@ class InputMediaVideo(InputMedia): Thumbnails can't be reused and can be only uploaded as a new file. caption (``str``, *optional*): - Caption of the video to be sent, 0-4096 characters. + Caption of the video to be sent, 0-1024 characters. If not specified, the original caption is kept. Pass "" (empty string) to remove the caption. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index 1ffdeafdb3..ab5e202f2a 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -177,7 +177,7 @@ class Message(Object, Update): Message is a video note, information about the video message. caption (``str``, *optional*): - Caption for the audio, document, photo, video or voice, 0-4096 characters. + Caption for the audio, document, photo, video or voice, 0-1024 characters. If the message contains caption entities (bold, italic, ...) you can access *caption.markdown* or *caption.html* to get the marked up caption text. In case there is no caption entity, the fields will contain the same text as *caption*. @@ -1022,7 +1022,7 @@ async def reply_animation( Defaults to ``True`` in group chats and ``False`` in private chats. caption (``str``, *optional*): - Animation caption, 0-4096 characters. + Animation caption, 0-1024 characters. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. @@ -1161,7 +1161,7 @@ async def reply_audio( Defaults to ``True`` in group chats and ``False`` in private chats. caption (``str``, *optional*): - Audio caption, 0-4096 characters. + Audio caption, 0-1024 characters. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. @@ -1292,7 +1292,7 @@ async def reply_cached_media( Defaults to ``True`` in group chats and ``False`` in private chats. caption (``bool``, *optional*): - Media caption, 0-4096 characters. + Media caption, 0-1024 characters. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. @@ -1514,7 +1514,7 @@ async def reply_document( Thumbnails can't be reused and can be only uploaded as a new file. caption (``str``, *optional*): - Document caption, 0-4096 characters. + Document caption, 0-1024 characters. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. @@ -1916,7 +1916,7 @@ async def reply_photo( Defaults to ``True`` in group chats and ``False`` in private chats. caption (``str``, *optional*): - Photo caption, 0-4096 characters. + Photo caption, 0-1024 characters. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. @@ -2387,7 +2387,7 @@ async def reply_video( Defaults to ``True`` in group chats and ``False`` in private chats. caption (``str``, *optional*): - Video caption, 0-4096 characters. + Video caption, 0-1024 characters. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. @@ -2651,7 +2651,7 @@ async def reply_voice( Defaults to ``True`` in group chats and ``False`` in private chats. caption (``str``, *optional*): - Voice message caption, 0-4096 characters. + Voice message caption, 0-1024 characters. parse_mode (:obj:`~pyrogram.enums.ParseMode`, *optional*): By default, texts are parsed using both Markdown and HTML styles. @@ -3007,7 +3007,7 @@ async def copy( For a contact that exists in your Telegram address book you can use his phone number (str). caption (``string``, *optional*): - New caption for media, 0-4096 characters after entities parsing. + New caption for media, 0-1024 characters after entities parsing. If not specified, the original caption is kept. Pass "" (empty string) to remove the caption. From 6fdf9a60f69800af00b676c8eb68b925bdf12f93 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 7 May 2022 12:04:22 +0200 Subject: [PATCH 0932/1185] Update example --- docs/source/faq/how-to-avoid-flood-waits.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/faq/how-to-avoid-flood-waits.rst b/docs/source/faq/how-to-avoid-flood-waits.rst index 0736e576f2..4d2994d1f4 100644 --- a/docs/source/faq/how-to-avoid-flood-waits.rst +++ b/docs/source/faq/how-to-avoid-flood-waits.rst @@ -16,7 +16,7 @@ The following shows how to catch the exception in your code and wait the require try: ... # Your code except FloodWait as e: - await asyncio.sleep(e.x) # Wait "x" seconds before continuing + await asyncio.sleep(e.value) # Wait "value" seconds before continuing ... From 8961da7a2ce0a68900d606d2fb23a3590502353a Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 13 May 2022 11:56:42 +0200 Subject: [PATCH 0933/1185] Update API schema to Layer 142 --- compiler/api/source/main_api.tl | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl index cb4bf0ac1c..e28f715e64 100644 --- a/compiler/api/source/main_api.tl +++ b/compiler/api/source/main_api.tl @@ -103,7 +103,7 @@ userStatusLastMonth#77ebc742 = UserStatus; chatEmpty#29562865 id:long = Chat; chat#41cbf256 flags:# creator:flags.0?true left:flags.2?true deactivated:flags.5?true call_active:flags.23?true call_not_empty:flags.24?true noforwards:flags.25?true id:long title:string photo:ChatPhoto participants_count:int date:int version:int migrated_to:flags.6?InputChannel admin_rights:flags.14?ChatAdminRights default_banned_rights:flags.18?ChatBannedRights = Chat; chatForbidden#6592a1a7 id:long title:string = Chat; -channel#8261ac61 flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true fake:flags.25?true gigagroup:flags.26?true noforwards:flags.27?true id:long access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int restriction_reason:flags.9?Vector admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int = Chat; +channel#8261ac61 flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true fake:flags.25?true gigagroup:flags.26?true noforwards:flags.27?true join_to_send:flags.28?true join_request:flags.29?true id:long access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int restriction_reason:flags.9?Vector admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int = Chat; channelForbidden#17d493d5 flags:# broadcast:flags.5?true megagroup:flags.8?true id:long access_hash:long title:string until_date:flags.16?int = Chat; chatFull#d18ee226 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:long about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer theme_emoticon:flags.16?string requests_pending:flags.17?int recent_requesters:flags.17?Vector available_reactions:flags.18?Vector = ChatFull; @@ -392,9 +392,9 @@ photos.photo#20212ca8 photo:Photo users:Vector = photos.Photo; upload.file#96a18d5 type:storage.FileType mtime:int bytes:bytes = upload.File; upload.fileCdnRedirect#f18cda44 dc_id:int file_token:bytes encryption_key:bytes encryption_iv:bytes file_hashes:Vector = upload.File; -dcOption#18b7a10d flags:# ipv6:flags.0?true media_only:flags.1?true tcpo_only:flags.2?true cdn:flags.3?true static:flags.4?true id:int ip_address:string port:int secret:flags.10?bytes = DcOption; +dcOption#18b7a10d flags:# ipv6:flags.0?true media_only:flags.1?true tcpo_only:flags.2?true cdn:flags.3?true static:flags.4?true this_port_only:flags.5?true id:int ip_address:string port:int secret:flags.10?bytes = DcOption; -config#330b4067 flags:# phonecalls_enabled:flags.1?true default_p2p_contacts:flags.3?true preload_featured_stickers:flags.4?true ignore_phone_entities:flags.5?true revoke_pm_inbox:flags.6?true blocked_mode:flags.8?true pfs_enabled:flags.13?true date:int expires:int test_mode:Bool this_dc:int dc_options:Vector dc_txt_domain_name:string chat_size_max:int megagroup_size_max:int forwarded_count_max:int online_update_period_ms:int offline_blur_timeout_ms:int offline_idle_timeout_ms:int online_cloud_timeout_ms:int notify_cloud_delay_ms:int notify_default_delay_ms:int push_chat_period_ms:int push_chat_limit:int saved_gifs_limit:int edit_time_limit:int revoke_time_limit:int revoke_pm_time_limit:int rating_e_decay:int stickers_recent_limit:int stickers_faved_limit:int channels_read_media_period:int tmp_sessions:flags.0?int pinned_dialogs_count_max:int pinned_infolder_count_max:int call_receive_timeout_ms:int call_ring_timeout_ms:int call_connect_timeout_ms:int call_packet_timeout_ms:int me_url_prefix:string autoupdate_url_prefix:flags.7?string gif_search_username:flags.9?string venue_search_username:flags.10?string img_search_username:flags.11?string static_maps_provider:flags.12?string caption_length_max:int message_length_max:int webfile_dc_id:int suggested_lang_code:flags.2?string lang_pack_version:flags.2?int base_lang_pack_version:flags.2?int = Config; +config#330b4067 flags:# phonecalls_enabled:flags.1?true default_p2p_contacts:flags.3?true preload_featured_stickers:flags.4?true ignore_phone_entities:flags.5?true revoke_pm_inbox:flags.6?true blocked_mode:flags.8?true pfs_enabled:flags.13?true force_try_ipv6:flags.14?true date:int expires:int test_mode:Bool this_dc:int dc_options:Vector dc_txt_domain_name:string chat_size_max:int megagroup_size_max:int forwarded_count_max:int online_update_period_ms:int offline_blur_timeout_ms:int offline_idle_timeout_ms:int online_cloud_timeout_ms:int notify_cloud_delay_ms:int notify_default_delay_ms:int push_chat_period_ms:int push_chat_limit:int saved_gifs_limit:int edit_time_limit:int revoke_time_limit:int revoke_pm_time_limit:int rating_e_decay:int stickers_recent_limit:int stickers_faved_limit:int channels_read_media_period:int tmp_sessions:flags.0?int pinned_dialogs_count_max:int pinned_infolder_count_max:int call_receive_timeout_ms:int call_ring_timeout_ms:int call_connect_timeout_ms:int call_packet_timeout_ms:int me_url_prefix:string autoupdate_url_prefix:flags.7?string gif_search_username:flags.9?string venue_search_username:flags.10?string img_search_username:flags.11?string static_maps_provider:flags.12?string caption_length_max:int message_length_max:int webfile_dc_id:int suggested_lang_code:flags.2?string lang_pack_version:flags.2?int base_lang_pack_version:flags.2?int = Config; nearestDc#8e1a1775 country:string this_dc:int nearest_dc:int = NearestDc; @@ -852,7 +852,7 @@ phoneCallAccepted#3660c311 flags:# video:flags.6?true id:long access_hash:long d phoneCall#967f7c67 flags:# p2p_allowed:flags.5?true video:flags.6?true id:long access_hash:long date:int admin_id:long participant_id:long g_a_or_b:bytes key_fingerprint:long protocol:PhoneCallProtocol connections:Vector start_date:int = PhoneCall; phoneCallDiscarded#50ca4de1 flags:# need_rating:flags.2?true need_debug:flags.3?true video:flags.6?true id:long reason:flags.0?PhoneCallDiscardReason duration:flags.1?int = PhoneCall; -phoneConnection#9d4c17c0 id:long ip:string ipv6:string port:int peer_tag:bytes = PhoneConnection; +phoneConnection#9cc123c7 flags:# tcp:flags.0?true id:long ip:string ipv6:string port:int peer_tag:bytes = PhoneConnection; phoneConnectionWebrtc#635fe375 flags:# turn:flags.0?true stun:flags.1?true id:long ip:string ipv6:string port:int username:string password:string = PhoneConnection; phoneCallProtocol#fc878fc8 flags:# udp_p2p:flags.0?true udp_reflector:flags.1?true min_layer:int max_layer:int library_versions:Vector = PhoneCallProtocol; @@ -1205,7 +1205,7 @@ messages.messageViews#b6c4f543 views:Vector chats:Vector use messages.discussionMessage#a6341782 flags:# messages:Vector max_id:flags.0?int read_inbox_max_id:flags.1?int read_outbox_max_id:flags.2?int unread_count:int chats:Vector users:Vector = messages.DiscussionMessage; -messageReplyHeader#a6d57763 flags:# reply_to_msg_id:int reply_to_peer_id:flags.0?Peer reply_to_top_id:flags.1?int = MessageReplyHeader; +messageReplyHeader#a6d57763 flags:# reply_to_scheduled:flags.2?true reply_to_msg_id:int reply_to_peer_id:flags.0?Peer reply_to_top_id:flags.1?int = MessageReplyHeader; messageReplies#83d60fc2 flags:# comments:flags.0?true replies:int replies_pts:int recent_repliers:flags.1?Vector channel_id:flags.0?long max_id:flags.2?int read_max_id:flags.3?int = MessageReplies; @@ -1273,7 +1273,7 @@ account.resetPasswordFailedWait#e3779861 retry_date:int = account.ResetPasswordR account.resetPasswordRequestedWait#e9effc7d until_date:int = account.ResetPasswordResult; account.resetPasswordOk#e926d63e = account.ResetPasswordResult; -sponsoredMessage#3a836df8 flags:# random_id:bytes from_id:flags.3?Peer chat_invite:flags.4?ChatInvite chat_invite_hash:flags.4?string channel_post:flags.2?int start_param:flags.0?string message:string entities:flags.1?Vector = SponsoredMessage; +sponsoredMessage#3a836df8 flags:# recommended:flags.5?true random_id:bytes from_id:flags.3?Peer chat_invite:flags.4?ChatInvite chat_invite_hash:flags.4?string channel_post:flags.2?int start_param:flags.0?string message:string entities:flags.1?Vector = SponsoredMessage; messages.sponsoredMessages#65a4c7d5 messages:Vector chats:Vector users:Vector = messages.SponsoredMessages; @@ -1785,6 +1785,7 @@ phone.joinGroupCallPresentation#cbea6bc4 call:InputGroupCall params:DataJSON = U phone.leaveGroupCallPresentation#1c50d144 call:InputGroupCall = Updates; phone.getGroupCallStreamChannels#1ab21940 call:InputGroupCall = phone.GroupCallStreamChannels; phone.getGroupCallStreamRtmpUrl#deb3abbf peer:InputPeer revoke:Bool = phone.GroupCallStreamRtmpUrl; +phone.saveCallLog#41248786 peer:InputPhoneCall file:InputFile = Bool; langpack.getLangPack#f2f2330a lang_pack:string lang_code:string = LangPackDifference; langpack.getStrings#efea3803 lang_pack:string lang_code:string keys:Vector = Vector; @@ -1801,4 +1802,4 @@ stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats; -// LAYER 140 +// LAYER 142 \ No newline at end of file From 6e1425ada3790b3ade70bacf21d968582d3882bf Mon Sep 17 00:00:00 2001 From: DevOps117 <55235206+devops117@users.noreply.github.com> Date: Sat, 14 May 2022 12:28:30 +0530 Subject: [PATCH 0934/1185] Drop support for iterators where they are not needed (#969) * delete_messages: Drop support for generators Since we used a list there anyway, this approach will lead to more localized errors and can reduce function overhead. Signed-off-by: devops117 <55235206+devops117@users.noreply.github.com> * delete_messages: Return pts_count:int An example usecase would be for a normal bot which uses range based on message ids instead of keeping a track of messages and using the DeletedMessagesHandler. Signed-off-by: devops117 <55235206+devops117@users.noreply.github.com> * Drop support for Iterators and update docstrings and some cleanups. Signed-off-by: devops117 <55235206+devops117@users.noreply.github.com> * Update get_users.py * Update get_messages.py * Update delete_messages.py * Update forward_messages.py * Update get_messages.py Co-authored-by: Dan <14043624+delivrance@users.noreply.github.com> --- pyrogram/methods/contacts/delete_contacts.py | 9 +++---- pyrogram/methods/messages/delete_messages.py | 21 ++++++++------- pyrogram/methods/messages/forward_messages.py | 17 ++++++------ pyrogram/methods/messages/get_messages.py | 27 ++++++++++--------- pyrogram/methods/users/get_common_chats.py | 4 +-- pyrogram/methods/users/get_users.py | 20 +++++++------- 6 files changed, 48 insertions(+), 50 deletions(-) diff --git a/pyrogram/methods/contacts/delete_contacts.py b/pyrogram/methods/contacts/delete_contacts.py index 31d2bcbec0..448e849a22 100644 --- a/pyrogram/methods/contacts/delete_contacts.py +++ b/pyrogram/methods/contacts/delete_contacts.py @@ -45,9 +45,9 @@ async def delete_contacts( await app.delete_contacts(user_id) await app.delete_contacts([user_id1, user_id2, user_id3]) """ - is_user_ids_list = isinstance(user_ids, list) + is_list = isinstance(user_ids, list) - if not is_user_ids_list: + if not is_list: user_ids = [user_ids] r = await self.invoke( @@ -61,7 +61,4 @@ async def delete_contacts( users = types.List([types.User._parse(self, i) for i in r.users]) - if is_user_ids_list: - return users - else: - return users[0] + return users if is_list else users[0] diff --git a/pyrogram/methods/messages/delete_messages.py b/pyrogram/methods/messages/delete_messages.py index b25c0ad4d5..a07b36ddcf 100644 --- a/pyrogram/methods/messages/delete_messages.py +++ b/pyrogram/methods/messages/delete_messages.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Union, Iterable +from typing import Union, List import pyrogram from pyrogram import raw @@ -26,9 +26,9 @@ class DeleteMessages: async def delete_messages( self: "pyrogram.Client", chat_id: Union[int, str], - message_ids: Union[int, Iterable[int]], + message_ids: Union[int, List[int]], revoke: bool = True - ) -> bool: + ) -> int: """Delete messages, including service messages. Parameters: @@ -37,9 +37,8 @@ async def delete_messages( For your personal cloud (Saved Messages) you can simply use "me" or "self". For a contact that exists in your Telegram address book you can use his phone number (str). - message_ids (``int`` | ``Iterable[int]``): + message_ids (``int`` | List of ``int``): A list of Message identifiers to delete (integers) or a single message id. - Iterators and Generators are also accepted. revoke (``bool``, *optional*): Deletes messages on both parts. @@ -48,7 +47,7 @@ async def delete_messages( Defaults to True. Returns: - ``bool``: True on success, False otherwise. + ``int``: Amount of affected messages Example: .. code-block:: python @@ -63,7 +62,9 @@ async def delete_messages( await app.delete_messages(chat_id, message_id, revoke=False) """ peer = await self.resolve_peer(chat_id) - message_ids = list(message_ids) if not isinstance(message_ids, int) else [message_ids] + # Follow type annotation of the raw function "DeleteMessage". + if not isinstance(message_ids, list): + message_ids = [message_ids] if isinstance(peer, raw.types.InputPeerChannel): r = await self.invoke( @@ -76,10 +77,10 @@ async def delete_messages( r = await self.invoke( raw.functions.messages.DeleteMessages( id=message_ids, - revoke=revoke or None + revoke=revoke or None # Follow the type annotation. ) ) - # Deleting messages you don't have right onto, won't raise any error. + # Deleting messages you don't have right onto won't raise any error. # Check for pts_count, which is 0 in case deletes fail. - return bool(r.pts_count) + return r.pts_count diff --git a/pyrogram/methods/messages/forward_messages.py b/pyrogram/methods/messages/forward_messages.py index 6ee8492294..2e2882532b 100644 --- a/pyrogram/methods/messages/forward_messages.py +++ b/pyrogram/methods/messages/forward_messages.py @@ -17,7 +17,7 @@ # along with Pyrogram. If not, see . from datetime import datetime -from typing import Union, Iterable, List +from typing import Union, List import pyrogram from pyrogram import raw, utils @@ -29,7 +29,7 @@ async def forward_messages( self: "pyrogram.Client", chat_id: Union[int, str], from_chat_id: Union[int, str], - message_ids: Union[int, Iterable[int]], + message_ids: Union[int, List[int]], disable_notification: bool = None, schedule_date: datetime = None, protect_content: bool = None @@ -49,7 +49,6 @@ async def forward_messages( message_ids (``int`` | List of ``int``): A list of Message identifiers in the chat specified in *from_chat_id* or a single message id. - Iterators and Generators are also accepted. disable_notification (``bool``, *optional*): Sends the message silently. @@ -62,9 +61,8 @@ async def forward_messages( Protects the contents of the sent message from forwarding and saving. Returns: - :obj:`~pyrogram.types.Message` | List of :obj:`~pyrogram.types.Message`: 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 a list of messages, even if such iterable contained just a single element. + :obj:`~pyrogram.types.Message` | List of :obj:`~pyrogram.types.Message`: In case *message_ids* was not + a list, a single message is returned, otherwise a list of messages is returned. Example: .. code-block:: python @@ -76,8 +74,9 @@ async def forward_messages( await app.forward_messages(to_chat, from_chat, [1, 2, 3]) """ - is_iterable = not isinstance(message_ids, int) - message_ids = list(message_ids) if is_iterable else [message_ids] + is_list = isinstance(message_ids, list) + if not is_list: + message_ids = [message_ids] r = await self.invoke( raw.functions.messages.ForwardMessages( @@ -107,4 +106,4 @@ async def forward_messages( ) ) - return types.List(forwarded_messages) if is_iterable else forwarded_messages[0] + return types.List(forwarded_messages) if is_list else forwarded_messages[0] diff --git a/pyrogram/methods/messages/get_messages.py b/pyrogram/methods/messages/get_messages.py index 8db24dd955..ce38b2322f 100644 --- a/pyrogram/methods/messages/get_messages.py +++ b/pyrogram/methods/messages/get_messages.py @@ -17,7 +17,7 @@ # along with Pyrogram. If not, see . import logging -from typing import Union, Iterable, List +from typing import Union, List import pyrogram from pyrogram import raw @@ -34,8 +34,8 @@ class GetMessages: async def get_messages( self: "pyrogram.Client", chat_id: Union[int, str], - message_ids: Union[int, Iterable[int]] = None, - reply_to_message_ids: Union[int, Iterable[int]] = None, + message_ids: Union[int, List[int]] = None, + reply_to_message_ids: Union[int, List[int]] = None, replies: int = 1 ) -> Union["types.Message", List["types.Message"]]: """Get one or more messages from a chat by using message identifiers. @@ -48,13 +48,13 @@ async def get_messages( For your personal cloud (Saved Messages) you can simply use "me" or "self". For a contact that exists in your Telegram address book you can use his phone number (str). - message_ids (``iterable``, *optional*): + message_ids (``int`` | List of ``int``, *optional*): Pass a single message identifier or a list of message ids (as integers) to get the content of the - message themselves. Iterators and Generators are also accepted. + message themselves. - reply_to_message_ids (``iterable``, *optional*): + reply_to_message_ids (``int`` | List of ``int``, *optional*): Pass a single message identifier or a list of message ids (as integers) to get the content of - the previous message you replied to using this message. Iterators and Generators are also accepted. + the previous message you replied to using this message. If *message_ids* is set, this argument will be ignored. replies (``int``, *optional*): @@ -63,9 +63,8 @@ async def get_messages( Defaults to 1. Returns: - :obj:`~pyrogram.types.Message` | List of :obj:`~pyrogram.types.Message`: 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 a list of messages, even if such iterable contained just a single element. + :obj:`~pyrogram.types.Message` | List of :obj:`~pyrogram.types.Message`: In case *message_ids* was not + a list, a single message is returned, otherwise a list of messages is returned. Example: .. code-block:: python @@ -99,8 +98,10 @@ async def get_messages( peer = await self.resolve_peer(chat_id) - is_iterable = not isinstance(ids, int) - ids = list(ids) if is_iterable else [ids] + is_list = isinstance(ids, list) + if not is_list: + ids = [ids] + ids = [ids_type(id=i) for i in ids] if replies < 0: @@ -115,4 +116,4 @@ async def get_messages( messages = await utils.parse_messages(self, r, replies=replies) - return messages if is_iterable else messages[0] if messages else None + return messages if is_list else messages[0] diff --git a/pyrogram/methods/users/get_common_chats.py b/pyrogram/methods/users/get_common_chats.py index 7269a81ed7..a45bda6f4c 100644 --- a/pyrogram/methods/users/get_common_chats.py +++ b/pyrogram/methods/users/get_common_chats.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Union +from typing import Union, List import pyrogram from pyrogram import raw @@ -27,7 +27,7 @@ class GetCommonChats: async def get_common_chats( self: "pyrogram.Client", user_id: Union[int, str] - ) -> list: + ) -> List["types.Chat"]: """Get the common chats you have with a user. Parameters: diff --git a/pyrogram/methods/users/get_users.py b/pyrogram/methods/users/get_users.py index caef79663f..c3f93d98fd 100644 --- a/pyrogram/methods/users/get_users.py +++ b/pyrogram/methods/users/get_users.py @@ -17,7 +17,7 @@ # along with Pyrogram. If not, see . import asyncio -from typing import Iterable, Union, List +from typing import Union, List import pyrogram from pyrogram import raw @@ -27,21 +27,19 @@ class GetUsers: async def get_users( self: "pyrogram.Client", - user_ids: Union[Iterable[Union[int, str]], int, str] + user_ids: Union[int, str, List[Union[int, str]]] ) -> Union["types.User", List["types.User"]]: """Get information about a user. You can retrieve up to 200 users at once. Parameters: - user_ids (``iterable``): + user_ids (``int`` | ``str`` | List of ``int`` or ``str``): A list of User identifiers (id or username) or a single user id/username. For a contact that exists in your Telegram address book you can use his phone number (str). - Iterators and Generators are also accepted. Returns: - :obj:`~pyrogram.types.User` | List of :obj:`~pyrogram.types.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. + :obj:`~pyrogram.types.User` | List of :obj:`~pyrogram.types.User`: In case *user_ids* was not a list, + a single user is returned, otherwise a list of users is returned. Example: .. code-block:: python @@ -52,8 +50,10 @@ async def get_users( # Get information about multiple users at once await app.get_users([user_id1, user_id2, user_id3]) """ - is_iterable = not isinstance(user_ids, (int, str)) - user_ids = list(user_ids) if is_iterable else [user_ids] + is_list = isinstance(user_ids, list) + if not is_list: + user_ids = [user_ids] + user_ids = await asyncio.gather(*[self.resolve_peer(i) for i in user_ids]) r = await self.invoke( @@ -67,4 +67,4 @@ async def get_users( for i in r: users.append(types.User._parse(self, i)) - return users if is_iterable else users[0] + return users if is_list else users[0] From 9c441ff16dbc536c36c34ab496c256d164b57164 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 14 May 2022 11:22:06 +0200 Subject: [PATCH 0935/1185] Update Pyrogram to v2.0.20 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 0be47e5f44..c9c190745e 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.19" +__version__ = "2.0.20" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From 7c3c0565b4a0ae26ce80b6c42033d34fa60309ea Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 14 May 2022 17:29:11 +0200 Subject: [PATCH 0936/1185] Fix wrapped function invocations --- pyrogram/session/session.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index ff195b96ec..2e90184a2c 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -351,9 +351,11 @@ async def invoke( pass if isinstance(query, (raw.functions.InvokeWithoutUpdates, raw.functions.InvokeWithTakeout)): - query = query.query + inner_query = query.query + else: + inner_query = query - query_name = ".".join(query.QUALNAME.split(".")[1:]) + query_name = ".".join(inner_query.QUALNAME.split(".")[1:]) while True: try: From 050a7304bac775fe6d6a2d13ced60a17d267e615 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 14 May 2022 17:29:55 +0200 Subject: [PATCH 0937/1185] Update Pyrogram to v2.0.21 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index c9c190745e..d1f66bf124 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.20" +__version__ = "2.0.21" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From d984ae24d5d878276c23fc44ae94005d1b504af3 Mon Sep 17 00:00:00 2001 From: leonardotty <81367045+leonardotty@users.noreply.github.com> Date: Sat, 14 May 2022 19:28:44 +0200 Subject: [PATCH 0938/1185] Add missing parameter to send_reaction (#993) --- pyrogram/methods/messages/send_reaction.py | 10 ++++++++-- pyrogram/types/messages_and_media/message.py | 9 +++++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/pyrogram/methods/messages/send_reaction.py b/pyrogram/methods/messages/send_reaction.py index 7f597cb626..1ede4ead79 100644 --- a/pyrogram/methods/messages/send_reaction.py +++ b/pyrogram/methods/messages/send_reaction.py @@ -27,7 +27,8 @@ async def send_reaction( self: "pyrogram.Client", chat_id: Union[int, str], message_id: int, - emoji: str = "" + emoji: str = "", + big: bool = False ) -> bool: """Send a reaction to a message. @@ -41,6 +42,10 @@ async def send_reaction( emoji (``str``, *optional*): Reaction emoji. Pass "" as emoji (default) to retract the reaction. + + big (``bool``, *optional*): + Pass True to show a bigger and longer reaction. + Defaults to False. Returns: ``bool``: On success, True is returned. @@ -58,7 +63,8 @@ async def send_reaction( raw.functions.messages.SendReaction( peer=await self.resolve_peer(chat_id), msg_id=message_id, - reaction=emoji + reaction=emoji, + big=big ) ) diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index ab5e202f2a..f742970210 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -3310,7 +3310,7 @@ async def click(self, x: Union[int, str] = 0, y: int = None, quote: bool = None, else: await self.reply(button, quote=quote) - async def react(self, emoji: str = "") -> bool: + async def react(self, emoji: str = "", big: bool = False) -> bool: """Bound method *react* of :obj:`~pyrogram.types.Message`. Use as a shortcut for: @@ -3332,6 +3332,10 @@ async def react(self, emoji: str = "") -> bool: emoji (``str``, *optional*): Reaction emoji. Pass "" as emoji (default) to retract the reaction. + + big (``bool``, *optional*): + Pass True to show a bigger and longer reaction. + Defaults to False. Returns: ``bool``: On success, True is returned. @@ -3343,7 +3347,8 @@ async def react(self, emoji: str = "") -> bool: return await self._client.send_reaction( chat_id=self.chat.id, message_id=self.id, - emoji=emoji + emoji=emoji, + big=big ) async def retract_vote( From 15bfaed254916aa7a85f77412ae2a85bee1ec6d1 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 14 May 2022 19:29:52 +0200 Subject: [PATCH 0939/1185] Update Pyrogram to v2.0.22 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index d1f66bf124..86b973b059 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.21" +__version__ = "2.0.22" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From ff90baffb3cc90cac971ff35f9cd46a1175abb3d Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 14 May 2022 21:12:37 +0200 Subject: [PATCH 0940/1185] Fix get_messages and usages --- pyrogram/methods/messages/get_messages.py | 2 +- pyrogram/utils.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/methods/messages/get_messages.py b/pyrogram/methods/messages/get_messages.py index ce38b2322f..f44b3b2f4c 100644 --- a/pyrogram/methods/messages/get_messages.py +++ b/pyrogram/methods/messages/get_messages.py @@ -116,4 +116,4 @@ async def get_messages( messages = await utils.parse_messages(self, r, replies=replies) - return messages if is_list else messages[0] + return messages if is_list else messages[0] if messages else None diff --git a/pyrogram/utils.py b/pyrogram/utils.py index d5862d1a67..bd000d949c 100644 --- a/pyrogram/utils.py +++ b/pyrogram/utils.py @@ -113,7 +113,7 @@ async def parse_messages(client, messages: "raw.types.messages.Messages", replie reply_messages = await client.get_messages( chat_id, - reply_to_message_ids=messages_with_replies.keys(), + reply_to_message_ids=list(messages_with_replies.keys()), replies=replies - 1 ) From 427738d02ab04d5c98002f16b33cfba135d2d24b Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 14 May 2022 21:12:54 +0200 Subject: [PATCH 0941/1185] Update Pyrogram to v2.0.23 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 86b973b059..2d00a75960 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.22" +__version__ = "2.0.23" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From 5681ccefe1de84047fa57bbb418afc60f7c9df2e Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 15 May 2022 14:24:59 +0200 Subject: [PATCH 0942/1185] Add back the ability to pass iterators to some methods --- pyrogram/methods/messages/delete_messages.py | 16 +++++--------- pyrogram/methods/messages/forward_messages.py | 15 ++++++------- pyrogram/methods/messages/get_messages.py | 22 +++++++++---------- pyrogram/methods/users/get_users.py | 13 +++++------ pyrogram/utils.py | 2 +- 5 files changed, 30 insertions(+), 38 deletions(-) diff --git a/pyrogram/methods/messages/delete_messages.py b/pyrogram/methods/messages/delete_messages.py index a07b36ddcf..523ecd544c 100644 --- a/pyrogram/methods/messages/delete_messages.py +++ b/pyrogram/methods/messages/delete_messages.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import Union, List +from typing import Union, Iterable import pyrogram from pyrogram import raw @@ -26,7 +26,7 @@ class DeleteMessages: async def delete_messages( self: "pyrogram.Client", chat_id: Union[int, str], - message_ids: Union[int, List[int]], + message_ids: Union[int, Iterable[int]], revoke: bool = True ) -> int: """Delete messages, including service messages. @@ -37,8 +37,8 @@ async def delete_messages( For your personal cloud (Saved Messages) you can simply use "me" or "self". For a contact that exists in your Telegram address book you can use his phone number (str). - message_ids (``int`` | List of ``int``): - A list of Message identifiers to delete (integers) or a single message id. + message_ids (``int`` | Iterable of ``int``): + An iterable of message identifiers to delete (integers) or a single message id. revoke (``bool``, *optional*): Deletes messages on both parts. @@ -62,9 +62,7 @@ async def delete_messages( await app.delete_messages(chat_id, message_id, revoke=False) """ peer = await self.resolve_peer(chat_id) - # Follow type annotation of the raw function "DeleteMessage". - if not isinstance(message_ids, list): - message_ids = [message_ids] + message_ids = list(message_ids) if not isinstance(message_ids, int) else [message_ids] if isinstance(peer, raw.types.InputPeerChannel): r = await self.invoke( @@ -77,10 +75,8 @@ async def delete_messages( r = await self.invoke( raw.functions.messages.DeleteMessages( id=message_ids, - revoke=revoke or None # Follow the type annotation. + revoke=revoke ) ) - # Deleting messages you don't have right onto won't raise any error. - # Check for pts_count, which is 0 in case deletes fail. return r.pts_count diff --git a/pyrogram/methods/messages/forward_messages.py b/pyrogram/methods/messages/forward_messages.py index 2e2882532b..be7b2ab5ef 100644 --- a/pyrogram/methods/messages/forward_messages.py +++ b/pyrogram/methods/messages/forward_messages.py @@ -17,7 +17,7 @@ # along with Pyrogram. If not, see . from datetime import datetime -from typing import Union, List +from typing import Union, List, Iterable import pyrogram from pyrogram import raw, utils @@ -29,7 +29,7 @@ async def forward_messages( self: "pyrogram.Client", chat_id: Union[int, str], from_chat_id: Union[int, str], - message_ids: Union[int, List[int]], + message_ids: Union[int, Iterable[int]], disable_notification: bool = None, schedule_date: datetime = None, protect_content: bool = None @@ -47,8 +47,8 @@ async def forward_messages( For your personal cloud (Saved Messages) you can simply use "me" or "self". For a contact that exists in your Telegram address book you can use his phone number (str). - message_ids (``int`` | List of ``int``): - A list of Message identifiers in the chat specified in *from_chat_id* or a single message id. + message_ids (``int`` | Iterable of ``int``): + An iterable of message identifiers in the chat specified in *from_chat_id* or a single message id. disable_notification (``bool``, *optional*): Sends the message silently. @@ -74,9 +74,8 @@ async def forward_messages( await app.forward_messages(to_chat, from_chat, [1, 2, 3]) """ - is_list = isinstance(message_ids, list) - if not is_list: - message_ids = [message_ids] + is_iterable = not isinstance(message_ids, int) + message_ids = list(message_ids) if is_iterable else [message_ids] r = await self.invoke( raw.functions.messages.ForwardMessages( @@ -106,4 +105,4 @@ async def forward_messages( ) ) - return types.List(forwarded_messages) if is_list else forwarded_messages[0] + return types.List(forwarded_messages) if is_iterable else forwarded_messages[0] diff --git a/pyrogram/methods/messages/get_messages.py b/pyrogram/methods/messages/get_messages.py index f44b3b2f4c..17d80e58a6 100644 --- a/pyrogram/methods/messages/get_messages.py +++ b/pyrogram/methods/messages/get_messages.py @@ -17,7 +17,7 @@ # along with Pyrogram. If not, see . import logging -from typing import Union, List +from typing import Union, List, Iterable import pyrogram from pyrogram import raw @@ -34,8 +34,8 @@ class GetMessages: async def get_messages( self: "pyrogram.Client", chat_id: Union[int, str], - message_ids: Union[int, List[int]] = None, - reply_to_message_ids: Union[int, List[int]] = None, + message_ids: Union[int, Iterable[int]] = None, + reply_to_message_ids: Union[int, Iterable[int]] = None, replies: int = 1 ) -> Union["types.Message", List["types.Message"]]: """Get one or more messages from a chat by using message identifiers. @@ -48,12 +48,12 @@ async def get_messages( For your personal cloud (Saved Messages) you can simply use "me" or "self". For a contact that exists in your Telegram address book you can use his phone number (str). - message_ids (``int`` | List of ``int``, *optional*): - Pass a single message identifier or a list of message ids (as integers) to get the content of the + message_ids (``int`` | Iterable of ``int``, *optional*): + Pass a single message identifier or an iterable of message ids (as integers) to get the content of the message themselves. - reply_to_message_ids (``int`` | List of ``int``, *optional*): - Pass a single message identifier or a list of message ids (as integers) to get the content of + reply_to_message_ids (``int`` | Iterable of ``int``, *optional*): + Pass a single message identifier or an iterable of message ids (as integers) to get the content of the previous message you replied to using this message. If *message_ids* is set, this argument will be ignored. @@ -98,10 +98,8 @@ async def get_messages( peer = await self.resolve_peer(chat_id) - is_list = isinstance(ids, list) - if not is_list: - ids = [ids] - + is_iterable = not isinstance(ids, int) + ids = list(ids) if is_iterable else [ids] ids = [ids_type(id=i) for i in ids] if replies < 0: @@ -116,4 +114,4 @@ async def get_messages( messages = await utils.parse_messages(self, r, replies=replies) - return messages if is_list else messages[0] if messages else None + return messages if is_iterable else messages[0] if messages else None diff --git a/pyrogram/methods/users/get_users.py b/pyrogram/methods/users/get_users.py index c3f93d98fd..384dded0af 100644 --- a/pyrogram/methods/users/get_users.py +++ b/pyrogram/methods/users/get_users.py @@ -17,7 +17,7 @@ # along with Pyrogram. If not, see . import asyncio -from typing import Union, List +from typing import Union, List, Iterable import pyrogram from pyrogram import raw @@ -27,13 +27,13 @@ class GetUsers: async def get_users( self: "pyrogram.Client", - user_ids: Union[int, str, List[Union[int, str]]] + user_ids: Union[int, str, Iterable[Union[int, str]]] ) -> Union["types.User", List["types.User"]]: """Get information about a user. You can retrieve up to 200 users at once. Parameters: - user_ids (``int`` | ``str`` | List of ``int`` or ``str``): + user_ids (``int`` | ``str`` | Iterable of ``int`` or ``str``): A list of User identifiers (id or username) or a single user id/username. For a contact that exists in your Telegram address book you can use his phone number (str). @@ -50,10 +50,9 @@ async def get_users( # Get information about multiple users at once await app.get_users([user_id1, user_id2, user_id3]) """ - is_list = isinstance(user_ids, list) - if not is_list: - user_ids = [user_ids] + is_iterable = not isinstance(user_ids, (int, str)) + user_ids = list(user_ids) if is_iterable else [user_ids] user_ids = await asyncio.gather(*[self.resolve_peer(i) for i in user_ids]) r = await self.invoke( @@ -67,4 +66,4 @@ async def get_users( for i in r: users.append(types.User._parse(self, i)) - return users if is_list else users[0] + return users if is_iterable else users[0] diff --git a/pyrogram/utils.py b/pyrogram/utils.py index bd000d949c..d5862d1a67 100644 --- a/pyrogram/utils.py +++ b/pyrogram/utils.py @@ -113,7 +113,7 @@ async def parse_messages(client, messages: "raw.types.messages.Messages", replie reply_messages = await client.get_messages( chat_id, - reply_to_message_ids=list(messages_with_replies.keys()), + reply_to_message_ids=messages_with_replies.keys(), replies=replies - 1 ) From f6283757e14fce1d2edd19dd8bd29b7b73e6b3de Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 15 May 2022 14:26:12 +0200 Subject: [PATCH 0943/1185] Add sequential parameter to compose() --- pyrogram/methods/utilities/compose.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/pyrogram/methods/utilities/compose.py b/pyrogram/methods/utilities/compose.py index c8bdf76956..cfc5ca3e61 100644 --- a/pyrogram/methods/utilities/compose.py +++ b/pyrogram/methods/utilities/compose.py @@ -16,13 +16,17 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import asyncio from typing import List import pyrogram from .idle import idle -async def compose(clients: List["pyrogram.Client"]): +async def compose( + clients: List["pyrogram.Client"], + sequential: bool = False +): """Run multiple clients at once. This method can be used to run multiple clients at once and can be found directly in the ``pyrogram`` package. @@ -33,6 +37,10 @@ async def compose(clients: List["pyrogram.Client"]): clients (List of :obj:`~pyrogram.Client`): A list of client objects to run. + sequential (``bool``, *optional*): + Pass True to run clients sequentially. + Defaults to False (run clients concurrently) + Example: .. code-block:: python @@ -53,10 +61,16 @@ async def main(): asyncio.run(main()) """ - for c in clients: - await c.start() + if sequential: + for c in clients: + await c.start() + else: + await asyncio.gather(*[c.start() for c in clients]) await idle() - for c in clients: - await c.stop() + if sequential: + for c in clients: + await c.stop() + else: + await asyncio.gather(*[c.stop() for c in clients]) From 644dd55393c49e34cb894cb68ab24e4ff5ac6551 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 15 May 2022 14:26:58 +0200 Subject: [PATCH 0944/1185] Update Pyrogram to v2.0.24 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 2d00a75960..c96c574c64 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.23" +__version__ = "2.0.24" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From 6974d97fb4112a8662c9f311ba4cc281c1e27aa9 Mon Sep 17 00:00:00 2001 From: Gaung Ramadhan Date: Wed, 25 May 2022 15:56:55 +0700 Subject: [PATCH 0945/1185] Fix type hint of User.status (#998) --- pyrogram/types/user_and_chats/user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/types/user_and_chats/user.py b/pyrogram/types/user_and_chats/user.py index 666c72675d..8860784d8d 100644 --- a/pyrogram/types/user_and_chats/user.py +++ b/pyrogram/types/user_and_chats/user.py @@ -158,7 +158,7 @@ def __init__( is_support: bool = None, first_name: str = None, last_name: str = None, - status: str = None, + status: "enums.UserStatus" = None, last_online_date: datetime = None, next_offline_date: datetime = None, username: str = None, From f7c678855d051e69974d50318bdcb30526ccdfcf Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 25 May 2022 10:58:32 +0200 Subject: [PATCH 0946/1185] Update Pyrogram to v2.0.25 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index c96c574c64..a435d23820 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.24" +__version__ = "2.0.25" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From c5958fc0c43190b0bf6fd72188373e97ab02ce25 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 26 May 2022 11:30:20 +0200 Subject: [PATCH 0947/1185] Fix offset_date not being an integer timestamp Closes #1003 --- pyrogram/methods/messages/search_global.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/methods/messages/search_global.py b/pyrogram/methods/messages/search_global.py index 1a65702e01..5e54a965c8 100644 --- a/pyrogram/methods/messages/search_global.py +++ b/pyrogram/methods/messages/search_global.py @@ -102,7 +102,7 @@ async def search_global( last = messages[-1] - offset_date = last.date + offset_date = utils.datetime_to_timestamp(last.date) offset_peer = await self.resolve_peer(last.chat.id) offset_id = last.id From ba3104fd53e1397ec626511ec96aa7da725c8e0b Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 26 May 2022 11:30:41 +0200 Subject: [PATCH 0948/1185] Update Pyrogram to v2.0.26 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index a435d23820..c376cbb376 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.25" +__version__ = "2.0.26" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From edf5e9863f7a20c3be2c02ef83d5d14b39ef617d Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 6 Jun 2022 18:46:22 +0200 Subject: [PATCH 0949/1185] Update FAQ --- .../socket-send-raised-exception-oserror-timeouterror.rst | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/docs/source/faq/socket-send-raised-exception-oserror-timeouterror.rst b/docs/source/faq/socket-send-raised-exception-oserror-timeouterror.rst index 21ee1a900c..4d9aa89de9 100644 --- a/docs/source/faq/socket-send-raised-exception-oserror-timeouterror.rst +++ b/docs/source/faq/socket-send-raised-exception-oserror-timeouterror.rst @@ -1,9 +1,10 @@ socket.send() raised exception, OSError(), TimeoutError() ========================================================= -If you get this error chances are you are blocking the event loop for too long, most likely due to an improper use of -non-asynchronous or threaded operations which may lead to blocking code that prevents the event loop from running -properly. +If you get this error chances are you or Telegram ended up with a slow or inconsistent network connection, which +triggers internal timeouts due to data not being sent/received in time. Another reason could be because you are blocking +the event loop for too long, most likely due to an improper use of non-asynchronous or threaded operations which may +lead to blocking code that prevents the event loop from running properly. You can consider the following: From a1bdcd672ea9331fef4bcee49a50052cf8b11fee Mon Sep 17 00:00:00 2001 From: Prashant Sengar <45726744+prashantsengar@users.noreply.github.com> Date: Mon, 6 Jun 2022 22:17:15 +0530 Subject: [PATCH 0950/1185] Fix type of "has_protected_content" (#994) `has_protected_content` attribute of Message class was assigned the wrong type in the docstring (str), corrected it to `bool` --- pyrogram/types/messages_and_media/message.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index f742970210..56e4d7feec 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -133,7 +133,7 @@ class Message(Object, Update): Signature of the post author for messages in channels, or the custom title of an anonymous group administrator. - has_protected_content (``str``, *optional*): + has_protected_content (``bool``, *optional*): True, if the message can't be forwarded. text (``str``, *optional*): From 37e0015463216a212b6417248a89c9133052ad07 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 6 Jun 2022 18:47:51 +0200 Subject: [PATCH 0951/1185] Update Pyrogram to v2.0.27 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index c376cbb376..6e562ce075 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.26" +__version__ = "2.0.27" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From 7f9e841ccd44246ad855ad4855a6431a5823c554 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 14 Jun 2022 16:55:19 +0200 Subject: [PATCH 0952/1185] Update API schema to Layer 143 --- compiler/api/source/main_api.tl | 81 ++++++++++++++++++--------- pyrogram/types/user_and_chats/user.py | 6 ++ 2 files changed, 61 insertions(+), 26 deletions(-) diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl index e28f715e64..3f44fe0fbe 100644 --- a/compiler/api/source/main_api.tl +++ b/compiler/api/source/main_api.tl @@ -88,7 +88,7 @@ storage.fileMp4#b3cea0e4 = storage.FileType; storage.fileWebp#1081464c = storage.FileType; userEmpty#d3bc4b7a id:long = User; -user#3ff6ecb0 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true apply_min_photo:flags.25?true fake:flags.26?true bot_attach_menu:flags.27?true id:long access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector bot_inline_placeholder:flags.19?string lang_code:flags.22?string = User; +user#3ff6ecb0 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true apply_min_photo:flags.25?true fake:flags.26?true bot_attach_menu:flags.27?true premium:flags.28?true attach_menu_enabled:flags.29?true id:long access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector bot_inline_placeholder:flags.19?string lang_code:flags.22?string = User; userProfilePhotoEmpty#4f11bae1 = UserProfilePhoto; userProfilePhoto#82d1f706 flags:# has_video:flags.0?true photo_id:long stripped_thumb:flags.1?bytes dc_id:int = UserProfilePhoto; @@ -128,7 +128,7 @@ messageMediaPhoto#695150d7 flags:# photo:flags.0?Photo ttl_seconds:flags.2?int = messageMediaGeo#56e0d474 geo:GeoPoint = MessageMedia; messageMediaContact#70322949 phone_number:string first_name:string last_name:string vcard:string user_id:long = MessageMedia; messageMediaUnsupported#9f84f49e = MessageMedia; -messageMediaDocument#9cb070d7 flags:# document:flags.0?Document ttl_seconds:flags.2?int = MessageMedia; +messageMediaDocument#9cb070d7 flags:# nopremium:flags.3?true document:flags.0?Document ttl_seconds:flags.2?int = MessageMedia; messageMediaWebPage#a32dd600 webpage:WebPage = MessageMedia; messageMediaVenue#2ec0533f geo:GeoPoint title:string address:string provider:string venue_id:string venue_type:string = MessageMedia; messageMediaGame#fdb19008 game:Game = MessageMedia; @@ -151,8 +151,8 @@ messageActionChannelMigrateFrom#ea3948e9 title:string chat_id:long = MessageActi messageActionPinMessage#94bd38ed = MessageAction; messageActionHistoryClear#9fbab604 = MessageAction; messageActionGameScore#92a72876 game_id:long score:int = MessageAction; -messageActionPaymentSentMe#8f31b327 flags:# currency:string total_amount:long payload:bytes info:flags.0?PaymentRequestedInfo shipping_option_id:flags.1?string charge:PaymentCharge = MessageAction; -messageActionPaymentSent#40699cd0 currency:string total_amount:long = MessageAction; +messageActionPaymentSentMe#8f31b327 flags:# recurring_init:flags.2?true recurring_used:flags.3?true currency:string total_amount:long payload:bytes info:flags.0?PaymentRequestedInfo shipping_option_id:flags.1?string charge:PaymentCharge = MessageAction; +messageActionPaymentSent#96163f56 flags:# recurring_init:flags.2?true recurring_used:flags.3?true currency:string total_amount:long invoice_slug:flags.0?string = MessageAction; messageActionPhoneCall#80e11a7f flags:# video:flags.2?true call_id:long reason:flags.0?PhoneCallDiscardReason duration:flags.1?int = MessageAction; messageActionScreenshotTaken#4792929b = MessageAction; messageActionCustomAction#fae69f56 message:string = MessageAction; @@ -368,6 +368,7 @@ updateAttachMenuBots#17b7a20b = Update; updateWebViewResultSent#1592b79d query_id:long = Update; updateBotMenuButton#14b85813 bot_id:long button:BotMenuButton = Update; updateSavedRingtones#74d8be99 = Update; +updateTranscribedAudio#84cd5a flags:# pending:flags.0?true peer:Peer msg_id:int transcription_id:long text:string = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -412,7 +413,7 @@ encryptedChatDiscarded#1e1c7c45 flags:# history_deleted:flags.0?true id:int = En inputEncryptedChat#f141b5e1 chat_id:int access_hash:long = InputEncryptedChat; encryptedFileEmpty#c21f497e = EncryptedFile; -encryptedFile#4a70994c id:long access_hash:long size:int dc_id:int key_fingerprint:int = EncryptedFile; +encryptedFile#a8008cd8 id:long access_hash:long size:long dc_id:int key_fingerprint:int = EncryptedFile; inputEncryptedFileEmpty#1837c364 = InputEncryptedFile; inputEncryptedFileUploaded#64bd0306 id:long parts:int md5_checksum:string key_fingerprint:int = InputEncryptedFile; @@ -432,7 +433,7 @@ inputDocumentEmpty#72f0eaae = InputDocument; inputDocument#1abfb575 id:long access_hash:long file_reference:bytes = InputDocument; documentEmpty#36f8c871 id:long = Document; -document#1e87342b flags:# id:long access_hash:long file_reference:bytes date:int mime_type:string size:int thumbs:flags.0?Vector video_thumbs:flags.1?Vector dc_id:int attributes:Vector = Document; +document#8fd4c4d8 flags:# id:long access_hash:long file_reference:bytes date:int mime_type:string size:long thumbs:flags.0?Vector video_thumbs:flags.1?Vector dc_id:int attributes:Vector = Document; help.support#17c6b5f6 phone_number:string user:User = help.Support; @@ -540,6 +541,7 @@ auth.passwordRecovery#137948a5 email_pattern:string = auth.PasswordRecovery; receivedNotifyMessage#a384b779 id:int flags:int = ReceivedNotifyMessage; chatInviteExported#ab4a819 flags:# revoked:flags.0?true permanent:flags.5?true request_needed:flags.6?true link:string admin_id:long date:int start_date:flags.4?int expire_date:flags.1?int usage_limit:flags.2?int usage:flags.3?int requested:flags.7?int title:flags.8?string = ExportedChatInvite; +chatInvitePublicJoinRequests#ed107ab7 = ExportedChatInvite; chatInviteAlready#5a686d7c chat:Chat = ChatInvite; chatInvite#300c44c1 flags:# channel:flags.0?true broadcast:flags.1?true public:flags.2?true megagroup:flags.3?true request_needed:flags.6?true title:string about:flags.5?string photo:Photo participants_count:int participants:flags.4?Vector = ChatInvite; @@ -559,7 +561,7 @@ messages.stickerSetNotModified#d3f924eb = messages.StickerSet; botCommand#c27ac8c7 command:string description:string = BotCommand; -botInfo#e4169b5d user_id:long description:string commands:Vector menu_button:BotMenuButton = BotInfo; +botInfo#8f300b57 flags:# user_id:flags.0?long description:flags.1?string description_photo:flags.4?Photo description_document:flags.5?Document commands:flags.2?Vector menu_button:flags.3?BotMenuButton = BotInfo; keyboardButton#a2fa4880 text:string = KeyboardButton; keyboardButtonUrl#258aff05 text:string url:string = KeyboardButton; @@ -801,7 +803,7 @@ dataJSON#7d748d04 data:string = DataJSON; labeledPrice#cb296bf8 label:string amount:long = LabeledPrice; -invoice#cd886e0 flags:# test:flags.0?true name_requested:flags.1?true phone_requested:flags.2?true email_requested:flags.3?true shipping_address_requested:flags.4?true flexible:flags.5?true phone_to_provider:flags.6?true email_to_provider:flags.7?true currency:string prices:Vector max_tip_amount:flags.8?long suggested_tip_amounts:flags.8?Vector = Invoice; +invoice#3e85a91b flags:# test:flags.0?true name_requested:flags.1?true phone_requested:flags.2?true email_requested:flags.3?true shipping_address_requested:flags.4?true flexible:flags.5?true phone_to_provider:flags.6?true email_to_provider:flags.7?true recurring:flags.9?true currency:string prices:Vector max_tip_amount:flags.8?long suggested_tip_amounts:flags.8?Vector recurring_terms_url:flags.9?string = Invoice; paymentCharge#ea02c27e id:string provider_charge_id:string = PaymentCharge; @@ -821,7 +823,7 @@ inputWebFileGeoPointLocation#9f2221c9 geo_point:InputGeoPoint access_hash:long w upload.webFile#21e753bc size:int mime_type:string file_type:storage.FileType mtime:int bytes:bytes = upload.WebFile; -payments.paymentForm#1694761b flags:# can_save_credentials:flags.2?true password_missing:flags.3?true form_id:long bot_id:long invoice:Invoice provider_id:long url:string native_provider:flags.4?string native_params:flags.4?DataJSON saved_info:flags.0?PaymentRequestedInfo saved_credentials:flags.1?PaymentSavedCredentials users:Vector = payments.PaymentForm; +payments.paymentForm#b0133b37 flags:# can_save_credentials:flags.2?true password_missing:flags.3?true form_id:long bot_id:long title:string description:string photo:flags.5?WebDocument invoice:Invoice provider_id:long url:string native_provider:flags.4?string native_params:flags.4?DataJSON saved_info:flags.0?PaymentRequestedInfo saved_credentials:flags.1?PaymentSavedCredentials users:Vector = payments.PaymentForm; payments.validatedRequestedInfo#d1451883 flags:# id:flags.0?string shipping_options:flags.1?Vector = payments.ValidatedRequestedInfo; @@ -950,7 +952,7 @@ dialogPeerFolder#514519e2 folder_id:int = DialogPeer; messages.foundStickerSetsNotModified#d54b65d = messages.FoundStickerSets; messages.foundStickerSets#8af09dd2 hash:long sets:Vector = messages.FoundStickerSets; -fileHash#6242c773 offset:int limit:int hash:bytes = FileHash; +fileHash#f39b035c offset:long limit:int hash:bytes = FileHash; inputClientProxy#75588b3f address:string port:int = InputClientProxy; @@ -961,7 +963,7 @@ inputSecureFileUploaded#3334b0f0 id:long parts:int md5_checksum:string file_hash inputSecureFile#5367e5be id:long access_hash:long = InputSecureFile; secureFileEmpty#64199744 = SecureFile; -secureFile#e0277a62 id:long access_hash:long size:int dc_id:int date:int file_hash:bytes secret:bytes = SecureFile; +secureFile#7d09c27e id:long access_hash:long size:long dc_id:int date:int file_hash:bytes secret:bytes = SecureFile; secureData#8aeabec3 data:bytes data_hash:bytes secret:bytes = SecureData; @@ -1088,7 +1090,7 @@ codeSettings#8a6469c2 flags:# allow_flashcall:flags.0?true current_number:flags. wallPaperSettings#1dc1bca4 flags:# blur:flags.1?true motion:flags.2?true background_color:flags.0?int second_background_color:flags.4?int third_background_color:flags.5?int fourth_background_color:flags.6?int intensity:flags.3?int rotation:flags.4?int = WallPaperSettings; -autoDownloadSettings#e04232f3 flags:# disabled:flags.0?true video_preload_large:flags.1?true audio_preload_next:flags.2?true phonecalls_less_data:flags.3?true photo_size_max:int video_size_max:int file_size_max:int video_upload_maxbitrate:int = AutoDownloadSettings; +autoDownloadSettings#8efab953 flags:# disabled:flags.0?true video_preload_large:flags.1?true audio_preload_next:flags.2?true phonecalls_less_data:flags.3?true photo_size_max:int video_size_max:long file_size_max:long video_upload_maxbitrate:int = AutoDownloadSettings; account.autoDownloadSettings#63cacf26 low:AutoDownloadSettings medium:AutoDownloadSettings high:AutoDownloadSettings = account.AutoDownloadSettings; @@ -1160,6 +1162,7 @@ bankCardOpenUrl#f568028a url:string name:string = BankCardOpenUrl; payments.bankCardData#3e24e573 title:string open_urls:Vector = payments.BankCardData; dialogFilter#7438f7e8 flags:# contacts:flags.0?true non_contacts:flags.1?true groups:flags.2?true broadcasts:flags.3?true bots:flags.4?true exclude_muted:flags.11?true exclude_read:flags.12?true exclude_archived:flags.13?true id:int title:string emoticon:flags.25?string pinned_peers:Vector include_peers:Vector exclude_peers:Vector = DialogFilter; +dialogFilterDefault#363293ae = DialogFilter; dialogFilterSuggested#77744d4a filter:DialogFilter description:string = DialogFilterSuggested; @@ -1299,7 +1302,7 @@ messageReactions#4f2b9479 flags:# min:flags.0?true can_see_list:flags.2?true res messages.messageReactionsList#31bd492d flags:# count:int reactions:Vector chats:Vector users:Vector next_offset:flags.0?string = messages.MessageReactionsList; -availableReaction#c077ec01 flags:# inactive:flags.0?true reaction:string title:string static_icon:Document appear_animation:Document select_animation:Document activate_animation:Document effect_animation:Document around_animation:flags.1?Document center_icon:flags.1?Document = AvailableReaction; +availableReaction#c077ec01 flags:# inactive:flags.0?true premium:flags.2?true reaction:string title:string static_icon:Document appear_animation:Document select_animation:Document activate_animation:Document effect_animation:Document around_animation:flags.1?Document center_icon:flags.1?Document = AvailableReaction; messages.availableReactionsNotModified#9f071957 = messages.AvailableReactions; messages.availableReactions#768e3aad hash:int reactions:Vector = messages.AvailableReactions; @@ -1319,7 +1322,7 @@ attachMenuBotIconColor#4576f3f0 name:string color:int = AttachMenuBotIconColor; attachMenuBotIcon#b2a7386b flags:# name:string icon:Document colors:flags.0?Vector = AttachMenuBotIcon; -attachMenuBot#e93cb772 flags:# inactive:flags.0?true bot_id:long short_name:string icons:Vector = AttachMenuBot; +attachMenuBot#c8aa2cd2 flags:# inactive:flags.0?true has_settings:flags.1?true bot_id:long short_name:string peer_types:Vector icons:Vector = AttachMenuBot; attachMenuBotsNotModified#f1d88a5c = AttachMenuBots; attachMenuBots#3c4301c0 hash:long bots:Vector users:Vector = AttachMenuBots; @@ -1347,6 +1350,21 @@ notificationSoundRingtone#ff6c8049 id:long = NotificationSound; account.savedRingtone#b7263f6d = account.SavedRingtone; account.savedRingtoneConverted#1f307eb7 document:Document = account.SavedRingtone; +attachMenuPeerTypeSameBotPM#7d6be90e = AttachMenuPeerType; +attachMenuPeerTypeBotPM#c32bfa1a = AttachMenuPeerType; +attachMenuPeerTypePM#f146d31f = AttachMenuPeerType; +attachMenuPeerTypeChat#509113f = AttachMenuPeerType; +attachMenuPeerTypeBroadcast#7bfbdefc = AttachMenuPeerType; + +inputInvoiceMessage#c5b56859 peer:InputPeer msg_id:int = InputInvoice; +inputInvoiceSlug#c326caef slug:string = InputInvoice; + +payments.exportedInvoice#aed0cbd9 url:string = payments.ExportedInvoice; + +messages.transcribedAudio#93752c52 flags:# pending:flags.0?true transcription_id:long text:string = messages.TranscribedAudio; + +help.premiumPromo#8a4f3c29 status_text:string status_entities:Vector video_sections:Vector videos:Vector currency:string monthly_amount:long users:Vector = help.PremiumPromo; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -1417,7 +1435,7 @@ account.sendVerifyPhoneCode#a5a356f9 phone_number:string settings:CodeSettings = account.verifyPhone#4dd3a7f6 phone_number:string phone_code_hash:string phone_code:string = Bool; account.sendVerifyEmailCode#7011509f email:string = account.SentEmailCode; account.verifyEmail#ecba39db email:string code:string = Bool; -account.initTakeoutSession#f05b4804 flags:# contacts:flags.0?true message_users:flags.1?true message_chats:flags.2?true message_megagroups:flags.3?true message_channels:flags.4?true files:flags.5?true file_max_size:flags.5?int = account.Takeout; +account.initTakeoutSession#8ef3eab0 flags:# contacts:flags.0?true message_users:flags.1?true message_chats:flags.2?true message_megagroups:flags.3?true message_channels:flags.4?true files:flags.5?true file_max_size:flags.5?long = account.Takeout; account.finishTakeoutSession#1d2652ee flags:# success:flags.0?true = Bool; account.confirmPasswordEmail#8fdf1920 code:string = Bool; account.resendPasswordEmail#7a7f2a15 = Bool; @@ -1529,7 +1547,7 @@ messages.editChatAdmin#a85bd1c2 chat_id:long user_id:InputUser is_admin:Bool = B messages.migrateChat#a2875319 chat_id:long = Updates; messages.searchGlobal#4bc6589a flags:# folder_id:flags.0?int q:string filter:MessagesFilter min_date:int max_date:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; messages.reorderStickerSets#78337739 flags:# masks:flags.0?true order:Vector = Bool; -messages.getDocumentByHash#338e2464 sha256:bytes size:int mime_type:string = Document; +messages.getDocumentByHash#b1f2061f sha256:bytes size:long mime_type:string = Document; messages.getSavedGifs#5cf09635 hash:long = messages.SavedGifs; messages.saveGif#327a30cb id:InputDocument unsave:Bool = Bool; messages.getInlineBotResults#514e999d flags:# bot:InputUser peer:InputPeer geo_point:flags.0?InputGeoPoint query:string offset:string = messages.BotResults; @@ -1642,11 +1660,13 @@ messages.searchSentMedia#107e31a0 q:string filter:MessagesFilter limit:int = mes messages.getAttachMenuBots#16fcc2cb hash:long = AttachMenuBots; messages.getAttachMenuBot#77216192 bot:InputUser = AttachMenuBotsBot; messages.toggleBotInAttachMenu#1aee33af bot:InputUser enabled:Bool = Bool; -messages.requestWebView#fa04dff flags:# from_bot_menu:flags.4?true silent:flags.5?true peer:InputPeer bot:InputUser url:flags.1?string start_param:flags.3?string theme_params:flags.2?DataJSON reply_to_msg_id:flags.0?int = WebViewResult; -messages.prolongWebView#d22ad148 flags:# silent:flags.5?true peer:InputPeer bot:InputUser query_id:long reply_to_msg_id:flags.0?int = Bool; +messages.requestWebView#91b15831 flags:# from_bot_menu:flags.4?true silent:flags.5?true peer:InputPeer bot:InputUser url:flags.1?string start_param:flags.3?string theme_params:flags.2?DataJSON reply_to_msg_id:flags.0?int send_as:flags.13?InputPeer = WebViewResult; +messages.prolongWebView#ea5fbcce flags:# silent:flags.5?true peer:InputPeer bot:InputUser query_id:long reply_to_msg_id:flags.0?int send_as:flags.13?InputPeer = Bool; messages.requestSimpleWebView#6abb2f73 flags:# bot:InputUser url:string theme_params:flags.0?DataJSON = SimpleWebViewResult; messages.sendWebViewResultMessage#a4314f5 bot_query_id:string result:InputBotInlineResult = WebViewMessageSent; messages.sendWebViewData#dc0242c8 bot:InputUser random_id:long button_text:string data:string = Updates; +messages.transcribeAudio#269e9a49 peer:InputPeer msg_id:int = messages.TranscribedAudio; +messages.rateTranscribedAudio#7f1d072f peer:InputPeer msg_id:int transcription_id:long good:Bool = Bool; updates.getState#edd4882a = updates.State; updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference; @@ -1658,13 +1678,13 @@ photos.deletePhotos#87cf7f2f id:Vector = Vector; photos.getUserPhotos#91cd32a8 user_id:InputUser offset:int max_id:long limit:int = photos.Photos; upload.saveFilePart#b304a621 file_id:long file_part:int bytes:bytes = Bool; -upload.getFile#b15a9afc flags:# precise:flags.0?true cdn_supported:flags.1?true location:InputFileLocation offset:int limit:int = upload.File; +upload.getFile#be5335be flags:# precise:flags.0?true cdn_supported:flags.1?true location:InputFileLocation offset:long limit:int = upload.File; upload.saveBigFilePart#de7b673d file_id:long file_part:int file_total_parts:int bytes:bytes = Bool; upload.getWebFile#24e6818d location:InputWebFileLocation offset:int limit:int = upload.WebFile; -upload.getCdnFile#2000bcc3 file_token:bytes offset:int limit:int = upload.CdnFile; +upload.getCdnFile#395f69da file_token:bytes offset:long limit:int = upload.CdnFile; upload.reuploadCdnFile#9b2754a8 file_token:bytes request_token:bytes = Vector; -upload.getCdnFileHashes#4da54231 file_token:bytes offset:int = Vector; -upload.getFileHashes#c7025931 location:InputFileLocation offset:int = Vector; +upload.getCdnFileHashes#91dc3f31 file_token:bytes offset:long = Vector; +upload.getFileHashes#9156982a location:InputFileLocation offset:long = Vector; help.getConfig#c4f9186b = Config; help.getNearestDc#1fb33026 = NearestDc; @@ -1688,6 +1708,7 @@ help.getPromoData#c0977421 = help.PromoData; help.hidePromoData#1e251c95 peer:InputPeer = Bool; help.dismissSuggestion#f50dbaa1 peer:InputPeer suggestion:string = Bool; help.getCountriesList#735787a8 lang_code:string hash:int = help.CountriesList; +help.getPremiumPromo#b81b93d4 = help.PremiumPromo; channels.readHistory#cc104937 channel:InputChannel max_id:int = Bool; channels.deleteMessages#84c1fd4e channel:InputChannel id:Vector = messages.AffectedMessages; @@ -1728,6 +1749,8 @@ channels.viewSponsoredMessage#beaedb94 channel:InputChannel random_id:bytes = Bo channels.getSponsoredMessages#ec210fbf channel:InputChannel = messages.SponsoredMessages; channels.getSendAs#dc770ee peer:InputPeer = channels.SendAsPeers; channels.deleteParticipantHistory#367544db channel:InputChannel participant:InputPeer = messages.AffectedHistory; +channels.toggleJoinToSend#e4cb9580 channel:InputChannel enabled:Bool = Updates; +channels.toggleJoinRequest#4c2985b6 channel:InputChannel enabled:Bool = Updates; bots.sendCustomRequest#aa2769ed custom_method:string params:DataJSON = DataJSON; bots.answerWebhookJSONQuery#e6213f4d query_id:long data:DataJSON = Bool; @@ -1739,13 +1762,19 @@ bots.getBotMenuButton#9c60eb28 user_id:InputUser = BotMenuButton; bots.setBotBroadcastDefaultAdminRights#788464e1 admin_rights:ChatAdminRights = Bool; bots.setBotGroupDefaultAdminRights#925ec9ea admin_rights:ChatAdminRights = Bool; -payments.getPaymentForm#8a333c8d flags:# peer:InputPeer msg_id:int theme_params:flags.0?DataJSON = payments.PaymentForm; +payments.getPaymentForm#37148dbb flags:# invoice:InputInvoice theme_params:flags.0?DataJSON = payments.PaymentForm; payments.getPaymentReceipt#2478d1cc peer:InputPeer msg_id:int = payments.PaymentReceipt; -payments.validateRequestedInfo#db103170 flags:# save:flags.0?true peer:InputPeer msg_id:int info:PaymentRequestedInfo = payments.ValidatedRequestedInfo; -payments.sendPaymentForm#30c3bc9d flags:# form_id:long peer:InputPeer msg_id:int requested_info_id:flags.0?string shipping_option_id:flags.1?string credentials:InputPaymentCredentials tip_amount:flags.2?long = payments.PaymentResult; +payments.validateRequestedInfo#b6c8f12b flags:# save:flags.0?true invoice:InputInvoice info:PaymentRequestedInfo = payments.ValidatedRequestedInfo; +payments.sendPaymentForm#2d03522f flags:# form_id:long invoice:InputInvoice requested_info_id:flags.0?string shipping_option_id:flags.1?string credentials:InputPaymentCredentials tip_amount:flags.2?long = payments.PaymentResult; payments.getSavedInfo#227d824b = payments.SavedInfo; payments.clearSavedInfo#d83d70c1 flags:# credentials:flags.0?true info:flags.1?true = Bool; payments.getBankCardData#2e79d779 number:string = payments.BankCardData; +payments.exportInvoice#f91b065 invoice_media:InputMedia = payments.ExportedInvoice; +payments.assignAppStoreTransaction#fec13c6 flags:# restore:flags.0?true transaction_id:string receipt:bytes = Updates; +payments.assignPlayMarketTransaction#4faa4aed purchase_token:string = Updates; +payments.restorePlayMarketReceipt#d164e36a receipt:bytes = Updates; +payments.canPurchasePremium#aa6a90c8 = Bool; +payments.requestRecurringPayment#146e958d user_id:InputUser recurring_init_charge:string invoice_media:InputMedia = Updates; stickers.createStickerSet#9021ab67 flags:# masks:flags.0?true animated:flags.1?true videos:flags.4?true user_id:InputUser title:string short_name:string thumb:flags.2?InputDocument stickers:Vector software:flags.3?string = messages.StickerSet; stickers.removeStickerFromSet#f7760f51 sticker:InputDocument = messages.StickerSet; @@ -1802,4 +1831,4 @@ stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats; -// LAYER 142 \ No newline at end of file +// LAYER 143 \ No newline at end of file diff --git a/pyrogram/types/user_and_chats/user.py b/pyrogram/types/user_and_chats/user.py index 8860784d8d..4df3b17e76 100644 --- a/pyrogram/types/user_and_chats/user.py +++ b/pyrogram/types/user_and_chats/user.py @@ -97,6 +97,9 @@ class User(Object, Update): is_support (``bool``, *optional*): True, if this user is part of the Telegram support team. + is_premium (``bool``, *optional*): + True, if this user is a premium user. + first_name (``str``, *optional*): User's or bot's first name. @@ -156,6 +159,7 @@ def __init__( is_scam: bool = None, is_fake: bool = None, is_support: bool = None, + is_premium: bool = None, first_name: str = None, last_name: str = None, status: "enums.UserStatus" = None, @@ -181,6 +185,7 @@ def __init__( self.is_scam = is_scam self.is_fake = is_fake self.is_support = is_support + self.is_premium = is_premium self.first_name = first_name self.last_name = last_name self.status = status @@ -218,6 +223,7 @@ def _parse(client, user: "raw.base.User") -> Optional["User"]: is_scam=user.scam, is_fake=user.fake, is_support=user.support, + is_premium=user.premium, first_name=user.first_name, last_name=user.last_name, **User._parse_status(user.status, user.bot), From b59dcd16152d51467041cc68f9d4fdcfe37174c0 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 20 Jun 2022 09:32:10 +0200 Subject: [PATCH 0953/1185] Do not trigger a reconnection when skipping invalid packets --- pyrogram/session/session.py | 1 - 1 file changed, 1 deletion(-) diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index 2e90184a2c..a6537bb9a4 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -189,7 +189,6 @@ async def handle_packet(self, packet): self.stored_msg_ids ) except SecurityCheckMismatch: - self.connection.close() return messages = ( From d61a2ce8a9e6dcb337410b3d3f955e29820fe74e Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 20 Jun 2022 09:40:30 +0200 Subject: [PATCH 0954/1185] Remove syncer.py --- pyrogram/methods/auth/initialize.py | 2 - pyrogram/methods/auth/terminate.py | 3 +- pyrogram/syncer.py | 88 ----------------------------- 3 files changed, 1 insertion(+), 92 deletions(-) delete mode 100644 pyrogram/syncer.py diff --git a/pyrogram/methods/auth/initialize.py b/pyrogram/methods/auth/initialize.py index cb8fd0cc74..48bd6f10ff 100644 --- a/pyrogram/methods/auth/initialize.py +++ b/pyrogram/methods/auth/initialize.py @@ -19,7 +19,6 @@ import logging import pyrogram -from pyrogram.syncer import Syncer log = logging.getLogger(__name__) @@ -46,7 +45,6 @@ async def initialize( self.load_plugins() await self.dispatcher.start() - await Syncer.add(self) self.username = (await self.get_me()).username self.is_initialized = True diff --git a/pyrogram/methods/auth/terminate.py b/pyrogram/methods/auth/terminate.py index 548d030cb4..707c25e90e 100644 --- a/pyrogram/methods/auth/terminate.py +++ b/pyrogram/methods/auth/terminate.py @@ -20,7 +20,6 @@ import pyrogram from pyrogram import raw -from pyrogram.syncer import Syncer log = logging.getLogger(__name__) @@ -44,7 +43,7 @@ async def terminate( await self.invoke(raw.functions.account.FinishTakeoutSession()) log.warning(f"Takeout session {self.takeout_id} finished") - await Syncer.remove(self) + await self.storage.save() await self.dispatcher.stop() for media_session in self.media_sessions.values(): diff --git a/pyrogram/syncer.py b/pyrogram/syncer.py deleted file mode 100644 index 3ff1bfc9fd..0000000000 --- a/pyrogram/syncer.py +++ /dev/null @@ -1,88 +0,0 @@ -# Pyrogram - Telegram MTProto API Client Library for Python -# Copyright (C) 2017-present Dan -# -# 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 asyncio -import logging -import time - -log = logging.getLogger(__name__) - - -class Syncer: - INTERVAL = 20 - - clients = {} - event = None - lock = None - - @classmethod - async def add(cls, client): - if cls.event is None: - cls.event = asyncio.Event() - - if cls.lock is None: - cls.lock = asyncio.Lock() - - async with cls.lock: - await cls.sync(client) - - cls.clients[id(client)] = client - - if len(cls.clients) == 1: - cls.start() - - @classmethod - async def remove(cls, client): - async with cls.lock: - await cls.sync(client) - - del cls.clients[id(client)] - - if len(cls.clients) == 0: - cls.stop() - - @classmethod - def start(cls): - cls.event.clear() - asyncio.get_event_loop().create_task(cls.worker()) - - @classmethod - def stop(cls): - cls.event.set() - - @classmethod - async def worker(cls): - while True: - try: - await asyncio.wait_for(cls.event.wait(), cls.INTERVAL) - except asyncio.TimeoutError: - async with cls.lock: - for client in cls.clients.values(): - await cls.sync(client) - else: - break - - @classmethod - async def sync(cls, client): - try: - start = time.time() - await client.storage.save() - except Exception as e: - log.critical(e, exc_info=True) - else: - log.debug(f'Synced "{client.storage.name}" in {(time.time() - start) * 1000:.6} ms') From b35810dc9f1332d37a33847fd8846d12394c2db0 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 20 Jun 2022 09:48:03 +0200 Subject: [PATCH 0955/1185] Update compose example --- pyrogram/methods/utilities/compose.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pyrogram/methods/utilities/compose.py b/pyrogram/methods/utilities/compose.py index cfc5ca3e61..e05773b8e1 100644 --- a/pyrogram/methods/utilities/compose.py +++ b/pyrogram/methods/utilities/compose.py @@ -49,13 +49,15 @@ async def compose( async def main(): - app1 = Client("account1") - app2 = Client("account2") - app3 = Client("account3") + apps = [ + Client("account1"), + Client("account2"), + Client("account3") + ] ... - await compose([app1, app2, app3]) + await compose(apps) asyncio.run(main()) From 0a50520fc94ef53a55f6d36714102f934f891b37 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 20 Jun 2022 09:51:16 +0200 Subject: [PATCH 0956/1185] Improve idle() implementation --- pyrogram/methods/utilities/idle.py | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/pyrogram/methods/utilities/idle.py b/pyrogram/methods/utilities/idle.py index bcd685e08d..00db22a2e4 100644 --- a/pyrogram/methods/utilities/idle.py +++ b/pyrogram/methods/utilities/idle.py @@ -23,8 +23,6 @@ log = logging.getLogger(__name__) -is_idling = False - # Signal number to name signals = { k: v for v, k in signal.__dict__.items() @@ -71,18 +69,19 @@ async def main(): asyncio.run(main()) """ - global is_idling + task = None def signal_handler(signum, __): - global is_idling - logging.info(f"Stop signal received ({signals[signum]}). Exiting...") - is_idling = False + task.cancel() for s in (SIGINT, SIGTERM, SIGABRT): signal_fn(s, signal_handler) - is_idling = True + while True: + task = asyncio.create_task(asyncio.sleep(600)) - while is_idling: - await asyncio.sleep(1) + try: + await task + except asyncio.CancelledError: + break From 6a766faf2fa784c2db786bd57676e1c2ee982166 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 20 Jun 2022 09:53:27 +0200 Subject: [PATCH 0957/1185] Update README.md --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index 764526769f..6b904e2987 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,10 @@
Telegram MTProto API Framework for Python
+ + Homepage + + • Documentation From d71db29a8c6f579719f6a0895be43782a5619803 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 20 Jun 2022 10:28:49 +0200 Subject: [PATCH 0958/1185] Store the "me" user object --- pyrogram/client.py | 3 +-- pyrogram/filters.py | 2 +- pyrogram/methods/auth/initialize.py | 3 ++- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pyrogram/client.py b/pyrogram/client.py index c1d04046b2..d30a5e9a80 100644 --- a/pyrogram/client.py +++ b/pyrogram/client.py @@ -270,8 +270,7 @@ def __init__( self.disconnect_handler = None - # Username used for mentioned bot commands, e.g.: /start@usernamebot - self.username = None + self.me: Optional[User] = None self.message_cache = Cache(10000) diff --git a/pyrogram/filters.py b/pyrogram/filters.py index 76bff856ff..f31b385ed4 100644 --- a/pyrogram/filters.py +++ b/pyrogram/filters.py @@ -757,7 +757,7 @@ def command(commands: Union[str, List[str]], prefixes: Union[str, List[str]] = " command_re = re.compile(r"([\"'])(.*?)(? Date: Mon, 20 Jun 2022 10:32:17 +0200 Subject: [PATCH 0959/1185] Improve upload file size checks --- pyrogram/methods/advanced/save_file.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pyrogram/methods/advanced/save_file.py b/pyrogram/methods/advanced/save_file.py index 706f28e04f..6a43deac6f 100644 --- a/pyrogram/methods/advanced/save_file.py +++ b/pyrogram/methods/advanced/save_file.py @@ -125,8 +125,10 @@ async def worker(session): if file_size == 0: raise ValueError("File size equals to 0 B") - if file_size > 2000 * 1024 * 1024: - raise ValueError("Telegram doesn't support uploading files bigger than 2000 MiB") + file_size_limit_mib = 4000 if self.me.is_premium else 2000 + + if file_size > file_size_limit_mib * 1024 * 1024: + raise ValueError(f"Can't upload files bigger than {file_size_limit_mib} MiB") file_total_parts = int(math.ceil(file_size / part_size)) is_big = file_size > 10 * 1024 * 1024 From 34ffc4991ae1c3ce17ae39bbdedf722269644f3b Mon Sep 17 00:00:00 2001 From: noreph <60476630+noreph@users.noreply.github.com> Date: Mon, 20 Jun 2022 17:41:10 +0800 Subject: [PATCH 0960/1185] Fix example typo (#1020) --- pyrogram/methods/messages/send_photo.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/methods/messages/send_photo.py b/pyrogram/methods/messages/send_photo.py index 836ce1b1d4..6add68cd6c 100644 --- a/pyrogram/methods/messages/send_photo.py +++ b/pyrogram/methods/messages/send_photo.py @@ -131,7 +131,7 @@ async def send_photo( await app.send_photo("me", "photo.jpg") # Send photo by uploading from URL - await app.send_photo("me", "https://example.com/example.jpg) + await app.send_photo("me", "https://example.com/example.jpg") # Add caption to a photo await app.send_photo("me", "photo.jpg", caption="Caption") From b904a4f3e28b7ba95a605f2c64c09c73a416712e Mon Sep 17 00:00:00 2001 From: "Mr. Developer" <77911154+MrBotDeveloper@users.noreply.github.com> Date: Mon, 20 Jun 2022 15:12:11 +0530 Subject: [PATCH 0961/1185] Fix FAQ example (#1007) --- docs/source/faq/how-to-avoid-flood-waits.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/source/faq/how-to-avoid-flood-waits.rst b/docs/source/faq/how-to-avoid-flood-waits.rst index 4d2994d1f4..06d1cdc2a9 100644 --- a/docs/source/faq/how-to-avoid-flood-waits.rst +++ b/docs/source/faq/how-to-avoid-flood-waits.rst @@ -9,7 +9,7 @@ The following shows how to catch the exception in your code and wait the require .. code-block:: python - import time + import asyncio from pyrogram.errors import FloodWait ... @@ -20,4 +20,4 @@ The following shows how to catch the exception in your code and wait the require ... -More info about error handling can be found :doc:`here <../start/errors>`. \ No newline at end of file +More info about error handling can be found :doc:`here <../start/errors>`. From eb4ff1427be795407e1138192a764286099658f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E0=AE=AE=E0=AE=A9=E0=AF=8B=E0=AE=9C=E0=AF=8D=E0=AE=95?= =?UTF-8?q?=E0=AF=81=E0=AE=AE=E0=AE=BE=E0=AE=B0=E0=AF=8D=20=E0=AE=AA?= =?UTF-8?q?=E0=AE=B4=E0=AE=A9=E0=AE=BF=E0=AE=9A=E0=AF=8D=E0=AE=9A=E0=AE=BE?= =?UTF-8?q?=E0=AE=AE=E0=AE=BF?= Date: Mon, 20 Jun 2022 15:12:37 +0530 Subject: [PATCH 0962/1185] Fix delete_profile_photos example (#990) --- pyrogram/methods/users/delete_profile_photos.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/methods/users/delete_profile_photos.py b/pyrogram/methods/users/delete_profile_photos.py index e7205661eb..2e7615444f 100644 --- a/pyrogram/methods/users/delete_profile_photos.py +++ b/pyrogram/methods/users/delete_profile_photos.py @@ -43,7 +43,7 @@ async def delete_profile_photos( .. code-block:: python # Get the photos to be deleted - photos = await app.get_profile_photos("me") + photos = list(await app.get_chat_photos("me")) # Delete one photo await app.delete_profile_photos(photos[0].file_id) From 4b10ec8e87956bf220eaf0dc180adf1b14d51392 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 20 Jun 2022 11:43:40 +0200 Subject: [PATCH 0963/1185] Pickle datetime objects into timestamps (#1016) * Pickle datetime objects into timestamps * Rename variable * Add length check --- pyrogram/types/object.py | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/pyrogram/types/object.py b/pyrogram/types/object.py index 28bee9a7fc..3253c8ecff 100644 --- a/pyrogram/types/object.py +++ b/pyrogram/types/object.py @@ -98,7 +98,24 @@ def __eq__(self, other: "Object") -> bool: return True + def __setstate__(self, state): + for attr in state: + obj = state[attr] + + # Maybe a better alternative would be https://docs.python.org/3/library/inspect.html#inspect.signature + if isinstance(obj, tuple) and len(obj) == 2 and obj[0] == "dt": + state[attr] = datetime.fromtimestamp(obj[1]) + + self.__dict__ = state + def __getstate__(self): - new_dict = self.__dict__.copy() - new_dict.pop("_client", None) - return new_dict + state = self.__dict__.copy() + state.pop("_client", None) + + for attr in state: + obj = state[attr] + + if isinstance(obj, datetime): + state[attr] = ("dt", obj.timestamp()) + + return state From 81baf853b514539b367f007428991db655dca3d5 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 20 Jun 2022 11:44:41 +0200 Subject: [PATCH 0964/1185] Update Pyrogram to v2.0.28 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 6e562ce075..ca5dd4e9cd 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.27" +__version__ = "2.0.28" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From 212ea5739095fbb00553818e812727999aeabeff Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 20 Jun 2022 11:49:17 +0200 Subject: [PATCH 0965/1185] Fix tests --- tests/filters/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/filters/__init__.py b/tests/filters/__init__.py index ebc674c470..6711b51b4c 100644 --- a/tests/filters/__init__.py +++ b/tests/filters/__init__.py @@ -18,10 +18,10 @@ class Client: def __init__(self): - self.username = "username" + self.me = User("username") async def get_me(self): - return User(self.username) + return self.me class User: From 78e1d29b3744a12beef7bca69dc8776d4e28e91f Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 20 Jun 2022 11:49:43 +0200 Subject: [PATCH 0966/1185] Update Pyrogram to v2.0.29 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index ca5dd4e9cd..3be904d46a 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.28" +__version__ = "2.0.29" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From 0e0642ebfdb70281f62b7aef61bbd998be2efe30 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 21 Jun 2022 12:12:08 +0200 Subject: [PATCH 0967/1185] Add [403 PREMIUM_ACCOUNT_REQUIRED] error Closes #1024 --- compiler/errors/source/403_FORBIDDEN.tsv | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/errors/source/403_FORBIDDEN.tsv b/compiler/errors/source/403_FORBIDDEN.tsv index ff6955f1f1..69777cd249 100644 --- a/compiler/errors/source/403_FORBIDDEN.tsv +++ b/compiler/errors/source/403_FORBIDDEN.tsv @@ -24,4 +24,5 @@ USER_INVALID The provided user is invalid USER_IS_BLOCKED The user is blocked USER_NOT_MUTUAL_CONTACT The provided user is not a mutual contact USER_PRIVACY_RESTRICTED The user's privacy settings is preventing you to perform this action -USER_RESTRICTED You are limited/restricted. You can't perform this action \ No newline at end of file +USER_RESTRICTED You are limited/restricted. You can't perform this action +PREMIUM_ACCOUNT_REQUIRED This action requires a premium account \ No newline at end of file From 3aaf35792f2cfa2f2adbf84992ee5411af65c52d Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 21 Jun 2022 12:16:14 +0200 Subject: [PATCH 0968/1185] Update Pyrogram to v2.0.30 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 3be904d46a..146c3ce879 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.29" +__version__ = "2.0.30" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From 4398cbb561ac92af18c61378d3bebcb6fb2ae73a Mon Sep 17 00:00:00 2001 From: Davide Galilei <43778739+DavideGalilei@users.noreply.github.com> Date: Thu, 14 Jul 2022 20:21:34 +0200 Subject: [PATCH 0969/1185] Improve edit_inline_media (#1036) --- .../methods/messages/edit_inline_media.py | 48 +++++++++++++++---- pyrogram/methods/messages/inline_session.py | 4 +- 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/pyrogram/methods/messages/edit_inline_media.py b/pyrogram/methods/messages/edit_inline_media.py index 59a18aa7b1..cec4a3e1d3 100644 --- a/pyrogram/methods/messages/edit_inline_media.py +++ b/pyrogram/methods/messages/edit_inline_media.py @@ -18,16 +18,20 @@ import os import re +import asyncio import pyrogram from pyrogram import raw from pyrogram import types from pyrogram import utils +from pyrogram.errors import RPCError, MediaEmpty from pyrogram.file_id import FileType from .inline_session import get_session class EditInlineMedia: + MAX_RETRIES = 3 + async def edit_inline_media( self: "pyrogram.Client", inline_message_id: str, @@ -147,7 +151,8 @@ async def edit_inline_media( file_name=os.path.basename(media.media) ), raw.types.DocumentAttributeAnimated() - ] + ], + nosound_video=True ) elif re.match("^https?://", media.media): media = raw.types.InputMediaDocumentExternal( @@ -165,7 +170,8 @@ async def edit_inline_media( raw.types.DocumentAttributeFilename( file_name=os.path.basename(media.media) ) - ] + ], + force_file=True ) elif re.match("^https?://", media.media): media = raw.types.InputMediaDocumentExternal( @@ -179,12 +185,34 @@ async def edit_inline_media( session = await get_session(self, dc_id) - return await session.invoke( - raw.functions.messages.EditInlineBotMessage( - id=unpacked, - media=media, - reply_markup=await reply_markup.write(self) if reply_markup else None, - **await self.parser.parse(caption, parse_mode) - ), - sleep_threshold=self.sleep_threshold + actual_media = await self.invoke( + raw.functions.messages.UploadMedia( + peer=raw.types.InputPeerSelf(), + media=media + ) ) + + for i in range(self.MAX_RETRIES): + try: + return await session.invoke( + raw.functions.messages.EditInlineBotMessage( + id=unpacked, + media=raw.types.InputMediaDocument( + id=raw.types.InputDocument( + id=actual_media.document.id, + access_hash=actual_media.document.access_hash, + file_reference=actual_media.document.file_reference + ) + ), + reply_markup=await reply_markup.write(self) if reply_markup else None, + **await self.parser.parse(caption, parse_mode) + ), + sleep_threshold=self.sleep_threshold + ) + except RPCError as e: + if i == self.MAX_RETRIES - 1: + raise + + if isinstance(e, MediaEmpty): + # Must wait due to a server race condition + await asyncio.sleep(1) diff --git a/pyrogram/methods/messages/inline_session.py b/pyrogram/methods/messages/inline_session.py index 57fa794584..a514f5a68c 100644 --- a/pyrogram/methods/messages/inline_session.py +++ b/pyrogram/methods/messages/inline_session.py @@ -33,8 +33,8 @@ async def get_session(client: "pyrogram.Client", dc_id: int): session = client.media_sessions[dc_id] = Session( client, dc_id, - await Auth(client, dc_id, False).create(), - False, is_media=True + await Auth(client, dc_id, await client.storage.test_mode()).create(), + await client.storage.test_mode(), is_media=True ) await session.start() From 11d6a4a833c4e9f925507d255d5306d94e466094 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 14 Jul 2022 20:22:48 +0200 Subject: [PATCH 0970/1185] Update Pyrogram to v2.0.31 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 146c3ce879..a9390e6418 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.30" +__version__ = "2.0.31" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From 70e73d3ca8953bf1dad9bfc5c29f604b343007e0 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 14 Jul 2022 21:01:07 +0200 Subject: [PATCH 0971/1185] Add ENTITY_BOUNDS_INVALID error description --- compiler/errors/source/400_BAD_REQUEST.tsv | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/compiler/errors/source/400_BAD_REQUEST.tsv b/compiler/errors/source/400_BAD_REQUEST.tsv index 990d875ea8..726f5f829a 100644 --- a/compiler/errors/source/400_BAD_REQUEST.tsv +++ b/compiler/errors/source/400_BAD_REQUEST.tsv @@ -349,4 +349,5 @@ WEBDOCUMENT_URL_EMPTY The web document URL is empty WEBDOCUMENT_URL_INVALID The web document URL is invalid WEBPAGE_CURL_FAILED Telegram server could not fetch the provided URL WEBPAGE_MEDIA_EMPTY The URL doesn't contain any valid media -YOU_BLOCKED_USER You blocked this user \ No newline at end of file +YOU_BLOCKED_USER You blocked this user +ENTITY_BOUNDS_INVALID The message entity bounds are invalid \ No newline at end of file From d1e8b3bf1e87e65bf5724cbae709e8dadac02183 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 14 Jul 2022 21:01:30 +0200 Subject: [PATCH 0972/1185] Update Pyrogram to v2.0.32 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index a9390e6418..8cc2b9e0bd 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.31" +__version__ = "2.0.32" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From 298d361092aee2c410bb1086d65483151fae2de7 Mon Sep 17 00:00:00 2001 From: Mahesh <44301650+Mahesh0253@users.noreply.github.com> Date: Sat, 16 Jul 2022 22:55:26 +0530 Subject: [PATCH 0973/1185] Store "me" user object before starting dispatcher (#1042) --- pyrogram/methods/auth/initialize.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/methods/auth/initialize.py b/pyrogram/methods/auth/initialize.py index 3e69204430..26c9d0bf8c 100644 --- a/pyrogram/methods/auth/initialize.py +++ b/pyrogram/methods/auth/initialize.py @@ -44,8 +44,8 @@ async def initialize( self.load_plugins() - await self.dispatcher.start() - self.me = await self.get_me() + await self.dispatcher.start() + self.is_initialized = True From d9c8e0450b0913e3949ab5938bb5d91dc6af7c63 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 16 Jul 2022 19:25:57 +0200 Subject: [PATCH 0974/1185] Update Pyrogram to v2.0.33 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 8cc2b9e0bd..673b89fb36 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.32" +__version__ = "2.0.33" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From ed748952b5d7acb33ed64e3d63de895419144cdb Mon Sep 17 00:00:00 2001 From: Harsh <65716674+Harsh-br0@users.noreply.github.com> Date: Fri, 22 Jul 2022 20:45:18 +0530 Subject: [PATCH 0975/1185] Filter out empty entities internally (#1041) * Filter out empty entities internally I guess it's fine to handle empty entities internally to avoid ENTITY_BOUNDS_INVALID , so the client won't send the empty entities * revert utils and apply changes to parser/html.py * Update utils.py * Update utils.py * Update utils.py * Update html.py * Update utils.py * Update utils.py Co-authored-by: Dan <14043624+delivrance@users.noreply.github.com> --- pyrogram/parser/html.py | 19 +++++++++++++++---- pyrogram/utils.py | 23 +++++++++++++++++------ 2 files changed, 32 insertions(+), 10 deletions(-) diff --git a/pyrogram/parser/html.py b/pyrogram/parser/html.py index 8a7063e58a..d9a8d36876 100644 --- a/pyrogram/parser/html.py +++ b/pyrogram/parser/html.py @@ -140,6 +140,9 @@ async def parse(self, text: str): entities.append(entity) + # Remove zero-length entities + entities = list(filter(lambda x: x.length > 0, entities)) + return { "message": utils.remove_surrogates(parser.text), "entities": sorted(entities, key=lambda e: e.offset) @@ -156,13 +159,21 @@ def unparse(text: str, entities: list): start = entity.offset end = start + entity.length - if entity_type in (MessageEntityType.BOLD, MessageEntityType.ITALIC, MessageEntityType.UNDERLINE, - MessageEntityType.STRIKETHROUGH): + if entity_type in ( + MessageEntityType.BOLD, + MessageEntityType.ITALIC, + MessageEntityType.UNDERLINE, + MessageEntityType.STRIKETHROUGH, + ): name = entity_type.name[0].lower() start_tag = f"<{name}>" end_tag = f"" - elif entity_type in (MessageEntityType.CODE, MessageEntityType.PRE, MessageEntityType.BLOCKQUOTE, - MessageEntityType.SPOILER): + elif entity_type in ( + MessageEntityType.CODE, + MessageEntityType.PRE, + MessageEntityType.BLOCKQUOTE, + MessageEntityType.SPOILER, + ): name = entity_type.name.lower() start_tag = f"<{name}>" end_tag = f"" diff --git a/pyrogram/utils.py b/pyrogram/utils.py index d5862d1a67..b6a8d6bfb6 100644 --- a/pyrogram/utils.py +++ b/pyrogram/utils.py @@ -48,8 +48,10 @@ def get_input_media_from_file_id( try: decoded = FileId.decode(file_id) except Exception: - raise ValueError(f'Failed to decode "{file_id}". The value does not represent an existing local file, ' - f'HTTP URL, or valid file id.') + raise ValueError( + f'Failed to decode "{file_id}". The value does not represent an existing local file, ' + f"HTTP URL, or valid file id." + ) file_type = decoded.file_type @@ -82,7 +84,11 @@ def get_input_media_from_file_id( raise ValueError(f"Unknown file id: {file_id}") -async def parse_messages(client, messages: "raw.types.messages.Messages", replies: int = 1) -> List["types.Message"]: +async def parse_messages( + client, + messages: "raw.types.messages.Messages", + replies: int = 1 +) -> List["types.Message"]: users = {i.id: i for i in messages.users} chats = {i.id: i for i in messages.chats} @@ -260,8 +266,10 @@ def xor(a: bytes, b: bytes) -> bytes: return bytes(i ^ j for i, j in zip(a, b)) -def compute_password_hash(algo: raw.types.PasswordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow, - password: str) -> bytes: +def compute_password_hash( + algo: raw.types.PasswordKdfAlgoSHA256SHA256PBKDF2HMACSHA512iter100000SHA256ModPow, + password: str +) -> bytes: hash1 = sha256(algo.salt1 + password.encode() + algo.salt1) hash2 = sha256(algo.salt2 + hash1 + algo.salt2) hash3 = hashlib.pbkdf2_hmac("sha512", hash2, algo.salt1, 100000) @@ -270,7 +278,10 @@ def compute_password_hash(algo: raw.types.PasswordKdfAlgoSHA256SHA256PBKDF2HMACS # noinspection PyPep8Naming -def compute_password_check(r: raw.types.account.Password, password: str) -> raw.types.InputCheckPasswordSRP: +def compute_password_check( + r: raw.types.account.Password, + password: str +) -> raw.types.InputCheckPasswordSRP: algo = r.current_algo p_bytes = algo.p From 6f7ec0de03a2b497d3a5c844c1c9e1ec40896483 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 22 Jul 2022 17:16:13 +0200 Subject: [PATCH 0976/1185] Update Pyrogram to v2.0.34 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 673b89fb36..35373be398 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.33" +__version__ = "2.0.34" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From 673660242422fb4b852e84dd028af6a2a50002cb Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 23 Jul 2022 22:52:21 +0200 Subject: [PATCH 0977/1185] Handle socket.connect() blocking-ness --- pyrogram/connection/transport/tcp/tcp.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pyrogram/connection/transport/tcp/tcp.py b/pyrogram/connection/transport/tcp/tcp.py index 5cd67937e5..c0efb625ab 100644 --- a/pyrogram/connection/transport/tcp/tcp.py +++ b/pyrogram/connection/transport/tcp/tcp.py @@ -21,6 +21,7 @@ import logging import socket import time +from concurrent.futures import ThreadPoolExecutor try: import socks @@ -78,7 +79,11 @@ def __init__(self, ipv6: bool, proxy: dict): self.socket.settimeout(TCP.TIMEOUT) async def connect(self, address: tuple): - self.socket.connect(address) + # The socket used by the whole logic is blocking and thus it blocks when connecting. + # Offload the task to a thread executor to avoid blocking the main event loop. + with ThreadPoolExecutor(1) as executor: + await self.loop.run_in_executor(executor, self.socket.connect, address) + self.reader, self.writer = await asyncio.open_connection(sock=self.socket) def close(self): From de3127720ecce9ece8da99f32c9dfd8ce55793cd Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 23 Jul 2022 22:52:47 +0200 Subject: [PATCH 0978/1185] Update Pyrogram to v2.0.35 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 35373be398..95fa60c7ba 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.34" +__version__ = "2.0.35" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From 0505ff0d5e22ae13239a9737053933476d712214 Mon Sep 17 00:00:00 2001 From: AsmSafone <77989182+AsmSafone@users.noreply.github.com> Date: Thu, 11 Aug 2022 17:56:21 +0600 Subject: [PATCH 0979/1185] Add some sticker-related errors (#1055) * Add STICKERS_TOO_MUCH, STICKERSET_NOT_MODIFIED, STICKER_VIDEO_NOWEBM error description * Update 400_BAD_REQUEST.tsv Co-authored-by: Dan <14043624+delivrance@users.noreply.github.com> --- compiler/errors/source/400_BAD_REQUEST.tsv | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/compiler/errors/source/400_BAD_REQUEST.tsv b/compiler/errors/source/400_BAD_REQUEST.tsv index 726f5f829a..877c967b37 100644 --- a/compiler/errors/source/400_BAD_REQUEST.tsv +++ b/compiler/errors/source/400_BAD_REQUEST.tsv @@ -285,7 +285,9 @@ START_PARAM_EMPTY The start parameter is empty START_PARAM_INVALID The start parameter is invalid START_PARAM_TOO_LONG The start parameter is too long STICKERSET_INVALID The requested sticker set is invalid +STICKERSET_NOT_MODIFIED The sticker set is not modified STICKERS_EMPTY The sticker provided is empty +STICKERS_TOO_MUCH Too many stickers in the set STICKER_DOCUMENT_INVALID The sticker document is invalid STICKER_EMOJI_INVALID The sticker emoji is invalid STICKER_FILE_INVALID The sticker file is invalid @@ -294,6 +296,7 @@ STICKER_INVALID The provided sticker is invalid STICKER_PNG_DIMENSIONS The sticker png dimensions are invalid STICKER_PNG_NOPNG Stickers must be png files but the provided image was not a png STICKER_TGS_NOTGS A tgs sticker file was expected, but something else was provided +STICKER_VIDEO_NOWEBM A webm video file was expected, but something else was provided STICKER_THUMB_PNG_NOPNG A png sticker thumbnail file was expected, but something else was provided TAKEOUT_INVALID The takeout id is invalid TAKEOUT_REQUIRED The method must be invoked inside a takeout session @@ -350,4 +353,4 @@ WEBDOCUMENT_URL_INVALID The web document URL is invalid WEBPAGE_CURL_FAILED Telegram server could not fetch the provided URL WEBPAGE_MEDIA_EMPTY The URL doesn't contain any valid media YOU_BLOCKED_USER You blocked this user -ENTITY_BOUNDS_INVALID The message entity bounds are invalid \ No newline at end of file +ENTITY_BOUNDS_INVALID The message entity bounds are invalid From 8da085198442a879103e813b035672c2ab827c52 Mon Sep 17 00:00:00 2001 From: dogghi <75123663+doggyhaha@users.noreply.github.com> Date: Thu, 11 Aug 2022 13:58:36 +0200 Subject: [PATCH 0980/1185] Add support for BytesIO in InputMedia objects (#1047) fix docstrings and fix "TypeError: stat: path should be string, bytes, os.PathLike or integer, not BytesIO" when passing a BytesIO object to an InputMedia subclass --- pyrogram/methods/messages/edit_inline_media.py | 11 ++++++----- pyrogram/methods/messages/edit_message_media.py | 11 ++++++----- pyrogram/types/input_media/input_media.py | 4 ++-- pyrogram/types/input_media/input_media_animation.py | 2 +- pyrogram/types/input_media/input_media_audio.py | 2 +- pyrogram/types/input_media/input_media_document.py | 2 +- pyrogram/types/input_media/input_media_video.py | 2 +- 7 files changed, 18 insertions(+), 16 deletions(-) diff --git a/pyrogram/methods/messages/edit_inline_media.py b/pyrogram/methods/messages/edit_inline_media.py index cec4a3e1d3..4126c802fb 100644 --- a/pyrogram/methods/messages/edit_inline_media.py +++ b/pyrogram/methods/messages/edit_inline_media.py @@ -19,6 +19,7 @@ import os import re import asyncio +import io import pyrogram from pyrogram import raw @@ -77,7 +78,7 @@ async def edit_inline_media( parse_mode = media.parse_mode if isinstance(media, types.InputMediaPhoto): - if os.path.isfile(media.media): + if isinstance(media.media, io.BytesIO) or os.path.isfile(media.media): media = raw.types.InputMediaUploadedPhoto( file=await self.save_file(media.media) ) @@ -88,7 +89,7 @@ async def edit_inline_media( else: media = utils.get_input_media_from_file_id(media.media, FileType.PHOTO) elif isinstance(media, types.InputMediaVideo): - if os.path.isfile(media.media): + if isinstance(media.media, io.BytesIO) or os.path.isfile(media.media): media = raw.types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(media.media) or "video/mp4", thumb=await self.save_file(media.thumb), @@ -112,7 +113,7 @@ async def edit_inline_media( else: media = utils.get_input_media_from_file_id(media.media, FileType.VIDEO) elif isinstance(media, types.InputMediaAudio): - if os.path.isfile(media.media): + if isinstance(media.media, io.BytesIO) or os.path.isfile(media.media): media = raw.types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(media.media) or "audio/mpeg", thumb=await self.save_file(media.thumb), @@ -135,7 +136,7 @@ async def edit_inline_media( else: media = utils.get_input_media_from_file_id(media.media, FileType.AUDIO) elif isinstance(media, types.InputMediaAnimation): - if os.path.isfile(media.media): + if isinstance(media.media, io.BytesIO) or os.path.isfile(media.media): media = raw.types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(media.media) or "video/mp4", thumb=await self.save_file(media.thumb), @@ -161,7 +162,7 @@ async def edit_inline_media( else: media = utils.get_input_media_from_file_id(media.media, FileType.ANIMATION) elif isinstance(media, types.InputMediaDocument): - if os.path.isfile(media.media): + if isinstance(media.media, io.BytesIO) or os.path.isfile(media.media): media = raw.types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(media.media) or "application/zip", thumb=await self.save_file(media.thumb), diff --git a/pyrogram/methods/messages/edit_message_media.py b/pyrogram/methods/messages/edit_message_media.py index 3dce81edb9..a3840f2374 100644 --- a/pyrogram/methods/messages/edit_message_media.py +++ b/pyrogram/methods/messages/edit_message_media.py @@ -18,6 +18,7 @@ import os import re +import io from typing import Union import pyrogram @@ -89,7 +90,7 @@ async def edit_message_media( message, entities = (await self.parser.parse(caption, parse_mode)).values() if isinstance(media, types.InputMediaPhoto): - if os.path.isfile(media.media): + if isinstance(media.media, io.BytesIO) or os.path.isfile(media.media): media = await self.invoke( raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), @@ -113,7 +114,7 @@ async def edit_message_media( else: media = utils.get_input_media_from_file_id(media.media, FileType.PHOTO) elif isinstance(media, types.InputMediaVideo): - if os.path.isfile(media.media): + if isinstance(media.media, io.BytesIO) or os.path.isfile(media.media): media = await self.invoke( raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), @@ -150,7 +151,7 @@ async def edit_message_media( else: media = utils.get_input_media_from_file_id(media.media, FileType.VIDEO) elif isinstance(media, types.InputMediaAudio): - if os.path.isfile(media.media): + if isinstance(media.media, io.BytesIO) or os.path.isfile(media.media): media = await self.invoke( raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), @@ -186,7 +187,7 @@ async def edit_message_media( else: media = utils.get_input_media_from_file_id(media.media, FileType.AUDIO) elif isinstance(media, types.InputMediaAnimation): - if os.path.isfile(media.media): + if isinstance(media.media, io.BytesIO) or os.path.isfile(media.media): media = await self.invoke( raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), @@ -224,7 +225,7 @@ async def edit_message_media( else: media = utils.get_input_media_from_file_id(media.media, FileType.ANIMATION) elif isinstance(media, types.InputMediaDocument): - if os.path.isfile(media.media): + if isinstance(media.media, io.BytesIO) or os.path.isfile(media.media): media = await self.invoke( raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), diff --git a/pyrogram/types/input_media/input_media.py b/pyrogram/types/input_media/input_media.py index 8e2f3a7b1a..bd60aeb3e1 100644 --- a/pyrogram/types/input_media/input_media.py +++ b/pyrogram/types/input_media/input_media.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from typing import List +from typing import List, Union, BinaryIO from ..messages_and_media import MessageEntity from ..object import Object @@ -36,7 +36,7 @@ class InputMedia(Object): def __init__( self, - media: str, + media: Union[str, BinaryIO], caption: str = "", parse_mode: str = None, caption_entities: List[MessageEntity] = None diff --git a/pyrogram/types/input_media/input_media_animation.py b/pyrogram/types/input_media/input_media_animation.py index 04aa940edc..2eae1a66f0 100644 --- a/pyrogram/types/input_media/input_media_animation.py +++ b/pyrogram/types/input_media/input_media_animation.py @@ -27,7 +27,7 @@ class InputMediaAnimation(InputMedia): """An animation file (GIF or H.264/MPEG-4 AVC video without sound) to be sent inside an album. Parameters: - media (``str``): + media (``str`` | ``BinaryIO``): Animation to send. Pass a file_id as string to send a file that exists on the Telegram servers or pass a file path as string to upload a new file that exists on your local machine or diff --git a/pyrogram/types/input_media/input_media_audio.py b/pyrogram/types/input_media/input_media_audio.py index b4bb7575be..cc91e7bd5c 100644 --- a/pyrogram/types/input_media/input_media_audio.py +++ b/pyrogram/types/input_media/input_media_audio.py @@ -29,7 +29,7 @@ class InputMediaAudio(InputMedia): It is intended to be used with :meth:`~pyrogram.Client.send_media_group`. Parameters: - media (``str``): + media (``str`` | ``BinaryIO``): Audio to send. Pass a file_id as string to send an audio that exists on the Telegram servers or pass a file path as string to upload a new audio that exists on your local machine or diff --git a/pyrogram/types/input_media/input_media_document.py b/pyrogram/types/input_media/input_media_document.py index 91dfc7d673..3e4d510b95 100644 --- a/pyrogram/types/input_media/input_media_document.py +++ b/pyrogram/types/input_media/input_media_document.py @@ -27,7 +27,7 @@ class InputMediaDocument(InputMedia): """A generic file to be sent inside an album. Parameters: - media (``str``): + media (``str`` | ``BinaryIO``): File to send. Pass a file_id as string to send a file that exists on the Telegram servers or pass a file path as string to upload a new file that exists on your local machine or diff --git a/pyrogram/types/input_media/input_media_video.py b/pyrogram/types/input_media/input_media_video.py index bad4e3ef3a..c163cba9fb 100644 --- a/pyrogram/types/input_media/input_media_video.py +++ b/pyrogram/types/input_media/input_media_video.py @@ -28,7 +28,7 @@ class InputMediaVideo(InputMedia): It is intended to be used with :obj:`~pyrogram.Client.send_media_group`. Parameters: - media (``str``): + media (``str`` | ``BinaryIO``): Video to send. Pass a file_id as string to send a video that exists on the Telegram servers or pass a file path as string to upload a new video that exists on your local machine or From bc9161c354ab65c9363d363a1935fe9c1d8cf889 Mon Sep 17 00:00:00 2001 From: Nick <64551534+null-nick@users.noreply.github.com> Date: Thu, 11 Aug 2022 13:59:55 +0200 Subject: [PATCH 0981/1185] Update API schema to Layer 144 (#1050) --- compiler/api/source/main_api.tl | 44 +++++++++++++++++++++++---------- 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl index 3f44fe0fbe..bf8a37ee72 100644 --- a/compiler/api/source/main_api.tl +++ b/compiler/api/source/main_api.tl @@ -169,6 +169,7 @@ messageActionSetChatTheme#aa786345 emoticon:string = MessageAction; messageActionChatJoinedByRequest#ebbca3cb = MessageAction; messageActionWebViewDataSentMe#47dd8079 text:string data:string = MessageAction; messageActionWebViewDataSent#b4c38cb5 text:string = MessageAction; +messageActionGiftPremium#aba0f5c6 currency:string amount:long months:int = MessageAction; dialog#a8edd0f5 flags:# pinned:flags.2?true unread_mark:flags.3?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int unread_reactions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int = Dialog; dialogFolder#71bd134c flags:# pinned:flags.2?true folder:Folder peer:Peer top_message:int unread_muted_peers_count:int unread_unmuted_peers_count:int unread_muted_messages_count:int unread_unmuted_messages_count:int = Dialog; @@ -218,7 +219,7 @@ inputReportReasonFake#f5ddd6e7 = ReportReason; inputReportReasonIllegalDrugs#a8eb2be = ReportReason; inputReportReasonPersonalDetails#9ec7863d = ReportReason; -userFull#8c72ea81 flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true id:long about:flags.1?string settings:PeerSettings profile_photo:flags.2?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int ttl_period:flags.14?int theme_emoticon:flags.15?string private_forward_name:flags.16?string bot_group_admin_rights:flags.17?ChatAdminRights bot_broadcast_admin_rights:flags.18?ChatAdminRights = UserFull; +userFull#c4b1fc3f flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true voice_messages_forbidden:flags.20?true id:long about:flags.1?string settings:PeerSettings profile_photo:flags.2?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int ttl_period:flags.14?int theme_emoticon:flags.15?string private_forward_name:flags.16?string bot_group_admin_rights:flags.17?ChatAdminRights bot_broadcast_admin_rights:flags.18?ChatAdminRights premium_gifts:flags.19?Vector = UserFull; contact#145ade0b user_id:long mutual:Bool = Contact; @@ -300,7 +301,7 @@ updateDeleteChannelMessages#c32d5b12 channel_id:long messages:Vector pts:in updateChannelMessageViews#f226ac08 channel_id:long id:int views:int = Update; updateChatParticipantAdmin#d7ca61a2 chat_id:long user_id:long is_admin:Bool version:int = Update; updateNewStickerSet#688a30aa stickerset:messages.StickerSet = Update; -updateStickerSetsOrder#bb2d201 flags:# masks:flags.0?true order:Vector = Update; +updateStickerSetsOrder#bb2d201 flags:# masks:flags.0?true emojis:flags.1?true order:Vector = Update; updateStickerSets#43ae3dec = Update; updateSavedGifs#9375341e = Update; updateBotInlineQuery#496f379c flags:# query_id:long user_id:long query:string geo:flags.0?GeoPoint peer_type:flags.1?InlineQueryPeerType offset:string = Update; @@ -369,6 +370,7 @@ updateWebViewResultSent#1592b79d query_id:long = Update; updateBotMenuButton#14b85813 bot_id:long button:BotMenuButton = Update; updateSavedRingtones#74d8be99 = Update; updateTranscribedAudio#84cd5a flags:# pending:flags.0?true peer:Peer msg_id:int transcription_id:long text:string = Update; +updateReadFeaturedEmojiStickers#fb4c496c = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -471,6 +473,7 @@ inputPrivacyKeyForwards#a4dd4c08 = InputPrivacyKey; inputPrivacyKeyProfilePhoto#5719bacc = InputPrivacyKey; inputPrivacyKeyPhoneNumber#352dafa = InputPrivacyKey; inputPrivacyKeyAddedByPhone#d1219bdd = InputPrivacyKey; +inputPrivacyKeyVoiceMessages#aee69d68 = InputPrivacyKey; privacyKeyStatusTimestamp#bc2eab30 = PrivacyKey; privacyKeyChatInvite#500e6dfa = PrivacyKey; @@ -480,6 +483,7 @@ privacyKeyForwards#69ec56a3 = PrivacyKey; privacyKeyProfilePhoto#96151fed = PrivacyKey; privacyKeyPhoneNumber#d19ae46d = PrivacyKey; privacyKeyAddedByPhone#42ffd42b = PrivacyKey; +privacyKeyVoiceMessages#697f414 = PrivacyKey; inputPrivacyValueAllowContacts#d09e07b = InputPrivacyRule; inputPrivacyValueAllowAll#184b35ce = InputPrivacyRule; @@ -510,6 +514,7 @@ documentAttributeVideo#ef02ce6 flags:# round_message:flags.0?true supports_strea documentAttributeAudio#9852f9c6 flags:# voice:flags.10?true duration:int title:flags.0?string performer:flags.1?string waveform:flags.2?bytes = DocumentAttribute; documentAttributeFilename#15590068 file_name:string = DocumentAttribute; documentAttributeHasStickers#9801d2f7 = DocumentAttribute; +documentAttributeCustomEmoji#fd149899 flags:# free:flags.0?true alt:string stickerset:InputStickerSet = DocumentAttribute; messages.stickersNotModified#f1749a22 = messages.Stickers; messages.stickers#30a6ec7e hash:long stickers:Vector = messages.Stickers; @@ -553,8 +558,9 @@ inputStickerSetShortName#861cc8a0 short_name:string = InputStickerSet; inputStickerSetAnimatedEmoji#28703c8 = InputStickerSet; inputStickerSetDice#e67f520e emoticon:string = InputStickerSet; inputStickerSetAnimatedEmojiAnimations#cde3739 = InputStickerSet; +inputStickerSetPremiumGifts#c88b3b02 = InputStickerSet; -stickerSet#d7df217a flags:# archived:flags.1?true official:flags.2?true masks:flags.3?true animated:flags.5?true videos:flags.6?true installed_date:flags.0?int id:long access_hash:long title:string short_name:string thumbs:flags.4?Vector thumb_dc_id:flags.4?int thumb_version:flags.4?int count:int hash:int = StickerSet; +stickerSet#2dd14edc flags:# archived:flags.1?true official:flags.2?true masks:flags.3?true animated:flags.5?true videos:flags.6?true emojis:flags.7?true installed_date:flags.0?int id:long access_hash:long title:string short_name:string thumbs:flags.4?Vector thumb_dc_id:flags.4?int thumb_version:flags.4?int thumb_document_id:flags.8?long count:int hash:int = StickerSet; messages.stickerSet#b60a24a6 set:StickerSet packs:Vector documents:Vector = messages.StickerSet; messages.stickerSetNotModified#d3f924eb = messages.StickerSet; @@ -606,6 +612,7 @@ messageEntityStrike#bf0693d4 offset:int length:int = MessageEntity; messageEntityBlockquote#20df5d0 offset:int length:int = MessageEntity; messageEntityBankCard#761e6af4 offset:int length:int = MessageEntity; messageEntitySpoiler#32ca960f offset:int length:int = MessageEntity; +messageEntityCustomEmoji#c8cf05f8 offset:int length:int document_id:long = MessageEntity; inputChannelEmpty#ee8c1e86 = InputChannel; inputChannel#f35aec28 channel_id:long access_hash:long = InputChannel; @@ -720,7 +727,7 @@ draftMessageEmpty#1b0c841a flags:# date:flags.0?int = DraftMessage; draftMessage#fd8e711f flags:# no_webpage:flags.1?true reply_to_msg_id:flags.0?int message:string entities:flags.3?Vector date:int = DraftMessage; messages.featuredStickersNotModified#c6dc0c66 count:int = messages.FeaturedStickers; -messages.featuredStickers#84c02310 hash:long count:int sets:Vector unread:Vector = messages.FeaturedStickers; +messages.featuredStickers#be382906 flags:# premium:flags.0?true hash:long count:int sets:Vector unread:Vector = messages.FeaturedStickers; messages.recentStickersNotModified#b17f890 = messages.RecentStickers; messages.recentStickers#88d37c56 hash:long packs:Vector stickers:Vector dates:Vector = messages.RecentStickers; @@ -732,6 +739,7 @@ messages.stickerSetInstallResultArchive#35e410a8 sets:Vector stickerSetCovered#6410a5d2 set:StickerSet cover:Document = StickerSetCovered; stickerSetMultiCovered#3407e51b set:StickerSet covers:Vector = StickerSetCovered; +stickerSetFullCovered#1aed5ee5 set:StickerSet packs:Vector documents:Vector = StickerSetCovered; maskCoords#aed6dbb2 n:int x:double y:double zoom:double = MaskCoords; @@ -820,10 +828,11 @@ inputWebDocument#9bed434d url:string size:int mime_type:string attributes:Vector inputWebFileLocation#c239d686 url:string access_hash:long = InputWebFileLocation; inputWebFileGeoPointLocation#9f2221c9 geo_point:InputGeoPoint access_hash:long w:int h:int zoom:int scale:int = InputWebFileLocation; +inputWebFileAudioAlbumThumbLocation#f46fe924 flags:# small:flags.2?true document:flags.0?InputDocument title:flags.1?string performer:flags.1?string = InputWebFileLocation; upload.webFile#21e753bc size:int mime_type:string file_type:storage.FileType mtime:int bytes:bytes = upload.WebFile; -payments.paymentForm#b0133b37 flags:# can_save_credentials:flags.2?true password_missing:flags.3?true form_id:long bot_id:long title:string description:string photo:flags.5?WebDocument invoice:Invoice provider_id:long url:string native_provider:flags.4?string native_params:flags.4?DataJSON saved_info:flags.0?PaymentRequestedInfo saved_credentials:flags.1?PaymentSavedCredentials users:Vector = payments.PaymentForm; +payments.paymentForm#a0058751 flags:# can_save_credentials:flags.2?true password_missing:flags.3?true form_id:long bot_id:long title:string description:string photo:flags.5?WebDocument invoice:Invoice provider_id:long url:string native_provider:flags.4?string native_params:flags.4?DataJSON additional_methods:flags.6?Vector saved_info:flags.0?PaymentRequestedInfo saved_credentials:flags.1?Vector users:Vector = payments.PaymentForm; payments.validatedRequestedInfo#d1451883 flags:# id:flags.0?string shipping_options:flags.1?Vector = payments.ValidatedRequestedInfo; @@ -1365,6 +1374,13 @@ messages.transcribedAudio#93752c52 flags:# pending:flags.0?true transcription_id help.premiumPromo#8a4f3c29 status_text:string status_entities:Vector video_sections:Vector videos:Vector currency:string monthly_amount:long users:Vector = help.PremiumPromo; +inputStorePaymentPremiumSubscription#a6751e66 flags:# restore:flags.0?true = InputStorePaymentPurpose; +inputStorePaymentGiftPremium#616f7fe8 user_id:InputUser currency:string amount:long = InputStorePaymentPurpose; + +premiumGiftOption#74c34319 flags:# months:int currency:string amount:long bot_url:string store_product:flags.0?string = PremiumGiftOption; + +paymentFormMethod#88f8f21b url:string title:string = PaymentFormMethod; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -1408,7 +1424,7 @@ account.checkUsername#2714d86c username:string = Bool; account.updateUsername#3e0bdd7c username:string = User; account.getPrivacy#dadbc950 key:InputPrivacyKey = account.PrivacyRules; account.setPrivacy#c9f81ce8 key:InputPrivacyKey rules:Vector = account.PrivacyRules; -account.deleteAccount#418d4e0b reason:string = Bool; +account.deleteAccount#a2c0cf74 flags:# reason:string password:flags.0?InputCheckPasswordSRP = Bool; account.getAccountTTL#8fc711d = AccountDaysTTL; account.setAccountTTL#2442485e ttl:AccountDaysTTL = Bool; account.sendChangePhoneCode#82574ae5 phone_number:string settings:CodeSettings = auth.SentCode; @@ -1546,7 +1562,7 @@ messages.getMessagesViews#5784d3e1 peer:InputPeer id:Vector increment:Bool messages.editChatAdmin#a85bd1c2 chat_id:long user_id:InputUser is_admin:Bool = Bool; messages.migrateChat#a2875319 chat_id:long = Updates; messages.searchGlobal#4bc6589a flags:# folder_id:flags.0?int q:string filter:MessagesFilter min_date:int max_date:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; -messages.reorderStickerSets#78337739 flags:# masks:flags.0?true order:Vector = Bool; +messages.reorderStickerSets#78337739 flags:# masks:flags.0?true emojis:flags.1?true order:Vector = Bool; messages.getDocumentByHash#b1f2061f sha256:bytes size:long mime_type:string = Document; messages.getSavedGifs#5cf09635 hash:long = messages.SavedGifs; messages.saveGif#327a30cb id:InputDocument unsave:Bool = Bool; @@ -1566,7 +1582,7 @@ messages.readFeaturedStickers#5b118126 id:Vector = Bool; messages.getRecentStickers#9da9403b flags:# attached:flags.0?true hash:long = messages.RecentStickers; messages.saveRecentSticker#392718f8 flags:# attached:flags.0?true id:InputDocument unsave:Bool = Bool; messages.clearRecentStickers#8999602d flags:# attached:flags.0?true = Bool; -messages.getArchivedStickers#57f17692 flags:# masks:flags.0?true offset_id:long limit:int = messages.ArchivedStickers; +messages.getArchivedStickers#57f17692 flags:# masks:flags.0?true emojis:flags.1?true offset_id:long limit:int = messages.ArchivedStickers; messages.getMaskStickers#640f82b8 hash:long = messages.AllStickers; messages.getAttachedStickers#cc5b67cc media:InputStickeredMedia = Vector; messages.setGameScore#8ef8ecc0 flags:# edit_message:flags.0?true force:flags.1?true peer:InputPeer id:int user_id:InputUser score:int = Updates; @@ -1667,6 +1683,9 @@ messages.sendWebViewResultMessage#a4314f5 bot_query_id:string result:InputBotInl messages.sendWebViewData#dc0242c8 bot:InputUser random_id:long button_text:string data:string = Updates; messages.transcribeAudio#269e9a49 peer:InputPeer msg_id:int = messages.TranscribedAudio; messages.rateTranscribedAudio#7f1d072f peer:InputPeer msg_id:int transcription_id:long good:Bool = Bool; +messages.getCustomEmojiDocuments#d9ab0f54 document_id:Vector = Vector; +messages.getEmojiStickers#fbfca18f hash:long = messages.AllStickers; +messages.getFeaturedEmojiStickers#ecf6736 hash:long = messages.FeaturedStickers; updates.getState#edd4882a = updates.State; updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference; @@ -1770,10 +1789,9 @@ payments.getSavedInfo#227d824b = payments.SavedInfo; payments.clearSavedInfo#d83d70c1 flags:# credentials:flags.0?true info:flags.1?true = Bool; payments.getBankCardData#2e79d779 number:string = payments.BankCardData; payments.exportInvoice#f91b065 invoice_media:InputMedia = payments.ExportedInvoice; -payments.assignAppStoreTransaction#fec13c6 flags:# restore:flags.0?true transaction_id:string receipt:bytes = Updates; -payments.assignPlayMarketTransaction#4faa4aed purchase_token:string = Updates; -payments.restorePlayMarketReceipt#d164e36a receipt:bytes = Updates; -payments.canPurchasePremium#aa6a90c8 = Bool; +payments.assignAppStoreTransaction#80ed747d receipt:bytes purpose:InputStorePaymentPurpose = Updates; +payments.assignPlayMarketTransaction#dffd50d3 receipt:DataJSON purpose:InputStorePaymentPurpose = Updates; +payments.canPurchasePremium#9fc19eb6 purpose:InputStorePaymentPurpose = Bool; payments.requestRecurringPayment#146e958d user_id:InputUser recurring_init_charge:string invoice_media:InputMedia = Updates; stickers.createStickerSet#9021ab67 flags:# masks:flags.0?true animated:flags.1?true videos:flags.4?true user_id:InputUser title:string short_name:string thumb:flags.2?InputDocument stickers:Vector software:flags.3?string = messages.StickerSet; @@ -1831,4 +1849,4 @@ stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats; -// LAYER 143 \ No newline at end of file +// LAYER 144 \ No newline at end of file From e1923508f68c00892e460009c28153181db6c0e4 Mon Sep 17 00:00:00 2001 From: Davide Galilei <43778739+DavideGalilei@users.noreply.github.com> Date: Thu, 11 Aug 2022 14:07:31 +0200 Subject: [PATCH 0982/1185] Fixed edit_inline_media once again (#1052) Co-authored-by: Dan <14043624+delivrance@users.noreply.github.com> --- .../methods/messages/edit_inline_media.py | 108 +++++++++++------- 1 file changed, 64 insertions(+), 44 deletions(-) diff --git a/pyrogram/methods/messages/edit_inline_media.py b/pyrogram/methods/messages/edit_inline_media.py index 4126c802fb..81aa30ee6b 100644 --- a/pyrogram/methods/messages/edit_inline_media.py +++ b/pyrogram/methods/messages/edit_inline_media.py @@ -18,6 +18,7 @@ import os import re +import io import asyncio import io @@ -77,21 +78,41 @@ async def edit_inline_media( caption = media.caption parse_mode = media.parse_mode - if isinstance(media, types.InputMediaPhoto): - if isinstance(media.media, io.BytesIO) or os.path.isfile(media.media): + is_photo = isinstance(media, types.InputMediaPhoto) + + is_bytes_io = isinstance(media.media, io.BytesIO) + is_uploaded_file = is_bytes_io or os.path.isfile(media.media) + + is_external_url = not is_uploaded_file and re.match("^https?://", media.media) + + if is_bytes_io and not hasattr(media.media, "name"): + media.media.name = "media" + + if is_uploaded_file: + filename_attribute = [ + raw.types.DocumentAttributeFilename( + file_name=media.media.name if is_bytes_io else os.path.basename(media.media) + ) + ] + else: + filename_attribute = [] + + + if is_photo: + if is_uploaded_file: media = raw.types.InputMediaUploadedPhoto( file=await self.save_file(media.media) ) - elif re.match("^https?://", media.media): + elif is_external_url: media = raw.types.InputMediaPhotoExternal( url=media.media ) else: media = utils.get_input_media_from_file_id(media.media, FileType.PHOTO) elif isinstance(media, types.InputMediaVideo): - if isinstance(media.media, io.BytesIO) or os.path.isfile(media.media): + if is_uploaded_file: media = raw.types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(media.media) or "video/mp4", + mime_type=(None if is_bytes_io else self.guess_mime_type(media.media)) or "video/mp4", thumb=await self.save_file(media.thumb), file=await self.save_file(media.media), attributes=[ @@ -100,22 +121,19 @@ async def edit_inline_media( duration=media.duration, w=media.width, h=media.height - ), - raw.types.DocumentAttributeFilename( - file_name=os.path.basename(media.media) ) - ] + ] + filename_attribute ) - elif re.match("^https?://", media.media): + elif is_external_url: media = raw.types.InputMediaDocumentExternal( url=media.media ) else: media = utils.get_input_media_from_file_id(media.media, FileType.VIDEO) elif isinstance(media, types.InputMediaAudio): - if isinstance(media.media, io.BytesIO) or os.path.isfile(media.media): + if is_uploaded_file: media = raw.types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(media.media) or "audio/mpeg", + mime_type=(None if is_bytes_io else self.guess_mime_type(media.media)) or "audio/mpeg", thumb=await self.save_file(media.thumb), file=await self.save_file(media.media), attributes=[ @@ -123,22 +141,19 @@ async def edit_inline_media( duration=media.duration, performer=media.performer, title=media.title - ), - raw.types.DocumentAttributeFilename( - file_name=os.path.basename(media.media) ) - ] + ] + filename_attribute ) - elif re.match("^https?://", media.media): + elif is_external_url: media = raw.types.InputMediaDocumentExternal( url=media.media ) else: media = utils.get_input_media_from_file_id(media.media, FileType.AUDIO) elif isinstance(media, types.InputMediaAnimation): - if isinstance(media.media, io.BytesIO) or os.path.isfile(media.media): + if is_uploaded_file: media = raw.types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(media.media) or "video/mp4", + mime_type=(None if is_bytes_io else self.guess_mime_type(media.media)) or "video/mp4", thumb=await self.save_file(media.thumb), file=await self.save_file(media.media), attributes=[ @@ -148,33 +163,26 @@ async def edit_inline_media( w=media.width, h=media.height ), - raw.types.DocumentAttributeFilename( - file_name=os.path.basename(media.media) - ), raw.types.DocumentAttributeAnimated() - ], + ] + filename_attribute, nosound_video=True ) - elif re.match("^https?://", media.media): + elif is_external_url: media = raw.types.InputMediaDocumentExternal( url=media.media ) else: media = utils.get_input_media_from_file_id(media.media, FileType.ANIMATION) elif isinstance(media, types.InputMediaDocument): - if isinstance(media.media, io.BytesIO) or os.path.isfile(media.media): + if is_uploaded_file: media = raw.types.InputMediaUploadedDocument( - mime_type=self.guess_mime_type(media.media) or "application/zip", + mime_type=(None if is_bytes_io else self.guess_mime_type(media.media)) or "application/zip", thumb=await self.save_file(media.thumb), file=await self.save_file(media.media), - attributes=[ - raw.types.DocumentAttributeFilename( - file_name=os.path.basename(media.media) - ) - ], + attributes=filename_attribute, force_file=True ) - elif re.match("^https?://", media.media): + elif is_external_url: media = raw.types.InputMediaDocumentExternal( url=media.media ) @@ -186,25 +194,37 @@ async def edit_inline_media( session = await get_session(self, dc_id) - actual_media = await self.invoke( - raw.functions.messages.UploadMedia( - peer=raw.types.InputPeerSelf(), - media=media + + if is_uploaded_file: + uploaded_media = await self.invoke( + raw.functions.messages.UploadMedia( + peer=raw.types.InputPeerSelf(), + media=media + ) + ) + + actual_media = raw.types.InputMediaPhoto( + id=raw.types.InputPhoto( + id=uploaded_media.photo.id, + access_hash=uploaded_media.photo.access_hash, + file_reference=uploaded_media.photo.file_reference + ) + ) if is_photo else raw.types.InputMediaDocument( + id=raw.types.InputDocument( + id=uploaded_media.document.id, + access_hash=uploaded_media.document.access_hash, + file_reference=uploaded_media.document.file_reference + ) ) - ) + else: + actual_media = media for i in range(self.MAX_RETRIES): try: return await session.invoke( raw.functions.messages.EditInlineBotMessage( id=unpacked, - media=raw.types.InputMediaDocument( - id=raw.types.InputDocument( - id=actual_media.document.id, - access_hash=actual_media.document.access_hash, - file_reference=actual_media.document.file_reference - ) - ), + media=actual_media, reply_markup=await reply_markup.write(self) if reply_markup else None, **await self.parser.parse(caption, parse_mode) ), From c26c1004ad94f032db28ded23dd630f9f8af000e Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 11 Aug 2022 14:08:07 +0200 Subject: [PATCH 0983/1185] Update Pyrogram to v2.0.36 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 95fa60c7ba..2e6ca3ac4d 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.35" +__version__ = "2.0.36" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From cd69fb6d76193175a8dd2a298c69802e921479fb Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 12 Aug 2022 17:18:08 +0200 Subject: [PATCH 0984/1185] Add support for CUSTOM_EMOJI message entity type --- pyrogram/enums/message_entity_type.py | 3 +++ .../types/messages_and_media/message_entity.py | 18 ++++++++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/pyrogram/enums/message_entity_type.py b/pyrogram/enums/message_entity_type.py index 34655d37f9..4db75f93f7 100644 --- a/pyrogram/enums/message_entity_type.py +++ b/pyrogram/enums/message_entity_type.py @@ -77,5 +77,8 @@ class MessageEntityType(AutoName): BANK_CARD = raw.types.MessageEntityBankCard "Bank card text" + CUSTOM_EMOJI = raw.types.MessageEntityCustomEmoji + "Custom emoji" + UNKNOWN = raw.types.MessageEntityUnknown "Unknown message entity type" diff --git a/pyrogram/types/messages_and_media/message_entity.py b/pyrogram/types/messages_and_media/message_entity.py index 8a880a1fad..ac14b62143 100644 --- a/pyrogram/types/messages_and_media/message_entity.py +++ b/pyrogram/types/messages_and_media/message_entity.py @@ -40,13 +40,17 @@ class MessageEntity(Object): Length of the entity in UTF-16 code units. url (``str``, *optional*): - For "text_link" only, url that will be opened after user taps on the text. + For :obj:`~pyrogram.enums.MessageEntityType.TEXT_LINK` only, url that will be opened after user taps on the text. user (:obj:`~pyrogram.types.User`, *optional*): - For "text_mention" only, the mentioned user. + For :obj:`~pyrogram.enums.MessageEntityType.TEXT_MENTION` only, the mentioned user. - language (``str``. *optional*): + language (``str``, *optional*): For "pre" only, the programming language of the entity text. + + custom_emoji_id (``int``, *optional*): + For :obj:`~pyrogram.enums.MessageEntityType.CUSTOM_EMOJI` only, unique identifier of the custom emoji. + Use :meth:`~pyrogram.Client.get_custom_emoji_stickers` to get full information about the sticker. """ def __init__( @@ -58,7 +62,8 @@ def __init__( length: int, url: str = None, user: "types.User" = None, - language: str = None + language: str = None, + custom_emoji_id: int = None ): super().__init__(client) @@ -68,6 +73,7 @@ def __init__( self.url = url self.user = user self.language = language + self.custom_emoji_id = custom_emoji_id @staticmethod def _parse(client, entity: "raw.base.MessageEntity", users: dict) -> Optional["MessageEntity"]: @@ -87,6 +93,7 @@ def _parse(client, entity: "raw.base.MessageEntity", users: dict) -> Optional["M url=getattr(entity, "url", None), user=types.User._parse(client, users.get(user_id, None)), language=getattr(entity, "language", None), + custom_emoji_id=getattr(entity, "document_id", None), client=client ) @@ -105,6 +112,9 @@ async def write(self): if self.language is None: args.pop("language") + if self.custom_emoji_id is None: + args.pop("custom_emoji_id") + entity = self.type.value if entity is raw.types.MessageEntityMentionName: From 8c399323c875b31b8417d6893f78dc38d978dceb Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 12 Aug 2022 17:33:13 +0200 Subject: [PATCH 0985/1185] Add new method get_custom_emoji_stickers --- compiler/docs/compiler.py | 1 + pyrogram/methods/messages/__init__.py | 4 +- .../messages/get_custom_emoji_stickers.py | 60 +++++++++++++++++++ 3 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 pyrogram/methods/messages/get_custom_emoji_stickers.py diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py index a1cc115eae..05ab23fd53 100644 --- a/compiler/docs/compiler.py +++ b/compiler/docs/compiler.py @@ -194,6 +194,7 @@ def get_title_list(s: str) -> list: get_discussion_message get_discussion_replies get_discussion_replies_count + get_custom_emoji_stickers """, chats=""" Chats diff --git a/pyrogram/methods/messages/__init__.py b/pyrogram/methods/messages/__init__.py index dafce11e4f..7a5d2d49be 100644 --- a/pyrogram/methods/messages/__init__.py +++ b/pyrogram/methods/messages/__init__.py @@ -31,6 +31,7 @@ from .forward_messages import ForwardMessages from .get_chat_history import GetChatHistory from .get_chat_history_count import GetChatHistoryCount +from .get_custom_emoji_stickers import GetCustomEmojiStickers from .get_discussion_message import GetDiscussionMessage from .get_discussion_replies import GetDiscussionReplies from .get_discussion_replies_count import GetDiscussionRepliesCount @@ -112,6 +113,7 @@ class Messages( SendReaction, GetDiscussionReplies, GetDiscussionRepliesCount, - StreamMedia + StreamMedia, + GetCustomEmojiStickers ): pass diff --git a/pyrogram/methods/messages/get_custom_emoji_stickers.py b/pyrogram/methods/messages/get_custom_emoji_stickers.py new file mode 100644 index 0000000000..de5d8d4a61 --- /dev/null +++ b/pyrogram/methods/messages/get_custom_emoji_stickers.py @@ -0,0 +1,60 @@ +# Pyrogram - Telegram MTProto API Client Library for Python +# Copyright (C) 2017-present Dan +# +# 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 List + +import pyrogram +from pyrogram import raw +from pyrogram import types + + +class GetCustomEmojiStickers: + async def get_custom_emoji_stickers( + self: "pyrogram.Client", + custom_emoji_ids: List[int], + ) -> List["types.Sticker"]: + """Get information about custom emoji stickers by their identifiers. + + Parameters: + custom_emoji_ids (``int``): + List of custom emoji identifiers. + At most 200 custom emoji identifiers can be specified. + + Returns: + List of :obj:`~pyrogram.types.Sticker`: On success, a list of sticker objects is returned. + """ + result = await self.invoke( + raw.functions.messages.GetCustomEmojiDocuments( + document_id=custom_emoji_ids + ) + ) + + stickers = [] + for item in result: + attributes = {type(i): i for i in item.attributes} + + sticker = await types.Sticker._parse( + self, item, + attributes[raw.types.DocumentAttributeImageSize], + attributes[raw.types.DocumentAttributeCustomEmoji], + attributes[raw.types.DocumentAttributeFilename].file_name + ) + + stickers.append(sticker) + + return pyrogram.types.List(stickers) From 78fe290b404a4368ad0b700418afcb9e8fb72dc7 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 12 Aug 2022 17:38:41 +0200 Subject: [PATCH 0986/1185] Update Pyrogram to v2.0.37 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 2e6ca3ac4d..406c48077f 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.36" +__version__ = "2.0.37" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From 9aefff9f8db13ea4a962fc13d3ad10d96db1508b Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 12 Aug 2022 18:20:05 +0200 Subject: [PATCH 0987/1185] Fix join applications for public chats --- pyrogram/types/user_and_chats/chat_invite_link.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pyrogram/types/user_and_chats/chat_invite_link.py b/pyrogram/types/user_and_chats/chat_invite_link.py index 7aaa56a8cf..59f6315ba5 100644 --- a/pyrogram/types/user_and_chats/chat_invite_link.py +++ b/pyrogram/types/user_and_chats/chat_invite_link.py @@ -18,6 +18,7 @@ from datetime import datetime from typing import Dict +from typing import Optional import pyrogram from pyrogram import raw, utils @@ -103,7 +104,10 @@ def _parse( client: "pyrogram.Client", invite: "raw.base.ExportedChatInvite", users: Dict[int, "raw.types.User"] = None - ) -> "ChatInviteLink": + ) -> Optional["ChatInviteLink"]: + if not isinstance(invite, raw.types.ChatInviteExported): + return None + creator = ( types.User._parse(client, users[invite.admin_id]) if users is not None From 6c34c83a3ea82bd0c3d4f0e7bdff6f54c2f7bdbc Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 12 Aug 2022 18:20:31 +0200 Subject: [PATCH 0988/1185] Update Pyrogram to v2.0.38 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 406c48077f..863baf92d4 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.37" +__version__ = "2.0.38" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From 6b7e5dcd1aa498795a2db6ae6b012ebb6aee2a5c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 14 Aug 2022 10:46:48 +0200 Subject: [PATCH 0989/1185] Fix sending custom emoji --- pyrogram/types/messages_and_media/message_entity.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pyrogram/types/messages_and_media/message_entity.py b/pyrogram/types/messages_and_media/message_entity.py index ac14b62143..d2bf654dcd 100644 --- a/pyrogram/types/messages_and_media/message_entity.py +++ b/pyrogram/types/messages_and_media/message_entity.py @@ -112,8 +112,9 @@ async def write(self): if self.language is None: args.pop("language") - if self.custom_emoji_id is None: - args.pop("custom_emoji_id") + args.pop("custom_emoji_id") + if self.custom_emoji_id is not None: + args["document_id"] = self.custom_emoji_id entity = self.type.value From 95de5f7eae4f53b600310f165b6f973c6f8895f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=D1=91Nya?= <47749563+L3Nya@users.noreply.github.com> Date: Sun, 14 Aug 2022 11:50:48 +0300 Subject: [PATCH 0990/1185] Fix determining video sticker resolution. Add sticker duration to Sticker type (#1065) --- .../methods/messages/get_custom_emoji_stickers.py | 5 +++-- pyrogram/types/messages_and_media/message.py | 3 ++- pyrogram/types/messages_and_media/sticker.py | 12 +++++++++--- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/pyrogram/methods/messages/get_custom_emoji_stickers.py b/pyrogram/methods/messages/get_custom_emoji_stickers.py index de5d8d4a61..8e1b2a4c9e 100644 --- a/pyrogram/methods/messages/get_custom_emoji_stickers.py +++ b/pyrogram/methods/messages/get_custom_emoji_stickers.py @@ -50,9 +50,10 @@ async def get_custom_emoji_stickers( sticker = await types.Sticker._parse( self, item, - attributes[raw.types.DocumentAttributeImageSize], + attributes[raw.types.DocumentAttributeImageSize] if raw.types.DocumentAttributeImageSize in attributes else None, attributes[raw.types.DocumentAttributeCustomEmoji], - attributes[raw.types.DocumentAttributeFilename].file_name + attributes[raw.types.DocumentAttributeFilename].file_name, + attributes[raw.types.DocumentAttributeVideo] if raw.types.DocumentAttributeVideo in attributes else None ) stickers.append(sticker) diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index 56e4d7feec..06d9d716fe 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -700,7 +700,8 @@ async def _parse( client, doc, attributes.get(raw.types.DocumentAttributeImageSize, None), attributes[raw.types.DocumentAttributeSticker], - file_name + file_name, + attributes.get(raw.types.DocumentAttributeVideo, None) ) media_type = enums.MessageMediaType.STICKER elif raw.types.DocumentAttributeVideo in attributes: diff --git a/pyrogram/types/messages_and_media/sticker.py b/pyrogram/types/messages_and_media/sticker.py index 201b579f17..019fc180c7 100644 --- a/pyrogram/types/messages_and_media/sticker.py +++ b/pyrogram/types/messages_and_media/sticker.py @@ -50,6 +50,9 @@ class Sticker(Object): is_video (``bool``): True, if the sticker is a video sticker + duration (``int``): + Video sticker duration in seconds. + file_name (``str``, *optional*): Sticker file name. @@ -84,6 +87,7 @@ def __init__( height: int, is_animated: bool, is_video: bool, + duration: int = None, file_name: str = None, mime_type: str = None, file_size: int = None, @@ -148,7 +152,8 @@ async def _parse( sticker: "raw.types.Document", image_size_attributes: "raw.types.DocumentAttributeImageSize", sticker_attributes: "raw.types.DocumentAttributeSticker", - file_name: str + file_name: str, + video_attributes: "raw.types.DocumentAttributeVideo" ) -> "Sticker": sticker_set = sticker_attributes.stickerset @@ -170,10 +175,11 @@ async def _parse( file_unique_type=FileUniqueType.DOCUMENT, media_id=sticker.id ).encode(), - width=image_size_attributes.w if image_size_attributes else 512, - height=image_size_attributes.h if image_size_attributes else 512, + width=image_size_attributes.w if image_size_attributes else (video_attributes.w if video_attributes else 512), + height=image_size_attributes.h if image_size_attributes else (video_attributes.h if video_attributes else 512), is_animated=sticker.mime_type == "application/x-tgsticker", is_video=sticker.mime_type == "video/webm", + duration=video_attributes.duration if video_attributes else None, # TODO: mask_position set_name=set_name, emoji=sticker_attributes.alt or None, From 2e46514012167b84d5f984fcd00917af0420866d Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 14 Aug 2022 11:19:01 +0200 Subject: [PATCH 0991/1185] Refactor Sticker parsing --- .../messages/get_custom_emoji_stickers.py | 10 +----- pyrogram/types/messages_and_media/message.py | 8 +---- pyrogram/types/messages_and_media/sticker.py | 36 ++++++++++++------- 3 files changed, 26 insertions(+), 28 deletions(-) diff --git a/pyrogram/methods/messages/get_custom_emoji_stickers.py b/pyrogram/methods/messages/get_custom_emoji_stickers.py index 8e1b2a4c9e..f781bf6e55 100644 --- a/pyrogram/methods/messages/get_custom_emoji_stickers.py +++ b/pyrogram/methods/messages/get_custom_emoji_stickers.py @@ -47,15 +47,7 @@ async def get_custom_emoji_stickers( stickers = [] for item in result: attributes = {type(i): i for i in item.attributes} - - sticker = await types.Sticker._parse( - self, item, - attributes[raw.types.DocumentAttributeImageSize] if raw.types.DocumentAttributeImageSize in attributes else None, - attributes[raw.types.DocumentAttributeCustomEmoji], - attributes[raw.types.DocumentAttributeFilename].file_name, - attributes[raw.types.DocumentAttributeVideo] if raw.types.DocumentAttributeVideo in attributes else None - ) - + sticker = await types.Sticker._parse(self, item, attributes) stickers.append(sticker) return pyrogram.types.List(stickers) diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index 06d9d716fe..146e37c4a9 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -696,13 +696,7 @@ async def _parse( animation = types.Animation._parse(client, doc, video_attributes, file_name) media_type = enums.MessageMediaType.ANIMATION elif raw.types.DocumentAttributeSticker in attributes: - sticker = await types.Sticker._parse( - client, doc, - attributes.get(raw.types.DocumentAttributeImageSize, None), - attributes[raw.types.DocumentAttributeSticker], - file_name, - attributes.get(raw.types.DocumentAttributeVideo, None) - ) + sticker = await types.Sticker._parse(client, doc, attributes) media_type = enums.MessageMediaType.STICKER elif raw.types.DocumentAttributeVideo in attributes: video_attributes = attributes[raw.types.DocumentAttributeVideo] diff --git a/pyrogram/types/messages_and_media/sticker.py b/pyrogram/types/messages_and_media/sticker.py index 019fc180c7..56ddf2afed 100644 --- a/pyrogram/types/messages_and_media/sticker.py +++ b/pyrogram/types/messages_and_media/sticker.py @@ -17,7 +17,7 @@ # along with Pyrogram. If not, see . from datetime import datetime -from typing import List +from typing import List, Dict, Type import pyrogram from pyrogram import raw, utils @@ -50,9 +50,6 @@ class Sticker(Object): is_video (``bool``): True, if the sticker is a video sticker - duration (``int``): - Video sticker duration in seconds. - file_name (``str``, *optional*): Sticker file name. @@ -87,7 +84,6 @@ def __init__( height: int, is_animated: bool, is_video: bool, - duration: int = None, file_name: str = None, mime_type: str = None, file_size: int = None, @@ -150,11 +146,16 @@ async def _get_sticker_set_name(invoke, input_sticker_set_id): async def _parse( client, sticker: "raw.types.Document", - image_size_attributes: "raw.types.DocumentAttributeImageSize", - sticker_attributes: "raw.types.DocumentAttributeSticker", - file_name: str, - video_attributes: "raw.types.DocumentAttributeVideo" + document_attributes: Dict[Type["raw.base.DocumentAttribute"], "raw.base.DocumentAttribute"], ) -> "Sticker": + sticker_attributes = document_attributes.get( + raw.types.DocumentAttributeSticker, + document_attributes[raw.types.DocumentAttributeCustomEmoji] + ) + image_size_attributes = document_attributes.get(raw.types.DocumentAttributeImageSize, None) + file_name = getattr(document_attributes.get(raw.types.DocumentAttributeFilename, None), "file_name", None) + video_attributes = document_attributes.get(raw.types.DocumentAttributeVideo, None) + sticker_set = sticker_attributes.stickerset if isinstance(sticker_set, raw.types.InputStickerSetID): @@ -175,11 +176,22 @@ async def _parse( file_unique_type=FileUniqueType.DOCUMENT, media_id=sticker.id ).encode(), - width=image_size_attributes.w if image_size_attributes else (video_attributes.w if video_attributes else 512), - height=image_size_attributes.h if image_size_attributes else (video_attributes.h if video_attributes else 512), + width=( + image_size_attributes.w + if image_size_attributes + else video_attributes.w + if video_attributes + else 512 + ), + height=( + image_size_attributes.h + if image_size_attributes + else video_attributes.h + if video_attributes + else 512 + ), is_animated=sticker.mime_type == "application/x-tgsticker", is_video=sticker.mime_type == "video/webm", - duration=video_attributes.duration if video_attributes else None, # TODO: mask_position set_name=set_name, emoji=sticker_attributes.alt or None, From bb450d1cefcaf7f9d08808a5c8fbe63b04479e00 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 14 Aug 2022 11:31:10 +0200 Subject: [PATCH 0992/1185] Update Pyrogram to v2.0.39 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 863baf92d4..1b1b385120 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.38" +__version__ = "2.0.39" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From 803f8f0073a38a3c0724354e76f1f358fa5c252d Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 14 Aug 2022 22:37:09 +0200 Subject: [PATCH 0993/1185] Fix Sticker parsing --- pyrogram/types/messages_and_media/sticker.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pyrogram/types/messages_and_media/sticker.py b/pyrogram/types/messages_and_media/sticker.py index 56ddf2afed..de266b2fd2 100644 --- a/pyrogram/types/messages_and_media/sticker.py +++ b/pyrogram/types/messages_and_media/sticker.py @@ -148,10 +148,12 @@ async def _parse( sticker: "raw.types.Document", document_attributes: Dict[Type["raw.base.DocumentAttribute"], "raw.base.DocumentAttribute"], ) -> "Sticker": - sticker_attributes = document_attributes.get( - raw.types.DocumentAttributeSticker, - document_attributes[raw.types.DocumentAttributeCustomEmoji] + sticker_attributes = ( + document_attributes[raw.types.DocumentAttributeSticker] + if raw.types.DocumentAttributeSticker in document_attributes + else document_attributes[raw.types.DocumentAttributeCustomEmoji] ) + image_size_attributes = document_attributes.get(raw.types.DocumentAttributeImageSize, None) file_name = getattr(document_attributes.get(raw.types.DocumentAttributeFilename, None), "file_name", None) video_attributes = document_attributes.get(raw.types.DocumentAttributeVideo, None) From 2242adb5984e5e09ff970a3c6853e972c5c8ccd8 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 14 Aug 2022 22:37:26 +0200 Subject: [PATCH 0994/1185] Update Pyrogram to v2.0.40 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 1b1b385120..6b03880911 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.39" +__version__ = "2.0.40" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From f5bcce7c3f96e9ebfb87791f69ee96f649ab481c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 16 Aug 2022 08:59:06 +0200 Subject: [PATCH 0995/1185] Add support for custom emoji in HTML --- docs/source/topics/text-formatting.rst | 2 ++ pyrogram/parser/html.py | 8 ++++++++ 2 files changed, 10 insertions(+) diff --git a/docs/source/topics/text-formatting.rst b/docs/source/topics/text-formatting.rst index 4e11fb7410..00aa0cf8c8 100644 --- a/docs/source/topics/text-formatting.rst +++ b/docs/source/topics/text-formatting.rst @@ -122,6 +122,8 @@ To strictly use this mode, pass :obj:`~pyrogram.enums.HTML` to the *parse_mode* inline fixed-width code + 🔥 +
     pre-formatted
       fixed-width
diff --git a/pyrogram/parser/html.py b/pyrogram/parser/html.py
index d9a8d36876..29c8c4a6b9 100644
--- a/pyrogram/parser/html.py
+++ b/pyrogram/parser/html.py
@@ -75,6 +75,10 @@ def handle_starttag(self, tag, attrs):
             else:
                 entity = raw.types.MessageEntityTextUrl
                 extra["url"] = url
+        elif tag == "emoji":
+            entity = raw.types.MessageEntityCustomEmoji
+            custom_emoji_id = int(attrs.get("id"))
+            extra["document_id"] = custom_emoji_id
         else:
             return
 
@@ -185,6 +189,10 @@ def unparse(text: str, entities: list):
                 user = entity.user
                 start_tag = f''
                 end_tag = ""
+            elif entity_type == MessageEntityType.CUSTOM_EMOJI:
+                custom_emoji_id = entity.custom_emoji_id
+                start_tag = f''
+                end_tag = ""
             else:
                 continue
 

From ac0941109904d644578c7a971a19540dc01862ad Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Tue, 16 Aug 2022 08:59:32 +0200
Subject: [PATCH 0996/1185] Update Pyrogram to v2.0.41

---
 pyrogram/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index 6b03880911..bd8ee26292 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -16,7 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-__version__ = "2.0.40"
+__version__ = "2.0.41"
 __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)"
 __copyright__ = "Copyright (C) 2017-present Dan "
 

From cb5431d976e11ea9444075e4de88b8e23ffbf35b Mon Sep 17 00:00:00 2001
From: omg-xtao <100690902+omg-xtao@users.noreply.github.com>
Date: Wed, 17 Aug 2022 23:58:47 +0800
Subject: [PATCH 0997/1185] Fix get_custom_emoji_stickers parameter type in
 docs (#1066)

* Fix get_custom_emoji_stickers Int type

* Fix misleading docstring

* Update get_custom_emoji_stickers.py

* Update get_custom_emoji_stickers.py

Co-authored-by: Dan <14043624+delivrance@users.noreply.github.com>
---
 pyrogram/methods/messages/get_custom_emoji_stickers.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/methods/messages/get_custom_emoji_stickers.py b/pyrogram/methods/messages/get_custom_emoji_stickers.py
index f781bf6e55..335f4ce9d5 100644
--- a/pyrogram/methods/messages/get_custom_emoji_stickers.py
+++ b/pyrogram/methods/messages/get_custom_emoji_stickers.py
@@ -31,7 +31,7 @@ async def get_custom_emoji_stickers(
         """Get information about custom emoji stickers by their identifiers.
 
         Parameters:
-            custom_emoji_ids (``int``):
+            custom_emoji_ids (List of ``int``):
                 List of custom emoji identifiers.
                 At most 200 custom emoji identifiers can be specified.
 

From 371700ddec1080f5802868f0e2887d2fe384d791 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 28 Aug 2022 13:59:03 +0200
Subject: [PATCH 0998/1185] Update FAQ

---
 docs/source/faq/index.rst                           |  4 ++--
 ...d-oserror-timeouterror-connection-lost-reset.rst | 12 ++++++++++++
 ...t-send-raised-exception-oserror-timeouterror.rst | 13 -------------
 3 files changed, 14 insertions(+), 15 deletions(-)
 create mode 100644 docs/source/faq/socket-send-oserror-timeouterror-connection-lost-reset.rst
 delete mode 100644 docs/source/faq/socket-send-raised-exception-oserror-timeouterror.rst

diff --git a/docs/source/faq/index.rst b/docs/source/faq/index.rst
index e5bef2be0f..3d4a00362b 100644
--- a/docs/source/faq/index.rst
+++ b/docs/source/faq/index.rst
@@ -19,7 +19,7 @@ This FAQ page provides answers to common questions about Pyrogram and, to some e
 - :doc:`uploading-with-urls-gives-error-webpage-curl-failed`
 - :doc:`sqlite3-operationalerror-database-is-locked`
 - :doc:`sqlite3-interfaceerror-error-binding-parameter`
-- :doc:`socket-send-raised-exception-oserror-timeouterror`
+- :doc:`socket-send-oserror-timeouterror-connection-lost-reset`
 - :doc:`how-to-avoid-flood-waits`
 - :doc:`the-account-has-been-limited-deactivated`
 
@@ -40,6 +40,6 @@ This FAQ page provides answers to common questions about Pyrogram and, to some e
     uploading-with-urls-gives-error-webpage-curl-failed
     sqlite3-operationalerror-database-is-locked
     sqlite3-interfaceerror-error-binding-parameter
-    socket-send-raised-exception-oserror-timeouterror
+    socket-send-oserror-timeouterror-connection-lost-reset
     how-to-avoid-flood-waits
     the-account-has-been-limited-deactivated
\ No newline at end of file
diff --git a/docs/source/faq/socket-send-oserror-timeouterror-connection-lost-reset.rst b/docs/source/faq/socket-send-oserror-timeouterror-connection-lost-reset.rst
new file mode 100644
index 0000000000..85c5065015
--- /dev/null
+++ b/docs/source/faq/socket-send-oserror-timeouterror-connection-lost-reset.rst
@@ -0,0 +1,12 @@
+socket.send(), OSError(), TimeoutError(), Connection lost/reset
+===============================================================
+
+If you get any of these errors chances are you ended up with a slow or inconsistent network connection.
+Another reason could be because you are blocking the event loop for too long.
+
+You can consider the following:
+
+- Use Pyrogram asynchronously in its intended way.
+- Use shorter non-asynchronous processing loops.
+- Use ``asyncio.sleep()`` instead of ``time.sleep()``.
+- Use a stable network connection.
diff --git a/docs/source/faq/socket-send-raised-exception-oserror-timeouterror.rst b/docs/source/faq/socket-send-raised-exception-oserror-timeouterror.rst
deleted file mode 100644
index 4d9aa89de9..0000000000
--- a/docs/source/faq/socket-send-raised-exception-oserror-timeouterror.rst
+++ /dev/null
@@ -1,13 +0,0 @@
-socket.send() raised exception, OSError(), TimeoutError()
-=========================================================
-
-If you get this error chances are you or Telegram ended up with a slow or inconsistent network connection, which
-triggers internal timeouts due to data not being sent/received in time. Another reason could be because you are blocking
-the event loop for too long, most likely due to an improper use of non-asynchronous or threaded operations which may
-lead to blocking code that prevents the event loop from running properly.
-
-You can consider the following:
-
-- Use Pyrogram asynchronously in its intended way.
-- Use shorter non-asynchronous processing loops.
-- Use ``asyncio.sleep()`` instead of ``time.sleep()``.

From 95aae430a8ac5922553f3d15b6e6fd388828359f Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 28 Aug 2022 16:43:45 +0200
Subject: [PATCH 0999/1185] Fix serialization of empty optional lists

---
 compiler/api/compiler.py               | 2 +-
 pyrogram/methods/messages/send_poll.py | 7 +------
 2 files changed, 2 insertions(+), 7 deletions(-)

diff --git a/compiler/api/compiler.py b/compiler/api/compiler.py
index 7cbce6f396..c5372bff75 100644
--- a/compiler/api/compiler.py
+++ b/compiler/api/compiler.py
@@ -444,7 +444,7 @@ def start(format: bool = False):
                     sub_type = arg_type.split("<")[1][:-1]
 
                     write_types += "\n        "
-                    write_types += f"if self.{arg_name}:\n            "
+                    write_types += f"if self.{arg_name} is not None:\n            "
                     write_types += "b.write(Vector(self.{}{}))\n        ".format(
                         arg_name, f", {sub_type.title()}" if sub_type in CORE_TYPES else ""
                     )
diff --git a/pyrogram/methods/messages/send_poll.py b/pyrogram/methods/messages/send_poll.py
index 3f6f35d240..c5b606f1d0 100644
--- a/pyrogram/methods/messages/send_poll.py
+++ b/pyrogram/methods/messages/send_poll.py
@@ -131,15 +131,10 @@ async def send_poll(
                 await app.send_poll(chat_id, "Is this a poll question?", ["Yes", "No", "Maybe"])
         """
 
-        message, entities = (await utils.parse_text_entities(
+        solution, solution_entities = (await utils.parse_text_entities(
             self, explanation, explanation_parse_mode, explanation_entities
         )).values()
 
-        # For some reason passing None or [] as solution_entities will lead to INPUT_CONSTRUCTOR_INVALID_00
-        # Add a dummy message entity with no length as workaround
-        solution = message or None
-        solution_entities = entities or ([raw.types.MessageEntityBold(offset=0, length=0)] if solution else None)
-
         r = await self.invoke(
             raw.functions.messages.SendMedia(
                 peer=await self.resolve_peer(chat_id),

From f6e0e58f86ebf9f905ef72451adc6ece9a5c05f6 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 28 Aug 2022 16:44:07 +0200
Subject: [PATCH 1000/1185] Update Pyrogram to v2.0.42

---
 pyrogram/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index bd8ee26292..0af921c26a 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -16,7 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-__version__ = "2.0.41"
+__version__ = "2.0.42"
 __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)"
 __copyright__ = "Copyright (C) 2017-present Dan "
 

From ed008dd3bb9e225c954812764f27391a44fea701 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 28 Aug 2022 20:32:43 +0200
Subject: [PATCH 1001/1185] Fix message entity parsing and serialization

---
 pyrogram/methods/messages/send_message.py        | 2 +-
 pyrogram/methods/messages/send_poll.py           | 2 +-
 pyrogram/parser/html.py                          | 2 +-
 pyrogram/parser/parser.py                        | 2 +-
 pyrogram/types/authorization/terms_of_service.py | 2 +-
 pyrogram/utils.py                                | 2 +-
 6 files changed, 6 insertions(+), 6 deletions(-)

diff --git a/pyrogram/methods/messages/send_message.py b/pyrogram/methods/messages/send_message.py
index 0a7ab6d102..b365880ed5 100644
--- a/pyrogram/methods/messages/send_message.py
+++ b/pyrogram/methods/messages/send_message.py
@@ -159,7 +159,7 @@ async def send_message(
                 entities=[
                     types.MessageEntity._parse(None, entity, {})
                     for entity in entities
-                ],
+                ] if entities else None,
                 client=self
             )
 
diff --git a/pyrogram/methods/messages/send_poll.py b/pyrogram/methods/messages/send_poll.py
index c5b606f1d0..207cff9faf 100644
--- a/pyrogram/methods/messages/send_poll.py
+++ b/pyrogram/methods/messages/send_poll.py
@@ -155,7 +155,7 @@ async def send_poll(
                     ),
                     correct_answers=[bytes([correct_option_id])] if correct_option_id is not None else None,
                     solution=solution,
-                    solution_entities=solution_entities
+                    solution_entities=solution_entities or []
                 ),
                 message="",
                 silent=disable_notification,
diff --git a/pyrogram/parser/html.py b/pyrogram/parser/html.py
index 29c8c4a6b9..dee8a49dd4 100644
--- a/pyrogram/parser/html.py
+++ b/pyrogram/parser/html.py
@@ -149,7 +149,7 @@ async def parse(self, text: str):
 
         return {
             "message": utils.remove_surrogates(parser.text),
-            "entities": sorted(entities, key=lambda e: e.offset)
+            "entities": sorted(entities, key=lambda e: e.offset) or None
         }
 
     @staticmethod
diff --git a/pyrogram/parser/parser.py b/pyrogram/parser/parser.py
index 16701b39ee..0ce2b2375c 100644
--- a/pyrogram/parser/parser.py
+++ b/pyrogram/parser/parser.py
@@ -49,7 +49,7 @@ async def parse(self, text: str, mode: Optional[enums.ParseMode] = None):
             return await self.html.parse(text)
 
         if mode == enums.ParseMode.DISABLED:
-            return {"message": text, "entities": []}
+            return {"message": text, "entities": None}
 
         raise ValueError(f'Invalid parse mode "{mode}"')
 
diff --git a/pyrogram/types/authorization/terms_of_service.py b/pyrogram/types/authorization/terms_of_service.py
index 7e1376ea1d..3c5ffa6c6d 100644
--- a/pyrogram/types/authorization/terms_of_service.py
+++ b/pyrogram/types/authorization/terms_of_service.py
@@ -52,5 +52,5 @@ def _parse(terms_of_service: "raw.types.help.TermsOfService") -> "TermsOfService
             entities=[
                 types.MessageEntity._parse(None, entity, {})
                 for entity in terms_of_service.entities
-            ]
+            ] if terms_of_service.entities else None
         )
diff --git a/pyrogram/utils.py b/pyrogram/utils.py
index b6a8d6bfb6..f7fe59706d 100644
--- a/pyrogram/utils.py
+++ b/pyrogram/utils.py
@@ -349,7 +349,7 @@ async def parse_text_entities(
         for entity in entities:
             entity._client = client
 
-        text, entities = text, [await entity.write() for entity in entities]
+        text, entities = text, [await entity.write() for entity in entities] or None
     else:
         text, entities = (await client.parser.parse(text, parse_mode)).values()
 

From 3bd082094632b026a3bc5ac5c24741daab564d36 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 28 Aug 2022 20:33:03 +0200
Subject: [PATCH 1002/1185] Update Pyrogram to v2.0.43

---
 pyrogram/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index 0af921c26a..65f78d0035 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -16,7 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-__version__ = "2.0.42"
+__version__ = "2.0.43"
 __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)"
 __copyright__ = "Copyright (C) 2017-present Dan "
 

From 7055ee648e1a862d185277fb336c1d59ad3ee9bc Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Thu, 1 Sep 2022 21:27:59 +0200
Subject: [PATCH 1003/1185] Update get_peer_by_username query

---
 pyrogram/storage/sqlite_storage.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/pyrogram/storage/sqlite_storage.py b/pyrogram/storage/sqlite_storage.py
index f7fbb55bec..15e5ddc0c5 100644
--- a/pyrogram/storage/sqlite_storage.py
+++ b/pyrogram/storage/sqlite_storage.py
@@ -151,7 +151,8 @@ async def get_peer_by_id(self, peer_id: int):
 
     async def get_peer_by_username(self, username: str):
         r = self.conn.execute(
-            "SELECT id, access_hash, type, last_update_on FROM peers WHERE username = ?",
+            "SELECT id, access_hash, type, last_update_on FROM peers WHERE username = ?"
+            "ORDER BY last_update_on DESC",
             (username,)
         ).fetchone()
 

From 94c0031ed722d9248f176ba8dbc07a61b4829134 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Thu, 1 Sep 2022 21:28:18 +0200
Subject: [PATCH 1004/1185] Update Pyrogram to v2.0.44

---
 pyrogram/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index 65f78d0035..1989402303 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -16,7 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-__version__ = "2.0.43"
+__version__ = "2.0.44"
 __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)"
 __copyright__ = "Copyright (C) 2017-present Dan "
 

From 88af58f246ceb4723215e52777318ebee7bd2eaa Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Fri, 2 Sep 2022 14:25:13 +0200
Subject: [PATCH 1005/1185] Fix nonce checks

---
 pyrogram/connection/transport/tcp/tcp_abridged_o.py     | 2 +-
 pyrogram/connection/transport/tcp/tcp_intermediate_o.py | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/pyrogram/connection/transport/tcp/tcp_abridged_o.py b/pyrogram/connection/transport/tcp/tcp_abridged_o.py
index 12a832f6a9..c5a9f65f64 100644
--- a/pyrogram/connection/transport/tcp/tcp_abridged_o.py
+++ b/pyrogram/connection/transport/tcp/tcp_abridged_o.py
@@ -42,7 +42,7 @@ async def connect(self, address: tuple):
         while True:
             nonce = bytearray(os.urandom(64))
 
-            if nonce[0] != b"\xef" and nonce[:4] not in self.RESERVED and nonce[4:4] != b"\x00" * 4:
+            if nonce[0] != b"\xef" and nonce[:4] not in self.RESERVED and nonce[4:8] != b"\x00" * 4:
                 nonce[56] = nonce[57] = nonce[58] = nonce[59] = 0xef
                 break
 
diff --git a/pyrogram/connection/transport/tcp/tcp_intermediate_o.py b/pyrogram/connection/transport/tcp/tcp_intermediate_o.py
index 5b267661db..20eca3d4b9 100644
--- a/pyrogram/connection/transport/tcp/tcp_intermediate_o.py
+++ b/pyrogram/connection/transport/tcp/tcp_intermediate_o.py
@@ -42,7 +42,7 @@ async def connect(self, address: tuple):
         while True:
             nonce = bytearray(os.urandom(64))
 
-            if nonce[0] != b"\xef" and nonce[:4] not in self.RESERVED and nonce[4:4] != b"\x00" * 4:
+            if nonce[0] != b"\xef" and nonce[:4] not in self.RESERVED and nonce[4:8] != b"\x00" * 4:
                 nonce[56] = nonce[57] = nonce[58] = nonce[59] = 0xee
                 break
 

From 14c530327243cc390774601aa3638cc3ba433ff7 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Fri, 2 Sep 2022 14:25:29 +0200
Subject: [PATCH 1006/1185] Update Pyrogram to v2.0.45

---
 pyrogram/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index 1989402303..4b21dc3bb6 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -16,7 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-__version__ = "2.0.44"
+__version__ = "2.0.45"
 __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)"
 __copyright__ = "Copyright (C) 2017-present Dan "
 

From 761e4735d3701c5910450fb4ac8f6e09c7ff8d09 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Fri, 2 Sep 2022 14:44:02 +0200
Subject: [PATCH 1007/1185] More nonce check fixes

---
 pyrogram/connection/transport/tcp/tcp_abridged_o.py     | 2 +-
 pyrogram/connection/transport/tcp/tcp_intermediate_o.py | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/pyrogram/connection/transport/tcp/tcp_abridged_o.py b/pyrogram/connection/transport/tcp/tcp_abridged_o.py
index c5a9f65f64..6f57ab1154 100644
--- a/pyrogram/connection/transport/tcp/tcp_abridged_o.py
+++ b/pyrogram/connection/transport/tcp/tcp_abridged_o.py
@@ -42,7 +42,7 @@ async def connect(self, address: tuple):
         while True:
             nonce = bytearray(os.urandom(64))
 
-            if nonce[0] != b"\xef" and nonce[:4] not in self.RESERVED and nonce[4:8] != b"\x00" * 4:
+            if bytes([nonce[0]]) != b"\xef" and nonce[:4] not in self.RESERVED and nonce[4:8] != b"\x00" * 4:
                 nonce[56] = nonce[57] = nonce[58] = nonce[59] = 0xef
                 break
 
diff --git a/pyrogram/connection/transport/tcp/tcp_intermediate_o.py b/pyrogram/connection/transport/tcp/tcp_intermediate_o.py
index 20eca3d4b9..48b2d44520 100644
--- a/pyrogram/connection/transport/tcp/tcp_intermediate_o.py
+++ b/pyrogram/connection/transport/tcp/tcp_intermediate_o.py
@@ -42,7 +42,7 @@ async def connect(self, address: tuple):
         while True:
             nonce = bytearray(os.urandom(64))
 
-            if nonce[0] != b"\xef" and nonce[:4] not in self.RESERVED and nonce[4:8] != b"\x00" * 4:
+            if bytes([nonce[0]]) != b"\xef" and nonce[:4] not in self.RESERVED and nonce[4:8] != b"\x00" * 4:
                 nonce[56] = nonce[57] = nonce[58] = nonce[59] = 0xee
                 break
 

From 3632400956def8a8fa4219ac3f4c11c24e954f07 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Fri, 2 Sep 2022 14:44:16 +0200
Subject: [PATCH 1008/1185] Update Pyrogram to v2.0.46

---
 pyrogram/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index 4b21dc3bb6..92cccf4515 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -16,7 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-__version__ = "2.0.45"
+__version__ = "2.0.46"
 __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)"
 __copyright__ = "Copyright (C) 2017-present Dan "
 

From 2cd0f386025865396bcb7905150be8b00fa242f1 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sat, 3 Sep 2022 13:08:35 +0200
Subject: [PATCH 1009/1185] Update API schema to Layer 145

---
 compiler/api/source/main_api.tl | 98 ++++++++++++++++++++++++---------
 1 file changed, 73 insertions(+), 25 deletions(-)

diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl
index bf8a37ee72..465446b489 100644
--- a/compiler/api/source/main_api.tl
+++ b/compiler/api/source/main_api.tl
@@ -88,7 +88,7 @@ storage.fileMp4#b3cea0e4 = storage.FileType;
 storage.fileWebp#1081464c = storage.FileType;
 
 userEmpty#d3bc4b7a id:long = User;
-user#3ff6ecb0 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true apply_min_photo:flags.25?true fake:flags.26?true bot_attach_menu:flags.27?true premium:flags.28?true attach_menu_enabled:flags.29?true id:long access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector bot_inline_placeholder:flags.19?string lang_code:flags.22?string = User;
+user#5d99adee flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true apply_min_photo:flags.25?true fake:flags.26?true bot_attach_menu:flags.27?true premium:flags.28?true attach_menu_enabled:flags.29?true id:long access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector bot_inline_placeholder:flags.19?string lang_code:flags.22?string emoji_status:flags.30?EmojiStatus = User;
 
 userProfilePhotoEmpty#4f11bae1 = UserProfilePhoto;
 userProfilePhoto#82d1f706 flags:# has_video:flags.0?true photo_id:long stripped_thumb:flags.1?bytes dc_id:int = UserProfilePhoto;
@@ -106,8 +106,8 @@ chatForbidden#6592a1a7 id:long title:string = Chat;
 channel#8261ac61 flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true fake:flags.25?true gigagroup:flags.26?true noforwards:flags.27?true join_to_send:flags.28?true join_request:flags.29?true id:long access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int restriction_reason:flags.9?Vector admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int = Chat;
 channelForbidden#17d493d5 flags:# broadcast:flags.5?true megagroup:flags.8?true id:long access_hash:long title:string until_date:flags.16?int = Chat;
 
-chatFull#d18ee226 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:long about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer theme_emoticon:flags.16?string requests_pending:flags.17?int recent_requesters:flags.17?Vector available_reactions:flags.18?Vector = ChatFull;
-channelFull#ea68a619 flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true flags2:# can_delete_channel:flags2.0?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector default_send_as:flags.29?Peer available_reactions:flags.30?Vector = ChatFull;
+chatFull#c9d31138 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:long about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer theme_emoticon:flags.16?string requests_pending:flags.17?int recent_requesters:flags.17?Vector available_reactions:flags.18?ChatReactions = ChatFull;
+channelFull#f2355507 flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true flags2:# can_delete_channel:flags2.0?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector default_send_as:flags.29?Peer available_reactions:flags.30?ChatReactions = ChatFull;
 
 chatParticipant#c02d4007 user_id:long inviter_id:long date:int = ChatParticipant;
 chatParticipantCreator#e46bcee4 user_id:long = ChatParticipant;
@@ -302,7 +302,7 @@ updateChannelMessageViews#f226ac08 channel_id:long id:int views:int = Update;
 updateChatParticipantAdmin#d7ca61a2 chat_id:long user_id:long is_admin:Bool version:int = Update;
 updateNewStickerSet#688a30aa stickerset:messages.StickerSet = Update;
 updateStickerSetsOrder#bb2d201 flags:# masks:flags.0?true emojis:flags.1?true order:Vector = Update;
-updateStickerSets#43ae3dec = Update;
+updateStickerSets#31c24808 flags:# masks:flags.0?true emojis:flags.1?true = Update;
 updateSavedGifs#9375341e = Update;
 updateBotInlineQuery#496f379c flags:# query_id:long user_id:long query:string geo:flags.0?GeoPoint peer_type:flags.1?InlineQueryPeerType offset:string = Update;
 updateBotInlineSend#12f12a07 flags:# user_id:long query:string geo:flags.0?GeoPoint id:string msg_id:flags.1?InputBotInlineMessageID = Update;
@@ -371,6 +371,10 @@ updateBotMenuButton#14b85813 bot_id:long button:BotMenuButton = Update;
 updateSavedRingtones#74d8be99 = Update;
 updateTranscribedAudio#84cd5a flags:# pending:flags.0?true peer:Peer msg_id:int transcription_id:long text:string = Update;
 updateReadFeaturedEmojiStickers#fb4c496c = Update;
+updateUserEmojiStatus#28373599 user_id:long emoji_status:EmojiStatus = Update;
+updateRecentEmojiStatuses#30f443db = Update;
+updateRecentReactions#6f7863f4 = Update;
+updateMoveStickerSetToTop#86fccf85 flags:# masks:flags.0?true emojis:flags.1?true stickerset:long = Update;
 
 updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State;
 
@@ -397,7 +401,7 @@ upload.fileCdnRedirect#f18cda44 dc_id:int file_token:bytes encryption_key:bytes
 
 dcOption#18b7a10d flags:# ipv6:flags.0?true media_only:flags.1?true tcpo_only:flags.2?true cdn:flags.3?true static:flags.4?true this_port_only:flags.5?true id:int ip_address:string port:int secret:flags.10?bytes = DcOption;
 
-config#330b4067 flags:# phonecalls_enabled:flags.1?true default_p2p_contacts:flags.3?true preload_featured_stickers:flags.4?true ignore_phone_entities:flags.5?true revoke_pm_inbox:flags.6?true blocked_mode:flags.8?true pfs_enabled:flags.13?true force_try_ipv6:flags.14?true date:int expires:int test_mode:Bool this_dc:int dc_options:Vector dc_txt_domain_name:string chat_size_max:int megagroup_size_max:int forwarded_count_max:int online_update_period_ms:int offline_blur_timeout_ms:int offline_idle_timeout_ms:int online_cloud_timeout_ms:int notify_cloud_delay_ms:int notify_default_delay_ms:int push_chat_period_ms:int push_chat_limit:int saved_gifs_limit:int edit_time_limit:int revoke_time_limit:int revoke_pm_time_limit:int rating_e_decay:int stickers_recent_limit:int stickers_faved_limit:int channels_read_media_period:int tmp_sessions:flags.0?int pinned_dialogs_count_max:int pinned_infolder_count_max:int call_receive_timeout_ms:int call_ring_timeout_ms:int call_connect_timeout_ms:int call_packet_timeout_ms:int me_url_prefix:string autoupdate_url_prefix:flags.7?string gif_search_username:flags.9?string venue_search_username:flags.10?string img_search_username:flags.11?string static_maps_provider:flags.12?string caption_length_max:int message_length_max:int webfile_dc_id:int suggested_lang_code:flags.2?string lang_pack_version:flags.2?int base_lang_pack_version:flags.2?int = Config;
+config#232566ac flags:# phonecalls_enabled:flags.1?true default_p2p_contacts:flags.3?true preload_featured_stickers:flags.4?true ignore_phone_entities:flags.5?true revoke_pm_inbox:flags.6?true blocked_mode:flags.8?true pfs_enabled:flags.13?true force_try_ipv6:flags.14?true date:int expires:int test_mode:Bool this_dc:int dc_options:Vector dc_txt_domain_name:string chat_size_max:int megagroup_size_max:int forwarded_count_max:int online_update_period_ms:int offline_blur_timeout_ms:int offline_idle_timeout_ms:int online_cloud_timeout_ms:int notify_cloud_delay_ms:int notify_default_delay_ms:int push_chat_period_ms:int push_chat_limit:int saved_gifs_limit:int edit_time_limit:int revoke_time_limit:int revoke_pm_time_limit:int rating_e_decay:int stickers_recent_limit:int stickers_faved_limit:int channels_read_media_period:int tmp_sessions:flags.0?int pinned_dialogs_count_max:int pinned_infolder_count_max:int call_receive_timeout_ms:int call_ring_timeout_ms:int call_connect_timeout_ms:int call_packet_timeout_ms:int me_url_prefix:string autoupdate_url_prefix:flags.7?string gif_search_username:flags.9?string venue_search_username:flags.10?string img_search_username:flags.11?string static_maps_provider:flags.12?string caption_length_max:int message_length_max:int webfile_dc_id:int suggested_lang_code:flags.2?string lang_pack_version:flags.2?int base_lang_pack_version:flags.2?int reactions_default:flags.15?Reaction = Config;
 
 nearestDc#8e1a1775 country:string this_dc:int nearest_dc:int = NearestDc;
 
@@ -535,7 +539,7 @@ authorization#ad01d61d flags:# current:flags.0?true official_app:flags.1?true pa
 
 account.authorizations#4bff8ea0 authorization_ttl_days:int authorizations:Vector = account.Authorizations;
 
-account.password#185b184f flags:# has_recovery:flags.0?true has_secure_values:flags.1?true has_password:flags.2?true current_algo:flags.2?PasswordKdfAlgo srp_B:flags.2?bytes srp_id:flags.2?long hint:flags.3?string email_unconfirmed_pattern:flags.4?string new_algo:PasswordKdfAlgo new_secure_algo:SecurePasswordKdfAlgo secure_random:bytes pending_reset_date:flags.5?int = account.Password;
+account.password#957b50fb flags:# has_recovery:flags.0?true has_secure_values:flags.1?true has_password:flags.2?true current_algo:flags.2?PasswordKdfAlgo srp_B:flags.2?bytes srp_id:flags.2?long hint:flags.3?string email_unconfirmed_pattern:flags.4?string new_algo:PasswordKdfAlgo new_secure_algo:SecurePasswordKdfAlgo secure_random:bytes pending_reset_date:flags.5?int login_email_pattern:flags.6?string = account.Password;
 
 account.passwordSettings#9a5c33e5 flags:# email:flags.0?string secure_settings:flags.1?SecureSecretSettings = account.PasswordSettings;
 
@@ -559,6 +563,8 @@ inputStickerSetAnimatedEmoji#28703c8 = InputStickerSet;
 inputStickerSetDice#e67f520e emoticon:string = InputStickerSet;
 inputStickerSetAnimatedEmojiAnimations#cde3739 = InputStickerSet;
 inputStickerSetPremiumGifts#c88b3b02 = InputStickerSet;
+inputStickerSetEmojiGenericAnimations#4c4d4ce = InputStickerSet;
+inputStickerSetEmojiDefaultStatuses#29d0f5ee = InputStickerSet;
 
 stickerSet#2dd14edc flags:# archived:flags.1?true official:flags.2?true masks:flags.3?true animated:flags.5?true videos:flags.6?true emojis:flags.7?true installed_date:flags.0?int id:long access_hash:long title:string short_name:string thumbs:flags.4?Vector thumb_dc_id:flags.4?int thumb_version:flags.4?int thumb_document_id:flags.8?long count:int hash:int = StickerSet;
 
@@ -694,6 +700,8 @@ auth.sentCodeTypeSms#c000bba2 length:int = auth.SentCodeType;
 auth.sentCodeTypeCall#5353e5a7 length:int = auth.SentCodeType;
 auth.sentCodeTypeFlashCall#ab03c6d9 pattern:string = auth.SentCodeType;
 auth.sentCodeTypeMissedCall#82006484 prefix:string length:int = auth.SentCodeType;
+auth.sentCodeTypeEmailCode#5a159841 flags:# apple_signin_allowed:flags.0?true google_signin_allowed:flags.1?true email_pattern:string length:int next_phone_login_date:flags.2?int = auth.SentCodeType;
+auth.sentCodeTypeSetUpEmailRequired#a5491dea flags:# apple_signin_allowed:flags.0?true google_signin_allowed:flags.1?true = auth.SentCodeType;
 
 messages.botCallbackAnswer#36585ea4 flags:# alert:flags.1?true has_url:flags.3?true native_ui:flags.4?true message:flags.0?string url:flags.2?string cache_time:int = messages.BotCallbackAnswer;
 
@@ -920,7 +928,7 @@ channelAdminLogEventActionChangeHistoryTTL#6e941a38 prev_value:int new_value:int
 channelAdminLogEventActionParticipantJoinByRequest#afb6144a invite:ExportedChatInvite approved_by:long = ChannelAdminLogEventAction;
 channelAdminLogEventActionToggleNoForwards#cb2ac766 new_value:Bool = ChannelAdminLogEventAction;
 channelAdminLogEventActionSendMessage#278f2868 message:Message = ChannelAdminLogEventAction;
-channelAdminLogEventActionChangeAvailableReactions#9cf7f76a prev_value:Vector new_value:Vector = ChannelAdminLogEventAction;
+channelAdminLogEventActionChangeAvailableReactions#be4e0ef8 prev_value:ChatReactions new_value:ChatReactions = ChannelAdminLogEventAction;
 
 channelAdminLogEvent#1fad68cd id:long date:int user_id:long action:ChannelAdminLogEventAction = ChannelAdminLogEvent;
 
@@ -1297,7 +1305,7 @@ searchResultPosition#7f648b67 msg_id:int date:int offset:int = SearchResultsPosi
 
 messages.searchResultsPositions#53b22baf count:int positions:Vector = messages.SearchResultsPositions;
 
-channels.sendAsPeers#8356cda9 peers:Vector chats:Vector users:Vector = channels.SendAsPeers;
+channels.sendAsPeers#f496b0c6 peers:Vector chats:Vector users:Vector = channels.SendAsPeers;
 
 users.userFull#3b6d152e full_user:UserFull chats:Vector users:Vector = users.UserFull;
 
@@ -1305,7 +1313,7 @@ messages.peerSettings#6880b94d settings:PeerSettings chats:Vector users:Ve
 
 auth.loggedOut#c3a2835f flags:# future_auth_token:flags.0?bytes = auth.LoggedOut;
 
-reactionCount#6fb250d1 flags:# chosen:flags.0?true reaction:string count:int = ReactionCount;
+reactionCount#a3d1cb80 flags:# chosen_order:flags.0?int reaction:Reaction count:int = ReactionCount;
 
 messageReactions#4f2b9479 flags:# min:flags.0?true can_see_list:flags.2?true results:Vector recent_reactions:flags.1?Vector = MessageReactions;
 
@@ -1319,7 +1327,7 @@ messages.availableReactions#768e3aad hash:int reactions:Vector video_sections:Vector videos:Vector currency:string monthly_amount:long users:Vector = help.PremiumPromo;
+help.premiumPromo#5334759c status_text:string status_entities:Vector video_sections:Vector videos:Vector period_options:Vector users:Vector = help.PremiumPromo;
 
 inputStorePaymentPremiumSubscription#a6751e66 flags:# restore:flags.0?true = InputStorePaymentPurpose;
 inputStorePaymentGiftPremium#616f7fe8 user_id:InputUser currency:string amount:long = InputStorePaymentPurpose;
@@ -1381,6 +1389,39 @@ premiumGiftOption#74c34319 flags:# months:int currency:string amount:long bot_ur
 
 paymentFormMethod#88f8f21b url:string title:string = PaymentFormMethod;
 
+emojiStatusEmpty#2de11aae = EmojiStatus;
+emojiStatus#929b619d document_id:long = EmojiStatus;
+emojiStatusUntil#fa30a8c7 document_id:long until:int = EmojiStatus;
+
+account.emojiStatusesNotModified#d08ce645 = account.EmojiStatuses;
+account.emojiStatuses#90c467d1 hash:long statuses:Vector = account.EmojiStatuses;
+
+reactionEmpty#79f5d419 = Reaction;
+reactionEmoji#1b2286b8 emoticon:string = Reaction;
+reactionCustomEmoji#8935fc73 document_id:long = Reaction;
+
+chatReactionsNone#eafc32bc = ChatReactions;
+chatReactionsAll#52928bca flags:# allow_custom:flags.0?true = ChatReactions;
+chatReactionsSome#661d4037 reactions:Vector = ChatReactions;
+
+messages.reactionsNotModified#b06fdbdf = messages.Reactions;
+messages.reactions#eafdf716 hash:long reactions:Vector = messages.Reactions;
+
+emailVerifyPurposeLoginSetup#4345be73 phone_number:string phone_code_hash:string = EmailVerifyPurpose;
+emailVerifyPurposeLoginChange#527d22eb = EmailVerifyPurpose;
+emailVerifyPurposePassport#bbf51685 = EmailVerifyPurpose;
+
+emailVerificationCode#922e55a9 code:string = EmailVerification;
+emailVerificationGoogle#db909ec2 token:string = EmailVerification;
+emailVerificationApple#96d074fd token:string = EmailVerification;
+
+account.emailVerified#2b96cd1b email:string = account.EmailVerified;
+account.emailVerifiedLogin#e1bb0d61 email:string sent_code:auth.SentCode = account.EmailVerified;
+
+premiumSubscriptionOption#b6f11ebe flags:# current:flags.1?true can_purchase_upgrade:flags.2?true months:int currency:string amount:long bot_url:string store_product:flags.0?string = PremiumSubscriptionOption;
+
+sendAsPeer#b81c7034 flags:# premium_required:flags.0?true peer:Peer = SendAsPeer;
+
 ---functions---
 
 invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
@@ -1393,7 +1434,7 @@ invokeWithTakeout#aca9fd2e {X:Type} takeout_id:long query:!X = X;
 
 auth.sendCode#a677244f phone_number:string api_id:int api_hash:string settings:CodeSettings = auth.SentCode;
 auth.signUp#80eee427 phone_number:string phone_code_hash:string first_name:string last_name:string = auth.Authorization;
-auth.signIn#bcd51581 phone_number:string phone_code_hash:string phone_code:string = auth.Authorization;
+auth.signIn#8d52a951 flags:# phone_number:string phone_code_hash:string phone_code:flags.0?string email_verification:flags.1?EmailVerification = auth.Authorization;
 auth.logOut#3e72ba19 = auth.LoggedOut;
 auth.resetAuthorizations#9fab0d1a = Bool;
 auth.exportAuthorization#e5bfffcd dc_id:int = auth.ExportedAuthorization;
@@ -1449,8 +1490,8 @@ account.getAuthorizationForm#a929597a bot_id:long scope:string public_key:string
 account.acceptAuthorization#f3ed4c73 bot_id:long scope:string public_key:string value_hashes:Vector credentials:SecureCredentialsEncrypted = Bool;
 account.sendVerifyPhoneCode#a5a356f9 phone_number:string settings:CodeSettings = auth.SentCode;
 account.verifyPhone#4dd3a7f6 phone_number:string phone_code_hash:string phone_code:string = Bool;
-account.sendVerifyEmailCode#7011509f email:string = account.SentEmailCode;
-account.verifyEmail#ecba39db email:string code:string = Bool;
+account.sendVerifyEmailCode#98e037bb purpose:EmailVerifyPurpose email:string = account.SentEmailCode;
+account.verifyEmail#32da4cf purpose:EmailVerifyPurpose verification:EmailVerification = account.EmailVerified;
 account.initTakeoutSession#8ef3eab0 flags:# contacts:flags.0?true message_users:flags.1?true message_chats:flags.2?true message_megagroups:flags.3?true message_channels:flags.4?true files:flags.5?true file_max_size:flags.5?long = account.Takeout;
 account.finishTakeoutSession#1d2652ee flags:# success:flags.0?true = Bool;
 account.confirmPasswordEmail#8fdf1920 code:string = Bool;
@@ -1487,6 +1528,10 @@ account.changeAuthorizationSettings#40f48462 flags:# hash:long encrypted_request
 account.getSavedRingtones#e1902288 hash:long = account.SavedRingtones;
 account.saveRingtone#3dea5b03 id:InputDocument unsave:Bool = account.SavedRingtone;
 account.uploadRingtone#831a83a2 file:InputFile file_name:string mime_type:string = Document;
+account.updateEmojiStatus#fbd3de6b emoji_status:EmojiStatus = Bool;
+account.getDefaultEmojiStatuses#d6753386 hash:long = account.EmojiStatuses;
+account.getRecentEmojiStatuses#f578105 hash:long = account.EmojiStatuses;
+account.clearRecentEmojiStatuses#18201aae = Bool;
 
 users.getUsers#d91a548 id:Vector = Vector;
 users.getFullUser#b60f5918 id:InputUser = users.UserFull;
@@ -1523,8 +1568,8 @@ messages.deleteHistory#b08f922a flags:# just_clear:flags.0?true revoke:flags.1?t
 messages.deleteMessages#e58e95d2 flags:# revoke:flags.0?true id:Vector = messages.AffectedMessages;
 messages.receivedMessages#5a954c0 max_id:int = Vector;
 messages.setTyping#58943ee2 flags:# peer:InputPeer top_msg_id:flags.0?int action:SendMessageAction = Bool;
-messages.sendMessage#d9d75a4 flags:# no_webpage:flags.1?true silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true peer:InputPeer reply_to_msg_id:flags.0?int message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates;
-messages.sendMedia#e25ff8e0 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true peer:InputPeer reply_to_msg_id:flags.0?int media:InputMedia message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates;
+messages.sendMessage#d9d75a4 flags:# no_webpage:flags.1?true silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true peer:InputPeer reply_to_msg_id:flags.0?int message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates;
+messages.sendMedia#e25ff8e0 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true peer:InputPeer reply_to_msg_id:flags.0?int media:InputMedia message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates;
 messages.forwardMessages#cc30290b flags:# silent:flags.5?true background:flags.6?true with_my_score:flags.8?true drop_author:flags.11?true drop_media_captions:flags.12?true noforwards:flags.14?true from_peer:InputPeer id:Vector random_id:Vector to_peer:InputPeer schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates;
 messages.reportSpam#cf1592db peer:InputPeer = Bool;
 messages.getPeerSettings#efd9a6a2 peer:InputPeer = messages.PeerSettings;
@@ -1604,7 +1649,7 @@ messages.faveSticker#b9ffc55b id:InputDocument unfave:Bool = Bool;
 messages.getUnreadMentions#46578472 peer:InputPeer offset_id:int add_offset:int limit:int max_id:int min_id:int = messages.Messages;
 messages.readMentions#f0189d3 peer:InputPeer = messages.AffectedHistory;
 messages.getRecentLocations#702a40e0 peer:InputPeer limit:int hash:long = messages.Messages;
-messages.sendMultiMedia#f803138f flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true peer:InputPeer reply_to_msg_id:flags.0?int multi_media:Vector schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates;
+messages.sendMultiMedia#f803138f flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true peer:InputPeer reply_to_msg_id:flags.0?int multi_media:Vector schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates;
 messages.uploadEncryptedFile#5057c497 peer:InputEncryptedChat file:InputEncryptedFile = EncryptedFile;
 messages.searchStickerSets#35705b8a flags:# exclude_featured:flags.0?true q:string hash:long = messages.FoundStickerSets;
 messages.getSplitRanges#1cff7e08 = Vector;
@@ -1663,12 +1708,12 @@ messages.hideChatJoinRequest#7fe7e815 flags:# approved:flags.0?true peer:InputPe
 messages.hideAllChatJoinRequests#e085f4ea flags:# approved:flags.0?true peer:InputPeer link:flags.1?string = Updates;
 messages.toggleNoForwards#b11eafa2 peer:InputPeer enabled:Bool = Updates;
 messages.saveDefaultSendAs#ccfddf96 peer:InputPeer send_as:InputPeer = Bool;
-messages.sendReaction#25690ce4 flags:# big:flags.1?true peer:InputPeer msg_id:int reaction:flags.0?string = Updates;
+messages.sendReaction#d30d78d4 flags:# big:flags.1?true add_to_recent:flags.2?true peer:InputPeer msg_id:int reaction:flags.0?Vector = Updates;
 messages.getMessagesReactions#8bba90e6 peer:InputPeer id:Vector = Updates;
-messages.getMessageReactionsList#e0ee6b77 flags:# peer:InputPeer id:int reaction:flags.0?string offset:flags.1?string limit:int = messages.MessageReactionsList;
-messages.setChatAvailableReactions#14050ea6 peer:InputPeer available_reactions:Vector = Updates;
+messages.getMessageReactionsList#461b3f48 flags:# peer:InputPeer id:int reaction:flags.0?Reaction offset:flags.1?string limit:int = messages.MessageReactionsList;
+messages.setChatAvailableReactions#feb16771 peer:InputPeer available_reactions:ChatReactions = Updates;
 messages.getAvailableReactions#18dea0ac hash:int = messages.AvailableReactions;
-messages.setDefaultReaction#d960c4d4 reaction:string = Bool;
+messages.setDefaultReaction#4f47a016 reaction:Reaction = Bool;
 messages.translateText#24ce6dee flags:# peer:flags.0?InputPeer msg_id:flags.0?int text:flags.1?string from_lang:flags.2?string to_lang:string = messages.TranslatedText;
 messages.getUnreadReactions#e85bae1a peer:InputPeer offset_id:int add_offset:int limit:int max_id:int min_id:int = messages.Messages;
 messages.readReactions#82e251d7 peer:InputPeer = messages.AffectedHistory;
@@ -1676,9 +1721,9 @@ messages.searchSentMedia#107e31a0 q:string filter:MessagesFilter limit:int = mes
 messages.getAttachMenuBots#16fcc2cb hash:long = AttachMenuBots;
 messages.getAttachMenuBot#77216192 bot:InputUser = AttachMenuBotsBot;
 messages.toggleBotInAttachMenu#1aee33af bot:InputUser enabled:Bool = Bool;
-messages.requestWebView#91b15831 flags:# from_bot_menu:flags.4?true silent:flags.5?true peer:InputPeer bot:InputUser url:flags.1?string start_param:flags.3?string theme_params:flags.2?DataJSON reply_to_msg_id:flags.0?int send_as:flags.13?InputPeer = WebViewResult;
+messages.requestWebView#fc87a53c flags:# from_bot_menu:flags.4?true silent:flags.5?true peer:InputPeer bot:InputUser url:flags.1?string start_param:flags.3?string theme_params:flags.2?DataJSON platform:string reply_to_msg_id:flags.0?int send_as:flags.13?InputPeer = WebViewResult;
 messages.prolongWebView#ea5fbcce flags:# silent:flags.5?true peer:InputPeer bot:InputUser query_id:long reply_to_msg_id:flags.0?int send_as:flags.13?InputPeer = Bool;
-messages.requestSimpleWebView#6abb2f73 flags:# bot:InputUser url:string theme_params:flags.0?DataJSON = SimpleWebViewResult;
+messages.requestSimpleWebView#299bec8e flags:# bot:InputUser url:string theme_params:flags.0?DataJSON platform:string = SimpleWebViewResult;
 messages.sendWebViewResultMessage#a4314f5 bot_query_id:string result:InputBotInlineResult = WebViewMessageSent;
 messages.sendWebViewData#dc0242c8 bot:InputUser random_id:long button_text:string data:string = Updates;
 messages.transcribeAudio#269e9a49 peer:InputPeer msg_id:int = messages.TranscribedAudio;
@@ -1686,6 +1731,10 @@ messages.rateTranscribedAudio#7f1d072f peer:InputPeer msg_id:int transcription_i
 messages.getCustomEmojiDocuments#d9ab0f54 document_id:Vector = Vector;
 messages.getEmojiStickers#fbfca18f hash:long = messages.AllStickers;
 messages.getFeaturedEmojiStickers#ecf6736 hash:long = messages.FeaturedStickers;
+messages.reportReaction#3f64c076 peer:InputPeer id:int reaction_peer:InputPeer = Bool;
+messages.getTopReactions#bb8125ba limit:int hash:long = messages.Reactions;
+messages.getRecentReactions#39461db2 limit:int hash:long = messages.Reactions;
+messages.clearRecentReactions#9dfeefb4 = Bool;
 
 updates.getState#edd4882a = updates.State;
 updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference;
@@ -1792,7 +1841,6 @@ payments.exportInvoice#f91b065 invoice_media:InputMedia = payments.ExportedInvoi
 payments.assignAppStoreTransaction#80ed747d receipt:bytes purpose:InputStorePaymentPurpose = Updates;
 payments.assignPlayMarketTransaction#dffd50d3 receipt:DataJSON purpose:InputStorePaymentPurpose = Updates;
 payments.canPurchasePremium#9fc19eb6 purpose:InputStorePaymentPurpose = Bool;
-payments.requestRecurringPayment#146e958d user_id:InputUser recurring_init_charge:string invoice_media:InputMedia = Updates;
 
 stickers.createStickerSet#9021ab67 flags:# masks:flags.0?true animated:flags.1?true videos:flags.4?true user_id:InputUser title:string short_name:string thumb:flags.2?InputDocument stickers:Vector software:flags.3?string = messages.StickerSet;
 stickers.removeStickerFromSet#f7760f51 sticker:InputDocument = messages.StickerSet;
@@ -1849,4 +1897,4 @@ stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel
 stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages;
 stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats;
 
-// LAYER 144
\ No newline at end of file
+// LAYER 145
\ No newline at end of file

From 7a53c3da5769fc55037d40206a6a6cf12e2dbfa2 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sat, 3 Sep 2022 13:10:27 +0200
Subject: [PATCH 1010/1185] Add support for emoji status

---
 compiler/docs/compiler.py                     |  1 +
 pyrogram/types/user_and_chats/__init__.py     |  4 +-
 pyrogram/types/user_and_chats/emoji_status.py | 67 +++++++++++++++++++
 pyrogram/types/user_and_chats/user.py         |  6 ++
 4 files changed, 77 insertions(+), 1 deletion(-)
 create mode 100644 pyrogram/types/user_and_chats/emoji_status.py

diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py
index 05ab23fd53..a6f82f3229 100644
--- a/compiler/docs/compiler.py
+++ b/compiler/docs/compiler.py
@@ -385,6 +385,7 @@ def get_title_list(s: str) -> list:
             ChatJoiner
             Dialog
             Restriction
+            EmojiStatus
         """,
         messages_media="""
         Messages & Media
diff --git a/pyrogram/types/user_and_chats/__init__.py b/pyrogram/types/user_and_chats/__init__.py
index a9b633599f..60ebd30980 100644
--- a/pyrogram/types/user_and_chats/__init__.py
+++ b/pyrogram/types/user_and_chats/__init__.py
@@ -30,6 +30,7 @@
 from .chat_preview import ChatPreview
 from .chat_privileges import ChatPrivileges
 from .dialog import Dialog
+from .emoji_status import EmojiStatus
 from .invite_link_importer import InviteLinkImporter
 from .restriction import Restriction
 from .user import User
@@ -59,5 +60,6 @@
     "VideoChatScheduled",
     "ChatJoinRequest",
     "ChatPrivileges",
-    "ChatJoiner"
+    "ChatJoiner",
+    "EmojiStatus"
 ]
diff --git a/pyrogram/types/user_and_chats/emoji_status.py b/pyrogram/types/user_and_chats/emoji_status.py
new file mode 100644
index 0000000000..99159dbc95
--- /dev/null
+++ b/pyrogram/types/user_and_chats/emoji_status.py
@@ -0,0 +1,67 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  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 datetime import datetime
+from typing import Optional
+
+import pyrogram
+from pyrogram import raw
+from pyrogram import utils
+from ..object import Object
+
+
+class EmojiStatus(Object):
+    """A user emoji status.
+
+    Parameters:
+        custom_emoji_id (``int``):
+            Custom emoji id.
+
+        until_date (:py:obj:`~datetime.datetime`, *optional*):
+            Valid until date.
+    """
+
+    def __init__(
+        self,
+        *,
+        client: "pyrogram.Client" = None,
+        custom_emoji_id: int,
+        until_date: Optional[datetime] = None
+    ):
+        super().__init__(client)
+
+        self.custom_emoji_id = custom_emoji_id
+        self.until_date = until_date
+
+    @staticmethod
+    def _parse(client, emoji_status: "raw.base.EmojiStatus") -> Optional["EmojiStatus"]:
+        if isinstance(emoji_status, raw.types.EmojiStatusEmpty):
+            return None
+
+        if isinstance(emoji_status, raw.types.EmojiStatus):
+            return EmojiStatus(
+                client=client,
+                custom_emoji_id=emoji_status.document_id
+            )
+
+        if isinstance(emoji_status, raw.types.EmojiStatusUntil):
+            return EmojiStatus(
+                client=client,
+                custom_emoji_id=emoji_status.document_id,
+                until_date=utils.timestamp_to_datetime(emoji_status.until)
+            )
diff --git a/pyrogram/types/user_and_chats/user.py b/pyrogram/types/user_and_chats/user.py
index 4df3b17e76..2c9b58f8b6 100644
--- a/pyrogram/types/user_and_chats/user.py
+++ b/pyrogram/types/user_and_chats/user.py
@@ -121,6 +121,9 @@ class User(Object, Update):
         language_code (``str``, *optional*):
             IETF language tag of the user's language.
 
+        emoji_status (:obj:`~pyrogram.types.EmojiStatus`, *optional*):
+            Emoji status.
+
         dc_id (``int``, *optional*):
             User's or bot's assigned DC (data center). Available only in case the user has set a public profile photo.
             Note that this information is approximate; it is based on where Telegram stores a user profile pictures and
@@ -167,6 +170,7 @@ def __init__(
         next_offline_date: datetime = None,
         username: str = None,
         language_code: str = None,
+        emoji_status: Optional[str] = None,
         dc_id: int = None,
         phone_number: str = None,
         photo: "types.ChatPhoto" = None,
@@ -193,6 +197,7 @@ def __init__(
         self.next_offline_date = next_offline_date
         self.username = username
         self.language_code = language_code
+        self.emoji_status = emoji_status
         self.dc_id = dc_id
         self.phone_number = phone_number
         self.photo = photo
@@ -229,6 +234,7 @@ def _parse(client, user: "raw.base.User") -> Optional["User"]:
             **User._parse_status(user.status, user.bot),
             username=user.username,
             language_code=user.lang_code,
+            emoji_status=types.EmojiStatus._parse(client, user.emoji_status),
             dc_id=getattr(user.photo, "dc_id", None),
             phone_number=user.phone,
             photo=types.ChatPhoto._parse(client, user.photo, user.id, user.access_hash),

From 1fb04b76163ea258924410204992c89255c98a94 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sat, 3 Sep 2022 13:41:53 +0200
Subject: [PATCH 1011/1185] Update EmojiStatus

---
 pyrogram/types/user_and_chats/emoji_status.py | 5 ++---
 1 file changed, 2 insertions(+), 3 deletions(-)

diff --git a/pyrogram/types/user_and_chats/emoji_status.py b/pyrogram/types/user_and_chats/emoji_status.py
index 99159dbc95..746c98ef2c 100644
--- a/pyrogram/types/user_and_chats/emoji_status.py
+++ b/pyrogram/types/user_and_chats/emoji_status.py
@@ -50,9 +50,6 @@ def __init__(
 
     @staticmethod
     def _parse(client, emoji_status: "raw.base.EmojiStatus") -> Optional["EmojiStatus"]:
-        if isinstance(emoji_status, raw.types.EmojiStatusEmpty):
-            return None
-
         if isinstance(emoji_status, raw.types.EmojiStatus):
             return EmojiStatus(
                 client=client,
@@ -65,3 +62,5 @@ def _parse(client, emoji_status: "raw.base.EmojiStatus") -> Optional["EmojiStatu
                 custom_emoji_id=emoji_status.document_id,
                 until_date=utils.timestamp_to_datetime(emoji_status.until)
             )
+
+        return None

From fe7fcf344832b7092db70d0fb8175bbb37b805f2 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sat, 3 Sep 2022 13:42:16 +0200
Subject: [PATCH 1012/1185] Update reaction.py

---
 pyrogram/types/messages_and_media/reaction.py | 48 +++++++++++++++----
 1 file changed, 39 insertions(+), 9 deletions(-)

diff --git a/pyrogram/types/messages_and_media/reaction.py b/pyrogram/types/messages_and_media/reaction.py
index 83dda5829b..9d6b9e24b2 100644
--- a/pyrogram/types/messages_and_media/reaction.py
+++ b/pyrogram/types/messages_and_media/reaction.py
@@ -16,7 +16,10 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
+from typing import Optional
+
 import pyrogram
+from pyrogram import raw
 from ..object import Object
 
 
@@ -24,26 +27,53 @@ class Reaction(Object):
     """Contains information about a reaction.
 
     Parameters:
-        emoji (``str``):
+        emoji (``str``, *optional*):
             Reaction emoji.
 
-        count (``int``):
-            Reaction count.
+        custom_emoji_id (``int``, *optional*):
+            Custom emoji id.
 
-        chosen (``bool``):
-            Whether this is the chosen reaction.
+        count (``int``, *optional*):
+            Reaction count.
     """
 
     def __init__(
         self,
         *,
         client: "pyrogram.Client" = None,
-        emoji: str,
-        count: int,
-        chosen: bool
+        emoji: Optional[str] = None,
+        custom_emoji_id: Optional[int] = None,
+        count: Optional[int] = None
     ):
         super().__init__(client)
 
         self.emoji = emoji
+        self.custom_emoji_id = custom_emoji_id
         self.count = count
-        self.chosen = chosen
+
+    @staticmethod
+    def _parse(
+        client: "pyrogram.Client",
+        reaction: "raw.base.Reaction"
+    ) -> "Reaction":
+        if isinstance(reaction, raw.types.ReactionEmoji):
+            return Reaction(
+                client=client,
+                emoji=reaction.emoticon
+            )
+
+        if isinstance(reaction, raw.types.ReactionCustomEmoji):
+            return Reaction(
+                client=client,
+                custom_emoji_id=reaction.document_id
+            )
+
+    @staticmethod
+    def _parse_count(
+        client: "pyrogram.Client",
+        reaction_count: "raw.base.ReactionCount"
+    ) -> "Reaction":
+        reaction = Reaction._parse(client, reaction_count.reaction)
+        reaction.count = reaction_count.count
+
+        return reaction

From 9eb7589a7fbeb2986d987c38870edf8174d6deac Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sat, 3 Sep 2022 13:43:38 +0200
Subject: [PATCH 1013/1185] Add chat_reactions.py

---
 pyrogram/types/user_and_chats/__init__.py     |  4 +-
 pyrogram/types/user_and_chats/chat.py         |  6 +-
 .../types/user_and_chats/chat_reactions.py    | 69 +++++++++++++++++++
 3 files changed, 75 insertions(+), 4 deletions(-)
 create mode 100644 pyrogram/types/user_and_chats/chat_reactions.py

diff --git a/pyrogram/types/user_and_chats/__init__.py b/pyrogram/types/user_and_chats/__init__.py
index 60ebd30980..348ecd9f3c 100644
--- a/pyrogram/types/user_and_chats/__init__.py
+++ b/pyrogram/types/user_and_chats/__init__.py
@@ -29,6 +29,7 @@
 from .chat_photo import ChatPhoto
 from .chat_preview import ChatPreview
 from .chat_privileges import ChatPrivileges
+from .chat_reactions import ChatReactions
 from .dialog import Dialog
 from .emoji_status import EmojiStatus
 from .invite_link_importer import InviteLinkImporter
@@ -61,5 +62,6 @@
     "ChatJoinRequest",
     "ChatPrivileges",
     "ChatJoiner",
-    "EmojiStatus"
+    "EmojiStatus",
+    "ChatReactions"
 ]
diff --git a/pyrogram/types/user_and_chats/chat.py b/pyrogram/types/user_and_chats/chat.py
index dc345bbc63..aa727952e5 100644
--- a/pyrogram/types/user_and_chats/chat.py
+++ b/pyrogram/types/user_and_chats/chat.py
@@ -126,7 +126,7 @@ class Chat(Object):
             The default "send_as" chat.
             Returned only in :meth:`~pyrogram.Client.get_chat`.
 
-        available_reactions (List of ``str``, *optional*):
+        available_reactions (List of :obj:`~pyrogram.types.Reaction`, *optional*):
             Available reactions in the chat.
             Returned only in :meth:`~pyrogram.Client.get_chat`.
     """
@@ -162,7 +162,7 @@ def __init__(
         distance: int = None,
         linked_chat: "types.Chat" = None,
         send_as_chat: "types.Chat" = None,
-        available_reactions: List[str] = None
+        available_reactions: Optional[List["types.Reaction"]] = None
     ):
         super().__init__(client)
 
@@ -345,7 +345,7 @@ async def _parse_full(client, chat_full: Union[raw.types.messages.ChatFull, raw.
             if isinstance(full_chat.exported_invite, raw.types.ChatInviteExported):
                 parsed_chat.invite_link = full_chat.exported_invite.link
 
-            parsed_chat.available_reactions = full_chat.available_reactions or None
+            parsed_chat.available_reactions = types.ChatReactions._parse(client, full_chat.available_reactions)
 
         return parsed_chat
 
diff --git a/pyrogram/types/user_and_chats/chat_reactions.py b/pyrogram/types/user_and_chats/chat_reactions.py
new file mode 100644
index 0000000000..057fb96654
--- /dev/null
+++ b/pyrogram/types/user_and_chats/chat_reactions.py
@@ -0,0 +1,69 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  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 Optional, List
+
+import pyrogram
+from pyrogram import raw, types
+from ..object import Object
+
+
+class ChatReactions(Object):
+    """A chat reactions
+
+    Parameters:
+        all_are_enabled (``bool``, *optional*)
+
+        allow_custom_emoji (``bool``, *optional*):
+            Whether custom emoji are allowed or not.
+
+        reactions (List of :obj:`~pyrogram.types.Reaction`, *optional*):
+            Reactions available.
+    """
+
+    def __init__(
+        self,
+        *,
+        client: "pyrogram.Client" = None,
+        all_are_enabled: Optional[bool] = None,
+        allow_custom_emoji: Optional[bool] = None,
+        reactions: Optional[List["types.Reaction"]] = None,
+    ):
+        super().__init__(client)
+
+        self.all_are_enabled = all_are_enabled
+        self.allow_custom_emoji = allow_custom_emoji
+        self.reactions = reactions
+
+    @staticmethod
+    def _parse(client, chat_reactions: "raw.base.ChatReactions") -> Optional["ChatReactions"]:
+        if isinstance(chat_reactions, raw.types.ChatReactionsAll):
+            return ChatReactions(
+                client=client,
+                all_are_enabled=True,
+                allow_custom_emoji=chat_reactions.allow_custom
+            )
+
+        if isinstance(chat_reactions, raw.types.ChatReactionsSome):
+            return ChatReactions(
+                client=client,
+                reactions=[types.Reaction._parse(client, reaction)
+                           for reaction in chat_reactions.reactions]
+            )
+
+        return None

From 6496a87029a6b0987d3825b8a22d08474fc92aa9 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sat, 3 Sep 2022 13:46:37 +0200
Subject: [PATCH 1014/1185] Add message_reactions.py

---
 pyrogram/types/messages_and_media/__init__.py |  3 +-
 pyrogram/types/messages_and_media/message.py  |  3 +-
 .../messages_and_media/message_reactions.py   | 56 +++++++++++++++++++
 3 files changed, 59 insertions(+), 3 deletions(-)
 create mode 100644 pyrogram/types/messages_and_media/message_reactions.py

diff --git a/pyrogram/types/messages_and_media/__init__.py b/pyrogram/types/messages_and_media/__init__.py
index 3a18b2a5bf..54dfd56043 100644
--- a/pyrogram/types/messages_and_media/__init__.py
+++ b/pyrogram/types/messages_and_media/__init__.py
@@ -38,9 +38,10 @@
 from .voice import Voice
 from .web_app_data import WebAppData
 from .web_page import WebPage
+from .message_reactions import MessageReactions
 
 __all__ = [
     "Animation", "Audio", "Contact", "Document", "Game", "Location", "Message", "MessageEntity", "Photo", "Thumbnail",
     "StrippedThumbnail", "Poll", "PollOption", "Sticker", "Venue", "Video", "VideoNote", "Voice", "WebPage", "Dice",
-    "Reaction", "WebAppData"
+    "Reaction", "WebAppData", "MessageReactions"
 ]
diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py
index 146e37c4a9..40386af0f8 100644
--- a/pyrogram/types/messages_and_media/message.py
+++ b/pyrogram/types/messages_and_media/message.py
@@ -742,8 +742,7 @@ async def _parse(
             from_user = types.User._parse(client, users.get(user_id, None))
             sender_chat = types.Chat._parse(client, message, users, chats, is_chat=False) if not from_user else None
 
-            reactions = [types.Reaction(emoji=r.reaction, count=r.count, chosen=r.chosen)
-                         for r in message.reactions.results] if message.reactions else None
+            reactions = types.MessageReactions._parse(client, message.reactions)
 
             parsed_message = Message(
                 id=message.id,
diff --git a/pyrogram/types/messages_and_media/message_reactions.py b/pyrogram/types/messages_and_media/message_reactions.py
new file mode 100644
index 0000000000..8f057cf559
--- /dev/null
+++ b/pyrogram/types/messages_and_media/message_reactions.py
@@ -0,0 +1,56 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  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 Optional, List
+
+import pyrogram
+from pyrogram import raw, types
+from ..object import Object
+
+
+class MessageReactions(Object):
+    """Contains information about a message reactions.
+
+    Parameters:
+        reactions (List of :obj:`~pyrogram.types.Reaction`):
+            Reactions list.
+    """
+
+    def __init__(
+        self,
+        *,
+        client: "pyrogram.Client" = None,
+        reactions: Optional[List["types.Reaction"]] = None,
+    ):
+        super().__init__(client)
+
+        self.reactions = reactions
+
+    @staticmethod
+    def _parse(
+        client: "pyrogram.Client",
+        message_reactions: Optional["raw.base.MessageReactions"] = None
+    ) -> Optional["MessageReactions"]:
+        if not message_reactions:
+            return None
+
+        return MessageReactions(
+            client=client,
+            reactions=[types.Reaction._parse_count(client, reaction)
+                       for reaction in message_reactions.results]
+        )

From 06c62c5b4c5ad038d5072e57f74d280526a7392e Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sat, 3 Sep 2022 13:47:17 +0200
Subject: [PATCH 1015/1185] Add MessageReactions and ChatReactions to the docs

---
 compiler/docs/compiler.py | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py
index a6f82f3229..f642f288e9 100644
--- a/compiler/docs/compiler.py
+++ b/compiler/docs/compiler.py
@@ -414,6 +414,8 @@ def get_title_list(s: str) -> list:
             VideoChatEnded
             VideoChatMembersInvited
             WebAppData
+            MessageReactions
+            ChatReactions
         """,
         bot_keyboards="""
         Bot keyboards

From f7319858e1e6743a31a81eabe6ada8a78eae9146 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sat, 3 Sep 2022 13:58:01 +0200
Subject: [PATCH 1016/1185] Update Pyrogram to v2.0.47

---
 pyrogram/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index 92cccf4515..19c7e0c369 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -16,7 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-__version__ = "2.0.46"
+__version__ = "2.0.47"
 __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)"
 __copyright__ = "Copyright (C) 2017-present Dan "
 

From 04b343f61b9e85cdcc2a81b20267097a5a4eedab Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sat, 3 Sep 2022 14:06:46 +0200
Subject: [PATCH 1017/1185] Add get_default_emoji_statuses method

---
 compiler/docs/compiler.py                     |  1 +
 pyrogram/methods/users/__init__.py            |  2 +
 .../users/get_default_emoji_statuses.py       | 43 +++++++++++++++++++
 3 files changed, 46 insertions(+)
 create mode 100644 pyrogram/methods/users/get_default_emoji_statuses.py

diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py
index f642f288e9..149cdcf005 100644
--- a/compiler/docs/compiler.py
+++ b/compiler/docs/compiler.py
@@ -251,6 +251,7 @@ def get_title_list(s: str) -> list:
             block_user
             unblock_user
             get_common_chats
+            get_default_emoji_statuses
         """,
         invite_links="""
         Invite Links
diff --git a/pyrogram/methods/users/__init__.py b/pyrogram/methods/users/__init__.py
index 2ed719e8fc..52c4699f8c 100644
--- a/pyrogram/methods/users/__init__.py
+++ b/pyrogram/methods/users/__init__.py
@@ -21,6 +21,7 @@
 from .get_chat_photos import GetChatPhotos
 from .get_chat_photos_count import GetChatPhotosCount
 from .get_common_chats import GetCommonChats
+from .get_default_emoji_statuses import GetDefaultEmojiStatuses
 from .get_me import GetMe
 from .get_users import GetUsers
 from .set_profile_photo import SetProfilePhoto
@@ -41,5 +42,6 @@ class Users(
     GetChatPhotosCount,
     UnblockUser,
     UpdateProfile,
+    GetDefaultEmojiStatuses
 ):
     pass
diff --git a/pyrogram/methods/users/get_default_emoji_statuses.py b/pyrogram/methods/users/get_default_emoji_statuses.py
new file mode 100644
index 0000000000..b4d707bde0
--- /dev/null
+++ b/pyrogram/methods/users/get_default_emoji_statuses.py
@@ -0,0 +1,43 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  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 import raw
+from pyrogram import types
+
+
+class GetDefaultEmojiStatuses:
+    async def get_default_emoji_statuses(
+        self: "pyrogram.Client",
+    ) -> types.List["types.EmojiStatus"]:
+        """Get the default emoji statuses.
+
+        Returns:
+            List of :obj:`~pyrogram.types.EmojiStatus`: On success, a list of emoji statuses is returned.
+
+        Example:
+            .. code-block:: python
+
+                default_emoji_statuses = await app.get_default_emoji_statuses()
+                print(default_emoji_statuses)
+        """
+        r = await self.invoke(
+            raw.functions.account.GetDefaultEmojiStatuses(hash=0)
+        )
+
+        return types.List([types.EmojiStatus._parse(self, i) for i in r.statuses])

From 5d11c03b4ee74e805391b1955683a617c881052f Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sat, 3 Sep 2022 14:18:12 +0200
Subject: [PATCH 1018/1185] Add set_emoji_status method

---
 compiler/docs/compiler.py                     |  1 +
 pyrogram/methods/users/__init__.py            |  4 +-
 pyrogram/methods/users/set_emoji_status.py    | 56 +++++++++++++++++++
 pyrogram/types/user_and_chats/emoji_status.py | 11 ++++
 4 files changed, 71 insertions(+), 1 deletion(-)
 create mode 100644 pyrogram/methods/users/set_emoji_status.py

diff --git a/compiler/docs/compiler.py b/compiler/docs/compiler.py
index 149cdcf005..12a4d97221 100644
--- a/compiler/docs/compiler.py
+++ b/compiler/docs/compiler.py
@@ -252,6 +252,7 @@ def get_title_list(s: str) -> list:
             unblock_user
             get_common_chats
             get_default_emoji_statuses
+            set_emoji_status
         """,
         invite_links="""
         Invite Links
diff --git a/pyrogram/methods/users/__init__.py b/pyrogram/methods/users/__init__.py
index 52c4699f8c..31fda1dc3a 100644
--- a/pyrogram/methods/users/__init__.py
+++ b/pyrogram/methods/users/__init__.py
@@ -24,6 +24,7 @@
 from .get_default_emoji_statuses import GetDefaultEmojiStatuses
 from .get_me import GetMe
 from .get_users import GetUsers
+from .set_emoji_status import SetEmojiStatus
 from .set_profile_photo import SetProfilePhoto
 from .set_username import SetUsername
 from .unblock_user import UnblockUser
@@ -42,6 +43,7 @@ class Users(
     GetChatPhotosCount,
     UnblockUser,
     UpdateProfile,
-    GetDefaultEmojiStatuses
+    GetDefaultEmojiStatuses,
+    SetEmojiStatus
 ):
     pass
diff --git a/pyrogram/methods/users/set_emoji_status.py b/pyrogram/methods/users/set_emoji_status.py
new file mode 100644
index 0000000000..288e966b20
--- /dev/null
+++ b/pyrogram/methods/users/set_emoji_status.py
@@ -0,0 +1,56 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  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 Optional
+
+import pyrogram
+from pyrogram import raw, types
+
+
+class SetEmojiStatus:
+    async def set_emoji_status(
+        self: "pyrogram.Client",
+        emoji_status: Optional["types.EmojiStatus"] = None
+    ) -> bool:
+        """Set the emoji status.
+
+        Parameters:
+            emoji_status (:obj:`~pyrogram.types.EmojiStatus`, *optional*):
+                The emoji status to set. None to remove.
+
+        Returns:
+            ``bool``: On success, True is returned.
+
+        Example:
+            .. code-block:: python
+
+                from pyrogram import types
+
+                await app.set_emoji_status(types.EmojiStatus(custom_emoji_id=1234567890987654321))
+        """
+        await self.invoke(
+            raw.functions.account.UpdateEmojiStatus(
+                emoji_status=(
+                    emoji_status.write()
+                    if emoji_status
+                    else raw.types.EmojiStatusEmpty()
+                )
+            )
+        )
+
+        return True
diff --git a/pyrogram/types/user_and_chats/emoji_status.py b/pyrogram/types/user_and_chats/emoji_status.py
index 746c98ef2c..50b46bfa46 100644
--- a/pyrogram/types/user_and_chats/emoji_status.py
+++ b/pyrogram/types/user_and_chats/emoji_status.py
@@ -64,3 +64,14 @@ def _parse(client, emoji_status: "raw.base.EmojiStatus") -> Optional["EmojiStatu
             )
 
         return None
+
+    def write(self):
+        if self.until_date:
+            return raw.types.EmojiStatusUntil(
+                document_id=self.custom_emoji_id,
+                until=utils.datetime_to_timestamp(self.until_date)
+            )
+
+        return raw.types.EmojiStatus(
+            document_id=self.custom_emoji_id
+        )

From 210f538d552577249b1e42b9635fab06b2be9262 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sat, 3 Sep 2022 14:18:51 +0200
Subject: [PATCH 1019/1185] Update Pyrogram to v2.0.48

---
 pyrogram/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index 19c7e0c369..fb73f17f68 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -16,7 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-__version__ = "2.0.47"
+__version__ = "2.0.48"
 __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)"
 __copyright__ = "Copyright (C) 2017-present Dan "
 

From 1db03c4351b56e620af6a8a0c604aa5a5ce79ff5 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sat, 3 Sep 2022 14:23:52 +0200
Subject: [PATCH 1020/1185] Fix for older Python versions

---
 pyrogram/methods/users/get_default_emoji_statuses.py | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/pyrogram/methods/users/get_default_emoji_statuses.py b/pyrogram/methods/users/get_default_emoji_statuses.py
index b4d707bde0..8c85a3c202 100644
--- a/pyrogram/methods/users/get_default_emoji_statuses.py
+++ b/pyrogram/methods/users/get_default_emoji_statuses.py
@@ -16,6 +16,8 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
+from typing import List
+
 import pyrogram
 from pyrogram import raw
 from pyrogram import types
@@ -24,7 +26,7 @@
 class GetDefaultEmojiStatuses:
     async def get_default_emoji_statuses(
         self: "pyrogram.Client",
-    ) -> types.List["types.EmojiStatus"]:
+    ) -> List["types.EmojiStatus"]:
         """Get the default emoji statuses.
 
         Returns:

From 1a1075090d67ad32229f7a346d7c1950479de9d2 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sat, 3 Sep 2022 14:24:10 +0200
Subject: [PATCH 1021/1185] Update Pyrogram to v2.0.49

---
 pyrogram/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index fb73f17f68..98f6b3afee 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -16,7 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-__version__ = "2.0.48"
+__version__ = "2.0.49"
 __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)"
 __copyright__ = "Copyright (C) 2017-present Dan "
 

From e0fdcc7f3a33bac9e501a5922a55ef2871bf0c17 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Mon, 5 Sep 2022 18:18:42 +0200
Subject: [PATCH 1022/1185] Fix send_reaction Fixes #1086

---
 pyrogram/methods/messages/send_reaction.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/methods/messages/send_reaction.py b/pyrogram/methods/messages/send_reaction.py
index 1ede4ead79..b1e6540b68 100644
--- a/pyrogram/methods/messages/send_reaction.py
+++ b/pyrogram/methods/messages/send_reaction.py
@@ -63,7 +63,7 @@ async def send_reaction(
             raw.functions.messages.SendReaction(
                 peer=await self.resolve_peer(chat_id),
                 msg_id=message_id,
-                reaction=emoji,
+                reaction=[raw.types.ReactionEmoji(emoticon=emoji)] if emoji else None,
                 big=big
             )
         )

From 8490cfa0a92e2d57b6de9728b1d18ee3071be5b4 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Mon, 5 Sep 2022 18:19:02 +0200
Subject: [PATCH 1023/1185] Update Pyrogram to v2.0.50

---
 pyrogram/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index 98f6b3afee..b72afd0ceb 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -16,7 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-__version__ = "2.0.49"
+__version__ = "2.0.50"
 __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)"
 __copyright__ = "Copyright (C) 2017-present Dan "
 

From f9aacd814a60b60f61c8d989bdaeb9fa92492a24 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Tue, 6 Sep 2022 19:04:47 +0200
Subject: [PATCH 1024/1185] Update poll parsing

---
 pyrogram/types/messages_and_media/message.py |  3 +-
 pyrogram/types/messages_and_media/poll.py    | 78 ++++++++++++++++----
 2 files changed, 64 insertions(+), 17 deletions(-)

diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py
index 40386af0f8..c49fd0f6c7 100644
--- a/pyrogram/types/messages_and_media/message.py
+++ b/pyrogram/types/messages_and_media/message.py
@@ -827,7 +827,8 @@ async def _parse(
                     except MessageIdsEmpty:
                         pass
 
-            client.message_cache[(parsed_message.chat.id, parsed_message.id)] = parsed_message
+            if not parsed_message.poll:  # Do not cache poll messages
+                client.message_cache[(parsed_message.chat.id, parsed_message.id)] = parsed_message
 
             return parsed_message
 
diff --git a/pyrogram/types/messages_and_media/poll.py b/pyrogram/types/messages_and_media/poll.py
index 9d4c5118cb..cdb97ea100 100644
--- a/pyrogram/types/messages_and_media/poll.py
+++ b/pyrogram/types/messages_and_media/poll.py
@@ -16,10 +16,11 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-from typing import List, Union
+from datetime import datetime
+from typing import List, Union, Optional
 
 import pyrogram
-from pyrogram import raw, enums
+from pyrogram import raw, enums, utils
 from pyrogram import types
 from ..object import Object
 from ..update import Update
@@ -53,8 +54,26 @@ class Poll(Object, Update):
         allows_multiple_answers (``bool``, *optional*):
             True, if the poll allows multiple answers.
 
-        chosen_option (``int``, *optional*):
-            Index of your chosen option (0-9), None in case you haven't voted yet.
+        chosen_option_id (``int``, *optional*):
+            0-based index of the chosen option), None in case of no vote yet.
+
+        correct_option_id (``int``, *optional*):
+            0-based identifier of the correct answer option.
+            Available only for polls in the quiz mode, which are closed, or was sent (not forwarded) by the bot or to
+            the private chat with the bot.
+
+        explanation (``str``, *optional*):
+            Text that is shown when a user chooses an incorrect answer or taps on the lamp icon in a quiz-style poll,
+            0-200 characters.
+
+        explanation_entities (List of :obj:`~pyrogram.types.MessageEntity`, *optional*):
+            Special entities like usernames, URLs, bot commands, etc. that appear in the explanation.
+
+        open_period (``int``, *optional*):
+            Amount of time in seconds the poll will be active after creation.
+
+        close_date (:py:obj:`~datetime.datetime`, *optional*):
+            Point in time when the poll will be automatically closed.
     """
 
     def __init__(
@@ -69,8 +88,12 @@ def __init__(
         is_anonymous: bool = None,
         type: "enums.PollType" = None,
         allows_multiple_answers: bool = None,
-        # correct_option_id: int,
-        chosen_option: int = None
+        chosen_option_id: Optional[int] = None,
+        correct_option_id: Optional[int] = None,
+        explanation: Optional[str] = None,
+        explanation_entities: Optional[List["types.MessageEntity"]] = None,
+        open_period: Optional[int] = None,
+        close_date: Optional[datetime] = None
     ):
         super().__init__(client)
 
@@ -82,14 +105,21 @@ def __init__(
         self.is_anonymous = is_anonymous
         self.type = type
         self.allows_multiple_answers = allows_multiple_answers
-        # self.correct_option_id = correct_option_id
-        self.chosen_option = chosen_option
+        self.chosen_option_id = chosen_option_id
+        self.correct_option_id = correct_option_id
+        self.explanation = explanation
+        self.explanation_entities = explanation_entities
+        self.open_period = open_period
+        self.close_date = close_date
 
     @staticmethod
     def _parse(client, media_poll: Union["raw.types.MessageMediaPoll", "raw.types.UpdateMessagePoll"]) -> "Poll":
-        poll = media_poll.poll  # type: raw.types.Poll
-        results = media_poll.results.results
-        chosen_option = None
+        poll: raw.types.Poll = media_poll.poll
+        poll_results: raw.types.PollResults = media_poll.results
+        results: List[raw.types.PollAnswerVoters] = poll_results.results
+
+        chosen_option_id = None
+        correct_option_id = None
         options = []
 
         for i, answer in enumerate(poll.answers):
@@ -100,7 +130,10 @@ def _parse(client, media_poll: Union["raw.types.MessageMediaPoll", "raw.types.Up
                 voter_count = result.voters
 
                 if result.chosen:
-                    chosen_option = i
+                    chosen_option_id = i
+
+                if result.correct:
+                    correct_option_id = i
 
             options.append(
                 types.PollOption(
@@ -120,7 +153,15 @@ def _parse(client, media_poll: Union["raw.types.MessageMediaPoll", "raw.types.Up
             is_anonymous=not poll.public_voters,
             type=enums.PollType.QUIZ if poll.quiz else enums.PollType.REGULAR,
             allows_multiple_answers=poll.multiple_choice,
-            chosen_option=chosen_option,
+            chosen_option_id=chosen_option_id,
+            correct_option_id=correct_option_id,
+            explanation=poll_results.solution,
+            explanation_entities=[
+                types.MessageEntity._parse(client, i, {})
+                for i in poll_results.solution_entities
+            ] if poll_results.solution_entities else None,
+            open_period=poll.close_period,
+            close_date=utils.timestamp_to_datetime(poll.close_date),
             client=client
         )
 
@@ -130,12 +171,16 @@ def _parse_update(client, update: "raw.types.UpdateMessagePoll"):
             return Poll._parse(client, update)
 
         results = update.results.results
-        chosen_option = None
+        chosen_option_id = None
+        correct_option_id = None
         options = []
 
         for i, result in enumerate(results):
             if result.chosen:
-                chosen_option = i
+                chosen_option_id = i
+
+            if result.correct:
+                correct_option_id = i
 
             options.append(
                 types.PollOption(
@@ -152,6 +197,7 @@ def _parse_update(client, update: "raw.types.UpdateMessagePoll"):
             options=options,
             total_voter_count=update.results.total_voters,
             is_closed=False,
-            chosen_option=chosen_option,
+            chosen_option_id=chosen_option_id,
+            correct_option_id=correct_option_id,
             client=client
         )

From 7558e04cfc6aaeb8630cffe0d8fd9f77d4141d4d Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Tue, 6 Sep 2022 19:05:09 +0200
Subject: [PATCH 1025/1185] Update Pyrogram to v2.0.51

---
 pyrogram/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index b72afd0ceb..839f43f7b0 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -16,7 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-__version__ = "2.0.50"
+__version__ = "2.0.51"
 __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)"
 __copyright__ = "Copyright (C) 2017-present Dan "
 

From 6dced525ab67d9bfa9a8d26f02f0a87327eed8d7 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Thu, 15 Sep 2022 12:15:13 +0200
Subject: [PATCH 1026/1185] Update example

---
 pyrogram/methods/users/delete_profile_photos.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/methods/users/delete_profile_photos.py b/pyrogram/methods/users/delete_profile_photos.py
index 2e7615444f..4ee8b7b51d 100644
--- a/pyrogram/methods/users/delete_profile_photos.py
+++ b/pyrogram/methods/users/delete_profile_photos.py
@@ -43,7 +43,7 @@ async def delete_profile_photos(
             .. code-block:: python
 
                 # Get the photos to be deleted
-                photos = list(await app.get_chat_photos("me"))
+                photos = [p async for p in app.get_chat_photos("me")]
 
                 # Delete one photo
                 await app.delete_profile_photos(photos[0].file_id)

From 8fd79c93c75a15a25562d5bb835a1d96d3df35f7 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Thu, 15 Sep 2022 16:51:46 +0200
Subject: [PATCH 1027/1185] Update API schema to Layer 146

---
 compiler/api/source/main_api.tl | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)

diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl
index 465446b489..9d71625f9d 100644
--- a/compiler/api/source/main_api.tl
+++ b/compiler/api/source/main_api.tl
@@ -46,7 +46,7 @@ inputMediaVenue#c13d1c11 geo_point:InputGeoPoint title:string address:string pro
 inputMediaPhotoExternal#e5bbfe1a flags:# url:string ttl_seconds:flags.0?int = InputMedia;
 inputMediaDocumentExternal#fb52dc99 flags:# url:string ttl_seconds:flags.0?int = InputMedia;
 inputMediaGame#d33f43f3 id:InputGame = InputMedia;
-inputMediaInvoice#d9799874 flags:# title:string description:string photo:flags.0?InputWebDocument invoice:Invoice payload:bytes provider:string provider_data:DataJSON start_param:flags.1?string = InputMedia;
+inputMediaInvoice#8eb5a6d5 flags:# title:string description:string photo:flags.0?InputWebDocument invoice:Invoice payload:bytes provider:string provider_data:DataJSON start_param:flags.1?string extended_media:flags.2?InputMedia = InputMedia;
 inputMediaGeoLive#971fa843 flags:# stopped:flags.0?true geo_point:InputGeoPoint heading:flags.2?int period:flags.1?int proximity_notification_radius:flags.3?int = InputMedia;
 inputMediaPoll#f94e5f1 flags:# poll:Poll correct_answers:flags.0?Vector solution:flags.1?string solution_entities:flags.1?Vector = InputMedia;
 inputMediaDice#e66fbf7b emoticon:string = InputMedia;
@@ -132,7 +132,7 @@ messageMediaDocument#9cb070d7 flags:# nopremium:flags.3?true document:flags.0?Do
 messageMediaWebPage#a32dd600 webpage:WebPage = MessageMedia;
 messageMediaVenue#2ec0533f geo:GeoPoint title:string address:string provider:string venue_id:string venue_type:string = MessageMedia;
 messageMediaGame#fdb19008 game:Game = MessageMedia;
-messageMediaInvoice#84551347 flags:# shipping_address_requested:flags.1?true test:flags.3?true title:string description:string photo:flags.0?WebDocument receipt_msg_id:flags.2?int currency:string total_amount:long start_param:string = MessageMedia;
+messageMediaInvoice#f6a548d3 flags:# shipping_address_requested:flags.1?true test:flags.3?true title:string description:string photo:flags.0?WebDocument receipt_msg_id:flags.2?int currency:string total_amount:long start_param:string extended_media:flags.4?MessageExtendedMedia = MessageMedia;
 messageMediaGeoLive#b940c666 flags:# geo:GeoPoint heading:flags.0?int period:int proximity_notification_radius:flags.1?int = MessageMedia;
 messageMediaPoll#4bd6e798 poll:Poll results:PollResults = MessageMedia;
 messageMediaDice#3f7ee58b value:int emoticon:string = MessageMedia;
@@ -375,6 +375,7 @@ updateUserEmojiStatus#28373599 user_id:long emoji_status:EmojiStatus = Update;
 updateRecentEmojiStatuses#30f443db = Update;
 updateRecentReactions#6f7863f4 = Update;
 updateMoveStickerSetToTop#86fccf85 flags:# masks:flags.0?true emojis:flags.1?true stickerset:long = Update;
+updateMessageExtendedMedia#5a73a98c peer:Peer msg_id:int extended_media:MessageExtendedMedia = Update;
 
 updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State;
 
@@ -1422,6 +1423,9 @@ premiumSubscriptionOption#b6f11ebe flags:# current:flags.1?true can_purchase_upg
 
 sendAsPeer#b81c7034 flags:# premium_required:flags.0?true peer:Peer = SendAsPeer;
 
+messageExtendedMediaPreview#ad628cc8 flags:# w:flags.0?int h:flags.0?int thumb:flags.1?PhotoSize video_duration:flags.2?int = MessageExtendedMedia;
+messageExtendedMedia#ee479c64 media:MessageMedia = MessageExtendedMedia;
+
 ---functions---
 
 invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
@@ -1735,6 +1739,7 @@ messages.reportReaction#3f64c076 peer:InputPeer id:int reaction_peer:InputPeer =
 messages.getTopReactions#bb8125ba limit:int hash:long = messages.Reactions;
 messages.getRecentReactions#39461db2 limit:int hash:long = messages.Reactions;
 messages.clearRecentReactions#9dfeefb4 = Bool;
+messages.getExtendedMedia#84f80814 peer:InputPeer id:Vector = Updates;
 
 updates.getState#edd4882a = updates.State;
 updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference;
@@ -1897,4 +1902,4 @@ stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel
 stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages;
 stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats;
 
-// LAYER 145
\ No newline at end of file
+// LAYER 146
\ No newline at end of file

From 9f94aee9f8183e5a7222057a0c4753fee4016ed5 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Thu, 15 Sep 2022 16:52:14 +0200
Subject: [PATCH 1028/1185] Update Pyrogram to v2.0.52

---
 pyrogram/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index 839f43f7b0..9c34c9a786 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -16,7 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-__version__ = "2.0.51"
+__version__ = "2.0.52"
 __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)"
 __copyright__ = "Copyright (C) 2017-present Dan "
 

From 53584bc1dbd19c9b956d9c78932ec792c7744cf3 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 18 Sep 2022 15:44:18 +0200
Subject: [PATCH 1029/1185] Add chosen_order to Reaction

---
 pyrogram/types/messages_and_media/reaction.py | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/pyrogram/types/messages_and_media/reaction.py b/pyrogram/types/messages_and_media/reaction.py
index 9d6b9e24b2..17e08ff578 100644
--- a/pyrogram/types/messages_and_media/reaction.py
+++ b/pyrogram/types/messages_and_media/reaction.py
@@ -35,6 +35,10 @@ class Reaction(Object):
 
         count (``int``, *optional*):
             Reaction count.
+
+        chosen_order (``int``, *optional*):
+            Chosen reaction order.
+            Available for chosen reactions.
     """
 
     def __init__(
@@ -43,13 +47,15 @@ def __init__(
         client: "pyrogram.Client" = None,
         emoji: Optional[str] = None,
         custom_emoji_id: Optional[int] = None,
-        count: Optional[int] = None
+        count: Optional[int] = None,
+        chosen_order: Optional[int] = None
     ):
         super().__init__(client)
 
         self.emoji = emoji
         self.custom_emoji_id = custom_emoji_id
         self.count = count
+        self.chosen_order = chosen_order
 
     @staticmethod
     def _parse(
@@ -75,5 +81,6 @@ def _parse_count(
     ) -> "Reaction":
         reaction = Reaction._parse(client, reaction_count.reaction)
         reaction.count = reaction_count.count
+        reaction.chosen_order = reaction_count.chosen_order
 
         return reaction

From 81895d74c7e677c2d64b768b3c166bc2555d5993 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 18 Sep 2022 16:04:07 +0200
Subject: [PATCH 1030/1185] Fix Chat.available_reactions type hint

---
 pyrogram/types/user_and_chats/chat.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/pyrogram/types/user_and_chats/chat.py b/pyrogram/types/user_and_chats/chat.py
index aa727952e5..f37542fb0c 100644
--- a/pyrogram/types/user_and_chats/chat.py
+++ b/pyrogram/types/user_and_chats/chat.py
@@ -126,7 +126,7 @@ class Chat(Object):
             The default "send_as" chat.
             Returned only in :meth:`~pyrogram.Client.get_chat`.
 
-        available_reactions (List of :obj:`~pyrogram.types.Reaction`, *optional*):
+        available_reactions (:obj:`~pyrogram.types.ChatReactions`, *optional*):
             Available reactions in the chat.
             Returned only in :meth:`~pyrogram.Client.get_chat`.
     """
@@ -162,7 +162,7 @@ def __init__(
         distance: int = None,
         linked_chat: "types.Chat" = None,
         send_as_chat: "types.Chat" = None,
-        available_reactions: Optional[List["types.Reaction"]] = None
+        available_reactions: Optional["types.ChatReactions"] = None
     ):
         super().__init__(client)
 

From 468ebf50cf596b73175aa5826a44e5be00becb78 Mon Sep 17 00:00:00 2001
From: Nyan <93275906+el-garro@users.noreply.github.com>
Date: Sun, 18 Sep 2022 11:24:51 -0400
Subject: [PATCH 1031/1185] Add tempfile deletion in case of asyncio task
 cancellation. (#1080)

---
 pyrogram/client.py | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/pyrogram/client.py b/pyrogram/client.py
index d30a5e9a80..f4cee803a5 100644
--- a/pyrogram/client.py
+++ b/pyrogram/client.py
@@ -737,6 +737,12 @@ async def handle_download(self, packet):
                 os.remove(file.name)
 
             return None
+        except asyncio.CancelledError:
+            if not in_memory:
+                file.close()
+                os.remove(file.name)
+                
+            raise asyncio.CancelledError()
         else:
             if in_memory:
                 file.name = file_name

From 3940ca9611d3d7061996e4ee35e57136d87626f9 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 18 Sep 2022 17:32:02 +0200
Subject: [PATCH 1032/1185] Revamp handling of partial downloads

---
 pyrogram/client.py | 22 +++++++++-------------
 1 file changed, 9 insertions(+), 13 deletions(-)

diff --git a/pyrogram/client.py b/pyrogram/client.py
index f4cee803a5..4ab7c3447b 100644
--- a/pyrogram/client.py
+++ b/pyrogram/client.py
@@ -25,7 +25,6 @@
 import re
 import shutil
 import sys
-import tempfile
 from concurrent.futures.thread import ThreadPoolExecutor
 from hashlib import sha256
 from importlib import import_module
@@ -726,32 +725,29 @@ def load_plugins(self):
 
     async def handle_download(self, packet):
         file_id, directory, file_name, in_memory, file_size, progress, progress_args = packet
-        file = BytesIO() if in_memory else tempfile.NamedTemporaryFile("wb", delete=False)
 
+        os.makedirs(directory, exist_ok=True)
+        temp_file_path = os.path.abspath(re.sub("\\\\", "/", os.path.join(directory, file_name))) + ".temp"
+        file = BytesIO() if in_memory else open(temp_file_path, "wb")
+
+        # noinspection PyBroadException
         try:
             async for chunk in self.get_file(file_id, file_size, 0, 0, progress, progress_args):
                 file.write(chunk)
-        except pyrogram.StopTransmission:
+        except BaseException:
             if not in_memory:
                 file.close()
-                os.remove(file.name)
+                os.remove(temp_file_path)
 
             return None
-        except asyncio.CancelledError:
-            if not in_memory:
-                file.close()
-                os.remove(file.name)
-                
-            raise asyncio.CancelledError()
         else:
             if in_memory:
                 file.name = file_name
                 return file
             else:
-                file_path = os.path.abspath(re.sub("\\\\", "/", os.path.join(directory, file_name)))
-                os.makedirs(directory, exist_ok=True)
                 file.close()
-                shutil.move(file.name, file_path)
+                file_path = os.path.splitext(temp_file_path)[0]
+                shutil.move(temp_file_path, file_path)
                 return file_path
 
     async def get_file(

From 2d547ccf8c1c87dd2e8866d8fd72eba816cc0ff1 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 18 Sep 2022 17:33:16 +0200
Subject: [PATCH 1033/1185] Update Pyrogram to v2.0.53

---
 pyrogram/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index 9c34c9a786..2465ddf769 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -16,7 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-__version__ = "2.0.52"
+__version__ = "2.0.53"
 __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)"
 __copyright__ = "Copyright (C) 2017-present Dan "
 

From 71f263b30d3acfe079b3b72b3d2fed39d6325850 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 18 Sep 2022 17:55:50 +0200
Subject: [PATCH 1034/1185] Re-raise asyncio.CancelledError to avoid
 continuations

---
 pyrogram/client.py | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/pyrogram/client.py b/pyrogram/client.py
index 4ab7c3447b..6f07395ad8 100644
--- a/pyrogram/client.py
+++ b/pyrogram/client.py
@@ -730,15 +730,17 @@ async def handle_download(self, packet):
         temp_file_path = os.path.abspath(re.sub("\\\\", "/", os.path.join(directory, file_name))) + ".temp"
         file = BytesIO() if in_memory else open(temp_file_path, "wb")
 
-        # noinspection PyBroadException
         try:
             async for chunk in self.get_file(file_id, file_size, 0, 0, progress, progress_args):
                 file.write(chunk)
-        except BaseException:
+        except BaseException as e:
             if not in_memory:
                 file.close()
                 os.remove(temp_file_path)
 
+            if isinstance(e, asyncio.CancelledError):
+                raise e
+
             return None
         else:
             if in_memory:

From a2fe5b3e79ff9e44ec37293323b0ab844d371587 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 18 Sep 2022 17:56:05 +0200
Subject: [PATCH 1035/1185] Update Pyrogram to v2.0.54

---
 pyrogram/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index 2465ddf769..70188abcbb 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -16,7 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-__version__ = "2.0.53"
+__version__ = "2.0.54"
 __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)"
 __copyright__ = "Copyright (C) 2017-present Dan "
 

From 4edaa21c199e85386989e49901ff90facec9ffde Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Tue, 20 Sep 2022 16:39:53 +0200
Subject: [PATCH 1036/1185] Don't create download dirs for in-memory downloads

---
 pyrogram/client.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/client.py b/pyrogram/client.py
index 6f07395ad8..67f059e96b 100644
--- a/pyrogram/client.py
+++ b/pyrogram/client.py
@@ -726,7 +726,7 @@ def load_plugins(self):
     async def handle_download(self, packet):
         file_id, directory, file_name, in_memory, file_size, progress, progress_args = packet
 
-        os.makedirs(directory, exist_ok=True)
+        os.makedirs(directory, exist_ok=True) if not in_memory else None
         temp_file_path = os.path.abspath(re.sub("\\\\", "/", os.path.join(directory, file_name))) + ".temp"
         file = BytesIO() if in_memory else open(temp_file_path, "wb")
 

From 862285e1e69da8fa8701218a7ba66e1614abbd5e Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Tue, 20 Sep 2022 16:40:36 +0200
Subject: [PATCH 1037/1185] Update Pyrogram to v2.0.55

---
 pyrogram/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index 70188abcbb..1331a5d59d 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -16,7 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-__version__ = "2.0.54"
+__version__ = "2.0.55"
 __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)"
 __copyright__ = "Copyright (C) 2017-present Dan "
 

From 2870ae84e73cf37dc4a489299275f3584a9f773d Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Thu, 22 Sep 2022 15:26:42 +0200
Subject: [PATCH 1038/1185] Move get_me() call into start()

---
 pyrogram/methods/auth/initialize.py | 2 --
 pyrogram/methods/utilities/start.py | 2 ++
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/pyrogram/methods/auth/initialize.py b/pyrogram/methods/auth/initialize.py
index 26c9d0bf8c..1e7915e00e 100644
--- a/pyrogram/methods/auth/initialize.py
+++ b/pyrogram/methods/auth/initialize.py
@@ -44,8 +44,6 @@ async def initialize(
 
         self.load_plugins()
 
-        self.me = await self.get_me()
-
         await self.dispatcher.start()
 
         self.is_initialized = True
diff --git a/pyrogram/methods/utilities/start.py b/pyrogram/methods/utilities/start.py
index bf5468fbe6..20b451be32 100644
--- a/pyrogram/methods/utilities/start.py
+++ b/pyrogram/methods/utilities/start.py
@@ -71,4 +71,6 @@ async def main():
             raise
         else:
             await self.initialize()
+            self.me = await self.get_me()
+
             return self

From f407f88395e71eb2b534b6e9afe16ab90cd9e5bb Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Thu, 22 Sep 2022 15:27:03 +0200
Subject: [PATCH 1039/1185] Update Pyrogram to v2.0.56

---
 pyrogram/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index 1331a5d59d..bf6c5e25f2 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -16,7 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-__version__ = "2.0.55"
+__version__ = "2.0.56"
 __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)"
 __copyright__ = "Copyright (C) 2017-present Dan "
 

From ec476aa293d58ba1b46a4b6b61d947326d7387c7 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Fri, 23 Sep 2022 09:50:11 +0200
Subject: [PATCH 1040/1185] Call get_me() before initializing the client

---
 pyrogram/methods/utilities/start.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/methods/utilities/start.py b/pyrogram/methods/utilities/start.py
index 20b451be32..95cd9fc585 100644
--- a/pyrogram/methods/utilities/start.py
+++ b/pyrogram/methods/utilities/start.py
@@ -70,7 +70,7 @@ async def main():
             await self.disconnect()
             raise
         else:
-            await self.initialize()
             self.me = await self.get_me()
+            await self.initialize()
 
             return self

From 0e68bf35b70901e8d24bebbacf31a1609b18971f Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Fri, 23 Sep 2022 09:50:31 +0200
Subject: [PATCH 1041/1185] Update Pyrogram to v2.0.57

---
 pyrogram/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index bf6c5e25f2..e9dcc31f0b 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -16,7 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-__version__ = "2.0.56"
+__version__ = "2.0.57"
 __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)"
 __copyright__ = "Copyright (C) 2017-present Dan "
 

From 8077eb41303b07367d38e8eafa3c0884182274d0 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Thu, 6 Oct 2022 12:03:05 +0200
Subject: [PATCH 1042/1185] Update docs

---
 .gitignore                                    |   2 +
 Makefile                                      |  28 +-
 compiler/api/compiler.py                      | 104 ++++--
 compiler/docs/template/bound-methods.rst      |   4 -
 compiler/docs/template/methods.rst            |   4 -
 compiler/docs/template/toctree.txt            |   2 +
 compiler/docs/template/types.rst              |   4 -
 docs/requirements.txt                         |   4 -
 docs/source/api/client.rst                    |  24 --
 docs/source/api/decorators.rst                |  68 ----
 docs/source/api/enums/ChatAction.rst          |   8 -
 docs/source/api/enums/ChatEventAction.rst     |   8 -
 docs/source/api/enums/ChatMemberStatus.rst    |   8 -
 docs/source/api/enums/ChatMembersFilter.rst   |   8 -
 docs/source/api/enums/ChatType.rst            |   8 -
 docs/source/api/enums/MessageEntityType.rst   |   8 -
 docs/source/api/enums/MessageMediaType.rst    |   8 -
 docs/source/api/enums/MessageServiceType.rst  |   8 -
 docs/source/api/enums/MessagesFilter.rst      |   8 -
 docs/source/api/enums/NextCodeType.rst        |   8 -
 docs/source/api/enums/ParseMode.rst           |   8 -
 docs/source/api/enums/PollType.rst            |   8 -
 docs/source/api/enums/SentCodeType.rst        |   8 -
 docs/source/api/enums/UserStatus.rst          |   8 -
 docs/source/api/enums/cleanup.html            |   9 -
 docs/source/api/enums/index.rst               |  47 ---
 docs/source/api/errors/bad-request.rst        |   7 -
 docs/source/api/errors/flood.rst              |   7 -
 docs/source/api/errors/forbidden.rst          |   7 -
 docs/source/api/errors/index.rst              |  37 ---
 .../api/errors/internal-server-error.rst      |   7 -
 docs/source/api/errors/not-acceptable.rst     |   7 -
 docs/source/api/errors/see-other.rst          |   7 -
 docs/source/api/errors/unauthorized.rst       |   7 -
 docs/source/api/filters.rst                   |  11 -
 docs/source/api/handlers.rst                  |  66 ----
 docs/source/conf.py                           |  91 ------
 .../client-started-but-nothing-happens.rst    |  11 -
 ...alling-stop-restart-add-remove-handler.rst |  12 -
 docs/source/faq/how-to-avoid-flood-waits.rst  |  23 --
 docs/source/faq/how-to-use-webhooks.rst       |   9 -
 docs/source/faq/index.rst                     |  45 ---
 ...ing-the-account-to-another-data-center.rst |  10 -
 docs/source/faq/peer-id-invalid-error.rst     |  14 -
 ...ror-timeouterror-connection-lost-reset.rst |  12 -
 ...interfaceerror-error-binding-parameter.rst |  13 -
 ...e3-operationalerror-database-is-locked.rst |  17 -
 ...e-account-has-been-limited-deactivated.rst |  16 -
 .../unicodeencodeerror-codec-cant-encode.rst  |   7 -
 ...h-urls-gives-error-webpage-curl-failed.rst |   7 -
 ...le-clients-at-once-on-the-same-account.rst |   7 -
 ...same-file-id-across-different-accounts.rst |   6 -
 ...-ip-addresses-of-telegram-data-centers.rst |  30 --
 .../why-is-the-api-key-needed-for-bots.rst    |  12 -
 ...eacting-slowly-in-supergroups-channels.rst |  18 --
 docs/source/index.rst                         | 172 ----------
 docs/source/intro/install.rst                 |  50 ---
 docs/source/intro/quickstart.rst              |  56 ----
 docs/source/start/auth.rst                    |  93 ------
 docs/source/start/errors.rst                  | 101 ------
 docs/source/start/examples/bot_keyboards.rst  |  68 ----
 .../start/examples/callback_queries.rst       |  21 --
 docs/source/start/examples/echo_bot.rst       |  21 --
 .../start/examples/get_chat_history.rst       |  20 --
 .../start/examples/get_chat_members.rst       |  22 --
 docs/source/start/examples/get_dialogs.rst    |  19 --
 docs/source/start/examples/hello_world.rst    |  20 --
 docs/source/start/examples/index.rst          |  46 ---
 docs/source/start/examples/inline_queries.rst |  59 ----
 docs/source/start/examples/raw_updates.rst    |  18 --
 .../source/start/examples/use_inline_bots.rst |  25 --
 docs/source/start/examples/welcome_bot.rst    |  30 --
 docs/source/start/invoking.rst                | 110 -------
 docs/source/start/setup.rst                   |  40 ---
 docs/source/start/updates.rst                 |  78 -----
 docs/source/support.rst                       |  63 ----
 docs/source/topics/advanced-usage.rst         | 124 -------
 docs/source/topics/client-settings.rst        |  46 ---
 docs/source/topics/create-filters.rst         | 109 -------
 docs/source/topics/debugging.rst              | 122 -------
 docs/source/topics/more-on-updates.rst        | 226 -------------
 docs/source/topics/mtproto-vs-botapi.rst      | 112 -------
 docs/source/topics/proxy.rst                  |  34 --
 docs/source/topics/scheduling.rst             |  65 ----
 docs/source/topics/serializing.rst            |  56 ----
 docs/source/topics/smart-plugins.rst          | 306 ------------------
 docs/source/topics/speedups.rst               |  88 -----
 docs/source/topics/storage-engines.rst        |  90 ------
 docs/source/topics/synchronous.rst            |  88 -----
 docs/source/topics/test-servers.rst           |  41 ---
 docs/source/topics/text-formatting.rst        | 243 --------------
 docs/source/topics/use-filters.rst            | 114 -------
 docs/source/topics/voice-calls.rst            |  19 --
 93 files changed, 85 insertions(+), 3865 deletions(-)
 delete mode 100644 docs/requirements.txt
 delete mode 100644 docs/source/api/client.rst
 delete mode 100644 docs/source/api/decorators.rst
 delete mode 100644 docs/source/api/enums/ChatAction.rst
 delete mode 100644 docs/source/api/enums/ChatEventAction.rst
 delete mode 100644 docs/source/api/enums/ChatMemberStatus.rst
 delete mode 100644 docs/source/api/enums/ChatMembersFilter.rst
 delete mode 100644 docs/source/api/enums/ChatType.rst
 delete mode 100644 docs/source/api/enums/MessageEntityType.rst
 delete mode 100644 docs/source/api/enums/MessageMediaType.rst
 delete mode 100644 docs/source/api/enums/MessageServiceType.rst
 delete mode 100644 docs/source/api/enums/MessagesFilter.rst
 delete mode 100644 docs/source/api/enums/NextCodeType.rst
 delete mode 100644 docs/source/api/enums/ParseMode.rst
 delete mode 100644 docs/source/api/enums/PollType.rst
 delete mode 100644 docs/source/api/enums/SentCodeType.rst
 delete mode 100644 docs/source/api/enums/UserStatus.rst
 delete mode 100644 docs/source/api/enums/cleanup.html
 delete mode 100644 docs/source/api/enums/index.rst
 delete mode 100644 docs/source/api/errors/bad-request.rst
 delete mode 100644 docs/source/api/errors/flood.rst
 delete mode 100644 docs/source/api/errors/forbidden.rst
 delete mode 100644 docs/source/api/errors/index.rst
 delete mode 100644 docs/source/api/errors/internal-server-error.rst
 delete mode 100644 docs/source/api/errors/not-acceptable.rst
 delete mode 100644 docs/source/api/errors/see-other.rst
 delete mode 100644 docs/source/api/errors/unauthorized.rst
 delete mode 100644 docs/source/api/filters.rst
 delete mode 100644 docs/source/api/handlers.rst
 delete mode 100644 docs/source/conf.py
 delete mode 100644 docs/source/faq/client-started-but-nothing-happens.rst
 delete mode 100644 docs/source/faq/code-hangs-when-calling-stop-restart-add-remove-handler.rst
 delete mode 100644 docs/source/faq/how-to-avoid-flood-waits.rst
 delete mode 100644 docs/source/faq/how-to-use-webhooks.rst
 delete mode 100644 docs/source/faq/index.rst
 delete mode 100644 docs/source/faq/migrating-the-account-to-another-data-center.rst
 delete mode 100644 docs/source/faq/peer-id-invalid-error.rst
 delete mode 100644 docs/source/faq/socket-send-oserror-timeouterror-connection-lost-reset.rst
 delete mode 100644 docs/source/faq/sqlite3-interfaceerror-error-binding-parameter.rst
 delete mode 100644 docs/source/faq/sqlite3-operationalerror-database-is-locked.rst
 delete mode 100644 docs/source/faq/the-account-has-been-limited-deactivated.rst
 delete mode 100644 docs/source/faq/unicodeencodeerror-codec-cant-encode.rst
 delete mode 100644 docs/source/faq/uploading-with-urls-gives-error-webpage-curl-failed.rst
 delete mode 100644 docs/source/faq/using-multiple-clients-at-once-on-the-same-account.rst
 delete mode 100644 docs/source/faq/using-the-same-file-id-across-different-accounts.rst
 delete mode 100644 docs/source/faq/what-are-the-ip-addresses-of-telegram-data-centers.rst
 delete mode 100644 docs/source/faq/why-is-the-api-key-needed-for-bots.rst
 delete mode 100644 docs/source/faq/why-is-the-client-reacting-slowly-in-supergroups-channels.rst
 delete mode 100644 docs/source/index.rst
 delete mode 100644 docs/source/intro/install.rst
 delete mode 100644 docs/source/intro/quickstart.rst
 delete mode 100644 docs/source/start/auth.rst
 delete mode 100644 docs/source/start/errors.rst
 delete mode 100644 docs/source/start/examples/bot_keyboards.rst
 delete mode 100644 docs/source/start/examples/callback_queries.rst
 delete mode 100644 docs/source/start/examples/echo_bot.rst
 delete mode 100644 docs/source/start/examples/get_chat_history.rst
 delete mode 100644 docs/source/start/examples/get_chat_members.rst
 delete mode 100644 docs/source/start/examples/get_dialogs.rst
 delete mode 100644 docs/source/start/examples/hello_world.rst
 delete mode 100644 docs/source/start/examples/index.rst
 delete mode 100644 docs/source/start/examples/inline_queries.rst
 delete mode 100644 docs/source/start/examples/raw_updates.rst
 delete mode 100644 docs/source/start/examples/use_inline_bots.rst
 delete mode 100644 docs/source/start/examples/welcome_bot.rst
 delete mode 100644 docs/source/start/invoking.rst
 delete mode 100644 docs/source/start/setup.rst
 delete mode 100644 docs/source/start/updates.rst
 delete mode 100644 docs/source/support.rst
 delete mode 100644 docs/source/topics/advanced-usage.rst
 delete mode 100644 docs/source/topics/client-settings.rst
 delete mode 100644 docs/source/topics/create-filters.rst
 delete mode 100644 docs/source/topics/debugging.rst
 delete mode 100644 docs/source/topics/more-on-updates.rst
 delete mode 100644 docs/source/topics/mtproto-vs-botapi.rst
 delete mode 100644 docs/source/topics/proxy.rst
 delete mode 100644 docs/source/topics/scheduling.rst
 delete mode 100644 docs/source/topics/serializing.rst
 delete mode 100644 docs/source/topics/smart-plugins.rst
 delete mode 100644 docs/source/topics/speedups.rst
 delete mode 100644 docs/source/topics/storage-engines.rst
 delete mode 100644 docs/source/topics/synchronous.rst
 delete mode 100644 docs/source/topics/test-servers.rst
 delete mode 100644 docs/source/topics/text-formatting.rst
 delete mode 100644 docs/source/topics/use-filters.rst
 delete mode 100644 docs/source/topics/voice-calls.rst

diff --git a/.gitignore b/.gitignore
index ff9ee02406..7a99a5db85 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,8 +1,10 @@
 # Development
+docs
 *.session
 config.ini
 main.py
 unknown_errors.txt
+.DS_Store
 
 # Pyrogram generated code
 pyrogram/errors/exceptions/
diff --git a/Makefile b/Makefile
index 0d19832945..81dfe6c901 100644
--- a/Makefile
+++ b/Makefile
@@ -1,53 +1,33 @@
 VENV := venv
 PYTHON := $(VENV)/bin/python
+HOST = $(shell ifconfig | grep "inet " | tail -1 | cut -d\  -f2)
 
 RM := rm -rf
 
-.PHONY: venv build docs
+.PHONY: venv clean-build clean-api clean api build
 
 venv:
 	$(RM) $(VENV)
 	python3 -m venv $(VENV)
 	$(PYTHON) -m pip install -U pip wheel setuptools
-	$(PYTHON) -m pip install -U -r requirements.txt -r dev-requirements.txt -r docs/requirements.txt
+	$(PYTHON) -m pip install -U -r requirements.txt -r dev-requirements.txt
 	@echo "Created venv with $$($(PYTHON) --version)"
 
 clean-build:
 	$(RM) *.egg-info build dist
 
-clean-docs:
-	$(RM) docs/build
-	$(RM) docs/source/api/bound-methods docs/source/api/methods docs/source/api/types docs/source/telegram
-
 clean-api:
 	$(RM) pyrogram/errors/exceptions pyrogram/raw/all.py pyrogram/raw/base pyrogram/raw/functions pyrogram/raw/types
 
 clean:
 	make clean-build
-	make clean-docs
 	make clean-api
 
 api:
 	cd compiler/api && ../../$(PYTHON) compiler.py
 	cd compiler/errors && ../../$(PYTHON) compiler.py
 
-docs-live:
-	make clean-docs
-	cd compiler/docs && ../../$(PYTHON) compiler.py
-	$(RM) docs/source/telegram
-	$(VENV)/bin/sphinx-autobuild \
-		--host $(shell ifconfig | grep "inet " | grep -v 127.0.0.1 | cut -d\  -f2) \
-		--watch pyrogram --watch docs/resources \
-		-b html "docs/source" "docs/build/html" -j auto
-
-docs:
-	make clean-docs
-	cd compiler/docs && ../../$(PYTHON) compiler.py
-	$(VENV)/bin/sphinx-build \
-		-b html "docs/source" "docs/build/html" -j auto
-
 build:
-	make clean-build
-	make clean-api
+	make clean
 	$(PYTHON) setup.py sdist
 	$(PYTHON) setup.py bdist_wheel
\ No newline at end of file
diff --git a/compiler/api/compiler.py b/compiler/api/compiler.py
index c5372bff75..c29b7ed87f 100644
--- a/compiler/api/compiler.py
+++ b/compiler/api/compiler.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 json
 import os
 import re
 import shutil
@@ -59,6 +60,16 @@
 namespaces_to_constructors = {}
 namespaces_to_functions = {}
 
+try:
+    with open("docs.json") as f:
+        docs = json.load(f)
+except FileNotFoundError:
+    docs = {
+        "type": {},
+        "constructor": {},
+        "method": {}
+    }
+
 
 class Combinator(NamedTuple):
     section: str
@@ -149,7 +160,7 @@ def remove_whitespaces(source: str) -> str:
     return "\n".join(lines)
 
 
-def get_docstring_arg_type(t: str, is_list: bool = False, is_pyrogram_type: bool = False):
+def get_docstring_arg_type(t: str):
     if t in CORE_TYPES:
         if t == "long":
             return "``int`` ``64-bit``"
@@ -167,9 +178,9 @@ def get_docstring_arg_type(t: str, is_list: bool = False, is_pyrogram_type: bool
     elif t == "TLObject" or t == "X":
         return "Any object from :obj:`~pyrogram.raw.types`"
     elif t == "!X":
-        return "Any method from :obj:`~pyrogram.raw.functions`"
+        return "Any function from :obj:`~pyrogram.raw.functions`"
     elif t.lower().startswith("vector"):
-        return "List of " + get_docstring_arg_type(t.split("<", 1)[1][:-1], True)
+        return "List of " + get_docstring_arg_type(t.split("<", 1)[1][:-1])
     else:
         return f":obj:`{t} `"
 
@@ -183,10 +194,7 @@ def get_references(t: str, kind: str):
         raise ValueError("Invalid kind")
 
     if t:
-        return "\n            ".join(
-            f"- :obj:`{i} `"
-            for i in t
-        ), len(t)
+        return "\n            ".join(t), len(t)
 
     return None, 0
 
@@ -315,17 +323,33 @@ def start(format: bool = False):
 
         constructors = sorted(types_to_constructors[qualtype])
         constr_count = len(constructors)
-        items = "\n            ".join([f"- :obj:`{c} `" for c in constructors])
+        items = "\n            ".join([f"{c}" for c in constructors])
+
+        type_docs = docs["type"].get(qualtype, None)
+
+        if type_docs:
+            type_docs = type_docs["desc"]
+        else:
+            type_docs = "Telegram API base type."
 
-        docstring = f"This base type has {constr_count} constructor{'s' if constr_count > 1 else ''} available.\n\n"
-        docstring += f"    Constructors:\n        .. hlist::\n            :columns: 2\n\n            {items}"
+        docstring = type_docs
+
+        docstring += f"\n\n    Constructors:\n" \
+                     f"        This base type has {constr_count} constructor{'s' if constr_count > 1 else ''} available.\n\n" \
+                     f"        .. currentmodule:: pyrogram.raw.types\n\n" \
+                     f"        .. autosummary::\n" \
+                     f"            :nosignatures:\n\n" \
+                     f"            {items}"
 
         references, ref_count = get_references(qualtype, "types")
 
         if references:
-            docstring += f"\n\n    See Also:\n        This object can be returned by " \
-                         f"{ref_count} method{'s' if ref_count > 1 else ''}:" \
-                         f"\n\n        .. hlist::\n            :columns: 2\n\n            " + references
+            docstring += f"\n\n    Functions:\n        This object can be returned by " \
+                         f"{ref_count} function{'s' if ref_count > 1 else ''}.\n\n" \
+                         f"        .. currentmodule:: pyrogram.raw.functions\n\n" \
+                         f"        .. autosummary::\n" \
+                         f"            :nosignatures:\n\n" \
+                         f"            " + references
 
         with open(dir_path / f"{snake(module)}.py", "w") as f:
             f.write(
@@ -359,41 +383,67 @@ def start(format: bool = False):
         docstring = ""
         docstring_args = []
 
+        if c.section == "functions":
+            combinator_docs = docs["method"]
+        else:
+            combinator_docs = docs["constructor"]
+
         for i, arg in enumerate(sorted_args):
             arg_name, arg_type = arg
             is_optional = FLAGS_RE.match(arg_type)
             flag_number = is_optional.group(1) if is_optional else -1
             arg_type = arg_type.split("?")[-1]
 
+            arg_docs = combinator_docs.get(c.qualname, None)
+
+            if arg_docs:
+                arg_docs = arg_docs["params"].get(arg_name, "N/A")
+            else:
+                arg_docs = "N/A"
+
             docstring_args.append(
-                "{}{}: {}".format(
+                "{} ({}{}):\n            {}\n".format(
                     arg_name,
-                    " (optional)".format(flag_number) if is_optional else "",
-                    get_docstring_arg_type(arg_type, is_pyrogram_type=c.namespace == "pyrogram")
+                    get_docstring_arg_type(arg_type),
+                    ", *optional*".format(flag_number) if is_optional else "",
+                    arg_docs
                 )
             )
 
         if c.section == "types":
-            docstring += f"This object is a constructor of the base type :obj:`~pyrogram.raw.base.{c.qualtype}`.\n\n"
-        else:
-            docstring += f"Telegram API method.\n\n"
+            constructor_docs = docs["constructor"].get(c.qualname, None)
 
-        docstring += f"    Details:\n        - Layer: ``{layer}``\n        - ID: ``{c.id[2:].upper()}``\n\n"
+            if constructor_docs:
+                constructor_docs = constructor_docs["desc"]
+            else:
+                constructor_docs = "Telegram API type."
 
-        if docstring_args:
-            docstring += "    Parameters:\n        " + "\n        ".join(docstring_args)
+            docstring += constructor_docs + "\n"
+            docstring += f"\n    Constructor of :obj:`~pyrogram.raw.base.{c.qualtype}`."
         else:
-            docstring += "    **No parameters required.**"
+            function_docs = docs["method"].get(c.qualname, None)
+
+            if function_docs:
+                docstring += function_docs["desc"] + "\n"
+            else:
+                docstring += f"Telegram API function."
+
+        docstring += f"\n\n    Details:\n        - Layer: ``{layer}``\n        - ID: ``{c.id[2:].upper()}``\n\n"
+        docstring += f"    Parameters:\n        " + \
+                     (f"\n        ".join(docstring_args) if docstring_args else "No parameters required.\n")
 
         if c.section == "functions":
-            docstring += "\n\n    Returns:\n        " + get_docstring_arg_type(c.qualtype)
+            docstring += "\n    Returns:\n        " + get_docstring_arg_type(c.qualtype)
         else:
             references, count = get_references(c.qualname, "constructors")
 
             if references:
-                docstring += f"\n\n    See Also:\n        This object can be returned by " \
-                             f"{count} method{'s' if count > 1 else ''}:" \
-                             f"\n\n        .. hlist::\n            :columns: 2\n\n            " + references
+                docstring += f"\n    Functions:\n        This object can be returned by " \
+                             f"{count} function{'s' if count > 1 else ''}.\n\n" \
+                             f"        .. currentmodule:: pyrogram.raw.functions\n\n" \
+                             f"        .. autosummary::\n" \
+                             f"            :nosignatures:\n\n" \
+                             f"            " + references
 
         write_types = read_types = "" if c.has_flags else "# No flags\n        "
 
diff --git a/compiler/docs/template/bound-methods.rst b/compiler/docs/template/bound-methods.rst
index 33261d42b2..1e16e32ce9 100644
--- a/compiler/docs/template/bound-methods.rst
+++ b/compiler/docs/template/bound-methods.rst
@@ -19,10 +19,6 @@ some of the required arguments.
 
     app.run()
 
-.. contents:: Contents
-    :backlinks: none
-    :local:
-
 -----
 
 .. currentmodule:: pyrogram.types
diff --git a/compiler/docs/template/methods.rst b/compiler/docs/template/methods.rst
index 1620350de8..f76e249def 100644
--- a/compiler/docs/template/methods.rst
+++ b/compiler/docs/template/methods.rst
@@ -14,10 +14,6 @@ the main package directly.
     with app:
         app.send_message("me", "hi")
 
-.. contents:: Contents
-    :backlinks: none
-    :local:
-
 -----
 
 .. currentmodule:: pyrogram.Client
diff --git a/compiler/docs/template/toctree.txt b/compiler/docs/template/toctree.txt
index 717276c40e..7a36e4a592 100644
--- a/compiler/docs/template/toctree.txt
+++ b/compiler/docs/template/toctree.txt
@@ -4,4 +4,6 @@
 .. module:: {module}
 
 .. toctree::
+    :titlesonly:
+
     {entities}
\ No newline at end of file
diff --git a/compiler/docs/template/types.rst b/compiler/docs/template/types.rst
index 2011a61100..e404ad4cdf 100644
--- a/compiler/docs/template/types.rst
+++ b/compiler/docs/template/types.rst
@@ -17,10 +17,6 @@ are only returned by other methods. You also don't need to import them, unless y
 
     To tell whether a field is set or not, do a simple boolean check: ``if message.photo: ...``.
 
-.. contents:: Contents
-    :backlinks: none
-    :local:
-
 -----
 
 .. currentmodule:: pyrogram.types
diff --git a/docs/requirements.txt b/docs/requirements.txt
deleted file mode 100644
index 7283c8ba69..0000000000
--- a/docs/requirements.txt
+++ /dev/null
@@ -1,4 +0,0 @@
-sphinx
-sphinx_rtd_theme==1.0.0
-sphinx_copybutton
-sphinx-autobuild
\ No newline at end of file
diff --git a/docs/source/api/client.rst b/docs/source/api/client.rst
deleted file mode 100644
index 3db28693dc..0000000000
--- a/docs/source/api/client.rst
+++ /dev/null
@@ -1,24 +0,0 @@
-Pyrogram Client
-===============
-
-You have entered the API Reference section where you can find detailed information about Pyrogram's API. The main Client
-class, all available methods and types, filters, handlers, decorators and bound-methods detailed descriptions can be
-found starting from this page.
-
-This page is about the Client class, which exposes high-level methods for an easy access to the API.
-
-.. code-block:: python
-
-    from pyrogram import Client
-
-    app = Client("my_account")
-
-    with app:
-        app.send_message("me", "Hi!")
-
------
-
-Details
--------
-
-.. autoclass:: pyrogram.Client()
diff --git a/docs/source/api/decorators.rst b/docs/source/api/decorators.rst
deleted file mode 100644
index 2e7a04f263..0000000000
--- a/docs/source/api/decorators.rst
+++ /dev/null
@@ -1,68 +0,0 @@
-Decorators
-==========
-
-Decorators are able to register callback functions for handling updates in a much easier and cleaner way compared to
-:doc:`Handlers `; they do so by instantiating the correct handler and calling
-:meth:`~pyrogram.Client.add_handler` automatically. All you need to do is adding the decorators on top of your
-functions.
-
-.. code-block:: python
-
-    from pyrogram import Client
-
-    app = Client("my_account")
-
-
-    @app.on_message()
-    def log(client, message):
-        print(message)
-
-
-    app.run()
-
-.. contents:: Contents
-    :backlinks: none
-    :depth: 1
-    :local:
-
------
-
-.. currentmodule:: pyrogram
-
-Index
------
-
-.. hlist::
-    :columns: 3
-
-    - :meth:`~Client.on_message`
-    - :meth:`~Client.on_edited_message`
-    - :meth:`~Client.on_callback_query`
-    - :meth:`~Client.on_inline_query`
-    - :meth:`~Client.on_chosen_inline_result`
-    - :meth:`~Client.on_chat_member_updated`
-    - :meth:`~Client.on_chat_join_request`
-    - :meth:`~Client.on_deleted_messages`
-    - :meth:`~Client.on_user_status`
-    - :meth:`~Client.on_poll`
-    - :meth:`~Client.on_disconnect`
-    - :meth:`~Client.on_raw_update`
-
------
-
-Details
--------
-
-.. Decorators
-.. autodecorator:: pyrogram.Client.on_message()
-.. autodecorator:: pyrogram.Client.on_edited_message()
-.. autodecorator:: pyrogram.Client.on_callback_query()
-.. autodecorator:: pyrogram.Client.on_inline_query()
-.. autodecorator:: pyrogram.Client.on_chosen_inline_result()
-.. autodecorator:: pyrogram.Client.on_chat_member_updated()
-.. autodecorator:: pyrogram.Client.on_chat_join_request()
-.. autodecorator:: pyrogram.Client.on_deleted_messages()
-.. autodecorator:: pyrogram.Client.on_user_status()
-.. autodecorator:: pyrogram.Client.on_poll()
-.. autodecorator:: pyrogram.Client.on_disconnect()
-.. autodecorator:: pyrogram.Client.on_raw_update()
diff --git a/docs/source/api/enums/ChatAction.rst b/docs/source/api/enums/ChatAction.rst
deleted file mode 100644
index b66df5fd73..0000000000
--- a/docs/source/api/enums/ChatAction.rst
+++ /dev/null
@@ -1,8 +0,0 @@
-ChatAction
-==========
-
-.. autoclass:: pyrogram.enums.ChatAction()
-    :members:
-
-.. raw:: html
-    :file: ./cleanup.html
\ No newline at end of file
diff --git a/docs/source/api/enums/ChatEventAction.rst b/docs/source/api/enums/ChatEventAction.rst
deleted file mode 100644
index 0403e781db..0000000000
--- a/docs/source/api/enums/ChatEventAction.rst
+++ /dev/null
@@ -1,8 +0,0 @@
-ChatEventAction
-===============
-
-.. autoclass:: pyrogram.enums.ChatEventAction()
-    :members:
-
-.. raw:: html
-    :file: ./cleanup.html
\ No newline at end of file
diff --git a/docs/source/api/enums/ChatMemberStatus.rst b/docs/source/api/enums/ChatMemberStatus.rst
deleted file mode 100644
index bff23eda66..0000000000
--- a/docs/source/api/enums/ChatMemberStatus.rst
+++ /dev/null
@@ -1,8 +0,0 @@
-ChatMemberStatus
-================
-
-.. autoclass:: pyrogram.enums.ChatMemberStatus()
-    :members:
-
-.. raw:: html
-    :file: ./cleanup.html
\ No newline at end of file
diff --git a/docs/source/api/enums/ChatMembersFilter.rst b/docs/source/api/enums/ChatMembersFilter.rst
deleted file mode 100644
index 5a970ffc6e..0000000000
--- a/docs/source/api/enums/ChatMembersFilter.rst
+++ /dev/null
@@ -1,8 +0,0 @@
-ChatMembersFilter
-=================
-
-.. autoclass:: pyrogram.enums.ChatMembersFilter()
-    :members:
-
-.. raw:: html
-    :file: ./cleanup.html
\ No newline at end of file
diff --git a/docs/source/api/enums/ChatType.rst b/docs/source/api/enums/ChatType.rst
deleted file mode 100644
index dd653055fa..0000000000
--- a/docs/source/api/enums/ChatType.rst
+++ /dev/null
@@ -1,8 +0,0 @@
-ChatType
-========
-
-.. autoclass:: pyrogram.enums.ChatType()
-    :members:
-
-.. raw:: html
-    :file: ./cleanup.html
\ No newline at end of file
diff --git a/docs/source/api/enums/MessageEntityType.rst b/docs/source/api/enums/MessageEntityType.rst
deleted file mode 100644
index c7a8965f12..0000000000
--- a/docs/source/api/enums/MessageEntityType.rst
+++ /dev/null
@@ -1,8 +0,0 @@
-MessageEntityType
-=================
-
-.. autoclass:: pyrogram.enums.MessageEntityType()
-    :members:
-
-.. raw:: html
-    :file: ./cleanup.html
\ No newline at end of file
diff --git a/docs/source/api/enums/MessageMediaType.rst b/docs/source/api/enums/MessageMediaType.rst
deleted file mode 100644
index 04e439d20e..0000000000
--- a/docs/source/api/enums/MessageMediaType.rst
+++ /dev/null
@@ -1,8 +0,0 @@
-MessageMediaType
-================
-
-.. autoclass:: pyrogram.enums.MessageMediaType()
-    :members:
-
-.. raw:: html
-    :file: ./cleanup.html
\ No newline at end of file
diff --git a/docs/source/api/enums/MessageServiceType.rst b/docs/source/api/enums/MessageServiceType.rst
deleted file mode 100644
index 2de56818d8..0000000000
--- a/docs/source/api/enums/MessageServiceType.rst
+++ /dev/null
@@ -1,8 +0,0 @@
-MessageServiceType
-==================
-
-.. autoclass:: pyrogram.enums.MessageServiceType()
-    :members:
-
-.. raw:: html
-    :file: ./cleanup.html
\ No newline at end of file
diff --git a/docs/source/api/enums/MessagesFilter.rst b/docs/source/api/enums/MessagesFilter.rst
deleted file mode 100644
index 090907076a..0000000000
--- a/docs/source/api/enums/MessagesFilter.rst
+++ /dev/null
@@ -1,8 +0,0 @@
-MessagesFilter
-==============
-
-.. autoclass:: pyrogram.enums.MessagesFilter()
-    :members:
-
-.. raw:: html
-    :file: ./cleanup.html
\ No newline at end of file
diff --git a/docs/source/api/enums/NextCodeType.rst b/docs/source/api/enums/NextCodeType.rst
deleted file mode 100644
index 46164e47d6..0000000000
--- a/docs/source/api/enums/NextCodeType.rst
+++ /dev/null
@@ -1,8 +0,0 @@
-NextCodeType
-============
-
-.. autoclass:: pyrogram.enums.NextCodeType()
-    :members:
-
-.. raw:: html
-    :file: ./cleanup.html
\ No newline at end of file
diff --git a/docs/source/api/enums/ParseMode.rst b/docs/source/api/enums/ParseMode.rst
deleted file mode 100644
index 1bcc74da04..0000000000
--- a/docs/source/api/enums/ParseMode.rst
+++ /dev/null
@@ -1,8 +0,0 @@
-ParseMode
-=========
-
-.. autoclass:: pyrogram.enums.ParseMode()
-    :members:
-
-.. raw:: html
-    :file: ./cleanup.html
\ No newline at end of file
diff --git a/docs/source/api/enums/PollType.rst b/docs/source/api/enums/PollType.rst
deleted file mode 100644
index d00f9ce8a8..0000000000
--- a/docs/source/api/enums/PollType.rst
+++ /dev/null
@@ -1,8 +0,0 @@
-PollType
-========
-
-.. autoclass:: pyrogram.enums.PollType()
-    :members:
-
-.. raw:: html
-    :file: ./cleanup.html
\ No newline at end of file
diff --git a/docs/source/api/enums/SentCodeType.rst b/docs/source/api/enums/SentCodeType.rst
deleted file mode 100644
index d738b195b1..0000000000
--- a/docs/source/api/enums/SentCodeType.rst
+++ /dev/null
@@ -1,8 +0,0 @@
-SentCodeType
-============
-
-.. autoclass:: pyrogram.enums.SentCodeType()
-    :members:
-
-.. raw:: html
-    :file: ./cleanup.html
\ No newline at end of file
diff --git a/docs/source/api/enums/UserStatus.rst b/docs/source/api/enums/UserStatus.rst
deleted file mode 100644
index c9a77e1bf9..0000000000
--- a/docs/source/api/enums/UserStatus.rst
+++ /dev/null
@@ -1,8 +0,0 @@
-UserStatus
-==========
-
-.. autoclass:: pyrogram.enums.UserStatus()
-    :members:
-
-.. raw:: html
-    :file: ./cleanup.html
\ No newline at end of file
diff --git a/docs/source/api/enums/cleanup.html b/docs/source/api/enums/cleanup.html
deleted file mode 100644
index bb9db7801a..0000000000
--- a/docs/source/api/enums/cleanup.html
+++ /dev/null
@@ -1,9 +0,0 @@
-
\ No newline at end of file
diff --git a/docs/source/api/enums/index.rst b/docs/source/api/enums/index.rst
deleted file mode 100644
index bd9f8b1da7..0000000000
--- a/docs/source/api/enums/index.rst
+++ /dev/null
@@ -1,47 +0,0 @@
-Enumerations
-============
-
-This page is about Pyrogram enumerations.
-Enumerations are types that hold a group of related values to be used whenever a constant value is required.
-They will help you deal with those values in a type-safe way and also enable code completion so that you can be sure
-to apply only a valid value among the expected ones.
-
------
-
-.. currentmodule:: pyrogram.enums
-
-.. autosummary::
-    :nosignatures:
-
-    ChatAction
-    ChatEventAction
-    ChatMemberStatus
-    ChatMembersFilter
-    ChatType
-    MessageEntityType
-    MessageMediaType
-    MessageServiceType
-    MessagesFilter
-    ParseMode
-    PollType
-    SentCodeType
-    NextCodeType
-    UserStatus
-
-.. toctree::
-    :hidden:
-
-    ChatAction
-    ChatEventAction
-    ChatMemberStatus
-    ChatMembersFilter
-    ChatType
-    MessageEntityType
-    MessageMediaType
-    MessageServiceType
-    MessagesFilter
-    ParseMode
-    PollType
-    SentCodeType
-    NextCodeType
-    UserStatus
\ No newline at end of file
diff --git a/docs/source/api/errors/bad-request.rst b/docs/source/api/errors/bad-request.rst
deleted file mode 100644
index ab13fdabb2..0000000000
--- a/docs/source/api/errors/bad-request.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-400 - BadRequest
-----------------
-
-.. csv-table::
-    :file: ../../../../compiler/errors/source/400_BAD_REQUEST.tsv
-    :delim: tab
-    :header-rows: 1
diff --git a/docs/source/api/errors/flood.rst b/docs/source/api/errors/flood.rst
deleted file mode 100644
index e01e012274..0000000000
--- a/docs/source/api/errors/flood.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-420 - Flood
------------
-
-.. csv-table::
-    :file: ../../../../compiler/errors/source/420_FLOOD.tsv
-    :delim: tab
-    :header-rows: 1
\ No newline at end of file
diff --git a/docs/source/api/errors/forbidden.rst b/docs/source/api/errors/forbidden.rst
deleted file mode 100644
index c2a8dcb80b..0000000000
--- a/docs/source/api/errors/forbidden.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-403 - Forbidden
----------------
-
-.. csv-table::
-    :file: ../../../../compiler/errors/source/403_FORBIDDEN.tsv
-    :delim: tab
-    :header-rows: 1
\ No newline at end of file
diff --git a/docs/source/api/errors/index.rst b/docs/source/api/errors/index.rst
deleted file mode 100644
index be2b80d46a..0000000000
--- a/docs/source/api/errors/index.rst
+++ /dev/null
@@ -1,37 +0,0 @@
-RPC Errors
-==========
-
-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.
-
-.. code-block:: python
-
-    from pyrogram.errors import FloodWait
-
-    try:
-        ...
-    except FloodWait as e:
-        ...
-
-.. hlist::
-    :columns: 1
-
-    - :doc:`see-other`
-    - :doc:`bad-request`
-    - :doc:`unauthorized`
-    - :doc:`forbidden`
-    - :doc:`not-acceptable`
-    - :doc:`flood`
-    - :doc:`internal-server-error`
-
-.. toctree::
-    :hidden:
-
-    see-other
-    bad-request
-    unauthorized
-    forbidden
-    not-acceptable
-    flood
-    internal-server-error
\ No newline at end of file
diff --git a/docs/source/api/errors/internal-server-error.rst b/docs/source/api/errors/internal-server-error.rst
deleted file mode 100644
index 996d77eba5..0000000000
--- a/docs/source/api/errors/internal-server-error.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-500 - InternalServerError
--------------------------
-
-.. csv-table::
-    :file: ../../../../compiler/errors/source/500_INTERNAL_SERVER_ERROR.tsv
-    :delim: tab
-    :header-rows: 1
\ No newline at end of file
diff --git a/docs/source/api/errors/not-acceptable.rst b/docs/source/api/errors/not-acceptable.rst
deleted file mode 100644
index 79cfa8bcaf..0000000000
--- a/docs/source/api/errors/not-acceptable.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-406 - NotAcceptable
--------------------
-
-.. csv-table::
-    :file: ../../../../compiler/errors/source/406_NOT_ACCEPTABLE.tsv
-    :delim: tab
-    :header-rows: 1
\ No newline at end of file
diff --git a/docs/source/api/errors/see-other.rst b/docs/source/api/errors/see-other.rst
deleted file mode 100644
index 629b955764..0000000000
--- a/docs/source/api/errors/see-other.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-303 - SeeOther
---------------
-
-.. csv-table::
-    :file: ../../../../compiler/errors/source/303_SEE_OTHER.tsv
-    :delim: tab
-    :header-rows: 1
\ No newline at end of file
diff --git a/docs/source/api/errors/unauthorized.rst b/docs/source/api/errors/unauthorized.rst
deleted file mode 100644
index c56abeecc3..0000000000
--- a/docs/source/api/errors/unauthorized.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-401 - Unauthorized
-------------------
-
-.. csv-table::
-    :file: ../../../../compiler/errors/source/401_UNAUTHORIZED.tsv
-    :delim: tab
-    :header-rows: 1
diff --git a/docs/source/api/filters.rst b/docs/source/api/filters.rst
deleted file mode 100644
index eb3c9522b3..0000000000
--- a/docs/source/api/filters.rst
+++ /dev/null
@@ -1,11 +0,0 @@
-Update Filters
-==============
-
-Filters are objects that can be used to filter the content of incoming updates.
-:doc:`Read more about how filters work <../topics/use-filters>`.
-
-Details
--------
-
-.. automodule:: pyrogram.filters
-    :members:
diff --git a/docs/source/api/handlers.rst b/docs/source/api/handlers.rst
deleted file mode 100644
index 8a8ac71477..0000000000
--- a/docs/source/api/handlers.rst
+++ /dev/null
@@ -1,66 +0,0 @@
-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 :doc:`Decorators ` instead.
-
-.. code-block:: python
-
-    from pyrogram import Client
-    from pyrogram.handlers import MessageHandler
-
-    app = Client("my_account")
-
-
-    def dump(client, message):
-        print(message)
-
-
-    app.add_handler(MessageHandler(dump))
-
-    app.run()
-
-.. contents:: Contents
-    :backlinks: none
-    :depth: 1
-    :local:
-
------
-
-.. currentmodule:: pyrogram.handlers
-
-Index
------
-
-.. hlist::
-    :columns: 3
-
-    - :class:`MessageHandler`
-    - :class:`EditedMessageHandler`
-    - :class:`DeletedMessagesHandler`
-    - :class:`CallbackQueryHandler`
-    - :class:`InlineQueryHandler`
-    - :class:`ChosenInlineResultHandler`
-    - :class:`ChatMemberUpdatedHandler`
-    - :class:`UserStatusHandler`
-    - :class:`PollHandler`
-    - :class:`DisconnectHandler`
-    - :class:`RawUpdateHandler`
-
------
-
-Details
--------
-
-.. Handlers
-.. autoclass:: MessageHandler()
-.. autoclass:: EditedMessageHandler()
-.. autoclass:: DeletedMessagesHandler()
-.. autoclass:: CallbackQueryHandler()
-.. autoclass:: InlineQueryHandler()
-.. autoclass:: ChosenInlineResultHandler()
-.. autoclass:: ChatMemberUpdatedHandler()
-.. autoclass:: UserStatusHandler()
-.. autoclass:: PollHandler()
-.. autoclass:: DisconnectHandler()
-.. autoclass:: RawUpdateHandler()
diff --git a/docs/source/conf.py b/docs/source/conf.py
deleted file mode 100644
index 3071cdc17b..0000000000
--- a/docs/source/conf.py
+++ /dev/null
@@ -1,91 +0,0 @@
-#  Pyrogram - Telegram MTProto API Client Library for Python
-#  Copyright (C) 2017-present Dan 
-#
-#  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 os
-import sys
-
-sys.path.insert(0, os.path.abspath("../.."))
-
-from pyrogram import __version__
-
-from pygments.styles.friendly import FriendlyStyle
-
-FriendlyStyle.background_color = "#f3f2f1"
-
-project = "Pyrogram"
-copyright = f"2017-present, Dan"
-author = "Dan"
-
-version = ".".join(__version__.split(".")[:-1])
-
-extensions = [
-    "sphinx.ext.autodoc",
-    "sphinx.ext.napoleon",
-    "sphinx.ext.autosummary",
-    "sphinx.ext.intersphinx",
-    "sphinx_copybutton"
-]
-
-intersphinx_mapping = {
-    "python": ("https://docs.python.org/3", None)
-}
-
-master_doc = "index"
-source_suffix = ".rst"
-autodoc_member_order = "bysource"
-
-templates_path = ["../resources/templates"]
-html_copy_source = False
-
-napoleon_use_rtype = False
-napoleon_use_param = False
-
-pygments_style = "friendly"
-
-copybutton_prompt_text = "$ "
-
-suppress_warnings = ["image.not_readable"]
-
-html_title = "Pyrogram Documentation"
-html_theme = "sphinx_rtd_theme"
-html_static_path = ["../resources/static"]
-html_show_sourcelink = True
-html_show_copyright = False
-html_theme_options = {
-    "canonical_url": "https://docs.pyrogram.org/",
-    "collapse_navigation": True,
-    "sticky_navigation": False,
-    "logo_only": True,
-    "display_version": False,
-    "style_external_links": True
-}
-
-html_logo = "../resources/static/img/pyrogram.png"
-html_favicon = "../resources/static/img/favicon.ico"
-
-latex_engine = "xelatex"
-latex_logo = "../resources/static/img/pyrogram.png"
-
-latex_elements = {
-    "pointsize": "12pt",
-    "fontpkg": r"""
-        \setmainfont{Open Sans}
-        \setsansfont{Bitter}
-        \setmonofont{Ubuntu Mono}
-        """
-}
diff --git a/docs/source/faq/client-started-but-nothing-happens.rst b/docs/source/faq/client-started-but-nothing-happens.rst
deleted file mode 100644
index ab85f51832..0000000000
--- a/docs/source/faq/client-started-but-nothing-happens.rst
+++ /dev/null
@@ -1,11 +0,0 @@
-Client started, but nothing happens
-===================================
-
-A possible cause might be network issues, either yours or Telegram's. To check this, add the following code at
-the top of your script and run it again. You should see some error mentioning a socket timeout or an unreachable
-network:
-
-.. code-block:: python
-
-    import logging
-    logging.basicConfig(level=logging.INFO)
\ No newline at end of file
diff --git a/docs/source/faq/code-hangs-when-calling-stop-restart-add-remove-handler.rst b/docs/source/faq/code-hangs-when-calling-stop-restart-add-remove-handler.rst
deleted file mode 100644
index 37d47a6444..0000000000
--- a/docs/source/faq/code-hangs-when-calling-stop-restart-add-remove-handler.rst
+++ /dev/null
@@ -1,12 +0,0 @@
-Code hangs when calling stop, restart, add/remove_handler
-=========================================================
-
-You tried to ``.stop()``, ``.restart()``, ``.add_handler()`` or ``.remove_handler()`` inside a running handler, but
-that can't be done because the way Pyrogram deals with handlers would make it hang.
-
-When calling one of the methods above inside an event handler, Pyrogram needs to wait for all running handlers to finish
-in order to continue. Since your handler is blocking the execution by waiting for the called method to finish
-and since Pyrogram needs to wait for your handler to finish, you are left with a deadlock.
-
-The solution to this problem is to pass ``block=False`` to such methods so that they return immediately and the actual
-code called asynchronously.
\ No newline at end of file
diff --git a/docs/source/faq/how-to-avoid-flood-waits.rst b/docs/source/faq/how-to-avoid-flood-waits.rst
deleted file mode 100644
index 06d1cdc2a9..0000000000
--- a/docs/source/faq/how-to-avoid-flood-waits.rst
+++ /dev/null
@@ -1,23 +0,0 @@
-How to avoid Flood Waits?
-=========================
-
-Slow things down and make less requests. Moreover, exact limits are unknown and can change anytime based on normal
-usages.
-
-When a flood wait happens the server will tell you how much time to wait before continuing.
-The following shows how to catch the exception in your code and wait the required seconds.
-
-.. code-block:: python
-
-  import asyncio
-  from pyrogram.errors import FloodWait
-
-  ...
-      try:
-          ...  # Your code
-      except FloodWait as e:
-          await asyncio.sleep(e.value)  # Wait "value" seconds before continuing
-  ...
-
-
-More info about error handling can be found :doc:`here <../start/errors>`.
diff --git a/docs/source/faq/how-to-use-webhooks.rst b/docs/source/faq/how-to-use-webhooks.rst
deleted file mode 100644
index b0dd4008cd..0000000000
--- a/docs/source/faq/how-to-use-webhooks.rst
+++ /dev/null
@@ -1,9 +0,0 @@
-How to use webhooks?
-====================
-
-There is no webhook in Pyrogram, simply because there is no HTTP involved. However, a similar technique is
-being used to make receiving updates efficient.
-
-Pyrogram uses persistent connections via TCP sockets to interact with the server and instead of actively asking for
-updates every time (polling), Pyrogram will sit down and wait for the server to send updates by itself the very moment
-they are available (server push).
diff --git a/docs/source/faq/index.rst b/docs/source/faq/index.rst
deleted file mode 100644
index 3d4a00362b..0000000000
--- a/docs/source/faq/index.rst
+++ /dev/null
@@ -1,45 +0,0 @@
-Frequently Asked Questions
-==========================
-
-This FAQ page provides answers to common questions about Pyrogram and, to some extent, Telegram in general.
-
-**Contents**
-
-- :doc:`why-is-the-api-key-needed-for-bots`
-- :doc:`how-to-use-webhooks`
-- :doc:`using-the-same-file-id-across-different-accounts`
-- :doc:`using-multiple-clients-at-once-on-the-same-account`
-- :doc:`client-started-but-nothing-happens`
-- :doc:`what-are-the-ip-addresses-of-telegram-data-centers`
-- :doc:`migrating-the-account-to-another-data-center`
-- :doc:`why-is-the-client-reacting-slowly-in-supergroups-channels`
-- :doc:`peer-id-invalid-error`
-- :doc:`code-hangs-when-calling-stop-restart-add-remove-handler`
-- :doc:`unicodeencodeerror-codec-cant-encode`
-- :doc:`uploading-with-urls-gives-error-webpage-curl-failed`
-- :doc:`sqlite3-operationalerror-database-is-locked`
-- :doc:`sqlite3-interfaceerror-error-binding-parameter`
-- :doc:`socket-send-oserror-timeouterror-connection-lost-reset`
-- :doc:`how-to-avoid-flood-waits`
-- :doc:`the-account-has-been-limited-deactivated`
-
-.. toctree::
-    :hidden:
-
-    why-is-the-api-key-needed-for-bots
-    how-to-use-webhooks
-    using-the-same-file-id-across-different-accounts
-    using-multiple-clients-at-once-on-the-same-account
-    client-started-but-nothing-happens
-    what-are-the-ip-addresses-of-telegram-data-centers
-    migrating-the-account-to-another-data-center
-    why-is-the-client-reacting-slowly-in-supergroups-channels
-    peer-id-invalid-error
-    code-hangs-when-calling-stop-restart-add-remove-handler
-    unicodeencodeerror-codec-cant-encode
-    uploading-with-urls-gives-error-webpage-curl-failed
-    sqlite3-operationalerror-database-is-locked
-    sqlite3-interfaceerror-error-binding-parameter
-    socket-send-oserror-timeouterror-connection-lost-reset
-    how-to-avoid-flood-waits
-    the-account-has-been-limited-deactivated
\ No newline at end of file
diff --git a/docs/source/faq/migrating-the-account-to-another-data-center.rst b/docs/source/faq/migrating-the-account-to-another-data-center.rst
deleted file mode 100644
index 7ae76a1e36..0000000000
--- a/docs/source/faq/migrating-the-account-to-another-data-center.rst
+++ /dev/null
@@ -1,10 +0,0 @@
-Migrating the account to another data center
-============================================
-
-This question is asked by people who find their account always being connected to one DC (data center), but are
-connecting from a place far away, thus resulting in slower interactions when using the API because of the greater
-physical distance between the user and the associated DC.
-
-When registering an account for the first time, is up to Telegram to decide which DC the new user is going to be
-created in. It's also up to the server to decide whether to automatically migrate a user in case of prolonged usages
-from a distant location.
\ No newline at end of file
diff --git a/docs/source/faq/peer-id-invalid-error.rst b/docs/source/faq/peer-id-invalid-error.rst
deleted file mode 100644
index 197ea8374d..0000000000
--- a/docs/source/faq/peer-id-invalid-error.rst
+++ /dev/null
@@ -1,14 +0,0 @@
-PEER_ID_INVALID error
-=====================
-
-This error could mean several things:
-
-- The chat id you tried to use is simply wrong, check it again.
-- The chat id refers to a group or channel you are not a member of.
-- The chat id argument you passed is in form of a string; you have to convert it into an integer with ``int(chat_id)``.
-- The chat id refers to a user or chat your current session hasn't met yet.
-
-About the last point: in order for you to meet a user and thus communicate with them, you should ask yourself how to
-contact people using official apps. The answer is the same for Pyrogram too and involves normal usages such as searching
-for usernames, meeting them in a common group, having their phone contacts saved, getting a message mentioning them
-or obtaining the dialogs list.
\ No newline at end of file
diff --git a/docs/source/faq/socket-send-oserror-timeouterror-connection-lost-reset.rst b/docs/source/faq/socket-send-oserror-timeouterror-connection-lost-reset.rst
deleted file mode 100644
index 85c5065015..0000000000
--- a/docs/source/faq/socket-send-oserror-timeouterror-connection-lost-reset.rst
+++ /dev/null
@@ -1,12 +0,0 @@
-socket.send(), OSError(), TimeoutError(), Connection lost/reset
-===============================================================
-
-If you get any of these errors chances are you ended up with a slow or inconsistent network connection.
-Another reason could be because you are blocking the event loop for too long.
-
-You can consider the following:
-
-- Use Pyrogram asynchronously in its intended way.
-- Use shorter non-asynchronous processing loops.
-- Use ``asyncio.sleep()`` instead of ``time.sleep()``.
-- Use a stable network connection.
diff --git a/docs/source/faq/sqlite3-interfaceerror-error-binding-parameter.rst b/docs/source/faq/sqlite3-interfaceerror-error-binding-parameter.rst
deleted file mode 100644
index 5d148186b6..0000000000
--- a/docs/source/faq/sqlite3-interfaceerror-error-binding-parameter.rst
+++ /dev/null
@@ -1,13 +0,0 @@
-sqlite3.InterfaceError: Error binding parameter
-===============================================
-
-This error occurs when you pass a chat id value of the wrong type when trying to call a method. Most likely, you
-accidentally passed the whole user or chat object instead of the id or username.
-
-.. code-block:: python
-
-    # Wrong. You passed the whole Chat instance
-    app.send_message(chat, "text")
-
-    # Correct
-    app.send_message(chat.id, "text")
\ No newline at end of file
diff --git a/docs/source/faq/sqlite3-operationalerror-database-is-locked.rst b/docs/source/faq/sqlite3-operationalerror-database-is-locked.rst
deleted file mode 100644
index b5cb2d8293..0000000000
--- a/docs/source/faq/sqlite3-operationalerror-database-is-locked.rst
+++ /dev/null
@@ -1,17 +0,0 @@
-sqlite3.OperationalError: database is locked
-============================================
-
-This error occurs when more than one process is using the same session file, that is, when you run two or more clients
-at the same time using the same session name or in case another program has accessed the file.
-
-For example, it could occur when a background script is still running and you forgot about it. In this case, you either
-restart your system or find and kill the process that is locking the database. On Unix based systems, you can try the
-following:
-
-#. ``cd`` into your session file directory.
-#. ``fuser my_account.session`` to find the process id.
-#. ``kill 1234`` to gracefully stop the process.
-#. If the last command doesn't help, use ``kill -9 1234`` instead.
-
-If you want to run multiple clients on the same account, you must authorize your account (either user or bot)
-from the beginning every time, and use different session names for each parallel client you are going to use.
\ No newline at end of file
diff --git a/docs/source/faq/the-account-has-been-limited-deactivated.rst b/docs/source/faq/the-account-has-been-limited-deactivated.rst
deleted file mode 100644
index 79d589eaf8..0000000000
--- a/docs/source/faq/the-account-has-been-limited-deactivated.rst
+++ /dev/null
@@ -1,16 +0,0 @@
-The account has been limited/deactivated
-========================================
-
-Pyrogram is a framework that interfaces with Telegram; it is at your commands, meaning it only does what you tell it to
-do, the rest is up to you and Telegram (see `Telegram's ToS`_).
-
-If you found your account being limited/deactivated, it could be due spam/flood/abuse of the API or the usage of certain
-virtual/VoIP numbers.
-
-If you think your account was limited/deactivated by mistake, you can write to recover@telegram.org, contact
-`@SpamBot`_ or use `this form`_.
-
-.. _@SpamBot: https://t.me/spambot
-.. _this form: https://telegram.org/support
-.. _Telegram's ToS: https://telegram.org/tos
-
diff --git a/docs/source/faq/unicodeencodeerror-codec-cant-encode.rst b/docs/source/faq/unicodeencodeerror-codec-cant-encode.rst
deleted file mode 100644
index a4511ce5d1..0000000000
--- a/docs/source/faq/unicodeencodeerror-codec-cant-encode.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-UnicodeEncodeError: '...' codec can't encode ...
-================================================
-
-Where ```` might be *ascii*, *cp932*, *charmap* or anything else other than *utf-8*. This error usually
-shows up when you try to print something and has very little to do with Pyrogram itself as it is strictly related to
-your own terminal. To fix it, either find a way to change the encoding settings of your terminal to UTF-8 or switch to
-another terminal altogether.
diff --git a/docs/source/faq/uploading-with-urls-gives-error-webpage-curl-failed.rst b/docs/source/faq/uploading-with-urls-gives-error-webpage-curl-failed.rst
deleted file mode 100644
index 2b7c5a7e9f..0000000000
--- a/docs/source/faq/uploading-with-urls-gives-error-webpage-curl-failed.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-Uploading with URLs gives error WEBPAGE_CURL_FAILED
-===================================================
-
-When uploading media files using an URL, the server automatically tries to download the media and uploads it to the
-Telegram cloud. This error usually happens in case the provided URL is not publicly accessible by Telegram itself or the
-media file is too large. In such cases, your only option is to download the media yourself and upload it from your
-local machine.
\ No newline at end of file
diff --git a/docs/source/faq/using-multiple-clients-at-once-on-the-same-account.rst b/docs/source/faq/using-multiple-clients-at-once-on-the-same-account.rst
deleted file mode 100644
index ab73b29c1d..0000000000
--- a/docs/source/faq/using-multiple-clients-at-once-on-the-same-account.rst
+++ /dev/null
@@ -1,7 +0,0 @@
-Using multiple clients at once on the same account
-==================================================
-
-Both user and bot accounts are able to run multiple sessions in parallel. However, you must not use the same session
-in more than one client at the same time. The correct way to run multiple clients on the same account is by authorizing
-your account (either user or bot) from the beginning each time, and use one separate session for each parallel client.
-
diff --git a/docs/source/faq/using-the-same-file-id-across-different-accounts.rst b/docs/source/faq/using-the-same-file-id-across-different-accounts.rst
deleted file mode 100644
index 00305ef12d..0000000000
--- a/docs/source/faq/using-the-same-file-id-across-different-accounts.rst
+++ /dev/null
@@ -1,6 +0,0 @@
-Using the same file_id across different accounts
-================================================
-
-Telegram file_id strings are bound to the account which generated them. An attempt in using a foreign file id will
-result in errors such as ``[400 MEDIA_EMPTY]``. The only exception are stickers' file ids; you can use them across
-different accounts without any problem.
\ No newline at end of file
diff --git a/docs/source/faq/what-are-the-ip-addresses-of-telegram-data-centers.rst b/docs/source/faq/what-are-the-ip-addresses-of-telegram-data-centers.rst
deleted file mode 100644
index c951230ba6..0000000000
--- a/docs/source/faq/what-are-the-ip-addresses-of-telegram-data-centers.rst
+++ /dev/null
@@ -1,30 +0,0 @@
-What are the IP addresses of Telegram Data Centers?
-===================================================
-
-Telegram is currently composed by a decentralized, multi-DC infrastructure (currently 5 DCs, each of which can
-work independently) spread across different locations worldwide. However, some of the less busy DCs have been lately
-dismissed and their IP addresses are now kept as aliases to the nearest one.
-
-.. csv-table:: Production Environment
-    :header: ID, Location, IPv4, IPv6
-    :widths: auto
-    :align: center
-
-    DC1, "MIA, Miami FL, USA", ``149.154.175.53``, ``2001:b28:f23d:f001::a``
-    DC2, "AMS, Amsterdam, NL", ``149.154.167.51``, ``2001:67c:4e8:f002::a``
-    DC3*, "MIA, Miami FL, USA", ``149.154.175.100``, ``2001:b28:f23d:f003::a``
-    DC4, "AMS, Amsterdam, NL", ``149.154.167.91``, ``2001:67c:4e8:f004::a``
-    DC5, "SIN, Singapore, SG", ``91.108.56.130``, ``2001:b28:f23f:f005::a``
-
-.. csv-table:: Test Environment
-    :header: ID, Location, IPv4, IPv6
-    :widths: auto
-    :align: center
-
-    DC1, "MIA, Miami FL, USA", ``149.154.175.10``, ``2001:b28:f23d:f001::e``
-    DC2, "AMS, Amsterdam, NL", ``149.154.167.40``, ``2001:67c:4e8:f002::e``
-    DC3*, "MIA, Miami FL, USA", ``149.154.175.117``, ``2001:b28:f23d:f003::e``
-
-.. centered:: More info about the Test Environment can be found :doc:`here <../topics/test-servers>`.
-
-***** Alias DC
\ No newline at end of file
diff --git a/docs/source/faq/why-is-the-api-key-needed-for-bots.rst b/docs/source/faq/why-is-the-api-key-needed-for-bots.rst
deleted file mode 100644
index 2e062d405d..0000000000
--- a/docs/source/faq/why-is-the-api-key-needed-for-bots.rst
+++ /dev/null
@@ -1,12 +0,0 @@
-Why is the API key needed for bots?
-===================================
-
-Requests against the official bot API endpoints are made via JSON/HTTP and are handled by an intermediate server
-application that implements the MTProto protocol and uses its own API key to communicate with the MTProto servers.
-
-.. figure:: //_static/img/mtproto-vs-bot-api.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.
\ No newline at end of file
diff --git a/docs/source/faq/why-is-the-client-reacting-slowly-in-supergroups-channels.rst b/docs/source/faq/why-is-the-client-reacting-slowly-in-supergroups-channels.rst
deleted file mode 100644
index 4d19616469..0000000000
--- a/docs/source/faq/why-is-the-client-reacting-slowly-in-supergroups-channels.rst
+++ /dev/null
@@ -1,18 +0,0 @@
-Why is the client reacting slowly in supergroups/channels?
-==========================================================
-
-Because of how Telegram works internally, every message you receive and send must pass through the creator's DC, and in
-the worst case where you, the creator and another member all belong to three different DCs, the other member messages
-have to go through from their DC to the creator's DC and finally to your DC. This is applied to each message and member
-of a supergroup/channel and the process will inevitably take its time.
-
-Another reason that makes responses come slowly is that messages are dispatched by priority. Depending on the kind
-of member, some users receive messages faster than others and for big and busy supergroups the delay might become
-noticeable, especially if you are among the lower end of the priority list:
-
-1. Creator.
-2. Administrators.
-3. Bots.
-4. Mentioned users.
-5. Recent online users.
-6. Everyone else.
\ No newline at end of file
diff --git a/docs/source/index.rst b/docs/source/index.rst
deleted file mode 100644
index d96223cb13..0000000000
--- a/docs/source/index.rst
+++ /dev/null
@@ -1,172 +0,0 @@
-Welcome to Pyrogram
-===================
-
-.. raw:: html
-
-    
-
-    

- Telegram MTProto API Framework for Python - -
- - Homepage - - • - - Development - - • - - Releases - - • - - News - -

- -.. code-block:: python - - from pyrogram import Client, filters - - app = Client("my_account") - - - @app.on_message(filters.private) - async def hello(client, message): - await message.reply("Hello from Pyrogram!") - - - app.run() - -**Pyrogram** is a modern, elegant and asynchronous :doc:`MTProto API ` framework. -It enables you to easily interact with the main Telegram API through a user account (custom client) or a bot identity -(bot API alternative) using Python. - -Support -------- - -If you'd like to support Pyrogram, you can consider: - -- `Become a GitHub sponsor `_. -- `Become a LiberaPay patron `_. -- `Become an OpenCollective backer `_. - -How the Documentation is Organized ----------------------------------- - -Contents are organized into sections composed of self-contained topics which can be all accessed from the sidebar, or by -following them in order using the :guilabel:`Next` button at the end of each page. -You can also switch to Dark or Light theme or leave on Auto (follows system preferences) by using the dedicated button -in the top left corner. - -Here below you can, instead, find a list of the most relevant pages for a quick access. - -First Steps -^^^^^^^^^^^ - -.. hlist:: - :columns: 1 - - - :doc:`Quick Start `: Overview to get you started quickly. - - :doc:`Invoking Methods `: How to call Pyrogram's methods. - - :doc:`Handling Updates `: How to handle Telegram updates. - - :doc:`Error Handling `: How to handle API errors correctly. - -API Reference -^^^^^^^^^^^^^ - -.. hlist:: - :columns: 1 - - - :doc:`Pyrogram Client `: Reference details about the Client class. - - :doc:`Available Methods `: List of available high-level methods. - - :doc:`Available Types `: List of available high-level types. - - :doc:`Enumerations `: List of available enumerations. - - :doc:`Bound Methods `: List of convenient bound methods. - -Meta -^^^^ - -.. hlist:: - :columns: 1 - - - :doc:`Pyrogram FAQ `: Answers to common Pyrogram questions. - - :doc:`Support Pyrogram `: Ways to show your appreciation. - - :doc:`Release Notes `: Release notes for Pyrogram releases. - -.. toctree:: - :hidden: - :caption: Introduction - - intro/quickstart - intro/install - -.. toctree:: - :hidden: - :caption: Getting Started - - start/setup - start/auth - start/invoking - start/updates - start/errors - start/examples/index - -.. toctree:: - :hidden: - :caption: API Reference - - api/client - api/methods/index - api/types/index - api/bound-methods/index - api/enums/index - api/handlers - api/decorators - api/errors/index - api/filters - -.. toctree:: - :hidden: - :caption: Topic Guides - - topics/use-filters - topics/create-filters - topics/more-on-updates - topics/client-settings - topics/speedups - topics/text-formatting - topics/synchronous - topics/smart-plugins - topics/storage-engines - topics/serializing - topics/proxy - topics/scheduling - topics/mtproto-vs-botapi - topics/debugging - topics/test-servers - topics/advanced-usage - topics/voice-calls - -.. toctree:: - :hidden: - :caption: Meta - - faq/index - support - releases/index - -.. toctree:: - :hidden: - :caption: Telegram Raw API - - telegram/functions/index - telegram/types/index - telegram/base/index \ No newline at end of file diff --git a/docs/source/intro/install.rst b/docs/source/intro/install.rst deleted file mode 100644 index c45c384489..0000000000 --- a/docs/source/intro/install.rst +++ /dev/null @@ -1,50 +0,0 @@ -Install Guide -============= - -Being a modern Python framework, Pyrogram requires an up to date version of Python to be installed in your system. -We recommend using the latest versions of both Python 3 and pip. - -.. contents:: Contents - :backlinks: none - :depth: 1 - :local: - ------ - -Install Pyrogram ----------------- - -- The easiest way to install and upgrade Pyrogram to its latest stable version is by using **pip**: - - .. code-block:: text - - $ pip3 install -U pyrogram - -- or, with :doc:`TgCrypto <../topics/speedups>` as extra requirement (recommended): - - .. code-block:: text - - $ pip3 install -U pyrogram tgcrypto - -Bleeding Edge -------------- - -You can install the development version from the git ``master`` branch using this command: - -.. code-block:: text - - $ pip3 install -U https://github.com/pyrogram/pyrogram/archive/master.zip - -Verifying ---------- - -To verify that Pyrogram is correctly installed, open a Python shell and import it. -If no error shows up you are good to go. - -.. parsed-literal:: - - >>> import pyrogram - >>> pyrogram.__version__ - 'x.y.z' - -.. _`Github repo`: http://github.com/pyrogram/pyrogram diff --git a/docs/source/intro/quickstart.rst b/docs/source/intro/quickstart.rst deleted file mode 100644 index 29c355e7c7..0000000000 --- a/docs/source/intro/quickstart.rst +++ /dev/null @@ -1,56 +0,0 @@ -Quick Start -=========== - -The next few steps serve as a quick start to see Pyrogram in action as fast as possible. - -Get Pyrogram Real Fast ----------------------- - -.. admonition :: Cloud Credits - :class: tip - - If you need a cloud server to host your applications, try Hetzner Cloud. You can sign up with - `this link `_ to get €20 in cloud credits. - -1. Install Pyrogram with ``pip3 install -U pyrogram``. - -2. Get your own Telegram API key from https://my.telegram.org/apps. - -3. Open the text editor of your choice and paste the following: - - .. code-block:: python - - import asyncio - from pyrogram import Client - - api_id = 12345 - api_hash = "0123456789abcdef0123456789abcdef" - - - async def main(): - async with Client("my_account", api_id, api_hash) as app: - await app.send_message("me", "Greetings from **Pyrogram**!") - - - asyncio.run(main()) - -4. Replace *api_id* and *api_hash* values with your own. - -5. Save the file as ``hello.py``. - -6. Run the script with ``python3 hello.py`` - -7. Follow the instructions on your terminal to login. - -8. Watch Pyrogram send a message to yourself. - -Enjoy the API -------------- - -That was just a quick overview. In the next few pages of the introduction, we'll take a much more in-depth look of what -we have just done above. - -If you are feeling eager to continue you can take a shortcut to :doc:`../start/invoking` and come back -later to learn some more details. - -.. _community: https://t.me/Pyrogram diff --git a/docs/source/start/auth.rst b/docs/source/start/auth.rst deleted file mode 100644 index ba28ac69b7..0000000000 --- a/docs/source/start/auth.rst +++ /dev/null @@ -1,93 +0,0 @@ -Authorization -============= - -Once a :doc:`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 bot. - -.. contents:: Contents - :backlinks: none - :depth: 1 - :local: - ------ - -User Authorization ------------------- - -In order to use the API, Telegram requires that users be authorized via their phone numbers. -Pyrogram automatically manages this process, all you need to do is create an instance of the -:class:`~pyrogram.Client` class by passing to it a ``name`` of your choice (e.g.: "my_account") and call -the :meth:`~pyrogram.Client.run` method: - -.. code-block:: python - - from pyrogram import Client - - api_id = 12345 - api_hash = "0123456789abcdef0123456789abcdef" - - app = Client("my_account", api_id=api_id, api_hash=api_hash) - - app.run() - -This starts an interactive shell asking you to input your **phone number**, including your `Country Code`_ (the plus -``+`` and minus ``-`` symbols can be omitted) and the **phone code** you will receive in your devices that are already -authorized or via SMS: - -.. code-block:: text - - Enter phone number: +1-123-456-7890 - Is "+1-123-456-7890" correct? (y/n): y - Enter phone code: 12345 - Logged in successfully - -After successfully authorizing yourself, a new file called ``my_account.session`` will be created allowing Pyrogram to -execute API calls with your identity. This file is personal and will be loaded again when you restart your app. -You can now remove the api_id and api_hash values from the code as they are not needed anymore. - -.. note:: - - The code above does nothing except asking for credentials and keeping the client online, hit :guilabel:`CTRL+C` now - to stop your application and keep reading. - -Bot Authorization ------------------ - -Bots are a special kind of users that are authorized via their tokens (instead of phone numbers), which are created by -the `Bot Father`_. Bot tokens replace the users' phone numbers only — you still need to -:doc:`configure a Telegram API key <../start/setup>` with Pyrogram, even when using bots. - -The authorization process is automatically managed. All you need to do is choose a ``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 ``my_bot.session`` for the example below. - -.. code-block:: python - - from pyrogram import Client - - api_id = 12345 - api_hash = "0123456789abcdef0123456789abcdef" - bot_token = "123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11" - - app = Client( - "my_bot", - api_id=api_id, api_hash=api_hash, - bot_token=bot_token - ) - - app.run() - -.. _Country Code: https://en.wikipedia.org/wiki/List_of_country_calling_codes -.. _Bot Father: https://t.me/botfather - -.. note:: - - The API key (api_id and api_hash) and the bot_token are not required anymore after a successful authorization. - This means you can now simply use the following: - - .. code-block:: python - - from pyrogram import Client - - app = Client("my_account") - app.run() \ No newline at end of file diff --git a/docs/source/start/errors.rst b/docs/source/start/errors.rst deleted file mode 100644 index 402fea8b7c..0000000000 --- a/docs/source/start/errors.rst +++ /dev/null @@ -1,101 +0,0 @@ -Error Handling -============== - -Errors can be correctly handled with ``try...except`` blocks in order to control the behaviour of your application. -Pyrogram errors all live inside the ``errors`` package: - -.. code-block:: python - - from pyrogram import errors - -.. contents:: Contents - :backlinks: none - :depth: 1 - :local: - ------ - -RPCError --------- - -The father of all errors is named ``RPCError`` 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 - - from pyrogram.errors import RPCError - -.. warning:: - - Avoid catching this error everywhere, 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. - -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 and are subclass-ed from the ``RPCError``: - -.. code-block:: python - - from pyrogram.errors import BadRequest, Forbidden, ... - -- :doc:`303 - SeeOther <../api/errors/see-other>` -- :doc:`400 - BadRequest <../api/errors/bad-request>` -- :doc:`401 - Unauthorized <../api/errors/unauthorized>` -- :doc:`403 - Forbidden <../api/errors/forbidden>` -- :doc:`406 - NotAcceptable <../api/errors/not-acceptable>` -- :doc:`420 - Flood <../api/errors/flood>` -- :doc:`500 - InternalServerError <../api/errors/internal-server-error>` - -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 --------------- - -In case Pyrogram does not know anything about a specific error yet, it raises a generic error from its known category, -for example, an unknown error with error code ``400``, will be raised as a ``BadRequest``. This way you can catch the -whole category of errors and be sure to also handle these unknown errors. - -Errors with Values ------------------- - -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, some other errors contain the DC number on which the request must be repeated on. -The value is stored in the ``value`` attribute of the exception object: - -.. code-block:: python - - import asyncio - from pyrogram.errors import FloodWait - - ... - try: - ... # Your code - except FloodWait as e: - await asyncio.sleep(e.value) # Wait N seconds before continuing - ... \ No newline at end of file diff --git a/docs/source/start/examples/bot_keyboards.rst b/docs/source/start/examples/bot_keyboards.rst deleted file mode 100644 index b774a80e7e..0000000000 --- a/docs/source/start/examples/bot_keyboards.rst +++ /dev/null @@ -1,68 +0,0 @@ -bot_keyboards -============= - -This example will show you how to send normal and inline keyboards (as bot). - -You must log-in as a regular bot in order to send keyboards (use the token from @BotFather). -Any attempt in sending keyboards with a user account will be simply ignored by the server. - -send_message() is used as example, but a keyboard can be sent with any other send_* methods, -like send_audio(), send_document(), send_location(), etc... - -.. code-block:: python - - from pyrogram import Client - from pyrogram.types import (ReplyKeyboardMarkup, InlineKeyboardMarkup, - InlineKeyboardButton) - - # Create a client using your bot token - app = Client("my_bot", bot_token="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11") - - - async def main(): - async with app: - await app.send_message( - "me", # Edit this - "This is a ReplyKeyboardMarkup example", - reply_markup=ReplyKeyboardMarkup( - [ - ["A", "B", "C", "D"], # First row - ["E", "F", "G"], # Second row - ["H", "I"], # Third row - ["J"] # Fourth row - ], - resize_keyboard=True # Make the keyboard smaller - ) - ) - - await app.send_message( - "me", # Edit this - "This is a InlineKeyboardMarkup example", - reply_markup=InlineKeyboardMarkup( - [ - [ # First row - InlineKeyboardButton( # Generates a callback query when pressed - "Button", - callback_data="data" - ), - InlineKeyboardButton( # Opens a web URL - "URL", - url="https://docs.pyrogram.org" - ), - ], - [ # Second row - InlineKeyboardButton( # Opens the inline interface - "Choose chat", - switch_inline_query="pyrogram" - ), - InlineKeyboardButton( # Opens the inline interface in the current chat - "Inline here", - switch_inline_query_current_chat="pyrogram" - ) - ] - ] - ) - ) - - - app.run(main()) \ No newline at end of file diff --git a/docs/source/start/examples/callback_queries.rst b/docs/source/start/examples/callback_queries.rst deleted file mode 100644 index 64da57b39a..0000000000 --- a/docs/source/start/examples/callback_queries.rst +++ /dev/null @@ -1,21 +0,0 @@ -callback_queries -================ - -This example shows how to handle callback queries, i.e.: queries coming from inline button presses. -It uses the @on_callback_query decorator to register a CallbackQueryHandler. - -.. code-block:: python - - from pyrogram import Client - - app = Client("my_bot", bot_token="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11") - - - @app.on_callback_query() - async def answer(client, callback_query): - await callback_query.answer( - f"Button contains: '{callback_query.data}'", - show_alert=True) - - - app.run() # Automatically start() and idle() \ No newline at end of file diff --git a/docs/source/start/examples/echo_bot.rst b/docs/source/start/examples/echo_bot.rst deleted file mode 100644 index de8288b5e4..0000000000 --- a/docs/source/start/examples/echo_bot.rst +++ /dev/null @@ -1,21 +0,0 @@ -echo_bot -======== - -This simple echo bot replies to every private text message. - -It uses the ``@on_message`` decorator to register a ``MessageHandler`` and applies two filters on it: -``filters.text`` and ``filters.private`` to make sure it will reply to private text messages only. - -.. code-block:: python - - from pyrogram import Client, filters - - app = Client("my_account") - - - @app.on_message(filters.text & filters.private) - async def echo(client, message): - await message.reply(message.text) - - - app.run() # Automatically start() and idle() \ No newline at end of file diff --git a/docs/source/start/examples/get_chat_history.rst b/docs/source/start/examples/get_chat_history.rst deleted file mode 100644 index 59939948b3..0000000000 --- a/docs/source/start/examples/get_chat_history.rst +++ /dev/null @@ -1,20 +0,0 @@ -get_history -=========== - -This example shows how to get the full message history of a chat, starting from the latest message. - -.. code-block:: python - - from pyrogram import Client - - app = Client("my_account") - - - async def main(): - async with app: - # "me" refers to your own chat (Saved Messages) - async for message in app.get_chat_history("me"): - print(message) - - - app.run(main()) \ No newline at end of file diff --git a/docs/source/start/examples/get_chat_members.rst b/docs/source/start/examples/get_chat_members.rst deleted file mode 100644 index 26636ca344..0000000000 --- a/docs/source/start/examples/get_chat_members.rst +++ /dev/null @@ -1,22 +0,0 @@ -get_chat_members -================ - -This example shows how to get all the members of a chat. - -.. code-block:: python - - from pyrogram import Client - - # Target channel/supergroup - TARGET = -100123456789 - - app = Client("my_account") - - - async def main(): - async with app: - async for member in app.get_chat_members(TARGET): - print(member) - - - app.run(main()) \ No newline at end of file diff --git a/docs/source/start/examples/get_dialogs.rst b/docs/source/start/examples/get_dialogs.rst deleted file mode 100644 index e5b1060966..0000000000 --- a/docs/source/start/examples/get_dialogs.rst +++ /dev/null @@ -1,19 +0,0 @@ -get_dialogs -=========== - -This example shows how to get the full dialogs list (as user). - -.. code-block:: python - - from pyrogram import Client - - app = Client("my_account") - - - async def main(): - async with app: - async for dialog in app.get_dialogs(): - print(dialog.chat.title or dialog.chat.first_name) - - - app.run(main()) \ No newline at end of file diff --git a/docs/source/start/examples/hello_world.rst b/docs/source/start/examples/hello_world.rst deleted file mode 100644 index 2902241e8c..0000000000 --- a/docs/source/start/examples/hello_world.rst +++ /dev/null @@ -1,20 +0,0 @@ -hello_world -=========== - -This example demonstrates a basic API usage - -.. code-block:: python - - from pyrogram import Client - - # Create a new Client instance - app = Client("my_account") - - - async def main(): - async with app: - # Send a message, Markdown is enabled by default - await app.send_message("me", "Hi there! I'm using **Pyrogram**") - - - app.run(main()) diff --git a/docs/source/start/examples/index.rst b/docs/source/start/examples/index.rst deleted file mode 100644 index d47b60e80d..0000000000 --- a/docs/source/start/examples/index.rst +++ /dev/null @@ -1,46 +0,0 @@ -Examples -======== - -This page contains example scripts to show you how Pyrogram looks like. - -Every script is working right away (provided you correctly set up your credentials), meaning you can simply copy-paste -and run. The only things you have to change are session names and target chats, where applicable. - -The examples listed below can be treated as building blocks for your own applications and are meant to be simple enough -to give you a basic idea. - ------ - -.. csv-table:: - :header: Example, Description - :widths: auto - :align: center - - :doc:`hello_world`, "Demonstration of basic API usage" - :doc:`echo_bot`, "Echo every private text message" - :doc:`welcome_bot`, "The Welcome Bot in @PyrogramChat" - :doc:`get_chat_history`, "Get the full message history of a chat" - :doc:`get_chat_members`, "Get all the members of a chat" - :doc:`get_dialogs`, "Get all of your dialog chats" - :doc:`callback_queries`, "Handle callback queries (as bot) coming from inline button presses" - :doc:`inline_queries`, "Handle inline queries (as bot) and answer with results" - :doc:`use_inline_bots`, "Query an inline bot (as user) and send a result to a chat" - :doc:`bot_keyboards`, "Send normal and inline keyboards using regular bots" - :doc:`raw_updates`, "Handle raw updates (old, should be avoided)" - -For more advanced examples, see https://snippets.pyrogram.org. - -.. toctree:: - :hidden: - - hello_world - echo_bot - welcome_bot - get_chat_history - get_chat_members - get_dialogs - callback_queries - inline_queries - use_inline_bots - bot_keyboards - raw_updates diff --git a/docs/source/start/examples/inline_queries.rst b/docs/source/start/examples/inline_queries.rst deleted file mode 100644 index b78c6e1cb8..0000000000 --- a/docs/source/start/examples/inline_queries.rst +++ /dev/null @@ -1,59 +0,0 @@ -inline_queries -============== - -This example shows how to handle inline queries. - -Two results are generated when users invoke the bot inline mode, e.g.: @pyrogrambot hi. -It uses the @on_inline_query decorator to register an InlineQueryHandler. - -.. code-block:: python - - from pyrogram import Client - from pyrogram.types import (InlineQueryResultArticle, InputTextMessageContent, - InlineKeyboardMarkup, InlineKeyboardButton) - - app = Client("my_bot", bot_token="123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11") - - - @app.on_inline_query() - async def answer(client, inline_query): - await inline_query.answer( - results=[ - InlineQueryResultArticle( - title="Installation", - input_message_content=InputTextMessageContent( - "Here's how to install **Pyrogram**" - ), - url="https://docs.pyrogram.org/intro/install", - description="How to install Pyrogram", - reply_markup=InlineKeyboardMarkup( - [ - [InlineKeyboardButton( - "Open website", - url="https://docs.pyrogram.org/intro/install" - )] - ] - ) - ), - InlineQueryResultArticle( - title="Usage", - input_message_content=InputTextMessageContent( - "Here's how to use **Pyrogram**" - ), - url="https://docs.pyrogram.org/start/invoking", - description="How to use Pyrogram", - reply_markup=InlineKeyboardMarkup( - [ - [InlineKeyboardButton( - "Open website", - url="https://docs.pyrogram.org/start/invoking" - )] - ] - ) - ) - ], - cache_time=1 - ) - - - app.run() # Automatically start() and idle() \ No newline at end of file diff --git a/docs/source/start/examples/raw_updates.rst b/docs/source/start/examples/raw_updates.rst deleted file mode 100644 index 463a45a80b..0000000000 --- a/docs/source/start/examples/raw_updates.rst +++ /dev/null @@ -1,18 +0,0 @@ -raw_updates -=========== - -This example shows how to handle raw updates. - -.. code-block:: python - - from pyrogram import Client - - app = Client("my_account") - - - @app.on_raw_update() - async def raw(client, update, users, chats): - print(update) - - - app.run() # Automatically start() and idle() diff --git a/docs/source/start/examples/use_inline_bots.rst b/docs/source/start/examples/use_inline_bots.rst deleted file mode 100644 index 8a2a72acc4..0000000000 --- a/docs/source/start/examples/use_inline_bots.rst +++ /dev/null @@ -1,25 +0,0 @@ -use_inline_bots -=============== - -This example shows how to query an inline bot (as user). - -.. code-block:: python - - from pyrogram import Client - - # Create a new Client - app = Client("my_account") - - - async def main(): - async with app: - # Get bot results for "hello" from the inline bot @vid - bot_results = await app.get_inline_bot_results("vid", "hello") - - # Send the first result to your own chat (Saved Messages) - await app.send_inline_bot_result( - "me", bot_results.query_id, - bot_results.results[0].id) - - - app.run(main()) \ No newline at end of file diff --git a/docs/source/start/examples/welcome_bot.rst b/docs/source/start/examples/welcome_bot.rst deleted file mode 100644 index 4e30ea7f0a..0000000000 --- a/docs/source/start/examples/welcome_bot.rst +++ /dev/null @@ -1,30 +0,0 @@ -welcome_bot -=========== - -This example uses the ``emoji`` module to easily add emoji in your text messages and ``filters`` -to make it only work for specific messages in a specific chat. - -.. code-block:: python - - from pyrogram import Client, emoji, filters - - # Target chat. Can also be a list of multiple chat ids/usernames - TARGET = -100123456789 - # Welcome message template - MESSAGE = "{} Welcome to [Pyrogram](https://docs.pyrogram.org/)'s group chat {}!" - - app = Client("my_account") - - - # Filter in only new_chat_members updates generated in TARGET chat - @app.on_message(filters.chat(TARGET) & filters.new_chat_members) - async def welcome(client, message): - # Build the new members list (with mentions) by using their first_name - new_members = [u.mention for u in message.new_chat_members] - # Build the welcome message by using an emoji and the list we built above - text = MESSAGE.format(emoji.SPARKLES, ", ".join(new_members)) - # Send the welcome message, without the web page preview - await message.reply_text(text, disable_web_page_preview=True) - - - app.run() # Automatically start() and idle() \ No newline at end of file diff --git a/docs/source/start/invoking.rst b/docs/source/start/invoking.rst deleted file mode 100644 index 415ef848b4..0000000000 --- a/docs/source/start/invoking.rst +++ /dev/null @@ -1,110 +0,0 @@ -Invoking Methods -================ - -At this point, we have successfully :doc:`installed Pyrogram <../intro/install>` and :doc:`authorized ` our -account; we are now aiming towards the core of the framework. - -.. contents:: Contents - :backlinks: none - :depth: 1 - :local: - ------ - -Basic Usage ------------ - -Making API calls with Pyrogram is very simple. Here's a basic example we are going to examine step by step: - -.. code-block:: python - - from pyrogram import Client - - app = Client("my_account") - - - async def main(): - async with app: - await app.send_message("me", "Hi!") - - - app.run(main()) - -Step-by-step -^^^^^^^^^^^^ - -#. Let's begin by importing the Client class. - - .. code-block:: python - - from pyrogram import Client - -#. Now instantiate a new Client object, "my_account" is a session name of your choice. - - .. code-block:: python - - app = Client("my_account") - -#. Async methods must be invoked within an async context. - Here we define an async function and put our code inside. Also notice the ``await`` keyword in front of the method - call; this is required for all asynchronous methods. - - .. code-block:: python - - async def main(): - async with app: - await app.send_message("me", "Hi!") - -#. Finally, we tell Python to schedule our ``main()`` async function by using Pyrogram's :meth:`~pyrogram.Client.run` - method. - - .. code-block:: python - - app.run(main()) - -Context Manager ---------------- - -The ``async with`` statement starts a context manager, which is used as a shortcut for starting, executing and stopping -the Client, asynchronously. It does so by automatically calling :meth:`~pyrogram.Client.start` and -:meth:`~pyrogram.Client.stop` in a more convenient way which also gracefully stops the client, even in case of -unhandled exceptions in your code. - -Below there's the same example as above, but without the use of the context manager: - -.. code-block:: python - - from pyrogram import Client - - app = Client("my_account") - - - async def main(): - await app.start() - await app.send_message("me", "Hi!") - await app.stop() - - - app.run(main()) - -Using asyncio.run() -------------------- - -Alternatively to the :meth:`~pyrogram.Client.run` method, you can use Python's ``asyncio.run()`` to execute the main -function, with one little caveat: the Client instance (and possibly other asyncio resources you are going to use) must -be instantiated inside the main function. - -.. code-block:: python - - import asyncio - from pyrogram import Client - - - async def main(): - app = Client("my_account") - - async with app: - await app.send_message("me", "Hi!") - - - asyncio.run(main()) \ No newline at end of file diff --git a/docs/source/start/setup.rst b/docs/source/start/setup.rst deleted file mode 100644 index b8fd6effd7..0000000000 --- a/docs/source/start/setup.rst +++ /dev/null @@ -1,40 +0,0 @@ -Project Setup -============= - -We have just :doc:`installed Pyrogram <../intro/install>`. In this page we'll discuss what you need to do in order to set up a -project with the framework. - -.. contents:: Contents - :backlinks: none - :depth: 1 - :local: - ------ - -API Key -------- - -The first step requires you to obtain a valid Telegram API key (api_id and api_hash pair): - -#. Visit https://my.telegram.org/apps and log in with your Telegram account. -#. Fill out the form with your details and register a new Telegram application. -#. Done. The API key consists of two parts: **api_id** and **api_hash**. Keep it secret. - -.. note:: - - The API key defines a token for a Telegram *application* you are going to build. - This means that you are able to authorize multiple users or bots with a single API key. - -Configuration -------------- - -Having the API key from the previous step in handy, we can now begin to configure a Pyrogram project: pass your API key to Pyrogram by using the *api_id* and *api_hash* parameters of the Client class: - -.. code-block:: python - - from pyrogram import Client - - api_id = 12345 - api_hash = "0123456789abcdef0123456789abcdef" - - app = Client("my_account", api_id=api_id, api_hash=api_hash) \ No newline at end of file diff --git a/docs/source/start/updates.rst b/docs/source/start/updates.rst deleted file mode 100644 index 685128c21c..0000000000 --- a/docs/source/start/updates.rst +++ /dev/null @@ -1,78 +0,0 @@ -Handling Updates -================ - -:doc:`Invoking API methods ` sequentially is one way to use Pyrogram. This page deals with Telegram updates -and how to handle new incoming messages or other events in Pyrogram. - -.. contents:: Contents - :backlinks: none - :depth: 1 - :local: - ------ - -Defining Updates ----------------- - -Updates are 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 has changed. These updates are -handled by registering one or more callback functions in your app using :doc:`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 a Handler ---------------------- - -To explain how handlers work let's examine the one which will be in charge for handling :class:`~pyrogram.types.Message` -updates coming from all around your chats. Every other kind of handler shares the same setup logic and you should not -have troubles settings them up once you learn from this section. - -Using Decorators -^^^^^^^^^^^^^^^^ - -The most elegant way to register a message handler is by using the :meth:`~pyrogram.Client.on_message` decorator: - -.. code-block:: python - - from pyrogram import Client - - app = Client("my_account") - - - @app.on_message() - async def my_handler(client, message): - await message.forward("me") - - - app.run() - -The defined function ``my_handler``, which accepts the two arguments *(client, message)*, will be the function that gets -executed every time a new message arrives. - -In the last line we see again the :meth:`~pyrogram.Client.run` method, this time used without any argument. -Its purpose here is simply to automatically :meth:`~pyrogram.Client.start`, keep the Client online so that it can listen -for updates and :meth:`~pyrogram.Client.stop` it once you hit ``CTRL+C``. - -Using add_handler() -^^^^^^^^^^^^^^^^^^^ - -The :meth:`~pyrogram.Client.add_handler` method takes any handler instance that wraps around your defined callback -function and registers it in your Client. It is useful in case you want to programmatically add handlers. - -.. code-block:: python - - from pyrogram import Client - from pyrogram.handlers import MessageHandler - - - async def my_function(client, message): - await message.forward("me") - - - app = Client("my_account") - - my_handler = MessageHandler(my_function) - app.add_handler(my_handler) - - app.run() diff --git a/docs/source/support.rst b/docs/source/support.rst deleted file mode 100644 index 8efa4bc19f..0000000000 --- a/docs/source/support.rst +++ /dev/null @@ -1,63 +0,0 @@ -Support Pyrogram -================ - -.. raw:: html - - - -
- Star - - Fork -
- -
- -Pyrogram is a free and open source project. -If you enjoy Pyrogram and would like to show your appreciation, consider donating or becoming -a sponsor of the project. You can support Pyrogram via the ways shown below: - ------ - -GitHub Sponsor --------------- - -`Become a GitHub sponsor `_. - -.. raw:: html - - Sponsor - ------ - -LiberaPay Patron ----------------- - -`Become a LiberaPay patron `_. - -.. raw:: html - - - ------ - -OpenCollective Backer ---------------------- - -`Become an OpenCollective backer `_ - -.. raw:: html - - \ No newline at end of file diff --git a/docs/source/topics/advanced-usage.rst b/docs/source/topics/advanced-usage.rst deleted file mode 100644 index df99042d1b..0000000000 --- a/docs/source/topics/advanced-usage.rst +++ /dev/null @@ -1,124 +0,0 @@ -Advanced Usage -============== - -Pyrogram's API -- which consists of well documented :doc:`methods <../api/methods/index>` and -:doc:`types <../api/types/index>` -- exists to provide an easier interface to the more complex Telegram API. - -In this section, you'll be shown the alternative way of communicating with Telegram using Pyrogram: the main "raw" -Telegram API with its functions and types. - -.. contents:: Contents - :backlinks: none - :depth: 1 - :local: - ------ - -Telegram Raw API ----------------- - -If you can't find a high-level method for your needs or if you want complete, low-level access to the whole -Telegram API, you have to use the raw :mod:`~pyrogram.raw.functions` and :mod:`~pyrogram.raw.types`. - -As already hinted, raw functions and types can be less convenient. This section will therefore explain some pitfalls to -take into consideration when working with the raw API. - -.. tip:: - - Every available high-level method in Pyrogram is built on top of these raw functions. - -Invoking Functions ------------------- - -Unlike the :doc:`methods <../api/methods/index>` found in Pyrogram's API, which can be called in the usual simple way, -functions to be invoked from the raw Telegram API have a different way of usage. - -First of all, both :doc:`raw functions <../telegram/functions/index>` and :doc:`raw types <../telegram/types/index>` -live in their respective packages (and sub-packages): ``pyrogram.raw.functions``, ``pyrogram.raw.types``. They all exist -as Python classes, meaning you need to create an instance of each every time you need them and fill them in with the -correct values using named arguments. - -Next, to actually invoke the raw function you have to use the :meth:`~pyrogram.Client.invoke` method provided by the -Client class and pass the function object you created. - -Here's some examples: - -- Update first name, last name and bio: - - .. code-block:: python - - from pyrogram import Client - from pyrogram.raw import functions - - async with Client("my_account") as app: - await app.invoke( - functions.account.UpdateProfile( - first_name="First Name", last_name="Last Name", - about="New bio text" - ) - ) - -- Set online/offline status: - - .. code-block:: python - - from pyrogram import Client - from pyrogram.raw import functions, types - - async with Client("my_account") as app: - # Set online status - await app.invoke(functions.account.UpdateStatus(offline=False)) - - # Set offline status - await app.invoke(functions.account.UpdateStatus(offline=True)) - -- Get chat info: - - .. code-block:: python - - from pyrogram import Client - from pyrogram.raw import functions, types - - async with Client("my_account") as app: - r = await app.invoke( - functions.channels.GetFullChannel( - channel=app.resolve_peer("username") - ) - ) - - print(r) - -Chat IDs --------- - -The way Telegram works makes it not possible to directly send a message to a user or a chat by using their IDs only. -Instead, a pair of ``id`` and ``access_hash`` wrapped in a so called ``InputPeer`` is always needed. Pyrogram allows -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: - -- :class:`~pyrogram.raw.types.InputPeerUser` - Users -- :class:`~pyrogram.raw.types.InputPeerChat` - Basic Chats -- :class:`~pyrogram.raw.types.InputPeerChannel` - Channels & Supergroups - -But you don't necessarily have to manually instantiate each object because Pyrogram already provides -:meth:`~pyrogram.Client.resolve_peer` as a convenience utility method that returns the correct InputPeer -by accepting a peer ID only. - -Another thing to take into consideration about chat IDs is the way they are represented: they are all integers and -all positive within their respective raw types. - -Things are different when working with Pyrogram's API because having them in the same space could lead to -collisions, and that's why Pyrogram uses a slightly different representation for each kind of ID. - -For example, given the ID *123456789*, here's how Pyrogram can tell entities apart: - -- ``+ID`` User: *123456789* -- ``-ID`` Chat: *-123456789* -- ``-100ID`` Channel or Supergroup: *-100123456789* - -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. - -.. _Community: https://t.me/Pyrogram \ No newline at end of file diff --git a/docs/source/topics/client-settings.rst b/docs/source/topics/client-settings.rst deleted file mode 100644 index 02dce71375..0000000000 --- a/docs/source/topics/client-settings.rst +++ /dev/null @@ -1,46 +0,0 @@ -Client Settings -=============== - -You can control the way your client appears in the Active Sessions menu of an official client by changing some client -settings. By default you will see something like the following: - -- Device Model: ``CPython x.y.z`` -- Application: ``Pyrogram x.y.z`` -- System Version: ``Linux x.y.z`` - -.. contents:: Contents - :backlinks: none - :depth: 1 - :local: - ------ - -Set Custom Values ------------------ - -To set custom values, you can pass the arguments directly in the Client's constructor. - -.. code-block:: python - - app = Client( - "my_account", - app_version="1.2.3", - device_model="PC", - system_version="Linux" - ) - -Set Custom Languages --------------------- - -To tell Telegram in which language should speak to you (terms of service, bots, service messages, ...) you can -set ``lang_code`` in `ISO 639-1 `_ standard (defaults to "en", -English). - -With the following code we make Telegram know we want it to speak in Italian (it): - -.. code-block:: python - - app = Client( - "my_account", - lang_code="it", - ) \ No newline at end of file diff --git a/docs/source/topics/create-filters.rst b/docs/source/topics/create-filters.rst deleted file mode 100644 index f8c05af620..0000000000 --- a/docs/source/topics/create-filters.rst +++ /dev/null @@ -1,109 +0,0 @@ -Creating Filters -================ - -Pyrogram already provides lots of built-in :class:`~pyrogram.filters` to work with, but in case you can't find a -specific one for your needs or want to build a custom filter by yourself you can use -:meth:`filters.create() `. - -.. contents:: Contents - :backlinks: none - :depth: 1 - :local: - ------ - -Custom Filters --------------- - -An example to demonstrate how custom filters work is to show how to create and use one for the -:class:`~pyrogram.handlers.CallbackQueryHandler`. Note that callback queries updates are only received by bots as result -of a user pressing an inline button attached to the bot's message; create and :doc:`authorize your bot <../start/auth>`, -then send a message with an inline keyboard to yourself. This allows you to test your filter by pressing the inline -button: - -.. code-block:: python - - from pyrogram.types import InlineKeyboardMarkup, InlineKeyboardButton - - await app.send_message( - "username", # Change this to your username or id - "Pyrogram custom filter test", - reply_markup=InlineKeyboardMarkup( - [[InlineKeyboardButton("Press me", "pyrogram")]] - ) - ) - -Basic Filters -------------- - -For this basic filter we will be using only the first parameter of :meth:`~pyrogram.filters.create`. - -The heart of a filter is its callback function, which accepts three arguments *(self, client, update)* and returns -either ``True``, in case you want the update to pass the filter or ``False`` otherwise. - -In this example we are matching the query data to "pyrogram", which means that the filter will only allow callback -queries containing "pyrogram" as data: - -.. code-block:: python - - from pyrogram import filters - - async def func(_, __, query): - return query.data == "pyrogram" - - static_data_filter = filters.create(func) - - -The first two arguments of the callback function are unused here and because of this we named them using underscores. - -Finally, the filter usage remains the same: - -.. code-block:: python - - @app.on_callback_query(static_data_filter) - async def pyrogram_data(_, query): - query.answer("it works!") - -Filters with Arguments ----------------------- - -A more flexible filter would be one that accepts "pyrogram" or any other string as argument at usage time. -A dynamic filter like this will make use of named arguments for the :meth:`~pyrogram.filters.create` method and the -first argument of the callback function, which is a reference to the filter object itself holding the extra data passed -via named arguments. - -This is how a dynamic custom filter looks like: - -.. code-block:: python - - from pyrogram import filters - - def dynamic_data_filter(data): - async def func(flt, _, query): - return flt.data == query.data - - # "data" kwarg is accessed with "flt.data" above - return filters.create(func, data=data) - -And finally its usage: - -.. code-block:: python - - @app.on_callback_query(dynamic_data_filter("pyrogram")) - async def pyrogram_data(_, query): - query.answer("it works!") - - -Method Calls Inside Filters ---------------------------- - -The missing piece we haven't covered yet is the second argument of a filter callback function, namely, the ``client`` -argument. This is a reference to the :obj:`~pyrogram.Client` instance that is running the filter and it is useful in -case you would like to make some API calls before deciding whether the filter should allow the update or not: - -.. code-block:: python - - async def func(_, client, query): - # r = await client.some_api_method() - # check response "r" and decide to return True or False - ... \ No newline at end of file diff --git a/docs/source/topics/debugging.rst b/docs/source/topics/debugging.rst deleted file mode 100644 index 1c0ac069f9..0000000000 --- a/docs/source/topics/debugging.rst +++ /dev/null @@ -1,122 +0,0 @@ -Debugging -========= - -When working with the API, chances are you'll stumble upon bugs, get stuck and start wondering how to continue. Nothing -to actually worry about since Pyrogram provides some commodities to help you in this. - -.. contents:: Contents - :backlinks: none - :depth: 1 - :local: - ------ - -Caveman Debugging ------------------ - - *The most effective debugging tool is still careful thought, coupled with judiciously placed print statements.* - - -- Brian Kernighan, "Unix for Beginners" (1979) - -Adding ``print()`` statements in crucial parts of your code is by far the most ancient, yet efficient technique for -debugging programs, especially considering the concurrent nature of the framework itself. Pyrogram goodness in this -respect comes with the fact that any object can be nicely printed just by calling ``print(obj)``, thus giving to you -an insight of all its inner details. - -Consider the following code: - -.. code-block:: python - - me = await app.get_users("me") - print(me) # User - -This will show a JSON representation of the object returned by :meth:`~pyrogram.Client.get_users`, which is a -:class:`~pyrogram.types.User` instance, in this case. The output on your terminal will be something similar to this: - -.. code-block:: json - - { - "_": "User", - "id": 123456789, - "is_self": true, - "is_contact": false, - "is_mutual_contact": false, - "is_deleted": false, - "is_bot": false, - "is_verified": false, - "is_restricted": false, - "is_support": false, - "first_name": "Pyrogram", - "photo": { - "_": "ChatPhoto", - "small_file_id": "AbCdE...EdCbA", - "small_photo_unique_id": "VwXyZ...ZyXwV", - "big_file_id": "AbCdE...EdCbA", - "big_photo_unique_id": "VwXyZ...ZyXwV" - } - } - -As you've probably guessed already, Pyrogram objects can be nested. That's how compound data are built, and nesting -keeps going until we are left with base data types only, such as ``str``, ``int``, ``bool``, etc. - -Accessing Attributes --------------------- - -Even though you see a JSON output, it doesn't mean we are dealing with dictionaries; in fact, all Pyrogram types are -fully-fledged Python objects and the correct way to access any attribute of them is by using the dot notation ``.``: - -.. code-block:: python - - photo = me.photo - print(photo) # ChatPhoto - -.. code-block:: json - - { - "_": "ChatPhoto", - "small_file_id": "AbCdE...EdCbA", - "small_photo_unique_id": "VwXyZ...ZyXwV", - "big_file_id": "AbCdE...EdCbA", - "big_photo_unique_id": "VwXyZ...ZyXwV" - } - -Checking an Object's Type -------------------------- - -Another thing worth talking about is how to tell and check for an object's type. - -As you noticed already, when printing an object you'll see the special attribute ``"_"``. This is just a visual thing -useful to show humans the object type, but doesn't really exist anywhere; any attempt in accessing it will lead to an -error. The correct way to get the object type is by using the built-in function ``type()``: - -.. code-block:: python - - status = me.status - print(type(status)) - -.. code-block:: text - - - -And to check if an object is an instance of a given class, you use the built-in function ``isinstance()``: - -.. code-block:: python - :name: this-py - - from pyrogram.types import UserStatus - - status = me.status - print(isinstance(status, UserStatus)) - -.. code-block:: text - - True - -.. raw:: html - - \ No newline at end of file diff --git a/docs/source/topics/more-on-updates.rst b/docs/source/topics/more-on-updates.rst deleted file mode 100644 index 18c1a68af3..0000000000 --- a/docs/source/topics/more-on-updates.rst +++ /dev/null @@ -1,226 +0,0 @@ -More on Updates -=============== - -Here we'll show some advanced usages when working with :doc:`update handlers <../start/updates>` and -:doc:`filters `. - -.. contents:: Contents - :backlinks: none - :depth: 1 - :local: - ------ - -Handler Groups --------------- - -If you register handlers with overlapping (conflicting) filters, only the first one is executed and any other handler -will be ignored. This is intended by design. - -In order to handle the very same update more than once, you have to register your handler in a different dispatching -group. Dispatching groups hold one or more handlers and are processed sequentially, they are identified by a number -(number 0 being the default) and sorted, that is, a lower group number has a higher priority: - -For example, take these two handlers: - -.. code-block:: python - - @app.on_message(filters.text | filters.sticker) - async def text_or_sticker(client, message): - print("Text or Sticker") - - - @app.on_message(filters.text) - async def just_text(client, message): - print("Just Text") - -Here, ``just_text`` is never executed because ``text_or_sticker``, which has been registered first, already handles -texts (``filters.text`` is shared and conflicting). To enable it, register the handler using a different group: - -.. code-block:: python - - @app.on_message(filters.text, group=1) - async def just_text(client, message): - print("Just Text") - -Or, if you want ``just_text`` to be executed *before* ``text_or_sticker`` (note ``-1``, which is less than ``0``): - -.. code-block:: python - - @app.on_message(filters.text, group=-1) - async def just_text(client, message): - print("Just Text") - -With :meth:`~pyrogram.Client.add_handler` (without decorators) the same can be achieved with: - -.. code-block:: python - - app.add_handler(MessageHandler(just_text, filters.text), -1) - -Update propagation ------------------- - -Registering multiple handlers, each in a different group, becomes useful when you want to handle the same update more -than once. Any incoming update will be sequentially processed by all of your registered functions by respecting the -groups priority policy described above. Even in case any handler raises an unhandled exception, Pyrogram will still -continue to propagate the same update to the next groups until all the handlers are done. Example: - -.. code-block:: python - - @app.on_message(filters.private) - async def _(client, message): - print(0) - - - @app.on_message(filters.private, group=1) - async def _(client, message): - raise Exception("Unhandled exception!") # Simulate an unhandled exception - - - @app.on_message(filters.private, group=2) - async def _(client, message): - print(2) - -All these handlers will handle the same kind of messages, that are, messages sent or received in private chats. -The output for each incoming update will therefore be: - -.. code-block:: text - - 0 - Exception: Unhandled exception! - 2 - -Stop Propagation -^^^^^^^^^^^^^^^^ - -In order to prevent further propagation of an update in the dispatching phase, you can do *one* of the following: - -- Call the update's bound-method ``.stop_propagation()`` (preferred way). -- Manually ``raise StopPropagation`` exception (more suitable for raw updates only). - -.. note:: - - Internally, the propagation is stopped by handling a custom exception. ``.stop_propagation()`` is just an elegant - and intuitive way to ``raise StopPropagation``; this also means that any code coming *after* calling the method - won't be executed as your function just raised an exception to signal the dispatcher not to propagate the - update anymore. - -Example with ``stop_propagation()``: - -.. code-block:: python - - @app.on_message(filters.private) - async def _(client, message): - print(0) - - - @app.on_message(filters.private, group=1) - async def _(client, message): - print(1) - message.stop_propagation() - - - @app.on_message(filters.private, group=2) - async def _(client, message): - print(2) - -Example with ``raise StopPropagation``: - -.. code-block:: python - - from pyrogram import StopPropagation - - @app.on_message(filters.private) - async def _(client, message): - print(0) - - - @app.on_message(filters.private, group=1) - async ef _(client, message): - print(1) - raise StopPropagation - - - @app.on_message(filters.private, group=2) - async def _(client, message): - print(2) - -Each handler is registered in a different group, but the handler in group number 2 will never be executed because the -propagation was stopped earlier. The output of both (equivalent) examples will be: - -.. code-block:: text - - 0 - 1 - -Continue Propagation -^^^^^^^^^^^^^^^^^^^^ - -As opposed to `stopping the update propagation <#stop-propagation>`_ and also as an alternative to the -`handler groups <#handler-groups>`_, you can signal the internal dispatcher to continue the update propagation within -**the same group** despite having conflicting filters in the next registered handler. This allows you to register -multiple handlers with overlapping filters in the same group; to let the dispatcher process the next handler you can do -*one* of the following in each handler you want to grant permission to continue: - -- Call the update's bound-method ``.continue_propagation()`` (preferred way). -- Manually ``raise ContinuePropagation`` exception (more suitable for raw updates only). - -.. note:: - - Internally, the propagation is continued by handling a custom exception. ``.continue_propagation()`` is just an - elegant and intuitive way to ``raise ContinuePropagation``; this also means that any code coming *after* calling the - method won't be executed as your function just raised an exception to signal the dispatcher to continue with the - next available handler. - - -Example with ``continue_propagation()``: - -.. code-block:: python - - @app.on_message(filters.private) - async def _(client, message): - print(0) - message.continue_propagation() - - - @app.on_message(filters.private) - async def _(client, message): - print(1) - message.continue_propagation() - - - @app.on_message(filters.private) - async def _(client, message): - print(2) - -Example with ``raise ContinuePropagation``: - -.. code-block:: python - - from pyrogram import ContinuePropagation - - @app.on_message(filters.private) - async def _(client, message): - print(0) - raise ContinuePropagation - - - @app.on_message(filters.private) - async def _(client, message): - print(1) - raise ContinuePropagation - - - @app.on_message(filters.private) - async def _(client, message): - print(2) - -Three handlers are registered in the same group, and all of them will be executed because the propagation was continued -in each handler (except in the last one, where is useless to do so since there is no more handlers after). -The output of both (equivalent) examples will be: - -.. code-block:: text - - 0 - 1 - 2 diff --git a/docs/source/topics/mtproto-vs-botapi.rst b/docs/source/topics/mtproto-vs-botapi.rst deleted file mode 100644 index 9681c1eb65..0000000000 --- a/docs/source/topics/mtproto-vs-botapi.rst +++ /dev/null @@ -1,112 +0,0 @@ -MTProto vs. Bot API -=================== - -Pyrogram is a framework written from the ground up that acts as a fully-fledged Telegram client based on the MTProto -API. This means that Pyrogram is able to execute any official client and bot API action and more. This page will -therefore show you why Pyrogram might be a better choice for your project by comparing the two APIs, but first, let's -make it clear what actually is the MTProto and the Bot API. - -.. contents:: Contents - :backlinks: none - :depth: 1 - :local: - ------ - -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 and the actual Telegram servers. - -The MTProto API on the other hand, is what people for convenience call the main Telegram API in order to distinguish it -from the Bot API. The main Telegram API is able to authorize both users and bots and is built on top of the MTProto -encryption protocol by means of `binary data serialized`_ in a specific way, as described by the `TL language`_, and -delivered using UDP, TCP or even HTTP as transport-layer protocol. Clients that make use of Telegram's main API, such as -Pyrogram, implement all these details. - -.. _MTProto: https://core.telegram.org/mtproto -.. _binary data serialized: https://core.telegram.org/mtproto/serialize -.. _TL language: https://core.telegram.org/mtproto/TL - -What is the Bot API? --------------------- - -The `Bot API`_ is an HTTP(S) interface for building normal bots using a sub-set of the main Telegram API. 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:: //_static/img/mtproto-vs-bot-api.png - :align: center - -.. _Bot API: https://core.telegram.org/bots/api - -Advantages of the MTProto API ------------------------------ - -Here is a non-exhaustive list of all the advantages in using MTProto-based libraries -- such as Pyrogram -- instead of -the official HTTP Bot API. Using Pyrogram you can: - -.. hlist:: - :columns: 1 - - - :guilabel:`+` **Authorize both user and bot identities** - - :guilabel:`--` The Bot API only allows bot accounts - -.. hlist:: - :columns: 1 - - - :guilabel:`+` **Upload & download any file, up to 2000 MiB each (~2 GB)** - - :guilabel:`--` The Bot API allows uploads and downloads of files only up to 50 MB / 20 MB in size (respectively). - -.. hlist:: - :columns: 1 - - - :guilabel:`+` **Has less overhead due to direct connections to Telegram** - - :guilabel:`--` The Bot API uses an intermediate server to handle HTTP requests before they are sent to the actual - Telegram servers. - -.. hlist:: - :columns: 1 - - - :guilabel:`+` **Run multiple sessions at once (for both user and bot identities)** - - :guilabel:`--` The Bot API intermediate server will terminate any other session in case you try to use the same - bot again in a parallel connection. - -.. hlist:: - :columns: 1 - - - :guilabel:`+` **Has much more detailed types and powerful methods** - - :guilabel:`--` The Bot API types often miss some useful information about Telegram entities and some of the - methods are limited as well. - -.. hlist:: - :columns: 1 - - - :guilabel:`+` **Obtain information about any message existing in a chat using their ids** - - :guilabel:`--` The Bot API simply doesn't support this - -.. hlist:: - :columns: 1 - - - :guilabel:`+` **Retrieve the whole chat members list of either public or private chats** - - :guilabel:`--` The Bot API simply doesn't support this - -.. hlist:: - :columns: 1 - - - :guilabel:`+` **Receive extra updates, such as the one about a user name change** - - :guilabel:`--` The Bot API simply doesn't support this - -.. hlist:: - :columns: 1 - - - :guilabel:`+` **Has more meaningful errors in case something went wrong** - - :guilabel:`--` The Bot API reports less detailed errors - -.. hlist:: - :columns: 1 - - - :guilabel:`+` **Get API version updates, and thus new features, sooner** - - :guilabel:`--` The Bot API is simply slower in implementing new features diff --git a/docs/source/topics/proxy.rst b/docs/source/topics/proxy.rst deleted file mode 100644 index 286743493d..0000000000 --- a/docs/source/topics/proxy.rst +++ /dev/null @@ -1,34 +0,0 @@ -Proxy Settings -============== - -Pyrogram supports proxies with and without authentication. This feature allows Pyrogram to exchange data with Telegram -through an intermediate SOCKS 4/5 or HTTP (CONNECT) proxy server. - -.. contents:: Contents - :backlinks: none - :depth: 1 - :local: - ------ - -Usage ------ - -To use Pyrogram with a proxy, use the *proxy* parameter in the Client class. If your proxy doesn't require authorization -you can omit ``username`` and ``password``. - -.. code-block:: python - - from pyrogram import Client - - proxy = { - "scheme": "socks5", # "socks4", "socks5" and "http" are supported - "hostname": "11.22.33.44", - "port": 1234, - "username": "username", - "password": "password" - } - - app = Client("my_account", proxy=proxy) - - app.run() diff --git a/docs/source/topics/scheduling.rst b/docs/source/topics/scheduling.rst deleted file mode 100644 index a67a92548a..0000000000 --- a/docs/source/topics/scheduling.rst +++ /dev/null @@ -1,65 +0,0 @@ -Scheduling Tasks -================ - -Scheduling tasks means executing one or more functions periodically at pre-defined intervals or after a delay. This is -useful, for example, to send recurring messages to specific chats or users. - -This page will show examples on how to integrate Pyrogram with ``apscheduler`` in both asynchronous and -non-asynchronous contexts. For more detailed information, you can visit and learn from the library documentation. - -.. contents:: Contents - :backlinks: none - :depth: 1 - :local: - ------ - -Using apscheduler ------------------ - -- Install with ``pip3 install apscheduler`` -- Documentation: https://apscheduler.readthedocs.io - -Asynchronously -^^^^^^^^^^^^^^ - -.. code-block:: python - - from apscheduler.schedulers.asyncio import AsyncIOScheduler - - from pyrogram import Client - - app = Client("my_account") - - - async def job(): - await app.send_message("me", "Hi!") - - - scheduler = AsyncIOScheduler() - scheduler.add_job(job, "interval", seconds=3) - - scheduler.start() - app.run() - -Non-Asynchronously -^^^^^^^^^^^^^^^^^^ - -.. code-block:: python - - from apscheduler.schedulers.background import BackgroundScheduler - - from pyrogram import Client - - app = Client("my_account") - - - def job(): - app.send_message("me", "Hi!") - - - scheduler = BackgroundScheduler() - scheduler.add_job(job, "interval", seconds=3) - - scheduler.start() - app.run() diff --git a/docs/source/topics/serializing.rst b/docs/source/topics/serializing.rst deleted file mode 100644 index 3dc644f880..0000000000 --- a/docs/source/topics/serializing.rst +++ /dev/null @@ -1,56 +0,0 @@ -Object Serialization -==================== - -Serializing means converting a Pyrogram object, which exists as Python class instance, to a text string that can be -easily shared and stored anywhere. Pyrogram provides two formats for serializing its objects: one good looking for -humans and another more compact for machines that is able to recover the original structures. - -.. contents:: Contents - :backlinks: none - :depth: 1 - :local: - ------ - -For Humans - str(obj) ---------------------- - -If you want a nicely formatted, human readable JSON representation of any object in the API you can use ``str(obj)``. - -.. code-block:: python - - ... - - async with app: - r = await app.get_chat("me") - print(str(r)) - -.. tip:: - - When using ``print()`` you don't actually need to use ``str()`` on the object because it is called automatically, we - have done that above just to show you how to explicitly convert a Pyrogram object to JSON. - -For Machines - repr(obj) ------------------------- - -If you want to share or store objects for future references in a more compact way, you can use ``repr(obj)``. While -still pretty much readable, this format is not intended for humans. The advantage of this format is that once you -serialize your object, you can use ``eval()`` to get back the original structure; just make sure to ``import pyrogram``, -as the process requires the package to be in scope. - -.. code-block:: python - - import pyrogram - - ... - - async with app: - r = await app.get_chat("me") - - print(repr(r)) - print(eval(repr(r)) == r) # True - -.. note:: - - Type definitions are subject to changes between versions. You should make sure to store and load objects using the - same Pyrogram version. \ No newline at end of file diff --git a/docs/source/topics/smart-plugins.rst b/docs/source/topics/smart-plugins.rst deleted file mode 100644 index c378c9d81f..0000000000 --- a/docs/source/topics/smart-plugins.rst +++ /dev/null @@ -1,306 +0,0 @@ -Smart Plugins -============= - -Pyrogram embeds a smart, lightweight yet powerful plugin system that is meant to further simplify the organization -of large projects and to provide a way for creating pluggable (modular) components that can be easily shared across -different Pyrogram applications with minimal boilerplate code. - -.. tip:: - - Smart Plugins are completely optional and disabled by default. - -.. contents:: Contents - :backlinks: none - :depth: 1 - :local: - ------ - -Introduction ------------- - -Prior to the Smart Plugin system, pluggable handlers were already possible. For example, if you wanted to modularize -your applications, you had to put your function definitions in separate files and register them inside your main script -after importing your modules, like this: - -.. note:: - - This is an example application that replies in private chats with two messages: one containing the same - text message you sent and the other containing the reversed text message. - - Example: *"Pyrogram"* replies with *"Pyrogram"* and *"margoryP"* - -.. code-block:: text - - myproject/ - handlers.py - main.py - -- ``handlers.py`` - - .. code-block:: python - - async def echo(client, message): - await message.reply(message.text) - - - async def echo_reversed(client, message): - await message.reply(message.text[::-1]) - -- ``main.py`` - - .. code-block:: python - - from pyrogram import Client, filters - from pyrogram.handlers import MessageHandler - - from handlers import echo, echo_reversed - - app = Client("my_account") - - app.add_handler( - MessageHandler( - echo, - filters.text & filters.private)) - - app.add_handler( - MessageHandler( - echo_reversed, - filters.text & filters.private), - group=1) - - 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:`~pyrogram.Client.add_handler` and manually instantiate each -:class:`~pyrogram.handlers.MessageHandler` object because you can't use decorators for your functions. -So, what if you could? Smart Plugins solve this issue by taking care of handlers registration automatically. - -Using Smart Plugins -------------------- - -Setting up your Pyrogram project to accommodate Smart Plugins is pretty straightforward: - -#. Create a new folder to store all the plugins (e.g.: "plugins", "handlers", ...). -#. Put your python files full of plugins inside. Organize them as you wish. -#. Enable plugins in your Client. - -.. note:: - - This is the same example application as shown above, written using the Smart Plugin system. - -.. code-block:: text - - myproject/ - plugins/ - handlers.py - main.py - -- ``plugins/handlers.py`` - - .. code-block:: python - - from pyrogram import Client, filters - - - @Client.on_message(filters.text & filters.private) - async def echo(client, message): - await message.reply(message.text) - - - @Client.on_message(filters.text & filters.private, group=1) - async def echo_reversed(client, message): - await message.reply(message.text[::-1]) - -- ``main.py`` - - .. code-block:: python - - from pyrogram import Client - - plugins = dict(root="plugins") - - Client("my_account", plugins=plugins).run() - - -The first important thing to note is the new ``plugins`` folder. You can put *any python file* in *any subfolder* and -each file can contain *any decorated function* (handlers) with one limitation: within a single module (file) you must -use different names for each decorated function. - -The second thing is telling Pyrogram where to look for your plugins: you can use the Client parameter "plugins"; -the *root* value must match the name of your plugins root folder. Your Pyrogram Client instance will **automatically** -scan the folder upon starting to search for valid handlers and register them for you. - -Then you'll notice you can now use decorators. That's right, you can apply the usual decorators to your callback -functions in a static way, i.e. **without having the Client instance around**: simply use ``@Client`` (Client class) -instead of the usual ``@app`` (Client instance) and things will work just the same. - -Specifying the Plugins to include ---------------------------------- - -By default, if you don't explicitly supply a list of plugins, every valid one found inside your plugins root folder will -be included by following the alphabetical order of the directory structure (files and subfolders); the single handlers -found inside each module will be, instead, loaded in the order they are defined, from top to bottom. - -.. 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, by design. Learn more at :doc:`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`` -directives in the dictionary passed as Client argument. Here's how they work: - -- If both ``include`` and ``exclude`` are omitted, all plugins are loaded as described above. -- If ``include`` is given, only the specified plugins will be loaded, in the order they are passed. -- If ``exclude`` is given, the plugins specified here will be unloaded. - -The ``include`` and ``exclude`` value is a **list of strings**. Each string containing the path of the module relative -to the plugins root folder, in Python notation (dots instead of slashes). - - E.g.: ``subfolder.module`` refers to ``plugins/subfolder/module.py``, with ``root="plugins"``. - -You can also choose the order in which the single handlers inside a module are loaded, thus overriding the default -top-to-bottom loading policy. You can do this by appending the name of the functions to the module path, each one -separated by a blank space. - - E.g.: ``subfolder.module fn2 fn1 fn3`` will load *fn2*, *fn1* and *fn3* from *subfolder.module*, in this order. - -Examples -^^^^^^^^ - -Given this plugins folder structure with three modules, each containing their own handlers (fn1, fn2, etc...), which are -also organized in subfolders: - -.. code-block:: text - - myproject/ - plugins/ - subfolder1/ - plugins1.py - - fn1 - - fn2 - - fn3 - subfolder2/ - plugins2.py - ... - plugins0.py - ... - ... - -- Load every handler from every module, namely *plugins0.py*, *plugins1.py* and *plugins2.py* in alphabetical order - (files) and definition order (handlers inside files): - - .. code-block:: python - - plugins = dict(root="plugins") - - Client("my_account", plugins=plugins).run() - -- Load only handlers defined inside *plugins2.py* and *plugins0.py*, in this order: - - .. code-block:: python - - plugins = dict( - root="plugins", - include=[ - "subfolder2.plugins2", - "plugins0" - ] - ) - - Client("my_account", plugins=plugins).run() - -- Load everything except the handlers inside *plugins2.py*: - - .. code-block:: python - - plugins = dict( - root="plugins", - exclude=["subfolder2.plugins2"] - ) - - Client("my_account", plugins=plugins).run() - -- Load only *fn3*, *fn1* and *fn2* (in this order) from *plugins1.py*: - - .. code-block:: python - - plugins = dict( - root="plugins", - include=["subfolder1.plugins1 fn3 fn1 fn2"] - ) - - Client("my_account", plugins=plugins).run() - -Load/Unload Plugins at Runtime ------------------------------- - -In the previous section we've explained how to specify which plugins to load and which to ignore before your Client -starts. Here we'll show, instead, how to unload and load again a previously registered plugin at runtime. - -Each function decorated with the usual ``on_message`` decorator (or any other decorator that deals with Telegram -updates) will be modified in such a way that a special ``handlers`` attribute pointing to a list of tuples of -*(handler: Handler, group: int)* is attached to the function object itself. - -- ``plugins/handlers.py`` - - .. code-block:: python - - @Client.on_message(filters.text & filters.private) - async def echo(client, message): - await message.reply(message.text) - - print(echo) - print(echo.handlers) - -- Printing ``echo`` will show something like ````. - -- Printing ``echo.handlers`` will reveal the handlers, that is, a list of tuples containing the actual handlers and - the groups they were registered on ``[(, 0)]``. - -Unloading -^^^^^^^^^ - -In order to unload a plugin, all you need to do is obtain a reference to it by importing the relevant module and call -:meth:`~pyrogram.Client.remove_handler` Client's method with your function's *handler* instance: - -- ``main.py`` - - .. code-block:: python - - from plugins.handlers import echo - - handlers = echo.handlers - - for h in handlers: - app.remove_handler(*h) - -The star ``*`` operator is used to unpack the tuple into positional arguments so that *remove_handler* will receive -exactly what is needed. The same could have been achieved with: - -.. code-block:: python - - handlers = echo.handlers - handler, group = handlers[0] - - app.remove_handler(handler, group) - -Loading -^^^^^^^ - -Similarly to the unloading process, in order to load again a previously unloaded plugin you do the same, but this time -using :meth:`~pyrogram.Client.add_handler` instead. Example: - -- ``main.py`` - - .. code-block:: python - - from plugins.handlers import echo - - ... - - handlers = echo.handlers - - for h in handlers: - app.add_handler(*h) \ No newline at end of file diff --git a/docs/source/topics/speedups.rst b/docs/source/topics/speedups.rst deleted file mode 100644 index 821b26f4df..0000000000 --- a/docs/source/topics/speedups.rst +++ /dev/null @@ -1,88 +0,0 @@ -Speedups -======== - -Pyrogram's speed can be boosted up by using TgCrypto and uvloop. - -.. contents:: Contents - :backlinks: none - :depth: 1 - :local: - ------ - -TgCrypto --------- - -TgCrypto_ is a high-performance, easy-to-install cryptography library specifically written in C for Pyrogram as a Python -extension. It is a replacement for a slower Python-only alternative and implements the cryptographic algorithms Telegram -requires, namely: AES-256-IGE, AES-256-CTR and AES-256-CBC. - -Installation -^^^^^^^^^^^^ - -.. code-block:: bash - - $ pip3 install -U tgcrypto - -Usage -^^^^^ - -Pyrogram will automatically make use of TgCrypto when detected, all you need to do is to install it. - -uvloop ------- - -uvloop_ is a fast, drop-in replacement of the built-in asyncio event loop. uvloop is implemented in Cython and uses -libuv under the hood. It makes asyncio 2-4x faster. - -Installation -^^^^^^^^^^^^ - -.. code-block:: bash - - $ pip3 install -U uvloop - -Usage -^^^^^ - -Call ``uvloop.install()`` before calling ``asyncio.run()`` or ``app.run()``. - -.. code-block:: python - - import asyncio - import uvloop - - from pyrogram import Client - - - async def main(): - app = Client("my_account") - - async with app: - print(await app.get_me()) - - - uvloop.install() - asyncio.run(main()) - -The ``uvloop.install()`` call also needs to be placed before creating a Client instance. - -.. code-block:: python - - import uvloop - from pyrogram import Client - - uvloop.install() - - app = Client("my_account") - - - @app.on_message() - async def hello(client, message): - print(await client.get_me()) - - - app.run() - -.. _TgCrypto: https://github.com/pyrogram/tgcrypto -.. _uvloop: https://github.com/MagicStack/uvloop diff --git a/docs/source/topics/storage-engines.rst b/docs/source/topics/storage-engines.rst deleted file mode 100644 index 34147917ee..0000000000 --- a/docs/source/topics/storage-engines.rst +++ /dev/null @@ -1,90 +0,0 @@ -Storage Engines -=============== - -Every time you login to Telegram, some personal piece of data are created and held by both parties (the client, Pyrogram -and the server, Telegram). This session data is uniquely bound to your own account, indefinitely (until you logout or -decide to manually terminate it) and is used to authorize a client to execute API calls on behalf of your identity. - -.. contents:: Contents - :backlinks: none - :depth: 1 - :local: - ------ - -Persisting Sessions -------------------- - -In order to make a client reconnect successfully between restarts, that is, without having to start a new -authorization process from scratch each time, Pyrogram needs to store the generated session data somewhere. - -Different Storage Engines -------------------------- - -Pyrogram offers two different types of storage engines: a **File Storage** and a **Memory Storage**. -These engines are well integrated in the framework and require a minimal effort to set up. Here's how they work: - -File Storage -^^^^^^^^^^^^ - -This is the most common storage engine. It is implemented by using **SQLite**, which will store the session details. -The database will be saved to disk as a single portable file and is designed to efficiently store and retrieve -data whenever they are needed. - -To use this type of engine, simply pass any name of your choice to the ``name`` parameter of the -:obj:`~pyrogram.Client` constructor, as usual: - -.. code-block:: python - - from pyrogram import Client - - async with Client("my_account") as app: - print(await app.get_me()) - -Once you successfully log in (either with a user or a bot identity), a session file will be created and saved to disk as -``my_account.session``. Any subsequent client restart will make Pyrogram search for a file named that way and the -session database will be automatically loaded. - -Memory Storage -^^^^^^^^^^^^^^ - -In case you don't want to have any session file saved to disk, you can use an in-memory storage by passing True to the -``in_memory`` parameter of the :obj:`~pyrogram.Client` constructor: - -.. code-block:: python - - from pyrogram import Client - - async with Client("my_account", in_memory=True) as app: - print(await app.get_me()) - -This storage engine is still backed by SQLite, but the database exists purely in memory. This means that, once you stop -a client, the entire database is discarded and the session details used for logging in again will be lost forever. - -Session Strings ---------------- - -In case you want to use an in-memory storage, but also want to keep access to the session you created, call -:meth:`~pyrogram.Client.export_session_string` anytime before stopping the client... - -.. code-block:: python - - from pyrogram import Client - - async with Client("my_account", in_memory=True) as app: - print(await app.export_session_string()) - -...and save the resulting string. You can use this string by passing it as Client argument the next time you want to -login using the same session; the storage used will still be in-memory: - -.. code-block:: python - - from pyrogram import Client - - session_string = "...ZnUIFD8jsjXTb8g_vpxx48k1zkov9sapD-tzjz-S4WZv70M..." - - async with Client("my_account", session_string=session_string) as app: - print(await app.get_me()) - -Session strings are useful when you want to run authorized Pyrogram clients on platforms where their ephemeral -filesystems makes it harder for a file-based storage engine to properly work as intended. diff --git a/docs/source/topics/synchronous.rst b/docs/source/topics/synchronous.rst deleted file mode 100644 index 0a677b0e50..0000000000 --- a/docs/source/topics/synchronous.rst +++ /dev/null @@ -1,88 +0,0 @@ -Synchronous Usage -================= - -Pyrogram is an asynchronous framework and as such is subject to the asynchronous rules. It can, however, run in -synchronous mode (also known as non-asynchronous or sync/non-async for short). This mode exists mainly as a convenience -way for invoking methods without the need of ``async``/``await`` keywords and the extra boilerplate, but **it's not the -intended way to use the framework**. - -You can use Pyrogram in this synchronous mode when you want to write something short and contained without the -async boilerplate or in case you want to combine Pyrogram with other libraries that are not async. - -.. warning:: - - You have to be very careful when using the framework in its synchronous, non-native form, especially when combined - with other non-async libraries because thread blocking operations that clog the asynchronous event loop underneath - will make the program run erratically. - -.. contents:: Contents - :backlinks: none - :depth: 1 - :local: - ------ - -Synchronous Invocations ------------------------ - -The following is a standard example of running asynchronous functions with Python's asyncio. -Pyrogram is being used inside the main function with its asynchronous interface. - -.. code-block:: python - - import asyncio - from pyrogram import Client - - - async def main(): - app = Client("my_account") - - async with app: - await app.send_message("me", "Hi!") - - - asyncio.run(main()) - -To run Pyrogram synchronously, use the non-async context manager as shown in the following example. -As you can see, the non-async example becomes less cluttered. - -.. code-block:: python - - from pyrogram import Client - - app = Client("my_account") - - with app: - app.send_message("me", "Hi!") - -Synchronous handlers --------------------- - -You can also have synchronous handlers; you only need to define the callback function without using ``async def`` and -invoke API methods by not placing ``await`` in front of them. Mixing ``def`` and ``async def`` handlers together is also -possible. - -.. code-block:: python - - @app.on_message() - async def handler1(client, message): - await message.forward("me") - - @app.on_edited_message() - def handler2(client, message): - message.forward("me") - -uvloop usage ------------- - -When using Pyrogram in its synchronous mode combined with uvloop, you need to call ``uvloop.install()`` before importing -Pyrogram. - -.. code-block:: python - - import uvloop - uvloop.install() - - from pyrogram import Client - - ... \ No newline at end of file diff --git a/docs/source/topics/test-servers.rst b/docs/source/topics/test-servers.rst deleted file mode 100644 index 1ccfe286c8..0000000000 --- a/docs/source/topics/test-servers.rst +++ /dev/null @@ -1,41 +0,0 @@ -Test Servers -============ - -If you wish to test your application in a separate environment, Pyrogram is able to authorize your account into -Telegram's test servers without hassle. All you need to do is start a new session (e.g.: "my_account_test") using -``test_mode=True``: - -.. code-block:: python - - from pyrogram import Client - - async with Client("my_account_test", test_mode=True) as app: - print(await app.get_me()) - -.. note:: - - If this is the first time you login into test servers, you will be asked to register your account first. - Accounts registered on test servers reside in a different, parallel instance of a Telegram server. - -.. contents:: Contents - :backlinks: none - :depth: 1 - :local: - ------ - -Test Mode in Official Apps --------------------------- - -You can also login yourself into test servers using official desktop apps, such as Telegram Web and Telegram Desktop: - -- **Telegram Web**: Login here: https://web.telegram.org/?test=1 -- **Telegram Desktop**: Hold ``Alt+Shift`` and right click on "Add account", then choose "Test server". - -Test Numbers ------------- - -Beside normal numbers, the test environment allows you to login with reserved test numbers. -Valid phone numbers follow the pattern ``99966XYYYY``, where ``X`` is the DC number (1 to 3) and ``YYYY`` are random -numbers. Users with such numbers always get ``XXXXX`` or ``XXXXXX`` as the confirmation code (the DC number, repeated -five or six times). \ No newline at end of file diff --git a/docs/source/topics/text-formatting.rst b/docs/source/topics/text-formatting.rst deleted file mode 100644 index 00aa0cf8c8..0000000000 --- a/docs/source/topics/text-formatting.rst +++ /dev/null @@ -1,243 +0,0 @@ -Text Formatting -=============== - -.. role:: strike - :class: strike - -.. role:: underline - :class: underline - -.. role:: bold-underline - :class: bold-underline - -.. role:: strike-italic - :class: strike-italic - -Pyrogram uses a custom Markdown dialect for text formatting which adds some unique features that make writing styled -texts easier in both Markdown and HTML. You can send sophisticated text messages and media captions using a -variety of decorations that can also be nested in order to combine multiple styles together. - -.. contents:: Contents - :backlinks: none - :depth: 1 - :local: - ------ - -Basic Styles ------------- - -When formatting your messages, you can choose between Markdown-style, HTML-style or both (default). The following is a -list of the basic styles currently supported by Pyrogram. - -- **bold** -- *italic* -- :strike:`strike` -- :underline:`underline` -- spoiler -- `text URL `_ -- `user text mention `_ -- ``inline fixed-width code`` -- .. code-block:: text - - pre-formatted - fixed-width - code block - -Markdown Style --------------- - -To strictly use this mode, pass :obj:`~pyrogram.enums.ParseMode.MARKDOWN` to the *parse_mode* parameter when using -:meth:`~pyrogram.Client.send_message`. Use the following syntax in your message: - -.. code-block:: text - - **bold** - - __italic__ - - --underline-- - - ~~strike~~ - - ||spoiler|| - - [text URL](https://pyrogram.org/) - - [text user mention](tg://user?id=123456789) - - `inline fixed-width code` - - ``` - pre-formatted - fixed-width - code block - ``` - -**Example**: - -.. code-block:: python - - from pyrogram import enums - - await app.send_message( - "me", - ( - "**bold**, " - "__italic__, " - "--underline--, " - "~~strike~~, " - "||spoiler||, " - "[URL](https://pyrogram.org), " - "`code`, " - "```" - "for i in range(10):\n" - " print(i)" - "```" - ), - parse_mode=enums.ParseMode.MARKDOWN - ) - -HTML Style ----------- - -To strictly use this mode, pass :obj:`~pyrogram.enums.HTML` to the *parse_mode* parameter when using -:meth:`~pyrogram.Client.send_message`. The following tags are currently supported: - -.. code-block:: text - - bold, bold - - italic, italic - - underline - - strike, strike, strike - - spoiler - - text URL - - inline mention - - inline fixed-width code - - 🔥 - -
-    pre-formatted
-      fixed-width
-        code block
-    
- -**Example**: - -.. code-block:: python - - from pyrogram import enums - - await app.send_message( - "me", - ( - "bold, " - "italic, " - "underline, " - "strike, " - "spoiler, " - "URL, " - "code\n\n" - "
"
-            "for i in range(10):\n"
-            "    print(i)"
-            "
" - ), - parse_mode=enums.ParseMode.HTML - ) - -.. note:: - - All ``<``, ``>`` and ``&`` symbols that are not a part of a tag or an HTML entity must be replaced with the - corresponding HTML entities (``<`` with ``<``, ``>`` with ``>`` and ``&`` with ``&``). You can use this - snippet to quickly escape those characters: - - .. code-block:: python - - import html - - text = "" - text = html.escape(text) - - print(text) - - .. code-block:: text - - <my text> - -Different Styles ----------------- - -By default, when ignoring the *parse_mode* parameter, both Markdown and HTML styles are enabled together. -This means you can combine together both syntaxes in the same text: - -.. code-block:: python - - await app.send_message("me", "**bold**, italic") - -Result: - - **bold**, *italic* - -If you don't like this behaviour you can always choose to only enable either Markdown or HTML in strict mode by passing -:obj:`~pyrogram.enums.MARKDOWN` or :obj:`~pyrogram.enums.HTML` as argument to the *parse_mode* parameter. - -.. code-block:: python - - from pyrogram import enums - - await app.send_message("me", "**bold**, italic", parse_mode=enums.ParseMode.MARKDOWN) - await app.send_message("me", "**bold**, italic", parse_mode=enums.ParseMode.HTML) - -Result: - - **bold**, italic - - \*\*bold**, *italic* - -In case you want to completely turn off the style parser, simply pass :obj:`~pyrogram.enums.DISABLED` to *parse_mode*. -The text will be sent as-is. - -.. code-block:: python - - from pyrogram import enums - - await app.send_message("me", "**bold**, italic", parse_mode=enums.ParseMode.DISABLED) - -Result: - - \*\*bold**, italic - -Nested and Overlapping Entities -------------------------------- - -You can also style texts with more than one decoration at once by nesting entities together. For example, you can send -a text message with both :bold-underline:`bold and underline` styles, or a text that has both :strike-italic:`italic and -strike` styles, and you can still combine both Markdown and HTML together. - -Here there are some example texts you can try sending: - -**Markdown**: - -- ``**bold, --underline--**`` -- ``**bold __italic --underline ~~strike~~--__**`` -- ``**bold __and** italic__`` - -**HTML**: - -- ``bold, underline`` -- ``bold italic underline strike`` -- ``bold and italic`` - -**Combined**: - -- ``--you can combine HTML with **Markdown**--`` -- ``**and also overlap** --entities this way--`` diff --git a/docs/source/topics/use-filters.rst b/docs/source/topics/use-filters.rst deleted file mode 100644 index ab7296af85..0000000000 --- a/docs/source/topics/use-filters.rst +++ /dev/null @@ -1,114 +0,0 @@ -Using Filters -============= - -So far we've seen :doc:`how to register a callback function <../start/updates>` that executes every time an update comes -from the server, but there's much more than that to come. - -Here we'll discuss about :obj:`~pyrogram.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. - -.. contents:: Contents - :backlinks: none - :depth: 1 - :local: - ------ - -Single Filters --------------- - -Let's start right away with a simple example: - -- This example will show you how to **only** handle messages containing a :class:`~pyrogram.types.Sticker` object and - ignore any other message. Filters are passed as the first argument of the decorator: - - .. code-block:: python - - from pyrogram import filters - - - @app.on_message(filters.sticker) - async def my_handler(client, message): - print(message) - -- or, without decorators. Here filters are passed as the second argument of the handler constructor; the first is the - callback function itself: - - .. code-block:: python - - from pyrogram import filters - from pyrogram.handlers import MessageHandler - - - async def my_handler(client, message): - print(message) - - - app.add_handler(MessageHandler(my_handler, filters.sticker)) - -Combining Filters ------------------ - -Filters can be used in a more advanced way by inverting and combining more filters together using bitwise -operators ``~``, ``&`` and ``|``: - -- Use ``~`` to invert a filter (behaves like the ``not`` operator). -- Use ``&`` and ``|`` to merge two filters (behave like ``and``, ``or`` operators respectively). - -Here are some examples: - -- Message is a **text** message **or** a **photo**. - - .. code-block:: python - - @app.on_message(filters.text | filters.photo) - async def my_handler(client, message): - print(message) - -- Message is a **sticker** **and** is coming from a **channel or** a **private** chat. - - .. code-block:: python - - @app.on_message(filters.sticker & (filters.channel | filters.private)) - async def my_handler(client, message): - print(message) - -Advanced Filters ----------------- - -Some filters, like :meth:`~pyrogram.filters.command` or :meth:`~pyrogram.filters.regex` -can also accept arguments: - -- Message is either a */start* or */help* **command**. - - .. code-block:: python - - @app.on_message(filters.command(["start", "help"])) - async def my_handler(client, message): - print(message) - -- Message is a **text** message or a media **caption** matching the given **regex** pattern. - - .. code-block:: python - - @app.on_message(filters.regex("pyrogram")) - async def my_handler(client, message): - print(message) - -More handlers using different filters can also live together. - -.. code-block:: python - - @app.on_message(filters.command("start")) - async def start_command(client, message): - print("This is the /start command") - - - @app.on_message(filters.command("help")) - async def help_command(client, message): - print("This is the /help command") - - - @app.on_message(filters.chat("PyrogramChat")) - async def from_pyrogramchat(client, message): - print("New message in @PyrogramChat") diff --git a/docs/source/topics/voice-calls.rst b/docs/source/topics/voice-calls.rst deleted file mode 100644 index aef4030ca8..0000000000 --- a/docs/source/topics/voice-calls.rst +++ /dev/null @@ -1,19 +0,0 @@ -Voice Calls -=========== - -Both private voice calls and group voice calls are currently supported by third-party, external libraries that integrate -with Pyrogram. - -Libraries ---------- - -There are currently two main libraries (with very similar names) you can use: - -1. https://github.com/pytgcalls/pytgcalls -2. https://github.com/MarshalX/tgcalls - -Older implementations ---------------------- - -An older implementation of Telegram voice calls can be found at https://github.com/bakatrouble/pylibtgvoip (currently -outdated due to the deprecation of the Telegram VoIP library used underneath). \ No newline at end of file From bb44f3624791e770d47efc7550d1a70c869a016e Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 14 Oct 2022 11:54:26 +0200 Subject: [PATCH 1043/1185] Add usable-by labels for methods --- pyrogram/methods/advanced/invoke.py | 2 ++ pyrogram/methods/advanced/resolve_peer.py | 2 ++ pyrogram/methods/advanced/save_file.py | 2 ++ pyrogram/methods/auth/accept_terms_of_service.py | 2 ++ pyrogram/methods/auth/check_password.py | 2 ++ pyrogram/methods/auth/get_password_hint.py | 2 ++ pyrogram/methods/auth/log_out.py | 2 ++ pyrogram/methods/auth/recover_password.py | 2 ++ pyrogram/methods/auth/resend_code.py | 2 ++ pyrogram/methods/auth/send_code.py | 2 ++ pyrogram/methods/auth/send_recovery_code.py | 2 ++ pyrogram/methods/auth/sign_in.py | 2 ++ pyrogram/methods/auth/sign_in_bot.py | 2 ++ pyrogram/methods/auth/sign_up.py | 2 ++ pyrogram/methods/bots/answer_callback_query.py | 2 ++ pyrogram/methods/bots/answer_inline_query.py | 2 ++ pyrogram/methods/bots/answer_web_app_query.py | 2 ++ pyrogram/methods/bots/delete_bot_commands.py | 2 ++ pyrogram/methods/bots/get_bot_commands.py | 2 ++ pyrogram/methods/bots/get_bot_default_privileges.py | 2 ++ pyrogram/methods/bots/get_chat_menu_button.py | 2 ++ pyrogram/methods/bots/get_game_high_scores.py | 2 ++ pyrogram/methods/bots/get_inline_bot_results.py | 2 ++ pyrogram/methods/bots/request_callback_answer.py | 2 ++ pyrogram/methods/bots/send_game.py | 2 ++ pyrogram/methods/bots/send_inline_bot_result.py | 2 ++ pyrogram/methods/bots/set_bot_commands.py | 2 ++ pyrogram/methods/bots/set_bot_default_privileges.py | 2 ++ pyrogram/methods/bots/set_chat_menu_button.py | 2 ++ pyrogram/methods/bots/set_game_score.py | 2 ++ pyrogram/methods/chats/add_chat_members.py | 2 ++ pyrogram/methods/chats/archive_chats.py | 2 ++ pyrogram/methods/chats/ban_chat_member.py | 2 ++ pyrogram/methods/chats/create_channel.py | 2 ++ pyrogram/methods/chats/create_group.py | 2 ++ pyrogram/methods/chats/create_supergroup.py | 2 ++ pyrogram/methods/chats/delete_channel.py | 2 ++ pyrogram/methods/chats/delete_chat_photo.py | 2 ++ pyrogram/methods/chats/delete_supergroup.py | 2 ++ pyrogram/methods/chats/delete_user_history.py | 2 ++ pyrogram/methods/chats/get_chat.py | 2 ++ pyrogram/methods/chats/get_chat_event_log.py | 4 +++- pyrogram/methods/chats/get_chat_member.py | 2 ++ pyrogram/methods/chats/get_chat_members.py | 2 ++ pyrogram/methods/chats/get_chat_members_count.py | 2 ++ pyrogram/methods/chats/get_chat_online_count.py | 2 ++ pyrogram/methods/chats/get_dialogs.py | 2 ++ pyrogram/methods/chats/get_dialogs_count.py | 9 ++++++--- pyrogram/methods/chats/get_nearby_chats.py | 2 ++ pyrogram/methods/chats/get_send_as_chats.py | 2 ++ pyrogram/methods/chats/join_chat.py | 2 ++ pyrogram/methods/chats/leave_chat.py | 2 ++ pyrogram/methods/chats/mark_chat_unread.py | 2 ++ pyrogram/methods/chats/pin_chat_message.py | 2 ++ pyrogram/methods/chats/promote_chat_member.py | 2 ++ pyrogram/methods/chats/restrict_chat_member.py | 2 ++ pyrogram/methods/chats/set_administrator_title.py | 2 ++ pyrogram/methods/chats/set_chat_description.py | 2 ++ pyrogram/methods/chats/set_chat_permissions.py | 2 ++ pyrogram/methods/chats/set_chat_photo.py | 2 ++ pyrogram/methods/chats/set_chat_protected_content.py | 2 ++ pyrogram/methods/chats/set_chat_title.py | 2 ++ pyrogram/methods/chats/set_chat_username.py | 2 ++ pyrogram/methods/chats/set_send_as_chat.py | 2 ++ pyrogram/methods/chats/set_slow_mode.py | 2 ++ pyrogram/methods/chats/unarchive_chats.py | 2 ++ pyrogram/methods/chats/unban_chat_member.py | 2 ++ pyrogram/methods/chats/unpin_all_chat_messages.py | 2 ++ pyrogram/methods/chats/unpin_chat_message.py | 2 ++ pyrogram/methods/contacts/add_contact.py | 2 ++ pyrogram/methods/contacts/delete_contacts.py | 2 ++ pyrogram/methods/contacts/get_contacts.py | 2 ++ pyrogram/methods/contacts/get_contacts_count.py | 2 ++ pyrogram/methods/contacts/import_contacts.py | 2 ++ .../invite_links/approve_all_chat_join_requests.py | 2 ++ .../methods/invite_links/approve_chat_join_request.py | 4 +++- pyrogram/methods/invite_links/create_chat_invite_link.py | 2 ++ .../invite_links/decline_all_chat_join_requests.py | 2 ++ .../methods/invite_links/decline_chat_join_request.py | 4 +++- .../invite_links/delete_chat_admin_invite_links.py | 2 ++ pyrogram/methods/invite_links/delete_chat_invite_link.py | 2 ++ pyrogram/methods/invite_links/edit_chat_invite_link.py | 2 ++ pyrogram/methods/invite_links/export_chat_invite_link.py | 2 ++ .../methods/invite_links/get_chat_admin_invite_links.py | 2 ++ .../invite_links/get_chat_admin_invite_links_count.py | 2 ++ .../invite_links/get_chat_admins_with_invite_links.py | 4 +++- pyrogram/methods/invite_links/get_chat_invite_link.py | 2 ++ .../methods/invite_links/get_chat_invite_link_joiners.py | 2 ++ .../invite_links/get_chat_invite_link_joiners_count.py | 2 ++ pyrogram/methods/invite_links/get_chat_join_requests.py | 2 ++ pyrogram/methods/invite_links/revoke_chat_invite_link.py | 2 ++ pyrogram/methods/messages/copy_media_group.py | 2 ++ pyrogram/methods/messages/copy_message.py | 2 ++ pyrogram/methods/messages/delete_messages.py | 2 ++ pyrogram/methods/messages/download_media.py | 2 ++ pyrogram/methods/messages/edit_inline_caption.py | 2 ++ pyrogram/methods/messages/edit_inline_media.py | 2 ++ pyrogram/methods/messages/edit_inline_reply_markup.py | 2 ++ pyrogram/methods/messages/edit_inline_text.py | 2 ++ pyrogram/methods/messages/edit_message_caption.py | 2 ++ pyrogram/methods/messages/edit_message_media.py | 2 ++ pyrogram/methods/messages/edit_message_reply_markup.py | 2 ++ pyrogram/methods/messages/edit_message_text.py | 2 ++ pyrogram/methods/messages/forward_messages.py | 2 ++ pyrogram/methods/messages/get_chat_history.py | 2 ++ pyrogram/methods/messages/get_chat_history_count.py | 2 ++ pyrogram/methods/messages/get_custom_emoji_stickers.py | 2 ++ pyrogram/methods/messages/get_discussion_message.py | 2 ++ pyrogram/methods/messages/get_discussion_replies.py | 2 ++ .../methods/messages/get_discussion_replies_count.py | 2 ++ pyrogram/methods/messages/get_media_group.py | 4 +++- pyrogram/methods/messages/get_messages.py | 2 ++ pyrogram/methods/messages/read_chat_history.py | 2 ++ pyrogram/methods/messages/retract_vote.py | 2 ++ pyrogram/methods/messages/search_global.py | 2 ++ pyrogram/methods/messages/search_global_count.py | 2 ++ pyrogram/methods/messages/search_messages.py | 2 ++ pyrogram/methods/messages/search_messages_count.py | 2 ++ pyrogram/methods/messages/send_animation.py | 2 ++ pyrogram/methods/messages/send_audio.py | 2 ++ pyrogram/methods/messages/send_cached_media.py | 2 ++ pyrogram/methods/messages/send_chat_action.py | 2 ++ pyrogram/methods/messages/send_contact.py | 2 ++ pyrogram/methods/messages/send_dice.py | 2 ++ pyrogram/methods/messages/send_document.py | 2 ++ pyrogram/methods/messages/send_location.py | 2 ++ pyrogram/methods/messages/send_media_group.py | 2 ++ pyrogram/methods/messages/send_message.py | 2 ++ pyrogram/methods/messages/send_photo.py | 2 ++ pyrogram/methods/messages/send_poll.py | 2 ++ pyrogram/methods/messages/send_reaction.py | 2 ++ pyrogram/methods/messages/send_sticker.py | 2 ++ pyrogram/methods/messages/send_venue.py | 2 ++ pyrogram/methods/messages/send_video.py | 2 ++ pyrogram/methods/messages/send_video_note.py | 2 ++ pyrogram/methods/messages/send_voice.py | 2 ++ pyrogram/methods/messages/stop_poll.py | 2 ++ pyrogram/methods/messages/stream_media.py | 2 ++ pyrogram/methods/messages/vote_poll.py | 2 ++ pyrogram/methods/password/change_cloud_password.py | 2 ++ pyrogram/methods/password/enable_cloud_password.py | 2 ++ pyrogram/methods/password/remove_cloud_password.py | 2 ++ pyrogram/methods/users/block_user.py | 2 ++ pyrogram/methods/users/delete_profile_photos.py | 2 ++ pyrogram/methods/users/get_chat_photos.py | 2 ++ pyrogram/methods/users/get_chat_photos_count.py | 2 ++ pyrogram/methods/users/get_common_chats.py | 2 ++ pyrogram/methods/users/get_default_emoji_statuses.py | 2 ++ pyrogram/methods/users/get_me.py | 2 ++ pyrogram/methods/users/get_users.py | 2 ++ pyrogram/methods/users/set_emoji_status.py | 2 ++ pyrogram/methods/users/set_profile_photo.py | 2 ++ pyrogram/methods/users/set_username.py | 2 ++ pyrogram/methods/users/unblock_user.py | 2 ++ pyrogram/methods/users/update_profile.py | 2 ++ 155 files changed, 319 insertions(+), 8 deletions(-) diff --git a/pyrogram/methods/advanced/invoke.py b/pyrogram/methods/advanced/invoke.py index 9ae25d0084..0af771adc8 100644 --- a/pyrogram/methods/advanced/invoke.py +++ b/pyrogram/methods/advanced/invoke.py @@ -46,6 +46,8 @@ async def invoke( :obj:`functions ` (i.e: a Telegram API method you wish to use which is not available yet in the Client class as an easy-to-use method). + .. include:: /_includes/usable-by/users-bots.rst + Parameters: query (``RawFunction``): The API Schema function filled with proper arguments. diff --git a/pyrogram/methods/advanced/resolve_peer.py b/pyrogram/methods/advanced/resolve_peer.py index 80fe7975b0..ebb8ff4475 100644 --- a/pyrogram/methods/advanced/resolve_peer.py +++ b/pyrogram/methods/advanced/resolve_peer.py @@ -42,6 +42,8 @@ async def resolve_peer( :obj:`functions ` (i.e: a Telegram API method you wish to use which is not available yet in the Client class as an easy-to-use method). + .. include:: /_includes/usable-by/users-bots.rst + Parameters: peer_id (``int`` | ``str``): The peer id you want to extract the InputPeer from. diff --git a/pyrogram/methods/advanced/save_file.py b/pyrogram/methods/advanced/save_file.py index 6a43deac6f..c7f3a953e8 100644 --- a/pyrogram/methods/advanced/save_file.py +++ b/pyrogram/methods/advanced/save_file.py @@ -53,6 +53,8 @@ async def save_file( :obj:`functions ` (i.e: a Telegram API method you wish to use which is not available yet in the Client class as an easy-to-use method). + .. include:: /_includes/usable-by/users-bots.rst + Parameters: path (``str`` | ``BinaryIO``): The path of the file you want to upload that exists on your local machine or a binary file-like object diff --git a/pyrogram/methods/auth/accept_terms_of_service.py b/pyrogram/methods/auth/accept_terms_of_service.py index 3bc5fbad27..cc1fcf5453 100644 --- a/pyrogram/methods/auth/accept_terms_of_service.py +++ b/pyrogram/methods/auth/accept_terms_of_service.py @@ -27,6 +27,8 @@ async def accept_terms_of_service( ) -> bool: """Accept the given terms of service. + .. include:: /_includes/usable-by/users.rst + Parameters: terms_of_service_id (``str``): The terms of service identifier. diff --git a/pyrogram/methods/auth/check_password.py b/pyrogram/methods/auth/check_password.py index 9d8b08abfa..39aa82fcc0 100644 --- a/pyrogram/methods/auth/check_password.py +++ b/pyrogram/methods/auth/check_password.py @@ -33,6 +33,8 @@ async def check_password( ) -> "types.User": """Check your Two-Step Verification password and log in. + .. include:: /_includes/usable-by/users.rst + Parameters: password (``str``): Your Two-Step Verification password. diff --git a/pyrogram/methods/auth/get_password_hint.py b/pyrogram/methods/auth/get_password_hint.py index af6557589a..a57f7c8075 100644 --- a/pyrogram/methods/auth/get_password_hint.py +++ b/pyrogram/methods/auth/get_password_hint.py @@ -30,6 +30,8 @@ async def get_password_hint( ) -> str: """Get your Two-Step Verification password hint. + .. include:: /_includes/usable-by/users.rst + Returns: ``str``: On success, the password hint as string is returned. """ diff --git a/pyrogram/methods/auth/log_out.py b/pyrogram/methods/auth/log_out.py index b4a29f8273..84b7db64cd 100644 --- a/pyrogram/methods/auth/log_out.py +++ b/pyrogram/methods/auth/log_out.py @@ -33,6 +33,8 @@ async def log_out( When you log out, the current client is stopped and the storage session deleted. No more API calls can be made until you start the client and re-authorize again. + .. include:: /_includes/usable-by/users-bots.rst + Returns: ``bool``: On success, True is returned. diff --git a/pyrogram/methods/auth/recover_password.py b/pyrogram/methods/auth/recover_password.py index 9f75a93f79..0a34750752 100644 --- a/pyrogram/methods/auth/recover_password.py +++ b/pyrogram/methods/auth/recover_password.py @@ -32,6 +32,8 @@ async def recover_password( ) -> "types.User": """Recover your password with a recovery code and log in. + .. include:: /_includes/usable-by/users.rst + Parameters: recovery_code (``str``): The recovery code sent via email. diff --git a/pyrogram/methods/auth/resend_code.py b/pyrogram/methods/auth/resend_code.py index 4210e04d9b..9644ac4f54 100644 --- a/pyrogram/methods/auth/resend_code.py +++ b/pyrogram/methods/auth/resend_code.py @@ -36,6 +36,8 @@ async def resend_code( The type of the code to be re-sent is specified in the *next_type* attribute of the :obj:`~pyrogram.types.SentCode` object returned by :meth:`send_code`. + .. include:: /_includes/usable-by/users.rst + Parameters: phone_number (``str``): Phone number in international format (includes the country prefix). diff --git a/pyrogram/methods/auth/send_code.py b/pyrogram/methods/auth/send_code.py index 5f7f70915d..92ffc99996 100644 --- a/pyrogram/methods/auth/send_code.py +++ b/pyrogram/methods/auth/send_code.py @@ -34,6 +34,8 @@ async def send_code( ) -> "types.SentCode": """Send the confirmation code to the given phone number. + .. include:: /_includes/usable-by/users.rst + Parameters: phone_number (``str``): Phone number in international format (includes the country prefix). diff --git a/pyrogram/methods/auth/send_recovery_code.py b/pyrogram/methods/auth/send_recovery_code.py index d1f23bf965..9a25f3c68f 100644 --- a/pyrogram/methods/auth/send_recovery_code.py +++ b/pyrogram/methods/auth/send_recovery_code.py @@ -30,6 +30,8 @@ async def send_recovery_code( ) -> str: """Send a code to your email to recover your password. + .. include:: /_includes/usable-by/users.rst + Returns: ``str``: On success, the hidden email pattern is returned and a recovery code is sent to that email. diff --git a/pyrogram/methods/auth/sign_in.py b/pyrogram/methods/auth/sign_in.py index c328c95850..9d77f1cd37 100644 --- a/pyrogram/methods/auth/sign_in.py +++ b/pyrogram/methods/auth/sign_in.py @@ -35,6 +35,8 @@ async def sign_in( ) -> Union["types.User", "types.TermsOfService", bool]: """Authorize a user in Telegram with a valid confirmation code. + .. include:: /_includes/usable-by/users.rst + Parameters: phone_number (``str``): Phone number in international format (includes the country prefix). diff --git a/pyrogram/methods/auth/sign_in_bot.py b/pyrogram/methods/auth/sign_in_bot.py index dd322a8e66..09a2f28379 100644 --- a/pyrogram/methods/auth/sign_in_bot.py +++ b/pyrogram/methods/auth/sign_in_bot.py @@ -34,6 +34,8 @@ async def sign_in_bot( ) -> "types.User": """Authorize a bot using its bot token generated by BotFather. + .. include:: /_includes/usable-by/bots.rst + Parameters: bot_token (``str``): The bot token generated by BotFather diff --git a/pyrogram/methods/auth/sign_up.py b/pyrogram/methods/auth/sign_up.py index 6700fee481..f09a779d8a 100644 --- a/pyrogram/methods/auth/sign_up.py +++ b/pyrogram/methods/auth/sign_up.py @@ -35,6 +35,8 @@ async def sign_up( ) -> "types.User": """Register a new user in Telegram. + .. include:: /_includes/usable-by/users.rst + Parameters: phone_number (``str``): Phone number in international format (includes the country prefix). diff --git a/pyrogram/methods/bots/answer_callback_query.py b/pyrogram/methods/bots/answer_callback_query.py index 95d479314a..a6d8747cd5 100644 --- a/pyrogram/methods/bots/answer_callback_query.py +++ b/pyrogram/methods/bots/answer_callback_query.py @@ -32,6 +32,8 @@ async def answer_callback_query( """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. + .. include:: /_includes/usable-by/bots.rst + Parameters: callback_query_id (``str``): Unique identifier for the query to be answered. diff --git a/pyrogram/methods/bots/answer_inline_query.py b/pyrogram/methods/bots/answer_inline_query.py index e9acd9375d..c3a450a015 100644 --- a/pyrogram/methods/bots/answer_inline_query.py +++ b/pyrogram/methods/bots/answer_inline_query.py @@ -39,6 +39,8 @@ async def answer_inline_query( A maximum of 50 results per query is allowed. + .. include:: /_includes/usable-by/bots.rst + Parameters: inline_query_id (``str``): Unique identifier for the answered query. diff --git a/pyrogram/methods/bots/answer_web_app_query.py b/pyrogram/methods/bots/answer_web_app_query.py index f887e28098..74f56079bb 100644 --- a/pyrogram/methods/bots/answer_web_app_query.py +++ b/pyrogram/methods/bots/answer_web_app_query.py @@ -30,6 +30,8 @@ async def answer_web_app_query( """Set the result of an interaction with a `Web App `_ and send a corresponding message on behalf of the user to the chat from which the query originated. + .. include:: /_includes/usable-by/bots.rst + Parameters: web_app_query_id (``str``): Unique identifier for the answered query. diff --git a/pyrogram/methods/bots/delete_bot_commands.py b/pyrogram/methods/bots/delete_bot_commands.py index 2e638aed7f..e8173d32d9 100644 --- a/pyrogram/methods/bots/delete_bot_commands.py +++ b/pyrogram/methods/bots/delete_bot_commands.py @@ -32,6 +32,8 @@ async def delete_bot_commands( The commands passed will overwrite any command set previously. This method can be used by the own bot only. + .. include:: /_includes/usable-by/bots.rst + Parameters: scope (:obj:`~pyrogram.types.BotCommandScope`, *optional*): An object describing the scope of users for which the commands are relevant. diff --git a/pyrogram/methods/bots/get_bot_commands.py b/pyrogram/methods/bots/get_bot_commands.py index f62b63754a..78deff30fd 100644 --- a/pyrogram/methods/bots/get_bot_commands.py +++ b/pyrogram/methods/bots/get_bot_commands.py @@ -34,6 +34,8 @@ async def get_bot_commands( The commands passed will overwrite any command set previously. This method can be used by the own bot only. + .. include:: /_includes/usable-by/bots.rst + Parameters: scope (:obj:`~pyrogram.types.BotCommandScope`, *optional*): An object describing the scope of users for which the commands are relevant. diff --git a/pyrogram/methods/bots/get_bot_default_privileges.py b/pyrogram/methods/bots/get_bot_default_privileges.py index e1f70fa1e8..217d9b4384 100644 --- a/pyrogram/methods/bots/get_bot_default_privileges.py +++ b/pyrogram/methods/bots/get_bot_default_privileges.py @@ -30,6 +30,8 @@ async def get_bot_default_privileges( ) -> Optional["types.ChatPrivileges"]: """Get the current default privileges of the bot. + .. include:: /_includes/usable-by/bots.rst + Parameters: for_channels (``bool``, *optional*): Pass True to get default privileges of the bot in channels. Otherwise, default privileges of the bot diff --git a/pyrogram/methods/bots/get_chat_menu_button.py b/pyrogram/methods/bots/get_chat_menu_button.py index 8fb61b0173..9d143d5819 100644 --- a/pyrogram/methods/bots/get_chat_menu_button.py +++ b/pyrogram/methods/bots/get_chat_menu_button.py @@ -30,6 +30,8 @@ async def get_chat_menu_button( ) -> "types.MenuButton": """Get the current value of the bot's menu button in a private chat, or the default menu button. + .. include:: /_includes/usable-by/bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/bots/get_game_high_scores.py b/pyrogram/methods/bots/get_game_high_scores.py index 555e9af5e3..068ecab889 100644 --- a/pyrogram/methods/bots/get_game_high_scores.py +++ b/pyrogram/methods/bots/get_game_high_scores.py @@ -32,6 +32,8 @@ async def get_game_high_scores( ) -> List["types.GameHighScore"]: """Get data for high score tables. + .. include:: /_includes/usable-by/bots.rst + Parameters: user_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/bots/get_inline_bot_results.py b/pyrogram/methods/bots/get_inline_bot_results.py index 3ef1d4dea0..7cb0aa127d 100644 --- a/pyrogram/methods/bots/get_inline_bot_results.py +++ b/pyrogram/methods/bots/get_inline_bot_results.py @@ -35,6 +35,8 @@ async def get_inline_bot_results( """Get bot results via inline queries. You can then send a result using :meth:`~pyrogram.Client.send_inline_bot_result` + .. include:: /_includes/usable-by/users.rst + Parameters: bot (``int`` | ``str``): Unique identifier of the inline bot you want to get results from. You can specify diff --git a/pyrogram/methods/bots/request_callback_answer.py b/pyrogram/methods/bots/request_callback_answer.py index 9c0626a56e..22debcbe21 100644 --- a/pyrogram/methods/bots/request_callback_answer.py +++ b/pyrogram/methods/bots/request_callback_answer.py @@ -33,6 +33,8 @@ async def request_callback_answer( """Request a callback answer from bots. This is the equivalent of clicking an inline button containing callback data. + .. include:: /_includes/usable-by/users.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/bots/send_game.py b/pyrogram/methods/bots/send_game.py index e4181166f7..60155a6fe6 100644 --- a/pyrogram/methods/bots/send_game.py +++ b/pyrogram/methods/bots/send_game.py @@ -40,6 +40,8 @@ async def send_game( ) -> "types.Message": """Send a game. + .. include:: /_includes/usable-by/bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/bots/send_inline_bot_result.py b/pyrogram/methods/bots/send_inline_bot_result.py index ebe5ca8d24..0f31880636 100644 --- a/pyrogram/methods/bots/send_inline_bot_result.py +++ b/pyrogram/methods/bots/send_inline_bot_result.py @@ -34,6 +34,8 @@ async def send_inline_bot_result( """Send an inline bot result. Bot results can be retrieved using :meth:`~pyrogram.Client.get_inline_bot_results` + .. include:: /_includes/usable-by/users.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/bots/set_bot_commands.py b/pyrogram/methods/bots/set_bot_commands.py index 7dfbf10979..f6f7e6c992 100644 --- a/pyrogram/methods/bots/set_bot_commands.py +++ b/pyrogram/methods/bots/set_bot_commands.py @@ -34,6 +34,8 @@ async def set_bot_commands( The commands passed will overwrite any command set previously. This method can be used by the own bot only. + .. include:: /_includes/usable-by/bots.rst + Parameters: commands (List of :obj:`~pyrogram.types.BotCommand`): A list of bot commands. diff --git a/pyrogram/methods/bots/set_bot_default_privileges.py b/pyrogram/methods/bots/set_bot_default_privileges.py index 52e480266b..2890ee1ae1 100644 --- a/pyrogram/methods/bots/set_bot_default_privileges.py +++ b/pyrogram/methods/bots/set_bot_default_privileges.py @@ -31,6 +31,8 @@ async def set_bot_default_privileges( These privileges will be suggested to users, but they are are free to modify the list before adding the bot. + .. include:: /_includes/usable-by/bots.rst + Parameters: privileges (:obj:`~pyrogram.types.ChatPrivileges`): New default privileges. None to clear. diff --git a/pyrogram/methods/bots/set_chat_menu_button.py b/pyrogram/methods/bots/set_chat_menu_button.py index 87e26c036c..fa5af85ceb 100644 --- a/pyrogram/methods/bots/set_chat_menu_button.py +++ b/pyrogram/methods/bots/set_chat_menu_button.py @@ -31,6 +31,8 @@ async def set_chat_menu_button( ) -> bool: """Change the bot's menu button in a private chat, or the default menu button. + .. include:: /_includes/usable-by/bots.rst + Parameters: chat_id (``int`` | ``str``, *optional*): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/bots/set_game_score.py b/pyrogram/methods/bots/set_game_score.py index f9008e4435..b3b58b2362 100644 --- a/pyrogram/methods/bots/set_game_score.py +++ b/pyrogram/methods/bots/set_game_score.py @@ -36,6 +36,8 @@ async def set_game_score( # inline_message_id: str = None): TODO Add inline_message_id """Set the score of the specified user in a game. + .. include:: /_includes/usable-by/bots.rst + Parameters: user_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/chats/add_chat_members.py b/pyrogram/methods/chats/add_chat_members.py index 2bf145f907..b560a6affc 100644 --- a/pyrogram/methods/chats/add_chat_members.py +++ b/pyrogram/methods/chats/add_chat_members.py @@ -31,6 +31,8 @@ async def add_chat_members( ) -> bool: """Add new chat members to a group, supergroup or channel + .. include:: /_includes/usable-by/users.rst + Parameters: chat_id (``int`` | ``str``): The group, supergroup or channel id diff --git a/pyrogram/methods/chats/archive_chats.py b/pyrogram/methods/chats/archive_chats.py index 40544e0fdc..661b450cff 100644 --- a/pyrogram/methods/chats/archive_chats.py +++ b/pyrogram/methods/chats/archive_chats.py @@ -29,6 +29,8 @@ async def archive_chats( ) -> bool: """Archive one or more chats. + .. include:: /_includes/usable-by/users.rst + Parameters: chat_ids (``int`` | ``str`` | List[``int``, ``str``]): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/chats/ban_chat_member.py b/pyrogram/methods/chats/ban_chat_member.py index 16a1308247..635c48b5da 100644 --- a/pyrogram/methods/chats/ban_chat_member.py +++ b/pyrogram/methods/chats/ban_chat_member.py @@ -41,6 +41,8 @@ async def ban_chat_member( off in the target group. Otherwise members may only be removed by the group's creator or by the member that added them. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/chats/create_channel.py b/pyrogram/methods/chats/create_channel.py index 2a1497789d..292fa97dd7 100644 --- a/pyrogram/methods/chats/create_channel.py +++ b/pyrogram/methods/chats/create_channel.py @@ -28,6 +28,8 @@ async def create_channel( ) -> "types.Chat": """Create a new broadcast channel. + .. include:: /_includes/usable-by/users.rst + Parameters: title (``str``): The channel title. diff --git a/pyrogram/methods/chats/create_group.py b/pyrogram/methods/chats/create_group.py index a2ffa8de66..027315de2a 100644 --- a/pyrogram/methods/chats/create_group.py +++ b/pyrogram/methods/chats/create_group.py @@ -35,6 +35,8 @@ async def create_group( If you want to create a new supergroup, use :meth:`~pyrogram.Client.create_supergroup` instead. + .. include:: /_includes/usable-by/users.rst + Parameters: title (``str``): The group title. diff --git a/pyrogram/methods/chats/create_supergroup.py b/pyrogram/methods/chats/create_supergroup.py index 04da148a90..d1cbbbd890 100644 --- a/pyrogram/methods/chats/create_supergroup.py +++ b/pyrogram/methods/chats/create_supergroup.py @@ -32,6 +32,8 @@ async def create_supergroup( If you want to create a new basic group, use :meth:`~pyrogram.Client.create_group` instead. + .. include:: /_includes/usable-by/users.rst + Parameters: title (``str``): The supergroup title. diff --git a/pyrogram/methods/chats/delete_channel.py b/pyrogram/methods/chats/delete_channel.py index 7aad47d033..374d89a6f4 100644 --- a/pyrogram/methods/chats/delete_channel.py +++ b/pyrogram/methods/chats/delete_channel.py @@ -29,6 +29,8 @@ async def delete_channel( ) -> bool: """Delete a channel. + .. include:: /_includes/usable-by/users.rst + Parameters: chat_id (``int`` | ``str``): The id of the channel to be deleted. diff --git a/pyrogram/methods/chats/delete_chat_photo.py b/pyrogram/methods/chats/delete_chat_photo.py index 058b7fdb29..1984bc0cc2 100644 --- a/pyrogram/methods/chats/delete_chat_photo.py +++ b/pyrogram/methods/chats/delete_chat_photo.py @@ -31,6 +31,8 @@ async def delete_chat_photo( You must be an administrator in the chat for this to work and must have the appropriate admin rights. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/chats/delete_supergroup.py b/pyrogram/methods/chats/delete_supergroup.py index 39d5a07d91..ebd60befa8 100644 --- a/pyrogram/methods/chats/delete_supergroup.py +++ b/pyrogram/methods/chats/delete_supergroup.py @@ -29,6 +29,8 @@ async def delete_supergroup( ) -> bool: """Delete a supergroup. + .. include:: /_includes/usable-by/users.rst + Parameters: chat_id (``int`` | ``str``): The id of the supergroup to be deleted. diff --git a/pyrogram/methods/chats/delete_user_history.py b/pyrogram/methods/chats/delete_user_history.py index 4411ddbd24..7769c7c3c0 100644 --- a/pyrogram/methods/chats/delete_user_history.py +++ b/pyrogram/methods/chats/delete_user_history.py @@ -30,6 +30,8 @@ async def delete_user_history( ) -> bool: """Delete all messages sent by a certain user in a supergroup. + .. include:: /_includes/usable-by/users.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/chats/get_chat.py b/pyrogram/methods/chats/get_chat.py index d3246447a7..e2289935a9 100644 --- a/pyrogram/methods/chats/get_chat.py +++ b/pyrogram/methods/chats/get_chat.py @@ -34,6 +34,8 @@ async def get_chat( Information include current name of the user for one-on-one conversations, current username of a user, group or channel, etc. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/chats/get_chat_event_log.py b/pyrogram/methods/chats/get_chat_event_log.py index 622fe0bc0f..06fa01c849 100644 --- a/pyrogram/methods/chats/get_chat_event_log.py +++ b/pyrogram/methods/chats/get_chat_event_log.py @@ -38,7 +38,9 @@ async def get_chat_event_log( Only available for supergroups and channels. Requires administrator rights. Results are returned in reverse chronological order (i.e., newest first). - Args: + .. include:: /_includes/usable-by/users.rst + + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/chats/get_chat_member.py b/pyrogram/methods/chats/get_chat_member.py index 526cf744ca..53d2faad83 100644 --- a/pyrogram/methods/chats/get_chat_member.py +++ b/pyrogram/methods/chats/get_chat_member.py @@ -32,6 +32,8 @@ async def get_chat_member( ) -> "types.ChatMember": """Get information about one member of a chat. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/chats/get_chat_members.py b/pyrogram/methods/chats/get_chat_members.py index 3e11872cb1..49fb0a094d 100644 --- a/pyrogram/methods/chats/get_chat_members.py +++ b/pyrogram/methods/chats/get_chat_members.py @@ -70,6 +70,8 @@ async def get_chat_members( A chat can be either a basic group, a supergroup or a channel. Requires administrator rights in channels. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/chats/get_chat_members_count.py b/pyrogram/methods/chats/get_chat_members_count.py index e2f7fa8b92..2680702833 100644 --- a/pyrogram/methods/chats/get_chat_members_count.py +++ b/pyrogram/methods/chats/get_chat_members_count.py @@ -29,6 +29,8 @@ async def get_chat_members_count( ) -> int: """Get the number of members in a chat. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/chats/get_chat_online_count.py b/pyrogram/methods/chats/get_chat_online_count.py index 45c60d978b..6ecd5727dd 100644 --- a/pyrogram/methods/chats/get_chat_online_count.py +++ b/pyrogram/methods/chats/get_chat_online_count.py @@ -29,6 +29,8 @@ async def get_chat_online_count( ) -> int: """Get the number of members that are currently online in a chat. + .. include:: /_includes/usable-by/users.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/chats/get_dialogs.py b/pyrogram/methods/chats/get_dialogs.py index 2869a6a1ed..3509ccbcca 100644 --- a/pyrogram/methods/chats/get_dialogs.py +++ b/pyrogram/methods/chats/get_dialogs.py @@ -29,6 +29,8 @@ async def get_dialogs( ) -> Optional[AsyncGenerator["types.Dialog", None]]: """Get a user's dialogs sequentially. + .. include:: /_includes/usable-by/users.rst + Parameters: limit (``int``, *optional*): Limits the number of dialogs to be retrieved. diff --git a/pyrogram/methods/chats/get_dialogs_count.py b/pyrogram/methods/chats/get_dialogs_count.py index 3d96732cf1..ae22eb5cff 100644 --- a/pyrogram/methods/chats/get_dialogs_count.py +++ b/pyrogram/methods/chats/get_dialogs_count.py @@ -27,9 +27,12 @@ async def get_dialogs_count( ) -> int: """Get the total count of your dialogs. - pinned_only (``bool``, *optional*): - Pass True if you want to count only pinned dialogs. - Defaults to False. + .. include:: /_includes/usable-by/users.rst + + Parameters: + pinned_only (``bool``, *optional*): + Pass True if you want to count only pinned dialogs. + Defaults to False. Returns: ``int``: On success, the dialogs count is returned. diff --git a/pyrogram/methods/chats/get_nearby_chats.py b/pyrogram/methods/chats/get_nearby_chats.py index 2ebd930644..8d1163ac9d 100644 --- a/pyrogram/methods/chats/get_nearby_chats.py +++ b/pyrogram/methods/chats/get_nearby_chats.py @@ -32,6 +32,8 @@ async def get_nearby_chats( ) -> List["types.Chat"]: """Get nearby chats. + .. include:: /_includes/usable-by/users.rst + Parameters: latitude (``float``): Latitude of the location. diff --git a/pyrogram/methods/chats/get_send_as_chats.py b/pyrogram/methods/chats/get_send_as_chats.py index 2ad3b8b561..c9a358c398 100644 --- a/pyrogram/methods/chats/get_send_as_chats.py +++ b/pyrogram/methods/chats/get_send_as_chats.py @@ -30,6 +30,8 @@ async def get_send_as_chats( ) -> List["types.Chat"]: """Get the list of "send_as" chats available. + .. include:: /_includes/usable-by/users.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/chats/join_chat.py b/pyrogram/methods/chats/join_chat.py index 07d7b2a7e2..2ef82cff31 100644 --- a/pyrogram/methods/chats/join_chat.py +++ b/pyrogram/methods/chats/join_chat.py @@ -30,6 +30,8 @@ async def join_chat( ) -> "types.Chat": """Join a group chat or channel. + .. include:: /_includes/usable-by/users.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier for the target chat in form of a *t.me/joinchat/* link, a username of the target diff --git a/pyrogram/methods/chats/leave_chat.py b/pyrogram/methods/chats/leave_chat.py index 942173f469..b974e38771 100644 --- a/pyrogram/methods/chats/leave_chat.py +++ b/pyrogram/methods/chats/leave_chat.py @@ -30,6 +30,8 @@ async def leave_chat( ): """Leave a group chat or channel. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier for the target chat or username of the target channel/supergroup diff --git a/pyrogram/methods/chats/mark_chat_unread.py b/pyrogram/methods/chats/mark_chat_unread.py index 62cb8bee31..45aec2f878 100644 --- a/pyrogram/methods/chats/mark_chat_unread.py +++ b/pyrogram/methods/chats/mark_chat_unread.py @@ -29,6 +29,8 @@ async def mark_chat_unread( ) -> bool: """Mark a chat as unread. + .. include:: /_includes/usable-by/users.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/chats/pin_chat_message.py b/pyrogram/methods/chats/pin_chat_message.py index 0eaa152513..8ec06e7b13 100644 --- a/pyrogram/methods/chats/pin_chat_message.py +++ b/pyrogram/methods/chats/pin_chat_message.py @@ -34,6 +34,8 @@ async def pin_chat_message( 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. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/chats/promote_chat_member.py b/pyrogram/methods/chats/promote_chat_member.py index 46ffe44681..e453903773 100644 --- a/pyrogram/methods/chats/promote_chat_member.py +++ b/pyrogram/methods/chats/promote_chat_member.py @@ -34,6 +34,8 @@ async def promote_chat_member( 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. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/chats/restrict_chat_member.py b/pyrogram/methods/chats/restrict_chat_member.py index 717ba60bd2..6e42b364ee 100644 --- a/pyrogram/methods/chats/restrict_chat_member.py +++ b/pyrogram/methods/chats/restrict_chat_member.py @@ -37,6 +37,8 @@ async def restrict_chat_member( You must be an administrator in the supergroup for this to work and must have the appropriate admin rights. Pass True for all permissions to lift restrictions from a user. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/chats/set_administrator_title.py b/pyrogram/methods/chats/set_administrator_title.py index 15f9dea5ef..2c77066ed7 100644 --- a/pyrogram/methods/chats/set_administrator_title.py +++ b/pyrogram/methods/chats/set_administrator_title.py @@ -34,6 +34,8 @@ async def set_administrator_title( If you are an administrator of a supergroup (i.e. not the owner), you can only set the title of other administrators who have been promoted by you. If you are the owner, you can change every administrator's title. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/chats/set_chat_description.py b/pyrogram/methods/chats/set_chat_description.py index 3eee92cae2..563e990c4c 100644 --- a/pyrogram/methods/chats/set_chat_description.py +++ b/pyrogram/methods/chats/set_chat_description.py @@ -31,6 +31,8 @@ async def set_chat_description( """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. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/chats/set_chat_permissions.py b/pyrogram/methods/chats/set_chat_permissions.py index d1280ea725..d8ec0cf02b 100644 --- a/pyrogram/methods/chats/set_chat_permissions.py +++ b/pyrogram/methods/chats/set_chat_permissions.py @@ -34,6 +34,8 @@ async def set_chat_permissions( You must be an administrator in the group or a supergroup for this to work and must have the *can_restrict_members* admin rights. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/chats/set_chat_photo.py b/pyrogram/methods/chats/set_chat_photo.py index 615eca253e..f3db532991 100644 --- a/pyrogram/methods/chats/set_chat_photo.py +++ b/pyrogram/methods/chats/set_chat_photo.py @@ -41,6 +41,8 @@ async def set_chat_photo( You must be an administrator in the chat for this to work and must have the appropriate admin rights. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/chats/set_chat_protected_content.py b/pyrogram/methods/chats/set_chat_protected_content.py index ee72d7228e..b6a89c1190 100644 --- a/pyrogram/methods/chats/set_chat_protected_content.py +++ b/pyrogram/methods/chats/set_chat_protected_content.py @@ -30,6 +30,8 @@ async def set_chat_protected_content( ) -> bool: """Set the chat protected content setting. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/chats/set_chat_title.py b/pyrogram/methods/chats/set_chat_title.py index e2c270e6d2..9a963571c0 100644 --- a/pyrogram/methods/chats/set_chat_title.py +++ b/pyrogram/methods/chats/set_chat_title.py @@ -36,6 +36,8 @@ async def set_chat_title( In regular groups (non-supergroups), this method will only work if the "All Members Are Admins" setting is off. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/chats/set_chat_username.py b/pyrogram/methods/chats/set_chat_username.py index 2d4bc08a88..e6d64e98c1 100644 --- a/pyrogram/methods/chats/set_chat_username.py +++ b/pyrogram/methods/chats/set_chat_username.py @@ -32,6 +32,8 @@ async def set_chat_username( To set your own username (for users only, not bots) you can use :meth:`~pyrogram.Client.set_username`. + .. include:: /_includes/usable-by/users.rst + Parameters: chat_id (``int`` | ``str``) Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/chats/set_send_as_chat.py b/pyrogram/methods/chats/set_send_as_chat.py index 4cb50aed0c..15201f3bea 100644 --- a/pyrogram/methods/chats/set_send_as_chat.py +++ b/pyrogram/methods/chats/set_send_as_chat.py @@ -32,6 +32,8 @@ async def set_send_as_chat( Use :meth:`~pyrogram.Client.get_send_as_chats` to get all the "send_as" chats available for use. + .. include:: /_includes/usable-by/users.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/chats/set_slow_mode.py b/pyrogram/methods/chats/set_slow_mode.py index ca68c87f46..60e88e6de5 100644 --- a/pyrogram/methods/chats/set_slow_mode.py +++ b/pyrogram/methods/chats/set_slow_mode.py @@ -30,6 +30,8 @@ async def set_slow_mode( ) -> bool: """Set the slow mode interval for a chat. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/chats/unarchive_chats.py b/pyrogram/methods/chats/unarchive_chats.py index c518fe912c..4d2cb0e411 100644 --- a/pyrogram/methods/chats/unarchive_chats.py +++ b/pyrogram/methods/chats/unarchive_chats.py @@ -29,6 +29,8 @@ async def unarchive_chats( ) -> bool: """Unarchive one or more chats. + .. include:: /_includes/usable-by/users.rst + Parameters: chat_ids (``int`` | ``str`` | List[``int``, ``str``]): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/chats/unban_chat_member.py b/pyrogram/methods/chats/unban_chat_member.py index 46c2241e72..bbe7b45492 100644 --- a/pyrogram/methods/chats/unban_chat_member.py +++ b/pyrogram/methods/chats/unban_chat_member.py @@ -32,6 +32,8 @@ async def unban_chat_member( 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. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/chats/unpin_all_chat_messages.py b/pyrogram/methods/chats/unpin_all_chat_messages.py index 866ca7c0b7..25a53caf62 100644 --- a/pyrogram/methods/chats/unpin_all_chat_messages.py +++ b/pyrogram/methods/chats/unpin_all_chat_messages.py @@ -31,6 +31,8 @@ async def unpin_all_chat_messages( If the chat is not a private chat, the bot must be an administrator in the chat for this to work and must have the 'can_pin_messages' admin right in a supergroup or 'can_edit_messages' admin right in a channel. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/chats/unpin_chat_message.py b/pyrogram/methods/chats/unpin_chat_message.py index 5b3768c096..6c8e036e0d 100644 --- a/pyrogram/methods/chats/unpin_chat_message.py +++ b/pyrogram/methods/chats/unpin_chat_message.py @@ -32,6 +32,8 @@ async def unpin_chat_message( 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. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/contacts/add_contact.py b/pyrogram/methods/contacts/add_contact.py index ae7566b2fd..9c4faa7e48 100644 --- a/pyrogram/methods/contacts/add_contact.py +++ b/pyrogram/methods/contacts/add_contact.py @@ -34,6 +34,8 @@ async def add_contact( ): """Add an existing Telegram user as contact, even without a phone number. + .. include:: /_includes/usable-by/users.rst + Parameters: user_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target user. diff --git a/pyrogram/methods/contacts/delete_contacts.py b/pyrogram/methods/contacts/delete_contacts.py index 448e849a22..b4b2c628a3 100644 --- a/pyrogram/methods/contacts/delete_contacts.py +++ b/pyrogram/methods/contacts/delete_contacts.py @@ -29,6 +29,8 @@ async def delete_contacts( ) -> Union["types.User", List["types.User"], None]: """Delete contacts from your Telegram address book. + .. include:: /_includes/usable-by/users.rst + Parameters: user_ids (``int`` | ``str`` | List of ``int`` or ``str``): A single user id/username o a list of user identifiers (id or username). diff --git a/pyrogram/methods/contacts/get_contacts.py b/pyrogram/methods/contacts/get_contacts.py index 110d93a321..763f9a3058 100644 --- a/pyrogram/methods/contacts/get_contacts.py +++ b/pyrogram/methods/contacts/get_contacts.py @@ -32,6 +32,8 @@ async def get_contacts( ) -> List["types.User"]: """Get contacts from your Telegram address book. + .. include:: /_includes/usable-by/users.rst + Returns: List of :obj:`~pyrogram.types.User`: On success, a list of users is returned. diff --git a/pyrogram/methods/contacts/get_contacts_count.py b/pyrogram/methods/contacts/get_contacts_count.py index a709b6769f..56120bac71 100644 --- a/pyrogram/methods/contacts/get_contacts_count.py +++ b/pyrogram/methods/contacts/get_contacts_count.py @@ -26,6 +26,8 @@ async def get_contacts_count( ) -> int: """Get the total count of contacts from your Telegram address book. + .. include:: /_includes/usable-by/users.rst + Returns: ``int``: On success, the contacts count is returned. diff --git a/pyrogram/methods/contacts/import_contacts.py b/pyrogram/methods/contacts/import_contacts.py index 11df315be8..aa8fd4ae37 100644 --- a/pyrogram/methods/contacts/import_contacts.py +++ b/pyrogram/methods/contacts/import_contacts.py @@ -30,6 +30,8 @@ async def import_contacts( ): """Import contacts to your Telegram address book. + .. include:: /_includes/usable-by/users.rst + Parameters: contacts (List of :obj:`~pyrogram.types.InputPhoneContact`): The contact list to be added diff --git a/pyrogram/methods/invite_links/approve_all_chat_join_requests.py b/pyrogram/methods/invite_links/approve_all_chat_join_requests.py index ec2fc1bc6c..06894bc896 100644 --- a/pyrogram/methods/invite_links/approve_all_chat_join_requests.py +++ b/pyrogram/methods/invite_links/approve_all_chat_join_requests.py @@ -30,6 +30,8 @@ async def approve_all_chat_join_requests( ) -> bool: """Approve all pending join requests in a chat. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier for the target chat or username of the target channel/supergroup diff --git a/pyrogram/methods/invite_links/approve_chat_join_request.py b/pyrogram/methods/invite_links/approve_chat_join_request.py index 3cd2248902..2fc4e6d309 100644 --- a/pyrogram/methods/invite_links/approve_chat_join_request.py +++ b/pyrogram/methods/invite_links/approve_chat_join_request.py @@ -30,9 +30,11 @@ async def approve_chat_join_request( ) -> bool: """Approve a chat join request. - The bot must be an administrator in the chat for this to work and must have the *can_invite_users* administrator + You must be an administrator in the chat for this to work and must have the *can_invite_users* administrator right. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier for the target chat or username of the target channel/supergroup diff --git a/pyrogram/methods/invite_links/create_chat_invite_link.py b/pyrogram/methods/invite_links/create_chat_invite_link.py index 703d3d1434..ccf8d6505a 100644 --- a/pyrogram/methods/invite_links/create_chat_invite_link.py +++ b/pyrogram/methods/invite_links/create_chat_invite_link.py @@ -39,6 +39,8 @@ async def create_chat_invite_link( The link can be revoked using the method :meth:`~pyrogram.Client.revoke_chat_invite_link`. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier for the target chat or username of the target channel/supergroup diff --git a/pyrogram/methods/invite_links/decline_all_chat_join_requests.py b/pyrogram/methods/invite_links/decline_all_chat_join_requests.py index 620e26245e..4ce32de8c0 100644 --- a/pyrogram/methods/invite_links/decline_all_chat_join_requests.py +++ b/pyrogram/methods/invite_links/decline_all_chat_join_requests.py @@ -30,6 +30,8 @@ async def decline_all_chat_join_requests( ) -> bool: """Decline all pending join requests in a chat. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier for the target chat or username of the target channel/supergroup diff --git a/pyrogram/methods/invite_links/decline_chat_join_request.py b/pyrogram/methods/invite_links/decline_chat_join_request.py index 94e3c2fe7f..9a35b8ee4b 100644 --- a/pyrogram/methods/invite_links/decline_chat_join_request.py +++ b/pyrogram/methods/invite_links/decline_chat_join_request.py @@ -30,9 +30,11 @@ async def decline_chat_join_request( ) -> bool: """Decline a chat join request. - The bot must be an administrator in the chat for this to work and must have the *can_invite_users* administrator + You must be an administrator in the chat for this to work and must have the *can_invite_users* administrator right. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier for the target chat or username of the target channel/supergroup diff --git a/pyrogram/methods/invite_links/delete_chat_admin_invite_links.py b/pyrogram/methods/invite_links/delete_chat_admin_invite_links.py index 8ba6754d0a..6e1275066d 100644 --- a/pyrogram/methods/invite_links/delete_chat_admin_invite_links.py +++ b/pyrogram/methods/invite_links/delete_chat_admin_invite_links.py @@ -30,6 +30,8 @@ async def delete_chat_admin_invite_links( ) -> bool: """Delete all revoked invite links of an administrator. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier for the target chat or username of the target channel/supergroup diff --git a/pyrogram/methods/invite_links/delete_chat_invite_link.py b/pyrogram/methods/invite_links/delete_chat_invite_link.py index 1f38e46cc4..a5915979e7 100644 --- a/pyrogram/methods/invite_links/delete_chat_invite_link.py +++ b/pyrogram/methods/invite_links/delete_chat_invite_link.py @@ -30,6 +30,8 @@ async def delete_chat_invite_link( ) -> bool: """Delete an already revoked invite link. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier for the target chat or username of the target channel/supergroup diff --git a/pyrogram/methods/invite_links/edit_chat_invite_link.py b/pyrogram/methods/invite_links/edit_chat_invite_link.py index 9951684787..553c25dd17 100644 --- a/pyrogram/methods/invite_links/edit_chat_invite_link.py +++ b/pyrogram/methods/invite_links/edit_chat_invite_link.py @@ -38,6 +38,8 @@ async def edit_chat_invite_link( You must be an administrator in the chat for this to work and must have the appropriate admin rights. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier for the target chat or username of the target channel/supergroup diff --git a/pyrogram/methods/invite_links/export_chat_invite_link.py b/pyrogram/methods/invite_links/export_chat_invite_link.py index ae40391fe0..f06caf0f9a 100644 --- a/pyrogram/methods/invite_links/export_chat_invite_link.py +++ b/pyrogram/methods/invite_links/export_chat_invite_link.py @@ -39,6 +39,8 @@ async def export_chat_invite_link( :meth:`~pyrogram.Client.get_chat` method. If your bot needs to generate a new invite link replacing its previous one, use this method again. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier for the target chat or username of the target channel/supergroup diff --git a/pyrogram/methods/invite_links/get_chat_admin_invite_links.py b/pyrogram/methods/invite_links/get_chat_admin_invite_links.py index 1c79ce273c..c845902e4e 100644 --- a/pyrogram/methods/invite_links/get_chat_admin_invite_links.py +++ b/pyrogram/methods/invite_links/get_chat_admin_invite_links.py @@ -38,6 +38,8 @@ async def get_chat_admin_invite_links( As an administrator you can only get your own links you have exported. As the chat or channel owner you can get everyones links. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier for the target chat or username of the target channel/supergroup diff --git a/pyrogram/methods/invite_links/get_chat_admin_invite_links_count.py b/pyrogram/methods/invite_links/get_chat_admin_invite_links_count.py index c26af5063c..68479a61f3 100644 --- a/pyrogram/methods/invite_links/get_chat_admin_invite_links_count.py +++ b/pyrogram/methods/invite_links/get_chat_admin_invite_links_count.py @@ -31,6 +31,8 @@ async def get_chat_admin_invite_links_count( ) -> int: """Get the count of the invite links created by an administrator in a chat. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier for the target chat or username of the target channel/supergroup diff --git a/pyrogram/methods/invite_links/get_chat_admins_with_invite_links.py b/pyrogram/methods/invite_links/get_chat_admins_with_invite_links.py index 61e082bc0e..d5bf6569ad 100644 --- a/pyrogram/methods/invite_links/get_chat_admins_with_invite_links.py +++ b/pyrogram/methods/invite_links/get_chat_admins_with_invite_links.py @@ -31,7 +31,9 @@ async def get_chat_admins_with_invite_links( You must be the owner of a chat for this to work. - Args: + .. include:: /_includes/usable-by/users-bots.rst + + Parameters: chat_id (``int`` | ``str``): Unique identifier for the target chat or username of the target channel/supergroup (in the format @username). diff --git a/pyrogram/methods/invite_links/get_chat_invite_link.py b/pyrogram/methods/invite_links/get_chat_invite_link.py index 7447933658..8ad575f34a 100644 --- a/pyrogram/methods/invite_links/get_chat_invite_link.py +++ b/pyrogram/methods/invite_links/get_chat_invite_link.py @@ -31,6 +31,8 @@ async def get_chat_invite_link( ) -> "types.ChatInviteLink": """Get detailed information about a chat invite link. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier for the target chat or username of the target channel/supergroup diff --git a/pyrogram/methods/invite_links/get_chat_invite_link_joiners.py b/pyrogram/methods/invite_links/get_chat_invite_link_joiners.py index cfd6873433..d03d108311 100644 --- a/pyrogram/methods/invite_links/get_chat_invite_link_joiners.py +++ b/pyrogram/methods/invite_links/get_chat_invite_link_joiners.py @@ -32,6 +32,8 @@ async def get_chat_invite_link_joiners( ) -> Optional[AsyncGenerator["types.ChatJoiner", None]]: """Get the members who joined the chat with the invite link. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier for the target chat or username of the target channel/supergroup diff --git a/pyrogram/methods/invite_links/get_chat_invite_link_joiners_count.py b/pyrogram/methods/invite_links/get_chat_invite_link_joiners_count.py index 084fbae570..715053e611 100644 --- a/pyrogram/methods/invite_links/get_chat_invite_link_joiners_count.py +++ b/pyrogram/methods/invite_links/get_chat_invite_link_joiners_count.py @@ -30,6 +30,8 @@ async def get_chat_invite_link_joiners_count( ) -> int: """Get the count of the members who joined the chat with the invite link. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier for the target chat or username of the target channel/supergroup diff --git a/pyrogram/methods/invite_links/get_chat_join_requests.py b/pyrogram/methods/invite_links/get_chat_join_requests.py index 9a9bc38793..ee6fe7e388 100644 --- a/pyrogram/methods/invite_links/get_chat_join_requests.py +++ b/pyrogram/methods/invite_links/get_chat_join_requests.py @@ -32,6 +32,8 @@ async def get_chat_join_requests( ) -> Optional[AsyncGenerator["types.ChatJoiner", None]]: """Get the pending join requests of a chat. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier for the target chat or username of the target channel/supergroup diff --git a/pyrogram/methods/invite_links/revoke_chat_invite_link.py b/pyrogram/methods/invite_links/revoke_chat_invite_link.py index a334bb8de2..ff55a04e03 100644 --- a/pyrogram/methods/invite_links/revoke_chat_invite_link.py +++ b/pyrogram/methods/invite_links/revoke_chat_invite_link.py @@ -35,6 +35,8 @@ async def revoke_chat_invite_link( You must be an administrator in the chat for this to work and must have the appropriate admin rights. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier for the target chat or username of the target channel/supergroup diff --git a/pyrogram/methods/messages/copy_media_group.py b/pyrogram/methods/messages/copy_media_group.py index de47465d27..52911ea0bc 100644 --- a/pyrogram/methods/messages/copy_media_group.py +++ b/pyrogram/methods/messages/copy_media_group.py @@ -36,6 +36,8 @@ async def copy_media_group( ) -> List["types.Message"]: """Copy a media group by providing one of the message ids. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/messages/copy_message.py b/pyrogram/methods/messages/copy_message.py index b114a9e0af..66b7e37cb3 100644 --- a/pyrogram/methods/messages/copy_message.py +++ b/pyrogram/methods/messages/copy_message.py @@ -51,6 +51,8 @@ async def copy_message( The method is analogous to the method :meth:`~Client.forward_messages`, but the copied message doesn't have a link to the original message. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/messages/delete_messages.py b/pyrogram/methods/messages/delete_messages.py index 523ecd544c..07c3a7b85b 100644 --- a/pyrogram/methods/messages/delete_messages.py +++ b/pyrogram/methods/messages/delete_messages.py @@ -31,6 +31,8 @@ async def delete_messages( ) -> int: """Delete messages, including service messages. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/messages/download_media.py b/pyrogram/methods/messages/download_media.py index d46bd50391..4f44ff254f 100644 --- a/pyrogram/methods/messages/download_media.py +++ b/pyrogram/methods/messages/download_media.py @@ -40,6 +40,8 @@ async def download_media( ) -> Optional[Union[str, BinaryIO]]: """Download the media from a message. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: message (:obj:`~pyrogram.types.Message` | ``str``): Pass a Message containing the media, the media itself (message.audio, message.video, ...) or a file id diff --git a/pyrogram/methods/messages/edit_inline_caption.py b/pyrogram/methods/messages/edit_inline_caption.py index 0996d34e68..69c7333416 100644 --- a/pyrogram/methods/messages/edit_inline_caption.py +++ b/pyrogram/methods/messages/edit_inline_caption.py @@ -32,6 +32,8 @@ async def edit_inline_caption( ) -> bool: """Edit the caption of inline media messages. + .. include:: /_includes/usable-by/bots.rst + Parameters: inline_message_id (``str``): Identifier of the inline message. diff --git a/pyrogram/methods/messages/edit_inline_media.py b/pyrogram/methods/messages/edit_inline_media.py index 81aa30ee6b..77fa673ae6 100644 --- a/pyrogram/methods/messages/edit_inline_media.py +++ b/pyrogram/methods/messages/edit_inline_media.py @@ -45,6 +45,8 @@ async def edit_inline_media( When the inline message is edited, a new file can't be uploaded. Use a previously uploaded file via its file_id or specify a URL. + .. include:: /_includes/usable-by/bots.rst + Parameters: inline_message_id (``str``): Required if *chat_id* and *message_id* are not specified. diff --git a/pyrogram/methods/messages/edit_inline_reply_markup.py b/pyrogram/methods/messages/edit_inline_reply_markup.py index 4e06e447f7..e2ef40e160 100644 --- a/pyrogram/methods/messages/edit_inline_reply_markup.py +++ b/pyrogram/methods/messages/edit_inline_reply_markup.py @@ -31,6 +31,8 @@ async def edit_inline_reply_markup( ) -> bool: """Edit only the reply markup of inline messages sent via the bot (for inline bots). + .. include:: /_includes/usable-by/bots.rst + Parameters: inline_message_id (``str``): Identifier of the inline message. diff --git a/pyrogram/methods/messages/edit_inline_text.py b/pyrogram/methods/messages/edit_inline_text.py index 9caee55aaf..354c55a339 100644 --- a/pyrogram/methods/messages/edit_inline_text.py +++ b/pyrogram/methods/messages/edit_inline_text.py @@ -36,6 +36,8 @@ async def edit_inline_text( ) -> bool: """Edit the text of inline messages. + .. include:: /_includes/usable-by/bots.rst + Parameters: inline_message_id (``str``): Identifier of the inline message. diff --git a/pyrogram/methods/messages/edit_message_caption.py b/pyrogram/methods/messages/edit_message_caption.py index d3b311db6e..2e4a45a694 100644 --- a/pyrogram/methods/messages/edit_message_caption.py +++ b/pyrogram/methods/messages/edit_message_caption.py @@ -34,6 +34,8 @@ async def edit_message_caption( ) -> "types.Message": """Edit the caption of media messages. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/messages/edit_message_media.py b/pyrogram/methods/messages/edit_message_media.py index a3840f2374..16efb5b858 100644 --- a/pyrogram/methods/messages/edit_message_media.py +++ b/pyrogram/methods/messages/edit_message_media.py @@ -42,6 +42,8 @@ async def edit_message_media( If a message is a part of a message album, then it can be edited only to a photo or a video. Otherwise, the message type can be changed arbitrarily. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/messages/edit_message_reply_markup.py b/pyrogram/methods/messages/edit_message_reply_markup.py index 088e646ddb..1cd75c1a8b 100644 --- a/pyrogram/methods/messages/edit_message_reply_markup.py +++ b/pyrogram/methods/messages/edit_message_reply_markup.py @@ -32,6 +32,8 @@ async def edit_message_reply_markup( ) -> "types.Message": """Edit only the reply markup of messages sent by the bot. + .. include:: /_includes/usable-by/bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/messages/edit_message_text.py b/pyrogram/methods/messages/edit_message_text.py index 94852de582..540b6aa63c 100644 --- a/pyrogram/methods/messages/edit_message_text.py +++ b/pyrogram/methods/messages/edit_message_text.py @@ -37,6 +37,8 @@ async def edit_message_text( ) -> "types.Message": """Edit the text of messages. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/messages/forward_messages.py b/pyrogram/methods/messages/forward_messages.py index be7b2ab5ef..8635e17101 100644 --- a/pyrogram/methods/messages/forward_messages.py +++ b/pyrogram/methods/messages/forward_messages.py @@ -36,6 +36,8 @@ async def forward_messages( ) -> Union["types.Message", List["types.Message"]]: """Forward messages of any kind. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/messages/get_chat_history.py b/pyrogram/methods/messages/get_chat_history.py index a7fcc491e9..b384c6ba8f 100644 --- a/pyrogram/methods/messages/get_chat_history.py +++ b/pyrogram/methods/messages/get_chat_history.py @@ -62,6 +62,8 @@ async def get_chat_history( The messages are returned in reverse chronological order. + .. include:: /_includes/usable-by/users.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/messages/get_chat_history_count.py b/pyrogram/methods/messages/get_chat_history_count.py index 46f61eb056..1926224b35 100644 --- a/pyrogram/methods/messages/get_chat_history_count.py +++ b/pyrogram/methods/messages/get_chat_history_count.py @@ -38,6 +38,8 @@ async def get_chat_history_count( a **private** or a **basic group** chat has with a single method call. To overcome this limitation, Pyrogram has to iterate over all the messages. Channels and supergroups are not affected by this limitation. + .. include:: /_includes/usable-by/users.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/messages/get_custom_emoji_stickers.py b/pyrogram/methods/messages/get_custom_emoji_stickers.py index 335f4ce9d5..7a71f0587c 100644 --- a/pyrogram/methods/messages/get_custom_emoji_stickers.py +++ b/pyrogram/methods/messages/get_custom_emoji_stickers.py @@ -30,6 +30,8 @@ async def get_custom_emoji_stickers( ) -> List["types.Sticker"]: """Get information about custom emoji stickers by their identifiers. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: custom_emoji_ids (List of ``int``): List of custom emoji identifiers. diff --git a/pyrogram/methods/messages/get_discussion_message.py b/pyrogram/methods/messages/get_discussion_message.py index 93510e211e..58a6b7044d 100644 --- a/pyrogram/methods/messages/get_discussion_message.py +++ b/pyrogram/methods/messages/get_discussion_message.py @@ -34,6 +34,8 @@ async def get_discussion_message( Reply to the returned message to leave a comment on the linked channel post or to continue the discussion thread. + .. include:: /_includes/usable-by/users.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/messages/get_discussion_replies.py b/pyrogram/methods/messages/get_discussion_replies.py index 467cca8d2b..dd23751bb1 100644 --- a/pyrogram/methods/messages/get_discussion_replies.py +++ b/pyrogram/methods/messages/get_discussion_replies.py @@ -31,6 +31,8 @@ async def get_discussion_replies( ) -> Optional[AsyncGenerator["types.Message", None]]: """Get the message replies of a discussion thread. + .. include:: /_includes/usable-by/users.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/messages/get_discussion_replies_count.py b/pyrogram/methods/messages/get_discussion_replies_count.py index 260be802eb..bbb90ac3a2 100644 --- a/pyrogram/methods/messages/get_discussion_replies_count.py +++ b/pyrogram/methods/messages/get_discussion_replies_count.py @@ -30,6 +30,8 @@ async def get_discussion_replies_count( ) -> int: """Get the total count of replies in a discussion thread. + .. include:: /_includes/usable-by/users.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/messages/get_media_group.py b/pyrogram/methods/messages/get_media_group.py index b50d4785af..97770d90f5 100644 --- a/pyrogram/methods/messages/get_media_group.py +++ b/pyrogram/methods/messages/get_media_group.py @@ -32,7 +32,9 @@ async def get_media_group( message_id: int ) -> List["types.Message"]: """Get the media group a message belongs to. - + + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/messages/get_messages.py b/pyrogram/methods/messages/get_messages.py index 17d80e58a6..3f398c36eb 100644 --- a/pyrogram/methods/messages/get_messages.py +++ b/pyrogram/methods/messages/get_messages.py @@ -42,6 +42,8 @@ async def get_messages( You can retrieve up to 200 messages at once. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/messages/read_chat_history.py b/pyrogram/methods/messages/read_chat_history.py index b3cc3bfc4c..4b96273936 100644 --- a/pyrogram/methods/messages/read_chat_history.py +++ b/pyrogram/methods/messages/read_chat_history.py @@ -30,6 +30,8 @@ async def read_chat_history( ) -> bool: """Mark a chat's message history as read. + .. include:: /_includes/usable-by/users.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/messages/retract_vote.py b/pyrogram/methods/messages/retract_vote.py index 8420a4a318..c456f4d08b 100644 --- a/pyrogram/methods/messages/retract_vote.py +++ b/pyrogram/methods/messages/retract_vote.py @@ -31,6 +31,8 @@ async def retract_vote( ) -> "types.Poll": """Retract your vote in a poll. + .. include:: /_includes/usable-by/users.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/messages/search_global.py b/pyrogram/methods/messages/search_global.py index 5e54a965c8..f566c98150 100644 --- a/pyrogram/methods/messages/search_global.py +++ b/pyrogram/methods/messages/search_global.py @@ -40,6 +40,8 @@ async def search_global( Due to server-side limitations, you can only get up to around ~10,000 messages and each message retrieved will not have any *reply_to_message* field. + .. include:: /_includes/usable-by/users.rst + Parameters: query (``str``, *optional*): Text query string. diff --git a/pyrogram/methods/messages/search_global_count.py b/pyrogram/methods/messages/search_global_count.py index 2e559ded9f..8323a821d2 100644 --- a/pyrogram/methods/messages/search_global_count.py +++ b/pyrogram/methods/messages/search_global_count.py @@ -30,6 +30,8 @@ async def search_global_count( If you want to get the actual messages, see :meth:`~pyrogram.Client.search_global`. + .. include:: /_includes/usable-by/users.rst + Parameters: query (``str``, *optional*): Text query string. diff --git a/pyrogram/methods/messages/search_messages.py b/pyrogram/methods/messages/search_messages.py index 1f9fa3c76e..4497ff1cd3 100644 --- a/pyrogram/methods/messages/search_messages.py +++ b/pyrogram/methods/messages/search_messages.py @@ -72,6 +72,8 @@ async def search_messages( If you want to get the messages count only, see :meth:`~pyrogram.Client.search_messages_count`. + .. include:: /_includes/usable-by/users.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/messages/search_messages_count.py b/pyrogram/methods/messages/search_messages_count.py index 09f778559b..ea9ae4d18e 100644 --- a/pyrogram/methods/messages/search_messages_count.py +++ b/pyrogram/methods/messages/search_messages_count.py @@ -34,6 +34,8 @@ async def search_messages_count( If you want to get the actual messages, see :meth:`~pyrogram.Client.search_messages`. + .. include:: /_includes/usable-by/users.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/messages/send_animation.py b/pyrogram/methods/messages/send_animation.py index efa9cb1671..ec85dc0569 100644 --- a/pyrogram/methods/messages/send_animation.py +++ b/pyrogram/methods/messages/send_animation.py @@ -59,6 +59,8 @@ async def send_animation( ) -> Optional["types.Message"]: """Send animation files (animation or H.264/MPEG-4 AVC video without sound). + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/messages/send_audio.py b/pyrogram/methods/messages/send_audio.py index f4552832fe..7a81df0195 100644 --- a/pyrogram/methods/messages/send_audio.py +++ b/pyrogram/methods/messages/send_audio.py @@ -60,6 +60,8 @@ async def send_audio( For sending voice messages, use the :meth:`~pyrogram.Client.send_voice` method instead. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/messages/send_cached_media.py b/pyrogram/methods/messages/send_cached_media.py index f0e04d7232..5a9e2e1f1a 100644 --- a/pyrogram/methods/messages/send_cached_media.py +++ b/pyrogram/methods/messages/send_cached_media.py @@ -50,6 +50,8 @@ async def send_cached_media( It does the same as calling the relevant method for sending media using a file_id, thus saving you from the hassle of using the correct method for the media the file_id is pointing to. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/messages/send_chat_action.py b/pyrogram/methods/messages/send_chat_action.py index 15b5ac0853..44b16628f8 100644 --- a/pyrogram/methods/messages/send_chat_action.py +++ b/pyrogram/methods/messages/send_chat_action.py @@ -30,6 +30,8 @@ async def send_chat_action( ) -> bool: """Tell the other party that something is happening on your side. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/messages/send_contact.py b/pyrogram/methods/messages/send_contact.py index cedcd0b25b..30f7abd315 100644 --- a/pyrogram/methods/messages/send_contact.py +++ b/pyrogram/methods/messages/send_contact.py @@ -45,6 +45,8 @@ async def send_contact( ) -> "types.Message": """Send phone contacts. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/messages/send_dice.py b/pyrogram/methods/messages/send_dice.py index 942f681ff0..c657d85dd9 100644 --- a/pyrogram/methods/messages/send_dice.py +++ b/pyrogram/methods/messages/send_dice.py @@ -42,6 +42,8 @@ async def send_dice( ) -> Optional["types.Message"]: """Send a dice with a random value from 1 to 6. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/messages/send_document.py b/pyrogram/methods/messages/send_document.py index 99c90fbb25..b0a4a531e3 100644 --- a/pyrogram/methods/messages/send_document.py +++ b/pyrogram/methods/messages/send_document.py @@ -56,6 +56,8 @@ async def send_document( ) -> Optional["types.Message"]: """Send generic files. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/messages/send_location.py b/pyrogram/methods/messages/send_location.py index fe7d1ed16d..2bd17681b0 100644 --- a/pyrogram/methods/messages/send_location.py +++ b/pyrogram/methods/messages/send_location.py @@ -43,6 +43,8 @@ async def send_location( ) -> "types.Message": """Send points on the map. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/messages/send_media_group.py b/pyrogram/methods/messages/send_media_group.py index 43eb9d7c2b..0dfbbaa236 100644 --- a/pyrogram/methods/messages/send_media_group.py +++ b/pyrogram/methods/messages/send_media_group.py @@ -49,6 +49,8 @@ async def send_media_group( ) -> List["types.Message"]: """Send a group of photos or videos as an album. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/messages/send_message.py b/pyrogram/methods/messages/send_message.py index b365880ed5..ad73f9a182 100644 --- a/pyrogram/methods/messages/send_message.py +++ b/pyrogram/methods/messages/send_message.py @@ -45,6 +45,8 @@ async def send_message( ) -> "types.Message": """Send text messages. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/messages/send_photo.py b/pyrogram/methods/messages/send_photo.py index 6add68cd6c..994f0c9322 100644 --- a/pyrogram/methods/messages/send_photo.py +++ b/pyrogram/methods/messages/send_photo.py @@ -53,6 +53,8 @@ async def send_photo( ) -> Optional["types.Message"]: """Send photos. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/messages/send_poll.py b/pyrogram/methods/messages/send_poll.py index 207cff9faf..267dcb6dad 100644 --- a/pyrogram/methods/messages/send_poll.py +++ b/pyrogram/methods/messages/send_poll.py @@ -53,6 +53,8 @@ async def send_poll( ) -> "types.Message": """Send a new poll. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/messages/send_reaction.py b/pyrogram/methods/messages/send_reaction.py index b1e6540b68..f4d1d34340 100644 --- a/pyrogram/methods/messages/send_reaction.py +++ b/pyrogram/methods/messages/send_reaction.py @@ -32,6 +32,8 @@ async def send_reaction( ) -> bool: """Send a reaction to a message. + .. include:: /_includes/usable-by/users.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/messages/send_sticker.py b/pyrogram/methods/messages/send_sticker.py index 2fc4565cb5..ccfaada51e 100644 --- a/pyrogram/methods/messages/send_sticker.py +++ b/pyrogram/methods/messages/send_sticker.py @@ -50,6 +50,8 @@ async def send_sticker( ) -> Optional["types.Message"]: """Send static .webp or animated .tgs stickers. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/messages/send_venue.py b/pyrogram/methods/messages/send_venue.py index e8cc760b30..4d3167bf00 100644 --- a/pyrogram/methods/messages/send_venue.py +++ b/pyrogram/methods/messages/send_venue.py @@ -47,6 +47,8 @@ async def send_venue( ) -> "types.Message": """Send information about a venue. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/messages/send_video.py b/pyrogram/methods/messages/send_video.py index 669b42ec05..c20530641c 100644 --- a/pyrogram/methods/messages/send_video.py +++ b/pyrogram/methods/messages/send_video.py @@ -60,6 +60,8 @@ async def send_video( ) -> Optional["types.Message"]: """Send video files. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/messages/send_video_note.py b/pyrogram/methods/messages/send_video_note.py index bf33cdb724..9c615d3116 100644 --- a/pyrogram/methods/messages/send_video_note.py +++ b/pyrogram/methods/messages/send_video_note.py @@ -52,6 +52,8 @@ async def send_video_note( ) -> Optional["types.Message"]: """Send video messages. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/messages/send_voice.py b/pyrogram/methods/messages/send_voice.py index 5947ecc23d..266702fd83 100644 --- a/pyrogram/methods/messages/send_voice.py +++ b/pyrogram/methods/messages/send_voice.py @@ -54,6 +54,8 @@ async def send_voice( ) -> Optional["types.Message"]: """Send audio files. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/messages/stop_poll.py b/pyrogram/methods/messages/stop_poll.py index 6104403f5c..89a33434b7 100644 --- a/pyrogram/methods/messages/stop_poll.py +++ b/pyrogram/methods/messages/stop_poll.py @@ -34,6 +34,8 @@ async def stop_poll( Stopped polls can't be reopened and nobody will be able to vote in it anymore. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/messages/stream_media.py b/pyrogram/methods/messages/stream_media.py index 91ffefd7ee..b432badb4c 100644 --- a/pyrogram/methods/messages/stream_media.py +++ b/pyrogram/methods/messages/stream_media.py @@ -36,6 +36,8 @@ async def stream_media( You can use this method to partially download a file into memory or to selectively download chunks of file. The chunk maximum size is 1 MiB (1024 * 1024 bytes). + .. include:: /_includes/usable-by/users-bots.rst + Parameters: message (:obj:`~pyrogram.types.Message` | ``str``): Pass a Message containing the media, the media itself (message.audio, message.video, ...) or a file id diff --git a/pyrogram/methods/messages/vote_poll.py b/pyrogram/methods/messages/vote_poll.py index 72a912deae..620fac5a15 100644 --- a/pyrogram/methods/messages/vote_poll.py +++ b/pyrogram/methods/messages/vote_poll.py @@ -32,6 +32,8 @@ async def vote_poll( ) -> "types.Poll": """Vote a poll. + .. include:: /_includes/usable-by/users.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/password/change_cloud_password.py b/pyrogram/methods/password/change_cloud_password.py index c5e8bd27c7..29540d653b 100644 --- a/pyrogram/methods/password/change_cloud_password.py +++ b/pyrogram/methods/password/change_cloud_password.py @@ -32,6 +32,8 @@ async def change_cloud_password( ) -> bool: """Change your Two-Step Verification password (Cloud Password) with a new one. + .. include:: /_includes/usable-by/users.rst + Parameters: current_password (``str``): Your current password. diff --git a/pyrogram/methods/password/enable_cloud_password.py b/pyrogram/methods/password/enable_cloud_password.py index a6533dbf70..8f5e59c570 100644 --- a/pyrogram/methods/password/enable_cloud_password.py +++ b/pyrogram/methods/password/enable_cloud_password.py @@ -34,6 +34,8 @@ async def enable_cloud_password( This password will be asked when you log-in on a new device in addition to the SMS code. + .. include:: /_includes/usable-by/users.rst + Parameters: password (``str``): Your password. diff --git a/pyrogram/methods/password/remove_cloud_password.py b/pyrogram/methods/password/remove_cloud_password.py index dab37d8635..0ec16e12cd 100644 --- a/pyrogram/methods/password/remove_cloud_password.py +++ b/pyrogram/methods/password/remove_cloud_password.py @@ -28,6 +28,8 @@ async def remove_cloud_password( ) -> bool: """Turn off the Two-Step Verification security feature (Cloud Password) on your account. + .. include:: /_includes/usable-by/users.rst + Parameters: password (``str``): Your current password. diff --git a/pyrogram/methods/users/block_user.py b/pyrogram/methods/users/block_user.py index f14479558e..25cd0ce84e 100644 --- a/pyrogram/methods/users/block_user.py +++ b/pyrogram/methods/users/block_user.py @@ -29,6 +29,8 @@ async def block_user( ) -> bool: """Block a user. + .. include:: /_includes/usable-by/users.rst + Parameters: user_id (``int`` | ``str``):: Unique identifier (int) or username (str) of the target user. diff --git a/pyrogram/methods/users/delete_profile_photos.py b/pyrogram/methods/users/delete_profile_photos.py index 4ee8b7b51d..2b52d02f59 100644 --- a/pyrogram/methods/users/delete_profile_photos.py +++ b/pyrogram/methods/users/delete_profile_photos.py @@ -31,6 +31,8 @@ async def delete_profile_photos( ) -> bool: """Delete your own profile photos. + .. include:: /_includes/usable-by/users.rst + Parameters: photo_ids (``str`` | List of ``str``): A single :obj:`~pyrogram.types.Photo` id as string or multiple ids as list of strings for deleting diff --git a/pyrogram/methods/users/get_chat_photos.py b/pyrogram/methods/users/get_chat_photos.py index d3bc1f1feb..d22c68dcd8 100644 --- a/pyrogram/methods/users/get_chat_photos.py +++ b/pyrogram/methods/users/get_chat_photos.py @@ -30,6 +30,8 @@ async def get_chat_photos( ) -> Optional[AsyncGenerator["types.Photo", None]]: """Get a chat or a user profile photos sequentially. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/users/get_chat_photos_count.py b/pyrogram/methods/users/get_chat_photos_count.py index eca186a820..d4cf1594f1 100644 --- a/pyrogram/methods/users/get_chat_photos_count.py +++ b/pyrogram/methods/users/get_chat_photos_count.py @@ -29,6 +29,8 @@ async def get_chat_photos_count( ) -> int: """Get the total count of photos for a chat. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: chat_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/users/get_common_chats.py b/pyrogram/methods/users/get_common_chats.py index a45bda6f4c..31e2bac38e 100644 --- a/pyrogram/methods/users/get_common_chats.py +++ b/pyrogram/methods/users/get_common_chats.py @@ -30,6 +30,8 @@ async def get_common_chats( ) -> List["types.Chat"]: """Get the common chats you have with a user. + .. include:: /_includes/usable-by/users.rst + Parameters: user_id (``int`` | ``str``): Unique identifier (int) or username (str) of the target chat. diff --git a/pyrogram/methods/users/get_default_emoji_statuses.py b/pyrogram/methods/users/get_default_emoji_statuses.py index 8c85a3c202..cd3768b9a4 100644 --- a/pyrogram/methods/users/get_default_emoji_statuses.py +++ b/pyrogram/methods/users/get_default_emoji_statuses.py @@ -29,6 +29,8 @@ async def get_default_emoji_statuses( ) -> List["types.EmojiStatus"]: """Get the default emoji statuses. + .. include:: /_includes/usable-by/users-bots.rst + Returns: List of :obj:`~pyrogram.types.EmojiStatus`: On success, a list of emoji statuses is returned. diff --git a/pyrogram/methods/users/get_me.py b/pyrogram/methods/users/get_me.py index 610a11af53..a8df3ae9f3 100644 --- a/pyrogram/methods/users/get_me.py +++ b/pyrogram/methods/users/get_me.py @@ -27,6 +27,8 @@ async def get_me( ) -> "types.User": """Get your own user identity. + .. include:: /_includes/usable-by/users-bots.rst + Returns: :obj:`~pyrogram.types.User`: Information about the own logged in user/bot. diff --git a/pyrogram/methods/users/get_users.py b/pyrogram/methods/users/get_users.py index 384dded0af..39821a8c5d 100644 --- a/pyrogram/methods/users/get_users.py +++ b/pyrogram/methods/users/get_users.py @@ -32,6 +32,8 @@ async def get_users( """Get information about a user. You can retrieve up to 200 users at once. + .. include:: /_includes/usable-by/users-bots.rst + Parameters: user_ids (``int`` | ``str`` | Iterable of ``int`` or ``str``): A list of User identifiers (id or username) or a single user id/username. diff --git a/pyrogram/methods/users/set_emoji_status.py b/pyrogram/methods/users/set_emoji_status.py index 288e966b20..2d8c77cc02 100644 --- a/pyrogram/methods/users/set_emoji_status.py +++ b/pyrogram/methods/users/set_emoji_status.py @@ -29,6 +29,8 @@ async def set_emoji_status( ) -> bool: """Set the emoji status. + .. include:: /_includes/usable-by/users.rst + Parameters: emoji_status (:obj:`~pyrogram.types.EmojiStatus`, *optional*): The emoji status to set. None to remove. diff --git a/pyrogram/methods/users/set_profile_photo.py b/pyrogram/methods/users/set_profile_photo.py index 86aaf31f6d..e08e669c4e 100644 --- a/pyrogram/methods/users/set_profile_photo.py +++ b/pyrogram/methods/users/set_profile_photo.py @@ -39,6 +39,8 @@ async def set_profile_photo( This method only works for Users. Bots profile photos must be set using BotFather. + .. include:: /_includes/usable-by/users.rst + Parameters: photo (``str`` | ``BinaryIO``, *optional*): Profile photo to set. diff --git a/pyrogram/methods/users/set_username.py b/pyrogram/methods/users/set_username.py index 6f070bc5f2..d336628eec 100644 --- a/pyrogram/methods/users/set_username.py +++ b/pyrogram/methods/users/set_username.py @@ -33,6 +33,8 @@ async def set_username( them from scratch using BotFather. To set a channel or supergroup username you can use :meth:`~pyrogram.Client.set_chat_username`. + .. include:: /_includes/usable-by/users.rst + Parameters: username (``str`` | ``None``): Username to set. "" (empty string) or None to remove it. diff --git a/pyrogram/methods/users/unblock_user.py b/pyrogram/methods/users/unblock_user.py index 4809aa6ba6..db4fdddcff 100644 --- a/pyrogram/methods/users/unblock_user.py +++ b/pyrogram/methods/users/unblock_user.py @@ -29,6 +29,8 @@ async def unblock_user( ) -> bool: """Unblock a user. + .. include:: /_includes/usable-by/users.rst + Parameters: user_id (``int`` | ``str``):: Unique identifier (int) or username (str) of the target user. diff --git a/pyrogram/methods/users/update_profile.py b/pyrogram/methods/users/update_profile.py index 31f12508f5..6c10d16c9d 100644 --- a/pyrogram/methods/users/update_profile.py +++ b/pyrogram/methods/users/update_profile.py @@ -31,6 +31,8 @@ async def update_profile( You can omit the parameters you don't want to change. + .. include:: /_includes/usable-by/users.rst + Parameters: first_name (``str``, *optional*): The new first name. From bf52ec1e944d224c449fda32a900c46000a7e666 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 15 Oct 2022 15:49:36 +0200 Subject: [PATCH 1044/1185] Update usable-by labels --- pyrogram/methods/invite_links/approve_all_chat_join_requests.py | 2 +- pyrogram/methods/invite_links/decline_all_chat_join_requests.py | 2 +- pyrogram/methods/invite_links/delete_chat_admin_invite_links.py | 2 +- pyrogram/methods/invite_links/delete_chat_invite_link.py | 2 +- pyrogram/methods/invite_links/get_chat_admin_invite_links.py | 2 +- .../methods/invite_links/get_chat_admin_invite_links_count.py | 2 +- .../methods/invite_links/get_chat_admins_with_invite_links.py | 2 +- pyrogram/methods/invite_links/get_chat_invite_link_joiners.py | 2 +- .../methods/invite_links/get_chat_invite_link_joiners_count.py | 2 +- pyrogram/methods/invite_links/get_chat_join_requests.py | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/pyrogram/methods/invite_links/approve_all_chat_join_requests.py b/pyrogram/methods/invite_links/approve_all_chat_join_requests.py index 06894bc896..623fd87fcc 100644 --- a/pyrogram/methods/invite_links/approve_all_chat_join_requests.py +++ b/pyrogram/methods/invite_links/approve_all_chat_join_requests.py @@ -30,7 +30,7 @@ async def approve_all_chat_join_requests( ) -> bool: """Approve all pending join requests in a chat. - .. include:: /_includes/usable-by/users-bots.rst + .. include:: /_includes/usable-by/users.rst Parameters: chat_id (``int`` | ``str``): diff --git a/pyrogram/methods/invite_links/decline_all_chat_join_requests.py b/pyrogram/methods/invite_links/decline_all_chat_join_requests.py index 4ce32de8c0..9cf5095507 100644 --- a/pyrogram/methods/invite_links/decline_all_chat_join_requests.py +++ b/pyrogram/methods/invite_links/decline_all_chat_join_requests.py @@ -30,7 +30,7 @@ async def decline_all_chat_join_requests( ) -> bool: """Decline all pending join requests in a chat. - .. include:: /_includes/usable-by/users-bots.rst + .. include:: /_includes/usable-by/users.rst Parameters: chat_id (``int`` | ``str``): diff --git a/pyrogram/methods/invite_links/delete_chat_admin_invite_links.py b/pyrogram/methods/invite_links/delete_chat_admin_invite_links.py index 6e1275066d..32ef1de5e7 100644 --- a/pyrogram/methods/invite_links/delete_chat_admin_invite_links.py +++ b/pyrogram/methods/invite_links/delete_chat_admin_invite_links.py @@ -30,7 +30,7 @@ async def delete_chat_admin_invite_links( ) -> bool: """Delete all revoked invite links of an administrator. - .. include:: /_includes/usable-by/users-bots.rst + .. include:: /_includes/usable-by/users.rst Parameters: chat_id (``int`` | ``str``): diff --git a/pyrogram/methods/invite_links/delete_chat_invite_link.py b/pyrogram/methods/invite_links/delete_chat_invite_link.py index a5915979e7..82db623abe 100644 --- a/pyrogram/methods/invite_links/delete_chat_invite_link.py +++ b/pyrogram/methods/invite_links/delete_chat_invite_link.py @@ -30,7 +30,7 @@ async def delete_chat_invite_link( ) -> bool: """Delete an already revoked invite link. - .. include:: /_includes/usable-by/users-bots.rst + .. include:: /_includes/usable-by/users.rst Parameters: chat_id (``int`` | ``str``): diff --git a/pyrogram/methods/invite_links/get_chat_admin_invite_links.py b/pyrogram/methods/invite_links/get_chat_admin_invite_links.py index c845902e4e..62acca10e8 100644 --- a/pyrogram/methods/invite_links/get_chat_admin_invite_links.py +++ b/pyrogram/methods/invite_links/get_chat_admin_invite_links.py @@ -38,7 +38,7 @@ async def get_chat_admin_invite_links( As an administrator you can only get your own links you have exported. As the chat or channel owner you can get everyones links. - .. include:: /_includes/usable-by/users-bots.rst + .. include:: /_includes/usable-by/users.rst Parameters: chat_id (``int`` | ``str``): diff --git a/pyrogram/methods/invite_links/get_chat_admin_invite_links_count.py b/pyrogram/methods/invite_links/get_chat_admin_invite_links_count.py index 68479a61f3..528876ed4e 100644 --- a/pyrogram/methods/invite_links/get_chat_admin_invite_links_count.py +++ b/pyrogram/methods/invite_links/get_chat_admin_invite_links_count.py @@ -31,7 +31,7 @@ async def get_chat_admin_invite_links_count( ) -> int: """Get the count of the invite links created by an administrator in a chat. - .. include:: /_includes/usable-by/users-bots.rst + .. include:: /_includes/usable-by/users.rst Parameters: chat_id (``int`` | ``str``): diff --git a/pyrogram/methods/invite_links/get_chat_admins_with_invite_links.py b/pyrogram/methods/invite_links/get_chat_admins_with_invite_links.py index d5bf6569ad..f283e53464 100644 --- a/pyrogram/methods/invite_links/get_chat_admins_with_invite_links.py +++ b/pyrogram/methods/invite_links/get_chat_admins_with_invite_links.py @@ -31,7 +31,7 @@ async def get_chat_admins_with_invite_links( You must be the owner of a chat for this to work. - .. include:: /_includes/usable-by/users-bots.rst + .. include:: /_includes/usable-by/users.rst Parameters: chat_id (``int`` | ``str``): diff --git a/pyrogram/methods/invite_links/get_chat_invite_link_joiners.py b/pyrogram/methods/invite_links/get_chat_invite_link_joiners.py index d03d108311..c1fc43a71f 100644 --- a/pyrogram/methods/invite_links/get_chat_invite_link_joiners.py +++ b/pyrogram/methods/invite_links/get_chat_invite_link_joiners.py @@ -32,7 +32,7 @@ async def get_chat_invite_link_joiners( ) -> Optional[AsyncGenerator["types.ChatJoiner", None]]: """Get the members who joined the chat with the invite link. - .. include:: /_includes/usable-by/users-bots.rst + .. include:: /_includes/usable-by/users.rst Parameters: chat_id (``int`` | ``str``): diff --git a/pyrogram/methods/invite_links/get_chat_invite_link_joiners_count.py b/pyrogram/methods/invite_links/get_chat_invite_link_joiners_count.py index 715053e611..c591be1927 100644 --- a/pyrogram/methods/invite_links/get_chat_invite_link_joiners_count.py +++ b/pyrogram/methods/invite_links/get_chat_invite_link_joiners_count.py @@ -30,7 +30,7 @@ async def get_chat_invite_link_joiners_count( ) -> int: """Get the count of the members who joined the chat with the invite link. - .. include:: /_includes/usable-by/users-bots.rst + .. include:: /_includes/usable-by/users.rst Parameters: chat_id (``int`` | ``str``): diff --git a/pyrogram/methods/invite_links/get_chat_join_requests.py b/pyrogram/methods/invite_links/get_chat_join_requests.py index ee6fe7e388..a75498e2f6 100644 --- a/pyrogram/methods/invite_links/get_chat_join_requests.py +++ b/pyrogram/methods/invite_links/get_chat_join_requests.py @@ -32,7 +32,7 @@ async def get_chat_join_requests( ) -> Optional[AsyncGenerator["types.ChatJoiner", None]]: """Get the pending join requests of a chat. - .. include:: /_includes/usable-by/users-bots.rst + .. include:: /_includes/usable-by/users.rst Parameters: chat_id (``int`` | ``str``): From 9ade92c85525ecd439ab52506047991220664040 Mon Sep 17 00:00:00 2001 From: Andrea Princic <48788808+Princic-1837592@users.noreply.github.com> Date: Sat, 15 Oct 2022 18:19:38 +0200 Subject: [PATCH 1045/1185] Add languages to "pre" tags (HTML and Markdown) #1118 * added `language` to entities when unparsing (both markdown and html) * added `language` to entities also when parsing (html only) * Update html.py * Update markdown.py * Update markdown.py * Update markdown.py Co-authored-by: Dan <14043624+delivrance@users.noreply.github.com> --- pyrogram/parser/html.py | 8 ++++++-- pyrogram/parser/markdown.py | 12 +++++++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/pyrogram/parser/html.py b/pyrogram/parser/html.py index dee8a49dd4..af70c5cb34 100644 --- a/pyrogram/parser/html.py +++ b/pyrogram/parser/html.py @@ -61,7 +61,7 @@ def handle_starttag(self, tag, attrs): entity = raw.types.MessageEntityCode elif tag == "pre": entity = raw.types.MessageEntityPre - extra["language"] = "" + extra["language"] = attrs.get("language", "") elif tag == "spoiler": entity = raw.types.MessageEntitySpoiler elif tag == "a": @@ -172,9 +172,13 @@ def unparse(text: str, entities: list): name = entity_type.name[0].lower() start_tag = f"<{name}>" end_tag = f"" + elif entity_type == MessageEntityType.PRE: + name = entity_type.name.lower() + language = getattr(entity, "language", "") or "" + start_tag = f'<{name} language="{language}">' if language else f"<{name}>" + end_tag = f"" elif entity_type in ( MessageEntityType.CODE, - MessageEntityType.PRE, MessageEntityType.BLOCKQUOTE, MessageEntityType.SPOILER, ): diff --git a/pyrogram/parser/markdown.py b/pyrogram/parser/markdown.py index 364793406a..6219d9583d 100644 --- a/pyrogram/parser/markdown.py +++ b/pyrogram/parser/markdown.py @@ -105,6 +105,12 @@ async def parse(self, text: str, strict: bool = False): delims.remove(delim) tag = CLOSING_TAG.format(tag) + if delim == PRE_DELIM and delim in delims: + delim_and_language = text[text.find(PRE_DELIM):].split("\n")[0] + language = delim_and_language[len(PRE_DELIM):] + text = utils.replace_once(text, delim_and_language, f'
', start)
+                continue
+
             text = utils.replace_once(text, delim, tag, start)
 
         return await self.html.parse(text)
@@ -130,7 +136,11 @@ def unparse(text: str, entities: list):
                 start_tag = end_tag = STRIKE_DELIM
             elif entity_type == MessageEntityType.CODE:
                 start_tag = end_tag = CODE_DELIM
-            elif entity_type in (MessageEntityType.PRE, MessageEntityType.BLOCKQUOTE):
+            elif entity_type == MessageEntityType.PRE:
+                language = getattr(entity, "language", "") or ""
+                start_tag = f"{PRE_DELIM}{language}\n"
+                end_tag = f"\n{PRE_DELIM}"
+            elif entity_type == MessageEntityType.BLOCKQUOTE:
                 start_tag = end_tag = PRE_DELIM
             elif entity_type == MessageEntityType.SPOILER:
                 start_tag = end_tag = SPOILER_DELIM

From b660115a603c14938dc518688054b1b5181487a4 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sat, 15 Oct 2022 18:20:46 +0200
Subject: [PATCH 1046/1185] Update Pyrogram to v2.0.58

---
 pyrogram/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index e9dcc31f0b..0852e565b5 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -16,7 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-__version__ = "2.0.57"
+__version__ = "2.0.58"
 __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)"
 __copyright__ = "Copyright (C) 2017-present Dan "
 

From 3eda82d0aff94c24d2ba1ecd5767de5ae70c09e2 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 16 Oct 2022 12:07:24 +0200
Subject: [PATCH 1047/1185] Update html.py

---
 pyrogram/parser/html.py | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/pyrogram/parser/html.py b/pyrogram/parser/html.py
index af70c5cb34..7ed2a5be10 100644
--- a/pyrogram/parser/html.py
+++ b/pyrogram/parser/html.py
@@ -117,7 +117,8 @@ def __init__(self, client: Optional["pyrogram.Client"]):
         self.client = client
 
     async def parse(self, text: str):
-        # Strip whitespace characters from the end of the message, but preserve closing tags
+        # Strip whitespaces from the beginning and the end, but preserve closing tags
+        text = re.sub(r"^\s*(<[\w<>=\s\"]*>)\s*", r"\1", text)
         text = re.sub(r"\s*(]*>)\s*$", r"\1", text)
 
         parser = Parser(self.client)

From ef92389ed029ddc4d95e2d234cf3d302774c7e7d Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 16 Oct 2022 12:07:57 +0200
Subject: [PATCH 1048/1185] Update Pyrogram to v2.0.59

---
 pyrogram/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index 0852e565b5..924bb27644 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -16,7 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-__version__ = "2.0.58"
+__version__ = "2.0.59"
 __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)"
 __copyright__ = "Copyright (C) 2017-present Dan "
 

From a374e0175d524cbd43ea64801fc0c5b11eb959fd Mon Sep 17 00:00:00 2001
From: Nick <64551534+null-nick@users.noreply.github.com>
Date: Sun, 13 Nov 2022 13:55:44 +0100
Subject: [PATCH 1049/1185] Update API schema to Layer 148

---
 compiler/api/source/main_api.tl | 94 ++++++++++++++++++++++-----------
 1 file changed, 64 insertions(+), 30 deletions(-)

diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl
index 9d71625f9d..e89ef9ff49 100644
--- a/compiler/api/source/main_api.tl
+++ b/compiler/api/source/main_api.tl
@@ -88,7 +88,7 @@ storage.fileMp4#b3cea0e4 = storage.FileType;
 storage.fileWebp#1081464c = storage.FileType;
 
 userEmpty#d3bc4b7a id:long = User;
-user#5d99adee flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true apply_min_photo:flags.25?true fake:flags.26?true bot_attach_menu:flags.27?true premium:flags.28?true attach_menu_enabled:flags.29?true id:long access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector bot_inline_placeholder:flags.19?string lang_code:flags.22?string emoji_status:flags.30?EmojiStatus = User;
+user#8f97c628 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true apply_min_photo:flags.25?true fake:flags.26?true bot_attach_menu:flags.27?true premium:flags.28?true attach_menu_enabled:flags.29?true flags2:# id:long access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector bot_inline_placeholder:flags.19?string lang_code:flags.22?string emoji_status:flags.30?EmojiStatus usernames:flags2.0?Vector = User;
 
 userProfilePhotoEmpty#4f11bae1 = UserProfilePhoto;
 userProfilePhoto#82d1f706 flags:# has_video:flags.0?true photo_id:long stripped_thumb:flags.1?bytes dc_id:int = UserProfilePhoto;
@@ -103,7 +103,7 @@ userStatusLastMonth#77ebc742 = UserStatus;
 chatEmpty#29562865 id:long = Chat;
 chat#41cbf256 flags:# creator:flags.0?true left:flags.2?true deactivated:flags.5?true call_active:flags.23?true call_not_empty:flags.24?true noforwards:flags.25?true id:long title:string photo:ChatPhoto participants_count:int date:int version:int migrated_to:flags.6?InputChannel admin_rights:flags.14?ChatAdminRights default_banned_rights:flags.18?ChatBannedRights = Chat;
 chatForbidden#6592a1a7 id:long title:string = Chat;
-channel#8261ac61 flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true fake:flags.25?true gigagroup:flags.26?true noforwards:flags.27?true join_to_send:flags.28?true join_request:flags.29?true id:long access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int restriction_reason:flags.9?Vector admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int = Chat;
+channel#83259464 flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true fake:flags.25?true gigagroup:flags.26?true noforwards:flags.27?true join_to_send:flags.28?true join_request:flags.29?true forum:flags.30?true flags2:# id:long access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int restriction_reason:flags.9?Vector admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int usernames:flags2.0?Vector = Chat;
 channelForbidden#17d493d5 flags:# broadcast:flags.5?true megagroup:flags.8?true id:long access_hash:long title:string until_date:flags.16?int = Chat;
 
 chatFull#c9d31138 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:long about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer theme_emoticon:flags.16?string requests_pending:flags.17?int recent_requesters:flags.17?Vector available_reactions:flags.18?ChatReactions = ChatFull;
@@ -170,6 +170,8 @@ messageActionChatJoinedByRequest#ebbca3cb = MessageAction;
 messageActionWebViewDataSentMe#47dd8079 text:string data:string = MessageAction;
 messageActionWebViewDataSent#b4c38cb5 text:string = MessageAction;
 messageActionGiftPremium#aba0f5c6 currency:string amount:long months:int = MessageAction;
+messageActionTopicCreate#d999256 flags:# title:string icon_color:int icon_emoji_id:flags.0?long = MessageAction;
+messageActionTopicEdit#b18a431c flags:# title:flags.0?string icon_emoji_id:flags.1?long closed:flags.2?Bool = MessageAction;
 
 dialog#a8edd0f5 flags:# pinned:flags.2?true unread_mark:flags.3?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int unread_reactions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int = Dialog;
 dialogFolder#71bd134c flags:# pinned:flags.2?true folder:Folder peer:Peer top_message:int unread_muted_peers_count:int unread_unmuted_peers_count:int unread_muted_messages_count:int unread_unmuted_messages_count:int = Dialog;
@@ -198,6 +200,7 @@ inputNotifyPeer#b8bc5b0c peer:InputPeer = InputNotifyPeer;
 inputNotifyUsers#193b4417 = InputNotifyPeer;
 inputNotifyChats#4a95e84e = InputNotifyPeer;
 inputNotifyBroadcasts#b1db7c7e = InputNotifyPeer;
+inputNotifyForumTopic#5c467992 peer:InputPeer top_msg_id:int = InputNotifyPeer;
 
 inputPeerNotifySettings#df1f002b flags:# show_previews:flags.0?Bool silent:flags.1?Bool mute_until:flags.2?int sound:flags.3?NotificationSound = InputPeerNotifySettings;
 
@@ -276,7 +279,7 @@ updateUserTyping#c01e857f user_id:long action:SendMessageAction = Update;
 updateChatUserTyping#83487af0 chat_id:long from_id:Peer action:SendMessageAction = Update;
 updateChatParticipants#7761198 participants:ChatParticipants = Update;
 updateUserStatus#e5bdf8de user_id:long status:UserStatus = Update;
-updateUserName#c3f202e0 user_id:long first_name:string last_name:string username:string = Update;
+updateUserName#a7848924 user_id:long first_name:string last_name:string usernames:Vector = Update;
 updateUserPhoto#f227868c user_id:long date:int photo:UserProfilePhoto previous:Bool = Update;
 updateNewEncryptedMessage#12bcbd9a message:EncryptedMessage qts:int = Update;
 updateEncryptedChatTyping#1710f156 chat_id:int = Update;
@@ -311,7 +314,7 @@ updateBotCallbackQuery#b9cfc48d flags:# query_id:long user_id:long peer:Peer msg
 updateEditMessage#e40370a3 message:Message pts:int pts_count:int = Update;
 updateInlineBotCallbackQuery#691e9052 flags:# query_id:long user_id:long msg_id:InputBotInlineMessageID chat_instance:long data:flags.0?bytes game_short_name:flags.1?string = Update;
 updateReadChannelOutbox#b75f99a9 channel_id:long max_id:int = Update;
-updateDraftMessage#ee2bb969 peer:Peer draft:DraftMessage = Update;
+updateDraftMessage#1b49ec6d flags:# peer:Peer top_msg_id:flags.0?int draft:DraftMessage = Update;
 updateReadFeaturedStickers#571d2742 = Update;
 updateRecentStickers#9a422c20 = Update;
 updateConfig#a229dd06 = Update;
@@ -327,7 +330,7 @@ updatePhoneCall#ab0f6b1e phone_call:PhoneCall = Update;
 updateLangPackTooLong#46560264 lang_code:string = Update;
 updateLangPack#56022f4d difference:LangPackDifference = Update;
 updateFavedStickers#e511996d = Update;
-updateChannelReadMessagesContents#44bdd535 channel_id:long messages:Vector = Update;
+updateChannelReadMessagesContents#ea29055d flags:# channel_id:long top_msg_id:flags.0?int messages:Vector = Update;
 updateContactsReset#7084a7be = Update;
 updateChannelAvailableMessages#b23fc698 channel_id:long available_min_id:int = Update;
 updateDialogUnreadMark#e16459c3 flags:# unread:flags.0?true peer:DialogPeer = Update;
@@ -364,7 +367,7 @@ updateGroupCallConnection#b783982 flags:# presentation:flags.0?true params:DataJ
 updateBotCommands#4d712f2e peer:Peer bot_id:long commands:Vector = Update;
 updatePendingJoinRequests#7063c3db peer:Peer requests_pending:int recent_requesters:Vector = Update;
 updateBotChatInviteRequester#11dfa986 peer:Peer date:int user_id:long about:string invite:ExportedChatInvite qts:int = Update;
-updateMessageReactions#154798c3 peer:Peer msg_id:int reactions:MessageReactions = Update;
+updateMessageReactions#5e1b3cb8 flags:# peer:Peer msg_id:int top_msg_id:flags.0?int reactions:MessageReactions = Update;
 updateAttachMenuBots#17b7a20b = Update;
 updateWebViewResultSent#1592b79d query_id:long = Update;
 updateBotMenuButton#14b85813 bot_id:long button:BotMenuButton = Update;
@@ -376,6 +379,7 @@ updateRecentEmojiStatuses#30f443db = Update;
 updateRecentReactions#6f7863f4 = Update;
 updateMoveStickerSetToTop#86fccf85 flags:# masks:flags.0?true emojis:flags.1?true stickerset:long = Update;
 updateMessageExtendedMedia#5a73a98c peer:Peer msg_id:int extended_media:MessageExtendedMedia = Update;
+updateChannelPinnedTopic#f694b0ae flags:# channel_id:long topic_id:flags.0?int = Update;
 
 updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State;
 
@@ -448,6 +452,7 @@ notifyPeer#9fd40bd8 peer:Peer = NotifyPeer;
 notifyUsers#b4c83b4c = NotifyPeer;
 notifyChats#c007cec3 = NotifyPeer;
 notifyBroadcasts#d612e8ef = NotifyPeer;
+notifyForumTopic#226e6308 peer:Peer top_msg_id:int = NotifyPeer;
 
 sendMessageTypingAction#16bf744e = SendMessageAction;
 sendMessageCancelAction#fd5ec8f5 = SendMessageAction;
@@ -566,10 +571,11 @@ inputStickerSetAnimatedEmojiAnimations#cde3739 = InputStickerSet;
 inputStickerSetPremiumGifts#c88b3b02 = InputStickerSet;
 inputStickerSetEmojiGenericAnimations#4c4d4ce = InputStickerSet;
 inputStickerSetEmojiDefaultStatuses#29d0f5ee = InputStickerSet;
+inputStickerSetEmojiDefaultTopicIcons#44c1f8e9 = InputStickerSet;
 
 stickerSet#2dd14edc flags:# archived:flags.1?true official:flags.2?true masks:flags.3?true animated:flags.5?true videos:flags.6?true emojis:flags.7?true installed_date:flags.0?int id:long access_hash:long title:string short_name:string thumbs:flags.4?Vector thumb_dc_id:flags.4?int thumb_version:flags.4?int thumb_document_id:flags.8?long count:int hash:int = StickerSet;
 
-messages.stickerSet#b60a24a6 set:StickerSet packs:Vector documents:Vector = messages.StickerSet;
+messages.stickerSet#6e153f16 set:StickerSet packs:Vector keywords:Vector documents:Vector = messages.StickerSet;
 messages.stickerSetNotModified#d3f924eb = messages.StickerSet;
 
 botCommand#c27ac8c7 command:string description:string = BotCommand;
@@ -748,7 +754,7 @@ messages.stickerSetInstallResultArchive#35e410a8 sets:Vector
 
 stickerSetCovered#6410a5d2 set:StickerSet cover:Document = StickerSetCovered;
 stickerSetMultiCovered#3407e51b set:StickerSet covers:Vector = StickerSetCovered;
-stickerSetFullCovered#1aed5ee5 set:StickerSet packs:Vector documents:Vector = StickerSetCovered;
+stickerSetFullCovered#40d13c0e set:StickerSet packs:Vector keywords:Vector documents:Vector = StickerSetCovered;
 
 maskCoords#aed6dbb2 n:int x:double y:double zoom:double = MaskCoords;
 
@@ -930,12 +936,18 @@ channelAdminLogEventActionParticipantJoinByRequest#afb6144a invite:ExportedChatI
 channelAdminLogEventActionToggleNoForwards#cb2ac766 new_value:Bool = ChannelAdminLogEventAction;
 channelAdminLogEventActionSendMessage#278f2868 message:Message = ChannelAdminLogEventAction;
 channelAdminLogEventActionChangeAvailableReactions#be4e0ef8 prev_value:ChatReactions new_value:ChatReactions = ChannelAdminLogEventAction;
+channelAdminLogEventActionChangeUsernames#f04fb3a9 prev_value:Vector new_value:Vector = ChannelAdminLogEventAction;
+channelAdminLogEventActionToggleForum#2cc6383 new_value:Bool = ChannelAdminLogEventAction;
+channelAdminLogEventActionCreateTopic#58707d28 topic:ForumTopic = ChannelAdminLogEventAction;
+channelAdminLogEventActionEditTopic#f06fe208 prev_topic:ForumTopic new_topic:ForumTopic = ChannelAdminLogEventAction;
+channelAdminLogEventActionDeleteTopic#ae168909 topic:ForumTopic = ChannelAdminLogEventAction;
+channelAdminLogEventActionPinTopic#5d8d353b flags:# prev_topic:flags.0?ForumTopic new_topic:flags.1?ForumTopic = ChannelAdminLogEventAction;
 
 channelAdminLogEvent#1fad68cd id:long date:int user_id:long action:ChannelAdminLogEventAction = ChannelAdminLogEvent;
 
 channels.adminLogResults#ed8af74d events:Vector chats:Vector users:Vector = channels.AdminLogResults;
 
-channelAdminLogEventsFilter#ea107ae4 flags:# join:flags.0?true leave:flags.1?true invite:flags.2?true ban:flags.3?true unban:flags.4?true kick:flags.5?true unkick:flags.6?true promote:flags.7?true demote:flags.8?true info:flags.9?true settings:flags.10?true pinned:flags.11?true edit:flags.12?true delete:flags.13?true group_call:flags.14?true invites:flags.15?true send:flags.16?true = ChannelAdminLogEventsFilter;
+channelAdminLogEventsFilter#ea107ae4 flags:# join:flags.0?true leave:flags.1?true invite:flags.2?true ban:flags.3?true unban:flags.4?true kick:flags.5?true unkick:flags.6?true promote:flags.7?true demote:flags.8?true info:flags.9?true settings:flags.10?true pinned:flags.11?true edit:flags.12?true delete:flags.13?true group_call:flags.14?true invites:flags.15?true send:flags.16?true forums:flags.17?true = ChannelAdminLogEventsFilter;
 
 popularContact#5ce14175 client_id:long importers:int = PopularContact;
 
@@ -1093,9 +1105,9 @@ chatOnlines#f041e250 onlines:int = ChatOnlines;
 
 statsURL#47a971e0 url:string = StatsURL;
 
-chatAdminRights#5fb224d5 flags:# change_info:flags.0?true post_messages:flags.1?true edit_messages:flags.2?true delete_messages:flags.3?true ban_users:flags.4?true invite_users:flags.5?true pin_messages:flags.7?true add_admins:flags.9?true anonymous:flags.10?true manage_call:flags.11?true other:flags.12?true = ChatAdminRights;
+chatAdminRights#5fb224d5 flags:# change_info:flags.0?true post_messages:flags.1?true edit_messages:flags.2?true delete_messages:flags.3?true ban_users:flags.4?true invite_users:flags.5?true pin_messages:flags.7?true add_admins:flags.9?true anonymous:flags.10?true manage_call:flags.11?true other:flags.12?true manage_topics:flags.13?true = ChatAdminRights;
 
-chatBannedRights#9f120418 flags:# view_messages:flags.0?true send_messages:flags.1?true send_media:flags.2?true send_stickers:flags.3?true send_gifs:flags.4?true send_games:flags.5?true send_inline:flags.6?true embed_links:flags.7?true send_polls:flags.8?true change_info:flags.10?true invite_users:flags.15?true pin_messages:flags.17?true until_date:int = ChatBannedRights;
+chatBannedRights#9f120418 flags:# view_messages:flags.0?true send_messages:flags.1?true send_media:flags.2?true send_stickers:flags.3?true send_gifs:flags.4?true send_games:flags.5?true send_inline:flags.6?true embed_links:flags.7?true send_polls:flags.8?true change_info:flags.10?true invite_users:flags.15?true pin_messages:flags.17?true manage_topics:flags.18?true until_date:int = ChatBannedRights;
 
 inputWallPaper#e630b979 id:long access_hash:long = InputWallPaper;
 inputWallPaperSlug#72091c80 slug:string = InputWallPaper;
@@ -1226,7 +1238,7 @@ messages.messageViews#b6c4f543 views:Vector chats:Vector use
 
 messages.discussionMessage#a6341782 flags:# messages:Vector max_id:flags.0?int read_inbox_max_id:flags.1?int read_outbox_max_id:flags.2?int unread_count:int chats:Vector users:Vector = messages.DiscussionMessage;
 
-messageReplyHeader#a6d57763 flags:# reply_to_scheduled:flags.2?true reply_to_msg_id:int reply_to_peer_id:flags.0?Peer reply_to_top_id:flags.1?int = MessageReplyHeader;
+messageReplyHeader#a6d57763 flags:# reply_to_scheduled:flags.2?true forum_topic:flags.3?true reply_to_msg_id:int reply_to_peer_id:flags.0?Peer reply_to_top_id:flags.1?int = MessageReplyHeader;
 
 messageReplies#83d60fc2 flags:# comments:flags.0?true replies:int replies_pts:int recent_repliers:flags.1?Vector channel_id:flags.0?long max_id:flags.2?int read_max_id:flags.3?int = MessageReplies;
 
@@ -1294,9 +1306,10 @@ account.resetPasswordFailedWait#e3779861 retry_date:int = account.ResetPasswordR
 account.resetPasswordRequestedWait#e9effc7d until_date:int = account.ResetPasswordResult;
 account.resetPasswordOk#e926d63e = account.ResetPasswordResult;
 
-sponsoredMessage#3a836df8 flags:# recommended:flags.5?true random_id:bytes from_id:flags.3?Peer chat_invite:flags.4?ChatInvite chat_invite_hash:flags.4?string channel_post:flags.2?int start_param:flags.0?string message:string entities:flags.1?Vector = SponsoredMessage;
+sponsoredMessage#3a836df8 flags:# recommended:flags.5?true show_peer_photo:flags.6?true random_id:bytes from_id:flags.3?Peer chat_invite:flags.4?ChatInvite chat_invite_hash:flags.4?string channel_post:flags.2?int start_param:flags.0?string message:string entities:flags.1?Vector = SponsoredMessage;
 
-messages.sponsoredMessages#65a4c7d5 messages:Vector chats:Vector users:Vector = messages.SponsoredMessages;
+messages.sponsoredMessages#c9ee1d87 flags:# posts_between:flags.0?int messages:Vector chats:Vector users:Vector = messages.SponsoredMessages;
+messages.sponsoredMessagesEmpty#1839490f = messages.SponsoredMessages;
 
 searchResultsCalendarPeriod#c9b0539f date:int min_msg_id:int max_msg_id:int count:int = SearchResultsCalendarPeriod;
 
@@ -1426,6 +1439,15 @@ sendAsPeer#b81c7034 flags:# premium_required:flags.0?true peer:Peer = SendAsPeer
 messageExtendedMediaPreview#ad628cc8 flags:# w:flags.0?int h:flags.0?int thumb:flags.1?PhotoSize video_duration:flags.2?int = MessageExtendedMedia;
 messageExtendedMedia#ee479c64 media:MessageMedia = MessageExtendedMedia;
 
+stickerKeyword#fcfeb29c document_id:long keyword:Vector = StickerKeyword;
+
+username#b4073647 flags:# editable:flags.0?true active:flags.1?true username:string = Username;
+
+forumTopicDeleted#23f109b id:int = ForumTopic;
+forumTopic#71701da9 flags:# my:flags.1?true closed:flags.2?true pinned:flags.3?true id:int date:int title:string icon_color:int icon_emoji_id:flags.0?long top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int unread_reactions_count:int from_id:Peer notify_settings:PeerNotifySettings draft:flags.4?DraftMessage = ForumTopic;
+
+messages.forumTopics#367617d3 flags:# order_by_create_date:flags.0?true count:int topics:Vector messages:Vector chats:Vector users:Vector pts:int = messages.ForumTopics;
+
 ---functions---
 
 invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
@@ -1516,7 +1538,7 @@ account.createTheme#652e4400 flags:# slug:string title:string document:flags.2?I
 account.updateTheme#2bf40ccc flags:# format:string theme:InputTheme slug:flags.0?string title:flags.1?string document:flags.2?InputDocument settings:flags.3?Vector = Theme;
 account.saveTheme#f257106c theme:InputTheme unsave:Bool = Bool;
 account.installTheme#c727bb3b flags:# dark:flags.0?true theme:flags.1?InputTheme format:flags.2?string base_theme:flags.3?BaseTheme = Bool;
-account.getTheme#8d9d742b format:string theme:InputTheme document_id:long = Theme;
+account.getTheme#3a5869ec format:string theme:InputTheme = Theme;
 account.getThemes#7206e458 format:string hash:long = account.Themes;
 account.setContentSettings#b574b16b flags:# sensitive_enabled:flags.0?true = Bool;
 account.getContentSettings#8b9b4dae = account.ContentSettings;
@@ -1536,6 +1558,8 @@ account.updateEmojiStatus#fbd3de6b emoji_status:EmojiStatus = Bool;
 account.getDefaultEmojiStatuses#d6753386 hash:long = account.EmojiStatuses;
 account.getRecentEmojiStatuses#f578105 hash:long = account.EmojiStatuses;
 account.clearRecentEmojiStatuses#18201aae = Bool;
+account.reorderUsernames#ef500eab order:Vector = Bool;
+account.toggleUsername#58d6b376 username:string active:Bool = Bool;
 
 users.getUsers#d91a548 id:Vector = Vector;
 users.getFullUser#b60f5918 id:InputUser = users.UserFull;
@@ -1572,9 +1596,9 @@ messages.deleteHistory#b08f922a flags:# just_clear:flags.0?true revoke:flags.1?t
 messages.deleteMessages#e58e95d2 flags:# revoke:flags.0?true id:Vector = messages.AffectedMessages;
 messages.receivedMessages#5a954c0 max_id:int = Vector;
 messages.setTyping#58943ee2 flags:# peer:InputPeer top_msg_id:flags.0?int action:SendMessageAction = Bool;
-messages.sendMessage#d9d75a4 flags:# no_webpage:flags.1?true silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true peer:InputPeer reply_to_msg_id:flags.0?int message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates;
-messages.sendMedia#e25ff8e0 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true peer:InputPeer reply_to_msg_id:flags.0?int media:InputMedia message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates;
-messages.forwardMessages#cc30290b flags:# silent:flags.5?true background:flags.6?true with_my_score:flags.8?true drop_author:flags.11?true drop_media_captions:flags.12?true noforwards:flags.14?true from_peer:InputPeer id:Vector random_id:Vector to_peer:InputPeer schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates;
+messages.sendMessage#1cc20387 flags:# no_webpage:flags.1?true silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true peer:InputPeer reply_to_msg_id:flags.0?int top_msg_id:flags.9?int message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates;
+messages.sendMedia#7547c966 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true peer:InputPeer reply_to_msg_id:flags.0?int top_msg_id:flags.9?int media:InputMedia message:string random_id:long reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates;
+messages.forwardMessages#c661bbc4 flags:# silent:flags.5?true background:flags.6?true with_my_score:flags.8?true drop_author:flags.11?true drop_media_captions:flags.12?true noforwards:flags.14?true from_peer:InputPeer id:Vector random_id:Vector to_peer:InputPeer top_msg_id:flags.9?int schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates;
 messages.reportSpam#cf1592db peer:InputPeer = Bool;
 messages.getPeerSettings#efd9a6a2 peer:InputPeer = messages.PeerSettings;
 messages.report#8953ab4e peer:InputPeer id:Vector reason:ReportReason message:string = Bool;
@@ -1617,14 +1641,14 @@ messages.getSavedGifs#5cf09635 hash:long = messages.SavedGifs;
 messages.saveGif#327a30cb id:InputDocument unsave:Bool = Bool;
 messages.getInlineBotResults#514e999d flags:# bot:InputUser peer:InputPeer geo_point:flags.0?InputGeoPoint query:string offset:string = messages.BotResults;
 messages.setInlineBotResults#eb5ea206 flags:# gallery:flags.0?true private:flags.1?true query_id:long results:Vector cache_time:int next_offset:flags.2?string switch_pm:flags.3?InlineBotSwitchPM = Bool;
-messages.sendInlineBotResult#7aa11297 flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true hide_via:flags.11?true peer:InputPeer reply_to_msg_id:flags.0?int random_id:long query_id:long id:string schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates;
+messages.sendInlineBotResult#d3fbdccb flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true hide_via:flags.11?true peer:InputPeer reply_to_msg_id:flags.0?int top_msg_id:flags.9?int random_id:long query_id:long id:string schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates;
 messages.getMessageEditData#fda68d36 peer:InputPeer id:int = messages.MessageEditData;
 messages.editMessage#48f71778 flags:# no_webpage:flags.1?true peer:InputPeer id:int message:flags.11?string media:flags.14?InputMedia reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.15?int = Updates;
 messages.editInlineBotMessage#83557dba flags:# no_webpage:flags.1?true id:InputBotInlineMessageID message:flags.11?string media:flags.14?InputMedia reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector = Bool;
 messages.getBotCallbackAnswer#9342ca07 flags:# game:flags.1?true peer:InputPeer msg_id:int data:flags.0?bytes password:flags.2?InputCheckPasswordSRP = messages.BotCallbackAnswer;
 messages.setBotCallbackAnswer#d58f130a flags:# alert:flags.1?true query_id:long message:flags.0?string url:flags.2?string cache_time:int = Bool;
 messages.getPeerDialogs#e470bcfd peers:Vector = messages.PeerDialogs;
-messages.saveDraft#bc39e14b flags:# no_webpage:flags.1?true reply_to_msg_id:flags.0?int peer:InputPeer message:string entities:flags.3?Vector = Bool;
+messages.saveDraft#b4331e3f flags:# no_webpage:flags.1?true reply_to_msg_id:flags.0?int top_msg_id:flags.2?int peer:InputPeer message:string entities:flags.3?Vector = Bool;
 messages.getAllDrafts#6a3f8d65 = Updates;
 messages.getFeaturedStickers#64780b14 hash:long = messages.FeaturedStickers;
 messages.readFeaturedStickers#5b118126 id:Vector = Bool;
@@ -1650,10 +1674,10 @@ messages.uploadMedia#519bc2b1 peer:InputPeer media:InputMedia = MessageMedia;
 messages.sendScreenshotNotification#c97df020 peer:InputPeer reply_to_msg_id:int random_id:long = Updates;
 messages.getFavedStickers#4f1aaa9 hash:long = messages.FavedStickers;
 messages.faveSticker#b9ffc55b id:InputDocument unfave:Bool = Bool;
-messages.getUnreadMentions#46578472 peer:InputPeer offset_id:int add_offset:int limit:int max_id:int min_id:int = messages.Messages;
-messages.readMentions#f0189d3 peer:InputPeer = messages.AffectedHistory;
+messages.getUnreadMentions#f107e790 flags:# peer:InputPeer top_msg_id:flags.0?int offset_id:int add_offset:int limit:int max_id:int min_id:int = messages.Messages;
+messages.readMentions#36e5bf4d flags:# peer:InputPeer top_msg_id:flags.0?int = messages.AffectedHistory;
 messages.getRecentLocations#702a40e0 peer:InputPeer limit:int hash:long = messages.Messages;
-messages.sendMultiMedia#f803138f flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true peer:InputPeer reply_to_msg_id:flags.0?int multi_media:Vector schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates;
+messages.sendMultiMedia#b6f11a1c flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true noforwards:flags.14?true update_stickersets_order:flags.15?true peer:InputPeer reply_to_msg_id:flags.0?int top_msg_id:flags.9?int multi_media:Vector schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates;
 messages.uploadEncryptedFile#5057c497 peer:InputEncryptedChat file:InputEncryptedFile = EncryptedFile;
 messages.searchStickerSets#35705b8a flags:# exclude_featured:flags.0?true q:string hash:long = messages.FoundStickerSets;
 messages.getSplitRanges#1cff7e08 = Vector;
@@ -1670,7 +1694,7 @@ messages.getEmojiKeywords#35a0e062 lang_code:string = EmojiKeywordsDifference;
 messages.getEmojiKeywordsDifference#1508b6af lang_code:string from_version:int = EmojiKeywordsDifference;
 messages.getEmojiKeywordsLanguages#4e9963b2 lang_codes:Vector = Vector;
 messages.getEmojiURL#d5b10c26 lang_code:string = EmojiURL;
-messages.getSearchCounters#732eef00 peer:InputPeer filters:Vector = Vector;
+messages.getSearchCounters#ae7cc1 flags:# peer:InputPeer top_msg_id:flags.0?int filters:Vector = Vector;
 messages.requestUrlAuth#198fb446 flags:# peer:flags.1?InputPeer msg_id:flags.1?int button_id:flags.1?int url:flags.2?string = UrlAuthResult;
 messages.acceptUrlAuth#b12c7125 flags:# write_allowed:flags.0?true peer:flags.1?InputPeer msg_id:flags.1?int button_id:flags.1?int url:flags.2?string = UrlAuthResult;
 messages.hidePeerSettingsBar#4facb138 peer:InputPeer = Bool;
@@ -1688,7 +1712,7 @@ messages.getOldFeaturedStickers#7ed094a1 offset:int limit:int hash:long = messag
 messages.getReplies#22ddd30c peer:InputPeer msg_id:int offset_id:int offset_date:int add_offset:int limit:int max_id:int min_id:int hash:long = messages.Messages;
 messages.getDiscussionMessage#446972fd peer:InputPeer msg_id:int = messages.DiscussionMessage;
 messages.readDiscussion#f731a9f4 peer:InputPeer msg_id:int read_max_id:int = Bool;
-messages.unpinAllMessages#f025bc8b peer:InputPeer = messages.AffectedHistory;
+messages.unpinAllMessages#ee22b9a8 flags:# peer:InputPeer top_msg_id:flags.0?int = messages.AffectedHistory;
 messages.deleteChat#5bd0ee50 chat_id:long = Bool;
 messages.deletePhoneCallHistory#f9cbe409 flags:# revoke:flags.0?true = messages.AffectedFoundMessages;
 messages.checkHistoryImport#43fe19f3 import_head:string = messages.HistoryImportParsed;
@@ -1719,14 +1743,14 @@ messages.setChatAvailableReactions#feb16771 peer:InputPeer available_reactions:C
 messages.getAvailableReactions#18dea0ac hash:int = messages.AvailableReactions;
 messages.setDefaultReaction#4f47a016 reaction:Reaction = Bool;
 messages.translateText#24ce6dee flags:# peer:flags.0?InputPeer msg_id:flags.0?int text:flags.1?string from_lang:flags.2?string to_lang:string = messages.TranslatedText;
-messages.getUnreadReactions#e85bae1a peer:InputPeer offset_id:int add_offset:int limit:int max_id:int min_id:int = messages.Messages;
-messages.readReactions#82e251d7 peer:InputPeer = messages.AffectedHistory;
+messages.getUnreadReactions#3223495b flags:# peer:InputPeer top_msg_id:flags.0?int offset_id:int add_offset:int limit:int max_id:int min_id:int = messages.Messages;
+messages.readReactions#54aa7f8e flags:# peer:InputPeer top_msg_id:flags.0?int = messages.AffectedHistory;
 messages.searchSentMedia#107e31a0 q:string filter:MessagesFilter limit:int = messages.Messages;
 messages.getAttachMenuBots#16fcc2cb hash:long = AttachMenuBots;
 messages.getAttachMenuBot#77216192 bot:InputUser = AttachMenuBotsBot;
 messages.toggleBotInAttachMenu#1aee33af bot:InputUser enabled:Bool = Bool;
-messages.requestWebView#fc87a53c flags:# from_bot_menu:flags.4?true silent:flags.5?true peer:InputPeer bot:InputUser url:flags.1?string start_param:flags.3?string theme_params:flags.2?DataJSON platform:string reply_to_msg_id:flags.0?int send_as:flags.13?InputPeer = WebViewResult;
-messages.prolongWebView#ea5fbcce flags:# silent:flags.5?true peer:InputPeer bot:InputUser query_id:long reply_to_msg_id:flags.0?int send_as:flags.13?InputPeer = Bool;
+messages.requestWebView#178b480b flags:# from_bot_menu:flags.4?true silent:flags.5?true peer:InputPeer bot:InputUser url:flags.1?string start_param:flags.3?string theme_params:flags.2?DataJSON platform:string reply_to_msg_id:flags.0?int top_msg_id:flags.9?int send_as:flags.13?InputPeer = WebViewResult;
+messages.prolongWebView#7ff34309 flags:# silent:flags.5?true peer:InputPeer bot:InputUser query_id:long reply_to_msg_id:flags.0?int top_msg_id:flags.9?int send_as:flags.13?InputPeer = Bool;
 messages.requestSimpleWebView#299bec8e flags:# bot:InputUser url:string theme_params:flags.0?DataJSON platform:string = SimpleWebViewResult;
 messages.sendWebViewResultMessage#a4314f5 bot_query_id:string result:InputBotInlineResult = WebViewMessageSent;
 messages.sendWebViewData#dc0242c8 bot:InputUser random_id:long button_text:string data:string = Updates;
@@ -1824,6 +1848,16 @@ channels.getSendAs#dc770ee peer:InputPeer = channels.SendAsPeers;
 channels.deleteParticipantHistory#367544db channel:InputChannel participant:InputPeer = messages.AffectedHistory;
 channels.toggleJoinToSend#e4cb9580 channel:InputChannel enabled:Bool = Updates;
 channels.toggleJoinRequest#4c2985b6 channel:InputChannel enabled:Bool = Updates;
+channels.reorderUsernames#b45ced1d channel:InputChannel order:Vector = Bool;
+channels.toggleUsername#50f24105 channel:InputChannel username:string active:Bool = Bool;
+channels.deactivateAllUsernames#a245dd3 channel:InputChannel = Bool;
+channels.toggleForum#a4298b29 channel:InputChannel enabled:Bool = Updates;
+channels.createForumTopic#f40c0224 flags:# channel:InputChannel title:string icon_color:flags.0?int icon_emoji_id:flags.3?long random_id:long send_as:flags.2?InputPeer = Updates;
+channels.getForumTopics#de560d1 flags:# channel:InputChannel q:flags.0?string offset_date:int offset_id:int offset_topic:int limit:int = messages.ForumTopics;
+channels.getForumTopicsByID#b0831eb9 channel:InputChannel topics:Vector = messages.ForumTopics;
+channels.editForumTopic#6c883e2d flags:# channel:InputChannel topic_id:int title:flags.0?string icon_emoji_id:flags.1?long closed:flags.2?Bool = Updates;
+channels.updatePinnedForumTopic#6c2d9026 channel:InputChannel topic_id:int pinned:Bool = Updates;
+channels.deleteTopicHistory#34435f2d channel:InputChannel top_msg_id:int = messages.AffectedHistory;
 
 bots.sendCustomRequest#aa2769ed custom_method:string params:DataJSON = DataJSON;
 bots.answerWebhookJSONQuery#e6213f4d query_id:long data:DataJSON = Bool;
@@ -1902,4 +1936,4 @@ stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel
 stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages;
 stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats;
 
-// LAYER 146
\ No newline at end of file
+// LAYER 148

From 035bbbb76eb2a6d58d7e909e97a01660e2c62852 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 13 Nov 2022 14:13:22 +0100
Subject: [PATCH 1050/1185] Update python.yml

---
 .github/workflows/python.yml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml
index 9d25f33f7b..e12233fb20 100644
--- a/.github/workflows/python.yml
+++ b/.github/workflows/python.yml
@@ -9,10 +9,10 @@ jobs:
     strategy:
       matrix:
         os: [ubuntu-latest, macos-latest]
-        python-version: ["3.7", "3.8", "3.9", "3.10"]
+        python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
 
     steps:
-      - uses: actions/checkout@v2
+      - uses: actions/checkout@v3
 
       - name: Set up Python ${{ matrix.python-version }}
         uses: actions/setup-python@v2

From b423730dcbd59d650362ade56457e156805b1c2b Mon Sep 17 00:00:00 2001
From: Sam <100821827+01101sam@users.noreply.github.com>
Date: Sun, 13 Nov 2022 21:18:42 +0800
Subject: [PATCH 1051/1185] Fix on_disconnect decorator not working (#1134)

* Fix on_disconnect decorator not working

* Remove useless if else statment in on_raw_update
---
 pyrogram/methods/decorators/on_disconnect.py | 5 +++++
 pyrogram/methods/decorators/on_raw_update.py | 2 +-
 2 files changed, 6 insertions(+), 1 deletion(-)

diff --git a/pyrogram/methods/decorators/on_disconnect.py b/pyrogram/methods/decorators/on_disconnect.py
index ae54800f86..26aa62f8fb 100644
--- a/pyrogram/methods/decorators/on_disconnect.py
+++ b/pyrogram/methods/decorators/on_disconnect.py
@@ -32,6 +32,11 @@ def on_disconnect(self=None) -> Callable:
         def decorator(func: Callable) -> Callable:
             if isinstance(self, pyrogram.Client):
                 self.add_handler(pyrogram.handlers.DisconnectHandler(func))
+            else:
+                if not hasattr(func, "handlers"):
+                    func.handlers = []
+
+                func.handlers.append((pyrogram.handlers.DisconnectHandler(func), 0))
 
             return func
 
diff --git a/pyrogram/methods/decorators/on_raw_update.py b/pyrogram/methods/decorators/on_raw_update.py
index dffc50b924..644bc8a5b9 100644
--- a/pyrogram/methods/decorators/on_raw_update.py
+++ b/pyrogram/methods/decorators/on_raw_update.py
@@ -46,7 +46,7 @@ def decorator(func: Callable) -> Callable:
                 func.handlers.append(
                     (
                         pyrogram.handlers.RawUpdateHandler(func),
-                        group if self is None else group
+                        group
                     )
                 )
 

From c98963973ee38100e482ec28e231124275ebd342 Mon Sep 17 00:00:00 2001
From: Ihor Boichuk <107807464+IhorBoichuk@users.noreply.github.com>
Date: Sun, 13 Nov 2022 15:22:12 +0200
Subject: [PATCH 1052/1185] Add Message.forwards field (#1103)

* Added missing field: forwards

* Update message.py

Co-authored-by: Dan <14043624+delivrance@users.noreply.github.com>
---
 pyrogram/types/messages_and_media/message.py | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py
index c49fd0f6c7..5ea1584d80 100644
--- a/pyrogram/types/messages_and_media/message.py
+++ b/pyrogram/types/messages_and_media/message.py
@@ -254,6 +254,9 @@ class Message(Object, Update):
 
         views (``int``, *optional*):
             Channel post views.
+	    
+	forwards (``int``, *optional*):
+            Channel post forwards.
 
         via_bot (:obj:`~pyrogram.types.User`):
             The information of the bot that generated the message from an inline query of a user.
@@ -361,6 +364,7 @@ def __init__(
         pinned_message: "Message" = None,
         game_high_score: int = None,
         views: int = None,
+	forwards: int = None,
         via_bot: "types.User" = None,
         outgoing: bool = None,
         matches: List[Match] = None,
@@ -436,6 +440,7 @@ def __init__(
         self.pinned_message = pinned_message
         self.game_high_score = game_high_score
         self.views = views
+        self.forwards = forwards
         self.via_bot = via_bot
         self.outgoing = outgoing
         self.matches = matches
@@ -800,6 +805,7 @@ async def _parse(
                 poll=poll,
                 dice=dice,
                 views=message.views,
+                forwards=message.forwards,
                 via_bot=types.User._parse(client, users.get(message.via_bot_id, None)),
                 outgoing=message.out,
                 reply_markup=reply_markup,

From 1b02a6a148fc87009bd6e60d9a0cf842543a6096 Mon Sep 17 00:00:00 2001
From: Albert Einstein <73480087+AlbertEinsteinTG@users.noreply.github.com>
Date: Sun, 13 Nov 2022 18:53:30 +0530
Subject: [PATCH 1053/1185] Add __all__ for better enums suggestions (#1126)

---
 pyrogram/enums/__init__.py | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/pyrogram/enums/__init__.py b/pyrogram/enums/__init__.py
index f847b3cf57..d19c7044ff 100644
--- a/pyrogram/enums/__init__.py
+++ b/pyrogram/enums/__init__.py
@@ -30,3 +30,20 @@
 from .poll_type import PollType
 from .sent_code_type import SentCodeType
 from .user_status import UserStatus
+
+__all__ = [
+    'ChatAction', 
+    'ChatEventAction', 
+    'ChatMemberStatus', 
+    'ChatMembersFilter', 
+    'ChatType', 
+    'MessageEntityType', 
+    'MessageMediaType', 
+    'MessageServiceType', 
+    'MessagesFilter', 
+    'NextCodeType', 
+    'ParseMode', 
+    'PollType', 
+    'SentCodeType', 
+    'UserStatus'
+]

From b848e052254e9f23a4e52b2f87b794f26cb42cd8 Mon Sep 17 00:00:00 2001
From: Artem Kvrvgv <50778263+kvrvgv@users.noreply.github.com>
Date: Sun, 13 Nov 2022 16:25:19 +0300
Subject: [PATCH 1054/1185] Fix copy_message return type hint (#1128)

* fixed copy_message method type-hint

* Update copy_message.py

Co-authored-by: Dan <14043624+delivrance@users.noreply.github.com>
---
 pyrogram/methods/messages/copy_message.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/methods/messages/copy_message.py b/pyrogram/methods/messages/copy_message.py
index 66b7e37cb3..0b6624b28d 100644
--- a/pyrogram/methods/messages/copy_message.py
+++ b/pyrogram/methods/messages/copy_message.py
@@ -45,7 +45,7 @@ async def copy_message(
             "types.ReplyKeyboardRemove",
             "types.ForceReply"
         ] = None
-    ) -> List["types.Message"]:
+    ) -> "types.Message":
         """Copy messages of any kind.
 
         The method is analogous to the method :meth:`~Client.forward_messages`, but the copied message doesn't have a

From 31b32184c927a4793bb5222624394e735b6722f8 Mon Sep 17 00:00:00 2001
From: "ALi.w" 
Date: Sun, 13 Nov 2022 16:57:19 +0330
Subject: [PATCH 1055/1185] Use getattr to get outgoing attribute in filters.me
 (#1137)

* Use getattr to get outgoing attribute from the message in me_filter.
Fixes #1136.

Signed-off-by: Aliwoto 

* Update filters.py

Signed-off-by: Aliwoto 
Co-authored-by: Dan <14043624+delivrance@users.noreply.github.com>
---
 pyrogram/filters.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/filters.py b/pyrogram/filters.py
index f31b385ed4..ac2dbf20fd 100644
--- a/pyrogram/filters.py
+++ b/pyrogram/filters.py
@@ -164,7 +164,7 @@ async def all_filter(_, __, ___):
 
 # region me_filter
 async def me_filter(_, __, m: Message):
-    return bool(m.from_user and m.from_user.is_self or m.outgoing)
+    return bool(m.from_user and m.from_user.is_self or getattr(m, "outgoing", False))
 
 
 me = create(me_filter)

From 0e64ebc0be1f84a8afd02a8a2c984df802faab3d Mon Sep 17 00:00:00 2001
From: Deekshith SH 
Date: Sun, 13 Nov 2022 18:58:18 +0530
Subject: [PATCH 1056/1185] Fix typo (#1029)

---
 pyrogram/methods/contacts/delete_contacts.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/methods/contacts/delete_contacts.py b/pyrogram/methods/contacts/delete_contacts.py
index b4b2c628a3..7f08f29730 100644
--- a/pyrogram/methods/contacts/delete_contacts.py
+++ b/pyrogram/methods/contacts/delete_contacts.py
@@ -33,7 +33,7 @@ async def delete_contacts(
 
         Parameters:
             user_ids (``int`` | ``str`` | List of ``int`` or ``str``):
-                A single user id/username o a list of user identifiers (id or username).
+                A single user id/username or a list of user identifiers (id or username).
 
         Returns:
             :obj:`~pyrogram.types.User` | List of :obj:`~pyrogram.types.User` | ``None``: In case *user_ids* was an

From 23d953237e448c803a83e1cc3282041ac80acfc5 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Sun, 13 Nov 2022 14:29:06 +0100
Subject: [PATCH 1057/1185] Update Pyrogram to v2.0.60

---
 pyrogram/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index 924bb27644..c5ad84d488 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -16,7 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-__version__ = "2.0.59"
+__version__ = "2.0.60"
 __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)"
 __copyright__ = "Copyright (C) 2017-present Dan "
 

From 1699ef0d4c6ed9f3e986e703de6680107e4e0bc0 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Tue, 15 Nov 2022 11:13:53 +0100
Subject: [PATCH 1058/1185] Disable parse mode when copying messages The
 entities are already taken from the original message

---
 pyrogram/types/messages_and_media/message.py | 1 +
 1 file changed, 1 insertion(+)

diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py
index 5ea1584d80..eb9d654f47 100644
--- a/pyrogram/types/messages_and_media/message.py
+++ b/pyrogram/types/messages_and_media/message.py
@@ -3057,6 +3057,7 @@ async def copy(
                 chat_id,
                 text=self.text,
                 entities=self.entities,
+                parse_mode=enums.ParseMode.DISABLED,
                 disable_web_page_preview=not self.web_page,
                 disable_notification=disable_notification,
                 reply_to_message_id=reply_to_message_id,

From e3e9731973952952b854676819c1b99e7858e910 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Tue, 15 Nov 2022 11:15:05 +0100
Subject: [PATCH 1059/1185] Update Pyrogram to v2.0.61

---
 pyrogram/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index c5ad84d488..e2fd5d3d59 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -16,7 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-__version__ = "2.0.60"
+__version__ = "2.0.61"
 __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)"
 __copyright__ = "Copyright (C) 2017-present Dan "
 

From 2aae10193e9f792b39edaa88a99bde92bf499cf5 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Fri, 18 Nov 2022 12:14:31 +0100
Subject: [PATCH 1060/1185] Add tag command to Makefile

---
 Makefile | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/Makefile b/Makefile
index 81dfe6c901..930d3be406 100644
--- a/Makefile
+++ b/Makefile
@@ -1,6 +1,7 @@
 VENV := venv
 PYTHON := $(VENV)/bin/python
 HOST = $(shell ifconfig | grep "inet " | tail -1 | cut -d\  -f2)
+TAG = v$(shell grep -E '__version__ = ".*"' pyrogram/__init__.py | cut -d\" -f2)
 
 RM := rm -rf
 
@@ -30,4 +31,12 @@ api:
 build:
 	make clean
 	$(PYTHON) setup.py sdist
-	$(PYTHON) setup.py bdist_wheel
\ No newline at end of file
+	$(PYTHON) setup.py bdist_wheel
+
+tag:
+	git tag $(TAG)
+	git push origin $(TAG)
+
+dtag:
+	git tag -d $(TAG)
+	git push origin -d $(TAG)
\ No newline at end of file

From 9b6cb070b98c3a117dc5ce1db1c973048496aec4 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Fri, 18 Nov 2022 12:14:49 +0100
Subject: [PATCH 1061/1185] Update Pyrogram to v2.0.62

---
 pyrogram/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index e2fd5d3d59..026fb9512c 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -16,7 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-__version__ = "2.0.61"
+__version__ = "2.0.62"
 __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)"
 __copyright__ = "Copyright (C) 2017-present Dan "
 

From b81c379548c9c08218f1d8c5d3e24d6fc9bc2031 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Fri, 25 Nov 2022 22:11:35 +0100
Subject: [PATCH 1062/1185] Update API schema to Layer 149

---
 compiler/api/source/main_api.tl | 10 ++++++----
 1 file changed, 6 insertions(+), 4 deletions(-)

diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl
index e89ef9ff49..e83a3b5ad5 100644
--- a/compiler/api/source/main_api.tl
+++ b/compiler/api/source/main_api.tl
@@ -244,7 +244,7 @@ messages.dialogsNotModified#f0e3e596 count:int = messages.Dialogs;
 
 messages.messages#8c718e87 messages:Vector chats:Vector users:Vector = messages.Messages;
 messages.messagesSlice#3a54685e flags:# inexact:flags.1?true count:int next_rate:flags.0?int offset_id_offset:flags.2?int messages:Vector chats:Vector users:Vector = messages.Messages;
-messages.channelMessages#64479808 flags:# inexact:flags.1?true pts:int count:int offset_id_offset:flags.2?int messages:Vector chats:Vector users:Vector = messages.Messages;
+messages.channelMessages#c776ba4e flags:# inexact:flags.1?true pts:int count:int offset_id_offset:flags.2?int messages:Vector topics:Vector chats:Vector users:Vector = messages.Messages;
 messages.messagesNotModified#74535f21 count:int = messages.Messages;
 
 messages.chats#64ff9fd5 chats:Vector = messages.Chats;
@@ -379,7 +379,8 @@ updateRecentEmojiStatuses#30f443db = Update;
 updateRecentReactions#6f7863f4 = Update;
 updateMoveStickerSetToTop#86fccf85 flags:# masks:flags.0?true emojis:flags.1?true stickerset:long = Update;
 updateMessageExtendedMedia#5a73a98c peer:Peer msg_id:int extended_media:MessageExtendedMedia = Update;
-updateChannelPinnedTopic#f694b0ae flags:# channel_id:long topic_id:flags.0?int = Update;
+updateChannelPinnedTopic#192efbe3 flags:# pinned:flags.0?true channel_id:long topic_id:int = Update;
+updateChannelPinnedTopics#fe198602 flags:# channel_id:long order:flags.0?Vector = Update;
 
 updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State;
 
@@ -1444,7 +1445,7 @@ stickerKeyword#fcfeb29c document_id:long keyword:Vector = StickerKeyword
 username#b4073647 flags:# editable:flags.0?true active:flags.1?true username:string = Username;
 
 forumTopicDeleted#23f109b id:int = ForumTopic;
-forumTopic#71701da9 flags:# my:flags.1?true closed:flags.2?true pinned:flags.3?true id:int date:int title:string icon_color:int icon_emoji_id:flags.0?long top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int unread_reactions_count:int from_id:Peer notify_settings:PeerNotifySettings draft:flags.4?DraftMessage = ForumTopic;
+forumTopic#71701da9 flags:# my:flags.1?true closed:flags.2?true pinned:flags.3?true short:flags.5?true id:int date:int title:string icon_color:int icon_emoji_id:flags.0?long top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int unread_reactions_count:int from_id:Peer notify_settings:PeerNotifySettings draft:flags.4?DraftMessage = ForumTopic;
 
 messages.forumTopics#367617d3 flags:# order_by_create_date:flags.0?true count:int topics:Vector messages:Vector chats:Vector users:Vector pts:int = messages.ForumTopics;
 
@@ -1858,6 +1859,7 @@ channels.getForumTopicsByID#b0831eb9 channel:InputChannel topics:Vector = m
 channels.editForumTopic#6c883e2d flags:# channel:InputChannel topic_id:int title:flags.0?string icon_emoji_id:flags.1?long closed:flags.2?Bool = Updates;
 channels.updatePinnedForumTopic#6c2d9026 channel:InputChannel topic_id:int pinned:Bool = Updates;
 channels.deleteTopicHistory#34435f2d channel:InputChannel top_msg_id:int = messages.AffectedHistory;
+channels.reorderPinnedForumTopics#2950a18f flags:# force:flags.0?true channel:InputChannel order:Vector = Updates;
 
 bots.sendCustomRequest#aa2769ed custom_method:string params:DataJSON = DataJSON;
 bots.answerWebhookJSONQuery#e6213f4d query_id:long data:DataJSON = Bool;
@@ -1936,4 +1938,4 @@ stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel
 stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages;
 stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats;
 
-// LAYER 148
+// LAYER 149
\ No newline at end of file

From 70b673890837749c06e7cd170d934bf9eda75e6b Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Fri, 25 Nov 2022 22:11:57 +0100
Subject: [PATCH 1063/1185] Update Pyrogram to v2.0.63

---
 pyrogram/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index 026fb9512c..aeb70a9a71 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -16,7 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-__version__ = "2.0.62"
+__version__ = "2.0.63"
 __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)"
 __copyright__ = "Copyright (C) 2017-present Dan "
 

From 1d7852a44b6e60e42aab85afd8977a1ee9dcd88f Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Tue, 6 Dec 2022 13:59:06 +0100
Subject: [PATCH 1064/1185] Update API schema to Layer 150

---
 compiler/api/source/main_api.tl | 32 +++++++++++++++++++++++---------
 1 file changed, 23 insertions(+), 9 deletions(-)

diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl
index e83a3b5ad5..e4f1ac0e9c 100644
--- a/compiler/api/source/main_api.tl
+++ b/compiler/api/source/main_api.tl
@@ -107,7 +107,7 @@ channel#83259464 flags:# creator:flags.0?true left:flags.2?true broadcast:flags.
 channelForbidden#17d493d5 flags:# broadcast:flags.5?true megagroup:flags.8?true id:long access_hash:long title:string until_date:flags.16?int = Chat;
 
 chatFull#c9d31138 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:long about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer theme_emoticon:flags.16?string requests_pending:flags.17?int recent_requesters:flags.17?Vector available_reactions:flags.18?ChatReactions = ChatFull;
-channelFull#f2355507 flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true flags2:# can_delete_channel:flags2.0?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector default_send_as:flags.29?Peer available_reactions:flags.30?ChatReactions = ChatFull;
+channelFull#f2355507 flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true flags2:# can_delete_channel:flags2.0?true antispam:flags2.1?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector default_send_as:flags.29?Peer available_reactions:flags.30?ChatReactions = ChatFull;
 
 chatParticipant#c02d4007 user_id:long inviter_id:long date:int = ChatParticipant;
 chatParticipantCreator#e46bcee4 user_id:long = ChatParticipant;
@@ -163,7 +163,7 @@ messageActionContactSignUp#f3f25f76 = MessageAction;
 messageActionGeoProximityReached#98e0d697 from_id:Peer to_id:Peer distance:int = MessageAction;
 messageActionGroupCall#7a0d7f42 flags:# call:InputGroupCall duration:flags.0?int = MessageAction;
 messageActionInviteToGroupCall#502f92f7 call:InputGroupCall users:Vector = MessageAction;
-messageActionSetMessagesTTL#aa1afbfd period:int = MessageAction;
+messageActionSetMessagesTTL#3c134d7b flags:# period:int auto_setting_from:flags.0?long = MessageAction;
 messageActionGroupCallScheduled#b3a07661 call:InputGroupCall schedule_date:int = MessageAction;
 messageActionSetChatTheme#aa786345 emoticon:string = MessageAction;
 messageActionChatJoinedByRequest#ebbca3cb = MessageAction;
@@ -171,9 +171,9 @@ messageActionWebViewDataSentMe#47dd8079 text:string data:string = MessageAction;
 messageActionWebViewDataSent#b4c38cb5 text:string = MessageAction;
 messageActionGiftPremium#aba0f5c6 currency:string amount:long months:int = MessageAction;
 messageActionTopicCreate#d999256 flags:# title:string icon_color:int icon_emoji_id:flags.0?long = MessageAction;
-messageActionTopicEdit#b18a431c flags:# title:flags.0?string icon_emoji_id:flags.1?long closed:flags.2?Bool = MessageAction;
+messageActionTopicEdit#c0944820 flags:# title:flags.0?string icon_emoji_id:flags.1?long closed:flags.2?Bool hidden:flags.3?Bool = MessageAction;
 
-dialog#a8edd0f5 flags:# pinned:flags.2?true unread_mark:flags.3?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int unread_reactions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int = Dialog;
+dialog#d58a08c6 flags:# pinned:flags.2?true unread_mark:flags.3?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int unread_reactions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int ttl_period:flags.5?int = Dialog;
 dialogFolder#71bd134c flags:# pinned:flags.2?true folder:Folder peer:Peer top_message:int unread_muted_peers_count:int unread_unmuted_peers_count:int unread_muted_messages_count:int unread_unmuted_messages_count:int = Dialog;
 
 photoEmpty#2331b22d id:long = Photo;
@@ -702,6 +702,7 @@ auth.codeTypeSms#72a3158c = auth.CodeType;
 auth.codeTypeCall#741cd3e3 = auth.CodeType;
 auth.codeTypeFlashCall#226ccefb = auth.CodeType;
 auth.codeTypeMissedCall#d61ad6ee = auth.CodeType;
+auth.codeTypeFragmentSms#6ed998c = auth.CodeType;
 
 auth.sentCodeTypeApp#3dbb5986 length:int = auth.SentCodeType;
 auth.sentCodeTypeSms#c000bba2 length:int = auth.SentCodeType;
@@ -710,6 +711,7 @@ auth.sentCodeTypeFlashCall#ab03c6d9 pattern:string = auth.SentCodeType;
 auth.sentCodeTypeMissedCall#82006484 prefix:string length:int = auth.SentCodeType;
 auth.sentCodeTypeEmailCode#5a159841 flags:# apple_signin_allowed:flags.0?true google_signin_allowed:flags.1?true email_pattern:string length:int next_phone_login_date:flags.2?int = auth.SentCodeType;
 auth.sentCodeTypeSetUpEmailRequired#a5491dea flags:# apple_signin_allowed:flags.0?true google_signin_allowed:flags.1?true = auth.SentCodeType;
+auth.sentCodeTypeFragmentSms#d9565c39 url:string length:int = auth.SentCodeType;
 
 messages.botCallbackAnswer#36585ea4 flags:# alert:flags.1?true has_url:flags.3?true native_ui:flags.4?true message:flags.0?string url:flags.2?string cache_time:int = messages.BotCallbackAnswer;
 
@@ -943,6 +945,7 @@ channelAdminLogEventActionCreateTopic#58707d28 topic:ForumTopic = ChannelAdminLo
 channelAdminLogEventActionEditTopic#f06fe208 prev_topic:ForumTopic new_topic:ForumTopic = ChannelAdminLogEventAction;
 channelAdminLogEventActionDeleteTopic#ae168909 topic:ForumTopic = ChannelAdminLogEventAction;
 channelAdminLogEventActionPinTopic#5d8d353b flags:# prev_topic:flags.0?ForumTopic new_topic:flags.1?ForumTopic = ChannelAdminLogEventAction;
+channelAdminLogEventActionToggleAntiSpam#64f36dfc new_value:Bool = ChannelAdminLogEventAction;
 
 channelAdminLogEvent#1fad68cd id:long date:int user_id:long action:ChannelAdminLogEventAction = ChannelAdminLogEvent;
 
@@ -1445,10 +1448,14 @@ stickerKeyword#fcfeb29c document_id:long keyword:Vector = StickerKeyword
 username#b4073647 flags:# editable:flags.0?true active:flags.1?true username:string = Username;
 
 forumTopicDeleted#23f109b id:int = ForumTopic;
-forumTopic#71701da9 flags:# my:flags.1?true closed:flags.2?true pinned:flags.3?true short:flags.5?true id:int date:int title:string icon_color:int icon_emoji_id:flags.0?long top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int unread_reactions_count:int from_id:Peer notify_settings:PeerNotifySettings draft:flags.4?DraftMessage = ForumTopic;
+forumTopic#71701da9 flags:# my:flags.1?true closed:flags.2?true pinned:flags.3?true short:flags.5?true hidden:flags.6?true id:int date:int title:string icon_color:int icon_emoji_id:flags.0?long top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int unread_reactions_count:int from_id:Peer notify_settings:PeerNotifySettings draft:flags.4?DraftMessage = ForumTopic;
 
 messages.forumTopics#367617d3 flags:# order_by_create_date:flags.0?true count:int topics:Vector messages:Vector chats:Vector users:Vector pts:int = messages.ForumTopics;
 
+defaultHistoryTTL#43b46b20 period:int = DefaultHistoryTTL;
+
+exportedContactToken#41bf109b url:string expires:int = ExportedContactToken;
+
 ---functions---
 
 invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
@@ -1478,6 +1485,7 @@ auth.exportLoginToken#b7e085fe api_id:int api_hash:string except_ids:Vector = Bool;
 account.unregisterDevice#6a0d3206 token_type:int token:string other_uids:Vector = Bool;
@@ -1587,6 +1595,8 @@ contacts.acceptContact#f831a20f id:InputUser = Updates;
 contacts.getLocated#d348bc44 flags:# background:flags.1?true geo_point:InputGeoPoint self_expires:flags.0?int = Updates;
 contacts.blockFromReplies#29a8962c flags:# delete_message:flags.0?true delete_history:flags.1?true report_spam:flags.2?true msg_id:int = Updates;
 contacts.resolvePhone#8af94344 phone:string = contacts.ResolvedPeer;
+contacts.exportContactToken#f8654027 = ExportedContactToken;
+contacts.importContactToken#13005788 token:string = User;
 
 messages.getMessages#63c66506 id:Vector = messages.Messages;
 messages.getDialogs#a0f4cb4f flags:# exclude_pinned:flags.0?true folder_id:flags.1?int offset_date:int offset_id:int offset_peer:InputPeer limit:int hash:long = messages.Dialogs;
@@ -1609,7 +1619,7 @@ messages.editChatTitle#73783ffd chat_id:long title:string = Updates;
 messages.editChatPhoto#35ddd674 chat_id:long photo:InputChatPhoto = Updates;
 messages.addChatUser#f24753e3 chat_id:long user_id:InputUser fwd_limit:int = Updates;
 messages.deleteChatUser#a2185cab flags:# revoke_history:flags.0?true chat_id:long user_id:InputUser = Updates;
-messages.createChat#9cb126e users:Vector title:string = Updates;
+messages.createChat#34a818 flags:# users:Vector title:string ttl_period:flags.0?int = Updates;
 messages.getDhConfig#26cf8950 version:int random_length:int = messages.DhConfig;
 messages.requestEncryption#f64daf43 user_id:InputUser random_id:int g_a:bytes = EncryptedChat;
 messages.acceptEncryption#3dbc0415 peer:InputEncryptedChat g_b:bytes key_fingerprint:long = EncryptedChat;
@@ -1765,6 +1775,8 @@ messages.getTopReactions#bb8125ba limit:int hash:long = messages.Reactions;
 messages.getRecentReactions#39461db2 limit:int hash:long = messages.Reactions;
 messages.clearRecentReactions#9dfeefb4 = Bool;
 messages.getExtendedMedia#84f80814 peer:InputPeer id:Vector = Updates;
+messages.setDefaultHistoryTTL#9eb51445 period:int = Bool;
+messages.getDefaultHistoryTTL#658b7188 = DefaultHistoryTTL;
 
 updates.getState#edd4882a = updates.State;
 updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference;
@@ -1816,7 +1828,7 @@ channels.getParticipants#77ced9d0 channel:InputChannel filter:ChannelParticipant
 channels.getParticipant#a0ab6cc6 channel:InputChannel participant:InputPeer = channels.ChannelParticipant;
 channels.getChannels#a7f6bbb id:Vector = messages.Chats;
 channels.getFullChannel#8736a09 channel:InputChannel = messages.ChatFull;
-channels.createChannel#3d5fb10f flags:# broadcast:flags.0?true megagroup:flags.1?true for_import:flags.3?true title:string about:string geo_point:flags.2?InputGeoPoint address:flags.2?string = Updates;
+channels.createChannel#91006707 flags:# broadcast:flags.0?true megagroup:flags.1?true for_import:flags.3?true title:string about:string geo_point:flags.2?InputGeoPoint address:flags.2?string ttl_period:flags.4?int = Updates;
 channels.editAdmin#d33c8902 channel:InputChannel user_id:InputUser admin_rights:ChatAdminRights rank:string = Updates;
 channels.editTitle#566decd0 channel:InputChannel title:string = Updates;
 channels.editPhoto#f12e57c9 channel:InputChannel photo:InputChatPhoto = Updates;
@@ -1856,10 +1868,12 @@ channels.toggleForum#a4298b29 channel:InputChannel enabled:Bool = Updates;
 channels.createForumTopic#f40c0224 flags:# channel:InputChannel title:string icon_color:flags.0?int icon_emoji_id:flags.3?long random_id:long send_as:flags.2?InputPeer = Updates;
 channels.getForumTopics#de560d1 flags:# channel:InputChannel q:flags.0?string offset_date:int offset_id:int offset_topic:int limit:int = messages.ForumTopics;
 channels.getForumTopicsByID#b0831eb9 channel:InputChannel topics:Vector = messages.ForumTopics;
-channels.editForumTopic#6c883e2d flags:# channel:InputChannel topic_id:int title:flags.0?string icon_emoji_id:flags.1?long closed:flags.2?Bool = Updates;
+channels.editForumTopic#f4dfa185 flags:# channel:InputChannel topic_id:int title:flags.0?string icon_emoji_id:flags.1?long closed:flags.2?Bool hidden:flags.3?Bool = Updates;
 channels.updatePinnedForumTopic#6c2d9026 channel:InputChannel topic_id:int pinned:Bool = Updates;
 channels.deleteTopicHistory#34435f2d channel:InputChannel top_msg_id:int = messages.AffectedHistory;
 channels.reorderPinnedForumTopics#2950a18f flags:# force:flags.0?true channel:InputChannel order:Vector = Updates;
+channels.toggleAntiSpam#68f3e4eb channel:InputChannel enabled:Bool = Updates;
+channels.reportAntiSpamFalsePositive#a850a693 channel:InputChannel msg_id:int = Bool;
 
 bots.sendCustomRequest#aa2769ed custom_method:string params:DataJSON = DataJSON;
 bots.answerWebhookJSONQuery#e6213f4d query_id:long data:DataJSON = Bool;
@@ -1938,4 +1952,4 @@ stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel
 stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages;
 stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats;
 
-// LAYER 149
\ No newline at end of file
+// LAYER 150
\ No newline at end of file

From fb85a142775b3163af4a3005bb9b5848c4c4ab01 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Tue, 6 Dec 2022 14:15:46 +0100
Subject: [PATCH 1065/1185] Update Pyrogram to v2.0.64

---
 pyrogram/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index aeb70a9a71..f860e0cd58 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -16,7 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-__version__ = "2.0.63"
+__version__ = "2.0.64"
 __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)"
 __copyright__ = "Copyright (C) 2017-present Dan "
 

From d734fbb180f4e8d080ef008cdd575b150c893891 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Tue, 6 Dec 2022 18:08:38 +0100
Subject: [PATCH 1066/1185] Fix User.emoji_status type hint

---
 pyrogram/types/user_and_chats/user.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/types/user_and_chats/user.py b/pyrogram/types/user_and_chats/user.py
index 2c9b58f8b6..e981357826 100644
--- a/pyrogram/types/user_and_chats/user.py
+++ b/pyrogram/types/user_and_chats/user.py
@@ -170,7 +170,7 @@ def __init__(
         next_offline_date: datetime = None,
         username: str = None,
         language_code: str = None,
-        emoji_status: Optional[str] = None,
+        emoji_status: Optional["types.EmojiStatus"] = None,
         dc_id: int = None,
         phone_number: str = None,
         photo: "types.ChatPhoto" = None,

From 3cf1ac7d92377372cd16bb54f2dd2ea789118c51 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Tue, 6 Dec 2022 18:09:42 +0100
Subject: [PATCH 1067/1185] Update the order in which media messages are parsed

---
 pyrogram/types/messages_and_media/message.py | 22 ++++++++++----------
 1 file changed, 11 insertions(+), 11 deletions(-)

diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py
index eb9d654f47..d844b06054 100644
--- a/pyrogram/types/messages_and_media/message.py
+++ b/pyrogram/types/messages_and_media/message.py
@@ -364,7 +364,7 @@ def __init__(
         pinned_message: "Message" = None,
         game_high_score: int = None,
         views: int = None,
-	forwards: int = None,
+        forwards: int = None,
         via_bot: "types.User" = None,
         outgoing: bool = None,
         matches: List[Match] = None,
@@ -687,16 +687,7 @@ async def _parse(
                             ), "file_name", None
                         )
 
-                        if raw.types.DocumentAttributeAudio in attributes:
-                            audio_attributes = attributes[raw.types.DocumentAttributeAudio]
-
-                            if audio_attributes.voice:
-                                voice = types.Voice._parse(client, doc, audio_attributes)
-                                media_type = enums.MessageMediaType.VOICE
-                            else:
-                                audio = types.Audio._parse(client, doc, audio_attributes, file_name)
-                                media_type = enums.MessageMediaType.AUDIO
-                        elif raw.types.DocumentAttributeAnimated in attributes:
+                        if raw.types.DocumentAttributeAnimated in attributes:
                             video_attributes = attributes.get(raw.types.DocumentAttributeVideo, None)
                             animation = types.Animation._parse(client, doc, video_attributes, file_name)
                             media_type = enums.MessageMediaType.ANIMATION
@@ -712,6 +703,15 @@ async def _parse(
                             else:
                                 video = types.Video._parse(client, doc, video_attributes, file_name, media.ttl_seconds)
                                 media_type = enums.MessageMediaType.VIDEO
+                        elif raw.types.DocumentAttributeAudio in attributes:
+                            audio_attributes = attributes[raw.types.DocumentAttributeAudio]
+
+                            if audio_attributes.voice:
+                                voice = types.Voice._parse(client, doc, audio_attributes)
+                                media_type = enums.MessageMediaType.VOICE
+                            else:
+                                audio = types.Audio._parse(client, doc, audio_attributes, file_name)
+                                media_type = enums.MessageMediaType.AUDIO
                         else:
                             document = types.Document._parse(client, doc, file_name)
                             media_type = enums.MessageMediaType.DOCUMENT

From ad773455a74ef6db7c1752734b6fe9b6647870c6 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Tue, 6 Dec 2022 18:10:36 +0100
Subject: [PATCH 1068/1185] Update Pyrogram to v2.0.65

---
 pyrogram/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index f860e0cd58..a7c9049bf8 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -16,7 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-__version__ = "2.0.64"
+__version__ = "2.0.65"
 __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)"
 __copyright__ = "Copyright (C) 2017-present Dan "
 

From 2ed000381d60bf8da9b0bec57693425b24b50c9d Mon Sep 17 00:00:00 2001
From: Andrea Princic <48788808+Princic-1837592@users.noreply.github.com>
Date: Tue, 6 Dec 2022 18:29:27 +0100
Subject: [PATCH 1069/1185] Update the HTML logic to output well-formed
 elements (#1155)

* unparsing html entities with deque

* unparsing using a stack (recursive)
---
 pyrogram/parser/html.py | 55 ++++++++++++++++++++++++++++-------------
 1 file changed, 38 insertions(+), 17 deletions(-)

diff --git a/pyrogram/parser/html.py b/pyrogram/parser/html.py
index 7ed2a5be10..4afeea2bd9 100644
--- a/pyrogram/parser/html.py
+++ b/pyrogram/parser/html.py
@@ -155,11 +155,10 @@ async def parse(self, text: str):
 
     @staticmethod
     def unparse(text: str, entities: list):
-        text = utils.add_surrogates(text)
-
-        entities_offsets = []
-
-        for entity in entities:
+        def parse_one(entity):
+            """
+            Parses a single entity and returns (start_tag, start), (end_tag, end)
+            """
             entity_type = entity.type
             start = entity.offset
             end = start + entity.length
@@ -199,21 +198,43 @@ def unparse(text: str, entities: list):
                 start_tag = f''
                 end_tag = ""
             else:
-                continue
+                return
+
+            return (start_tag, start), (end_tag, end)
+
+        def recursive(entity_i: int) -> int:
+            """
+            Takes the index of the entity to start parsing from, returns the number of parsed entities inside it.
+            Uses entities_offsets as a stack, pushing (start_tag, start) first, then parsing nested entities,
+            and finally pushing (end_tag, end) to the stack.
+            No need to sort at the end.
+            """
+            this = parse_one(entities[entity_i])
+            if this is None:
+                return 1
+            (start_tag, start), (end_tag, end) = this
+            entities_offsets.append((start_tag, start))
+            internal_i = entity_i + 1
+            # while the next entity is inside the current one, keep parsing
+            while internal_i < len(entities) and entities[internal_i].offset < end:
+                internal_i += recursive(internal_i)
+            entities_offsets.append((end_tag, end))
+            return internal_i - entity_i
+
+        text = utils.add_surrogates(text)
+
+        entities_offsets = []
 
-            entities_offsets.append((start_tag, start,))
-            entities_offsets.append((end_tag, end,))
+        # probably useless because entities are already sorted by telegram
+        entities.sort(key=lambda e: (e.offset, -e.length))
 
-        entities_offsets = map(
-            lambda x: x[1],
-            sorted(
-                enumerate(entities_offsets),
-                key=lambda x: (x[1][1], x[0]),
-                reverse=True
-            )
-        )
+        # main loop for first-level entities
+        i = 0
+        while i < len(entities):
+            i += recursive(i)
 
-        for entity, offset in entities_offsets:
+        # no need to sort, but still add entities starting from the end
+        for entity, offset in reversed(entities_offsets):
             text = text[:offset] + entity + text[offset:]
 
         return utils.remove_surrogates(text)

From 38e9745a80a5b5adc47bc8525648fd87cc73ead8 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Tue, 6 Dec 2022 18:50:43 +0100
Subject: [PATCH 1070/1185] Update Pyrogram to v2.0.66

---
 pyrogram/__init__.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py
index a7c9049bf8..11a2359e9a 100644
--- a/pyrogram/__init__.py
+++ b/pyrogram/__init__.py
@@ -16,7 +16,7 @@
 #  You should have received a copy of the GNU Lesser General Public License
 #  along with Pyrogram.  If not, see .
 
-__version__ = "2.0.65"
+__version__ = "2.0.66"
 __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)"
 __copyright__ = "Copyright (C) 2017-present Dan "
 

From db6bc2df1913d2c844537a8b9669d8e21c855f48 Mon Sep 17 00:00:00 2001
From: Dan <14043624+delivrance@users.noreply.github.com>
Date: Tue, 6 Dec 2022 19:11:23 +0100
Subject: [PATCH 1071/1185] Add tests for HTML.unparse

---
 tests/parser/__init__.py  |  17 ++++++
 tests/parser/test_html.py | 120 ++++++++++++++++++++++++++++++++++++++
 2 files changed, 137 insertions(+)
 create mode 100644 tests/parser/__init__.py
 create mode 100644 tests/parser/test_html.py

diff --git a/tests/parser/__init__.py b/tests/parser/__init__.py
new file mode 100644
index 0000000000..46887cb7a5
--- /dev/null
+++ b/tests/parser/__init__.py
@@ -0,0 +1,17 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  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 .
diff --git a/tests/parser/test_html.py b/tests/parser/test_html.py
new file mode 100644
index 0000000000..170b9031bd
--- /dev/null
+++ b/tests/parser/test_html.py
@@ -0,0 +1,120 @@
+#  Pyrogram - Telegram MTProto API Client Library for Python
+#  Copyright (C) 2017-present Dan 
+#
+#  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.parser.html import HTML
+
+
+# expected: the expected unparsed HTML
+# text: original text without entities
+# entities: message entities coming from the server
+
+def test_html_unparse_bold():
+    expected = "bold"
+    text = "bold"
+    entities = pyrogram.types.List(
+        [pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.BOLD, offset=0, length=4)])
+
+    assert HTML.unparse(text=text, entities=entities) == expected
+
+
+def test_html_unparse_italic():
+    expected = "italic"
+    text = "italic"
+    entities = pyrogram.types.List(
+        [pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.ITALIC, offset=0, length=6)])
+
+    assert HTML.unparse(text=text, entities=entities) == expected
+
+
+def test_html_unparse_underline():
+    expected = "underline"
+    text = "underline"
+    entities = pyrogram.types.List(
+        [pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.UNDERLINE, offset=0, length=9)])
+
+    assert HTML.unparse(text=text, entities=entities) == expected
+
+
+def test_html_unparse_strike():
+    expected = "strike"
+    text = "strike"
+    entities = pyrogram.types.List(
+        [pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.STRIKETHROUGH, offset=0, length=6)])
+
+    assert HTML.unparse(text=text, entities=entities) == expected
+
+
+def test_html_unparse_spoiler():
+    expected = "spoiler"
+    text = "spoiler"
+    entities = pyrogram.types.List(
+        [pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.SPOILER, offset=0, length=7)])
+
+    assert HTML.unparse(text=text, entities=entities) == expected
+
+
+def test_html_unparse_url():
+    expected = 'URL'
+    text = "URL"
+    entities = pyrogram.types.List([pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.TEXT_LINK,
+                                                                 offset=0, length=3, url='https://pyrogram.org/')])
+
+    assert HTML.unparse(text=text, entities=entities) == expected
+
+
+def test_html_unparse_code():
+    expected = 'code'
+    text = "code"
+    entities = pyrogram.types.List(
+        [pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.CODE, offset=0, length=4)])
+
+    assert HTML.unparse(text=text, entities=entities) == expected
+
+
+def test_html_unparse_pre():
+    expected = """
for i in range(10):
+    print(i)
""" + + text = """for i in range(10): + print(i)""" + + entities = pyrogram.types.List([pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.PRE, offset=0, + length=32, language='python')]) + + assert HTML.unparse(text=text, entities=entities) == expected + + +def test_html_unparse_mixed(): + expected = "aaaaaaaaaabbbbbbbbbbccccccccccdddddddddd" \ + "eeeeeeeeeeffffffffffgggggggggghhhhhhhhhh" + text = "aaaaaaaaaabbbbbbbbbbccccccccccddddddddddeeeeeeeeeeffffffffffgggggggggghhhhhhhhhh" + entities = pyrogram.types.List( + [pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.BOLD, offset=0, length=14), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.ITALIC, offset=7, length=7), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.UNDERLINE, offset=10, length=4), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.UNDERLINE, offset=14, length=9), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.ITALIC, offset=14, length=9), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.UNDERLINE, offset=23, length=10), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.STRIKETHROUGH, offset=30, length=3), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.STRIKETHROUGH, offset=33, length=10), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.SPOILER, offset=38, length=5), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.SPOILER, offset=43, length=10), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.CODE, offset=57, length=10)]) + + assert HTML.unparse(text=text, entities=entities) == expected From fd2819ca7f581f0ed3bbad6873aeb27a233272fd Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 6 Dec 2022 19:11:47 +0100 Subject: [PATCH 1072/1185] Update Pyrogram to v2.0.67 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 11a2359e9a..ffb70e5c12 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.66" +__version__ = "2.0.67" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From 669b39927b7fb48bd7464fc2bb7497ed8c75efb6 Mon Sep 17 00:00:00 2001 From: Andrea Princic <48788808+Princic-1837592@users.noreply.github.com> Date: Tue, 6 Dec 2022 20:09:31 +0100 Subject: [PATCH 1073/1185] Escape text inside entity when building unparsed text (#1156) --- pyrogram/parser/html.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pyrogram/parser/html.py b/pyrogram/parser/html.py index 4afeea2bd9..c574e6ffb1 100644 --- a/pyrogram/parser/html.py +++ b/pyrogram/parser/html.py @@ -233,8 +233,10 @@ def recursive(entity_i: int) -> int: while i < len(entities): i += recursive(i) + last_offset = entities_offsets[-1][1] # no need to sort, but still add entities starting from the end for entity, offset in reversed(entities_offsets): - text = text[:offset] + entity + text[offset:] + text = text[:offset] + entity + html.escape(text[offset:last_offset]) + text[last_offset:] + last_offset = offset return utils.remove_surrogates(text) From 7090dcba6807f9594d065514ced1ee2f89cf65be Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 6 Dec 2022 20:12:27 +0100 Subject: [PATCH 1074/1185] Add more tests for HTML.unparse --- tests/parser/test_html.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/tests/parser/test_html.py b/tests/parser/test_html.py index 170b9031bd..0027290561 100644 --- a/tests/parser/test_html.py +++ b/tests/parser/test_html.py @@ -118,3 +118,22 @@ def test_html_unparse_mixed(): pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.CODE, offset=57, length=10)]) assert HTML.unparse(text=text, entities=entities) == expected + + +def test_html_unparse_escaped(): + expected = "<b>bold</b>" + text = "bold" + entities = pyrogram.types.List( + [pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.BOLD, offset=0, length=11)]) + + assert HTML.unparse(text=text, entities=entities) == expected + + +def test_html_unparse_escaped_nested(): + expected = "<b>bold <u>underline</u> bold</b>" + text = "bold underline bold" + entities = pyrogram.types.List( + [pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.BOLD, offset=0, length=33), + pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.UNDERLINE, offset=8, length=16)]) + + assert HTML.unparse(text=text, entities=entities) == expected From 86515bb9d18ff21e8f2b33d8a80dd9076f88c832 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 6 Dec 2022 20:12:46 +0100 Subject: [PATCH 1075/1185] Update Pyrogram to v2.0.68 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index ffb70e5c12..a9a7d15bf8 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.67" +__version__ = "2.0.68" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From 217bcb1dfb409cd8afd5bf54826f6c7bfe30971c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 6 Dec 2022 21:33:46 +0100 Subject: [PATCH 1076/1185] Fix HTML unparsing when there's no entities --- pyrogram/parser/html.py | 11 ++++++----- tests/parser/test_html.py | 8 ++++++++ 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/pyrogram/parser/html.py b/pyrogram/parser/html.py index c574e6ffb1..987281b177 100644 --- a/pyrogram/parser/html.py +++ b/pyrogram/parser/html.py @@ -233,10 +233,11 @@ def recursive(entity_i: int) -> int: while i < len(entities): i += recursive(i) - last_offset = entities_offsets[-1][1] - # no need to sort, but still add entities starting from the end - for entity, offset in reversed(entities_offsets): - text = text[:offset] + entity + html.escape(text[offset:last_offset]) + text[last_offset:] - last_offset = offset + if entities_offsets: + last_offset = entities_offsets[-1][1] + # no need to sort, but still add entities starting from the end + for entity, offset in reversed(entities_offsets): + text = text[:offset] + entity + html.escape(text[offset:last_offset]) + text[last_offset:] + last_offset = offset return utils.remove_surrogates(text) diff --git a/tests/parser/test_html.py b/tests/parser/test_html.py index 0027290561..b9138f3cf1 100644 --- a/tests/parser/test_html.py +++ b/tests/parser/test_html.py @@ -137,3 +137,11 @@ def test_html_unparse_escaped_nested(): pyrogram.types.MessageEntity(type=pyrogram.enums.MessageEntityType.UNDERLINE, offset=8, length=16)]) assert HTML.unparse(text=text, entities=entities) == expected + + +def test_html_unparse_no_entities(): + expected = "text" + text = "text" + entities = [] + + assert HTML.unparse(text=text, entities=entities) == expected From 73554b9d38209c2eccdce28c29c5b8ed2d06f0cd Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 6 Dec 2022 21:34:09 +0100 Subject: [PATCH 1077/1185] Update Pyrogram to v2.0.69 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index a9a7d15bf8..3a44eb364d 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.68" +__version__ = "2.0.69" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From a76269ddaf09958590fccdeb416b6ade69801f06 Mon Sep 17 00:00:00 2001 From: Anton Kovalevich Date: Mon, 12 Dec 2022 20:53:09 +0000 Subject: [PATCH 1078/1185] Handle all given updates, avoid short circuit (#1162) --- pyrogram/client.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyrogram/client.py b/pyrogram/client.py index 67f059e96b..b88ea6918c 100644 --- a/pyrogram/client.py +++ b/pyrogram/client.py @@ -485,7 +485,10 @@ async def fetch_peers(self, peers: List[Union[raw.types.User, raw.types.Chat, ra async def handle_updates(self, updates): if isinstance(updates, (raw.types.Updates, raw.types.UpdatesCombined)): - is_min = (await self.fetch_peers(updates.users)) or (await self.fetch_peers(updates.chats)) + is_min = any(( + await self.fetch_peers(updates.users), + await self.fetch_peers(updates.chats), + )) users = {u.id: u for u in updates.users} chats = {c.id: c for c in updates.chats} From aeea07f83d9becd356ed2752ae5aea68f0fc168f Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 12 Dec 2022 21:54:07 +0100 Subject: [PATCH 1079/1185] Update Pyrogram to v2.0.70 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 3a44eb364d..7cb81726f8 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.69" +__version__ = "2.0.70" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From a9e7d15bf6f5f69ca8f6154178003f6ede01ce76 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 23 Dec 2022 15:40:56 +0100 Subject: [PATCH 1080/1185] Add a watchdog for incoming updates --- pyrogram/client.py | 25 +++++++++++++++++++++++++ pyrogram/methods/auth/initialize.py | 3 +++ pyrogram/methods/auth/terminate.py | 7 +++++++ 3 files changed, 35 insertions(+) diff --git a/pyrogram/client.py b/pyrogram/client.py index b88ea6918c..52c1d84428 100644 --- a/pyrogram/client.py +++ b/pyrogram/client.py @@ -26,6 +26,7 @@ import shutil import sys from concurrent.futures.thread import ThreadPoolExecutor +from datetime import datetime, timedelta from hashlib import sha256 from importlib import import_module from io import StringIO, BytesIO @@ -185,6 +186,9 @@ class Client(Methods): WORKERS = min(32, (os.cpu_count() or 0) + 4) # os.cpu_count() can be None WORKDIR = PARENT_DIR + # Interval of seconds in which the updates watchdog will kick in + UPDATES_WATCHDOG_INTERVAL = 5 * 60 + mimetypes = MimeTypes() mimetypes.readfp(StringIO(mime_types)) @@ -273,6 +277,13 @@ def __init__( self.message_cache = Cache(10000) + # Sometimes, for some reason, the server will stop sending updates and will only respond to pings. + # This watchdog will invoke updates.GetState in order to wake up the server and enable it sending updates again + # after some idle time has been detected. + self.updates_watchdog_task = None + self.updates_watchdog_event = asyncio.Event() + self.last_update_time = datetime.now() + self.loop = asyncio.get_event_loop() def __enter__(self): @@ -293,6 +304,18 @@ async def __aexit__(self, *args): except ConnectionError: pass + async def updates_watchdog(self): + while True: + try: + await asyncio.wait_for(self.updates_watchdog_event.wait(), self.UPDATES_WATCHDOG_INTERVAL) + except asyncio.TimeoutError: + pass + else: + break + + if datetime.now() - self.last_update_time > timedelta(seconds=self.UPDATES_WATCHDOG_INTERVAL): + await self.invoke(raw.functions.updates.GetState()) + async def authorize(self) -> User: if self.bot_token: return await self.sign_in_bot(self.bot_token) @@ -484,6 +507,8 @@ async def fetch_peers(self, peers: List[Union[raw.types.User, raw.types.Chat, ra return is_min async def handle_updates(self, updates): + self.last_update_time = datetime.now() + if isinstance(updates, (raw.types.Updates, raw.types.UpdatesCombined)): is_min = any(( await self.fetch_peers(updates.users), diff --git a/pyrogram/methods/auth/initialize.py b/pyrogram/methods/auth/initialize.py index 1e7915e00e..7188b66817 100644 --- a/pyrogram/methods/auth/initialize.py +++ b/pyrogram/methods/auth/initialize.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 pyrogram @@ -46,4 +47,6 @@ async def initialize( await self.dispatcher.start() + self.updates_watchdog_task = asyncio.create_task(self.updates_watchdog()) + self.is_initialized = True diff --git a/pyrogram/methods/auth/terminate.py b/pyrogram/methods/auth/terminate.py index 707c25e90e..d70103d03e 100644 --- a/pyrogram/methods/auth/terminate.py +++ b/pyrogram/methods/auth/terminate.py @@ -51,4 +51,11 @@ async def terminate( self.media_sessions.clear() + self.updates_watchdog_event.set() + + if self.updates_watchdog_task is not None: + await self.updates_watchdog_task + + self.updates_watchdog_event.clear() + self.is_initialized = False From ccb58f503c0d8344740e9209d70aa2fe7982db35 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 23 Dec 2022 15:41:34 +0100 Subject: [PATCH 1081/1185] Update Pyrogram to v2.0.71 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 7cb81726f8..ee0ca0214e 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.70" +__version__ = "2.0.71" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From 81573bce764fa4c2e5bd9ea1b0451154c7fe014e Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 23 Dec 2022 20:20:27 +0100 Subject: [PATCH 1082/1185] Remove threading.Lock usages --- pyrogram/storage/sqlite_storage.py | 26 ++++++++++---------------- 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/pyrogram/storage/sqlite_storage.py b/pyrogram/storage/sqlite_storage.py index 15e5ddc0c5..e28b9b746e 100644 --- a/pyrogram/storage/sqlite_storage.py +++ b/pyrogram/storage/sqlite_storage.py @@ -19,7 +19,6 @@ import inspect import sqlite3 import time -from threading import Lock from typing import List, Tuple, Any from pyrogram import raw @@ -98,10 +97,9 @@ def __init__(self, name: str): super().__init__(name) self.conn = None # type: sqlite3.Connection - self.lock = Lock() def create(self): - with self.lock, self.conn: + with self.conn: self.conn.executescript(SCHEMA) self.conn.execute( @@ -119,24 +117,20 @@ async def open(self): async def save(self): await self.date(int(time.time())) - - with self.lock: - self.conn.commit() + self.conn.commit() async def close(self): - with self.lock: - self.conn.close() + self.conn.close() async def delete(self): raise NotImplementedError async def update_peers(self, peers: List[Tuple[int, int, str, str, str]]): - with self.lock: - self.conn.executemany( - "REPLACE INTO peers (id, access_hash, type, username, phone_number)" - "VALUES (?, ?, ?, ?, ?)", - peers - ) + self.conn.executemany( + "REPLACE INTO peers (id, access_hash, type, username, phone_number)" + "VALUES (?, ?, ?, ?, ?)", + peers + ) async def get_peer_by_id(self, peer_id: int): r = self.conn.execute( @@ -185,7 +179,7 @@ def _get(self): def _set(self, value: Any): attr = inspect.stack()[2].function - with self.lock, self.conn: + with self.conn: self.conn.execute( f"UPDATE sessions SET {attr} = ?", (value,) @@ -221,7 +215,7 @@ def version(self, value: int = object): "SELECT number FROM version" ).fetchone()[0] else: - with self.lock, self.conn: + with self.conn: self.conn.execute( "UPDATE version SET number = ?", (value,) From 8afd4597fa0904eb6564d9521e4c81bc05d66188 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 23 Dec 2022 20:20:44 +0100 Subject: [PATCH 1083/1185] Update Pyrogram to v2.0.72 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index ee0ca0214e..c58c007e4e 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.71" +__version__ = "2.0.72" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From c4a47b99ae66467b547744d280694ac5f5e307da Mon Sep 17 00:00:00 2001 From: omg-xtao <100690902+omg-xtao@users.noreply.github.com> Date: Sat, 24 Dec 2022 03:36:00 +0800 Subject: [PATCH 1084/1185] Add support for Fragment SMS codes (#1170) --- pyrogram/client.py | 3 ++- pyrogram/enums/next_code_type.py | 3 +++ pyrogram/enums/sent_code_type.py | 3 +++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/pyrogram/client.py b/pyrogram/client.py index 52c1d84428..238b2efbf5 100644 --- a/pyrogram/client.py +++ b/pyrogram/client.py @@ -356,7 +356,8 @@ async def authorize(self) -> User: enums.SentCodeType.APP: "Telegram app", enums.SentCodeType.SMS: "SMS", enums.SentCodeType.CALL: "phone call", - enums.SentCodeType.FLASH_CALL: "phone flash call" + enums.SentCodeType.FLASH_CALL: "phone flash call", + enums.SentCodeType.FRAGMENT_SMS: "Fragment SMS", } print(f"The confirmation code has been sent via {sent_code_descriptions[sent_code.type]}") diff --git a/pyrogram/enums/next_code_type.py b/pyrogram/enums/next_code_type.py index eadaf191a1..7b3137a7ad 100644 --- a/pyrogram/enums/next_code_type.py +++ b/pyrogram/enums/next_code_type.py @@ -34,3 +34,6 @@ class NextCodeType(AutoName): SMS = raw.types.auth.CodeTypeSms "The code was sent via SMS." + + FRAGMENT_SMS = raw.types.auth.CodeTypeFragmentSms + "The code was sent via Fragment SMS." diff --git a/pyrogram/enums/sent_code_type.py b/pyrogram/enums/sent_code_type.py index bee9de3fcb..e3ec61120a 100644 --- a/pyrogram/enums/sent_code_type.py +++ b/pyrogram/enums/sent_code_type.py @@ -37,3 +37,6 @@ class SentCodeType(AutoName): SMS = raw.types.auth.SentCodeTypeSms "The code was sent via SMS." + + FRAGMENT_SMS = raw.types.auth.SentCodeTypeFragmentSms + "The code was sent via Fragment SMS." From 265f5fc72e37bba28824b5d661370976d37200be Mon Sep 17 00:00:00 2001 From: KyMaP13 Date: Sat, 24 Dec 2022 00:40:31 +0500 Subject: [PATCH 1085/1185] Add VOICE_MESSAGES_FORBIDDEN (#1151) * add VOICE_MESSAGES_FORBIDDEN * Update 400_BAD_REQUEST.tsv Co-authored-by: onefedov Co-authored-by: Dan <14043624+delivrance@users.noreply.github.com> --- compiler/errors/source/400_BAD_REQUEST.tsv | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/errors/source/400_BAD_REQUEST.tsv b/compiler/errors/source/400_BAD_REQUEST.tsv index 877c967b37..1ad4c6a593 100644 --- a/compiler/errors/source/400_BAD_REQUEST.tsv +++ b/compiler/errors/source/400_BAD_REQUEST.tsv @@ -340,6 +340,7 @@ USER_NOT_MUTUAL_CONTACT The user is not a mutual contact USER_NOT_PARTICIPANT The user is not a member of this chat VIDEO_CONTENT_TYPE_INVALID The video content type is invalid (i.e.: not streamable) VIDEO_FILE_INVALID The video file is invalid +VOICE_MESSAGES_FORBIDDEN Voice messages are restricted VOLUME_LOC_NOT_FOUND The volume location can't be found WALLPAPER_FILE_INVALID The provided file cannot be used as a wallpaper WALLPAPER_INVALID The input wallpaper was not valid From f350691c698c0cdc339128544e30d820a158d08b Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 23 Dec 2022 20:41:31 +0100 Subject: [PATCH 1086/1185] Update Pyrogram to v2.0.73 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index c58c007e4e..fac98532ce 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.72" +__version__ = "2.0.73" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From 84d60b56b3b32bda046570881bed12aaea4222a8 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 24 Dec 2022 16:15:07 +0100 Subject: [PATCH 1087/1185] Switch to non-blocking sockets & use a send queue --- pyrogram/connection/connection.py | 6 ++--- pyrogram/connection/transport/tcp/tcp.py | 30 +++++++++++++++++------- pyrogram/session/auth.py | 2 +- pyrogram/session/session.py | 2 +- 4 files changed, 26 insertions(+), 14 deletions(-) diff --git a/pyrogram/connection/connection.py b/pyrogram/connection/connection.py index 618c92a510..73c2312f83 100644 --- a/pyrogram/connection/connection.py +++ b/pyrogram/connection/connection.py @@ -57,7 +57,7 @@ async def connect(self): await self.protocol.connect(self.address) except OSError as e: log.warning(f"Unable to connect due to network issues: {e}") - self.protocol.close() + await self.protocol.close() await asyncio.sleep(1) else: log.info("Connected! {} DC{}{} - IPv{} - {}".format( @@ -72,8 +72,8 @@ async def connect(self): log.warning("Connection failed! Trying again...") raise TimeoutError - def close(self): - self.protocol.close() + async def close(self): + await self.protocol.close() log.info("Disconnected") async def send(self, data: bytes): diff --git a/pyrogram/connection/transport/tcp/tcp.py b/pyrogram/connection/transport/tcp/tcp.py index c0efb625ab..4524c7a8b9 100644 --- a/pyrogram/connection/transport/tcp/tcp.py +++ b/pyrogram/connection/transport/tcp/tcp.py @@ -21,7 +21,7 @@ import logging import socket import time -from concurrent.futures import ThreadPoolExecutor +from typing import Optional try: import socks @@ -76,17 +76,21 @@ def __init__(self, ipv6: bool, proxy: dict): else socket.AF_INET ) + self.socket.setblocking(False) self.socket.settimeout(TCP.TIMEOUT) - async def connect(self, address: tuple): - # The socket used by the whole logic is blocking and thus it blocks when connecting. - # Offload the task to a thread executor to avoid blocking the main event loop. - with ThreadPoolExecutor(1) as executor: - await self.loop.run_in_executor(executor, self.socket.connect, address) + self.send_queue = asyncio.Queue() + self.send_task = None + async def connect(self, address: tuple): + await asyncio.get_event_loop().sock_connect(self.socket, address) self.reader, self.writer = await asyncio.open_connection(sock=self.socket) + self.send_task = asyncio.create_task(self.send_worker()) + + async def close(self): + await self.send_queue.put(None) + await self.send_task - def close(self): try: self.writer.close() except AttributeError: @@ -100,8 +104,16 @@ def close(self): time.sleep(0.001) self.socket.close() - async def send(self, data: bytes): - async with self.lock: + async def send(self, data: Optional[bytes]): + await self.send_queue.put(data) + + async def send_worker(self): + while True: + data = await self.send_queue.get() + + if data is None: + break + self.writer.write(data) await self.writer.drain() diff --git a/pyrogram/session/auth.py b/pyrogram/session/auth.py index 7df4fede30..0bb393985b 100644 --- a/pyrogram/session/auth.py +++ b/pyrogram/session/auth.py @@ -258,4 +258,4 @@ async def create(self): else: return auth_key finally: - self.connection.close() + await self.connection.close() diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index a6537bb9a4..5e5d93fa01 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -157,7 +157,7 @@ async def stop(self): self.ping_task_event.clear() - self.connection.close() + await self.connection.close() if self.network_task: await self.network_task From f30510ab7d18c87e4e7af9781a3004cd95441948 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 24 Dec 2022 16:15:37 +0100 Subject: [PATCH 1088/1185] Update Pyrogram to v2.0.74 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index fac98532ce..0840af4bc7 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.73" +__version__ = "2.0.74" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From 1cb17152f8642097e00818a9a963936bc4b1d05a Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 24 Dec 2022 17:15:10 +0100 Subject: [PATCH 1089/1185] Keep a timeout while connecting and set non-blocking afterwards Also fix an await to None value --- pyrogram/connection/transport/tcp/tcp.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pyrogram/connection/transport/tcp/tcp.py b/pyrogram/connection/transport/tcp/tcp.py index 4524c7a8b9..ce8c090969 100644 --- a/pyrogram/connection/transport/tcp/tcp.py +++ b/pyrogram/connection/transport/tcp/tcp.py @@ -76,7 +76,6 @@ def __init__(self, ipv6: bool, proxy: dict): else socket.AF_INET ) - self.socket.setblocking(False) self.socket.settimeout(TCP.TIMEOUT) self.send_queue = asyncio.Queue() @@ -86,10 +85,13 @@ async def connect(self, address: tuple): await asyncio.get_event_loop().sock_connect(self.socket, address) self.reader, self.writer = await asyncio.open_connection(sock=self.socket) self.send_task = asyncio.create_task(self.send_worker()) + self.socket.setblocking(False) async def close(self): await self.send_queue.put(None) - await self.send_task + + if self.send_task is not None: + await self.send_task try: self.writer.close() From 91160bf834a486047a3c2eb3bcfa78277f10847f Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 24 Dec 2022 17:15:23 +0100 Subject: [PATCH 1090/1185] Update Pyrogram to v2.0.75 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 0840af4bc7..40d1179c9b 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.74" +__version__ = "2.0.75" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From deb560a05154f5371dc708d150da7a2b1fc1dea1 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 24 Dec 2022 17:26:40 +0100 Subject: [PATCH 1091/1185] Remove unused variables --- pyrogram/connection/transport/tcp/tcp.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/pyrogram/connection/transport/tcp/tcp.py b/pyrogram/connection/transport/tcp/tcp.py index ce8c090969..1d0296b8e1 100644 --- a/pyrogram/connection/transport/tcp/tcp.py +++ b/pyrogram/connection/transport/tcp/tcp.py @@ -45,9 +45,6 @@ def __init__(self, ipv6: bool, proxy: dict): self.reader = None # type: asyncio.StreamReader self.writer = None # type: asyncio.StreamWriter - self.lock = asyncio.Lock() - self.loop = asyncio.get_event_loop() - if proxy: hostname = proxy.get("hostname") From f12005b5d0dfa85ec435a4812873077a9c143bde Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 24 Dec 2022 17:30:23 +0100 Subject: [PATCH 1092/1185] Use Python's standard sockets in case of no proxy --- pyrogram/connection/transport/tcp/tcp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/connection/transport/tcp/tcp.py b/pyrogram/connection/transport/tcp/tcp.py index 1d0296b8e1..13ddb8e212 100644 --- a/pyrogram/connection/transport/tcp/tcp.py +++ b/pyrogram/connection/transport/tcp/tcp.py @@ -68,7 +68,7 @@ def __init__(self, ipv6: bool, proxy: dict): log.info(f"Using proxy {hostname}") else: - self.socket = socks.socksocket( + self.socket = socket.socket( socket.AF_INET6 if ipv6 else socket.AF_INET ) From 13e8c419917abbb22cd69d0334412cc2645e3df1 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 24 Dec 2022 17:32:02 +0100 Subject: [PATCH 1093/1185] Update Pyrogram to v2.0.76 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 40d1179c9b..3186ab1889 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.75" +__version__ = "2.0.76" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From c7d362990f1de45fcc950ae5b4d13bfd03ef3108 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 24 Dec 2022 17:53:24 +0100 Subject: [PATCH 1094/1185] Add back a reference to the loop --- pyrogram/connection/transport/tcp/tcp.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyrogram/connection/transport/tcp/tcp.py b/pyrogram/connection/transport/tcp/tcp.py index 13ddb8e212..67b5385d20 100644 --- a/pyrogram/connection/transport/tcp/tcp.py +++ b/pyrogram/connection/transport/tcp/tcp.py @@ -45,6 +45,8 @@ def __init__(self, ipv6: bool, proxy: dict): self.reader = None # type: asyncio.StreamReader self.writer = None # type: asyncio.StreamWriter + self.loop = asyncio.get_event_loop() + if proxy: hostname = proxy.get("hostname") From b3825c209e03f9293116f20b62479744dab22d0f Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 24 Dec 2022 17:53:49 +0100 Subject: [PATCH 1095/1185] Update Pyrogram to v2.0.77 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 3186ab1889..313bc9ace9 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.76" +__version__ = "2.0.77" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From dbf2e471b552b8e3012d89ef8acc21ac7929ea15 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 25 Dec 2022 10:26:30 +0100 Subject: [PATCH 1096/1185] Fix usages of removed attributes --- pyrogram/storage/file_storage.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/storage/file_storage.py b/pyrogram/storage/file_storage.py index 986787cd95..a2ebdf9efb 100644 --- a/pyrogram/storage/file_storage.py +++ b/pyrogram/storage/file_storage.py @@ -38,13 +38,13 @@ def update(self): version = self.version() if version == 1: - with self.lock, self.conn: + with self.conn: self.conn.execute("DELETE FROM peers") version += 1 if version == 2: - with self.lock, self.conn: + with self.conn: self.conn.execute("ALTER TABLE sessions ADD api_id INTEGER") version += 1 From 87ae79e0e2a68a7e215ac7a006cda207361e4087 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 25 Dec 2022 10:29:00 +0100 Subject: [PATCH 1097/1185] Remove special cases for older Python versions --- pyrogram/storage/file_storage.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/pyrogram/storage/file_storage.py b/pyrogram/storage/file_storage.py index a2ebdf9efb..aebe917671 100644 --- a/pyrogram/storage/file_storage.py +++ b/pyrogram/storage/file_storage.py @@ -63,10 +63,7 @@ async def open(self): self.update() with self.conn: - try: # Python 3.6.0 (exactly this version) is bugged and won't successfully execute the vacuum - self.conn.execute("VACUUM") - except sqlite3.OperationalError: - pass + self.conn.execute("VACUUM") async def delete(self): os.remove(self.database) From 4c32a15cfdd3aef38bbee8a6863615461aad5424 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 25 Dec 2022 10:30:56 +0100 Subject: [PATCH 1098/1185] Remove unneeded threading.Lock --- pyrogram/session/internals/seq_no.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/pyrogram/session/internals/seq_no.py b/pyrogram/session/internals/seq_no.py index 0abc4a2f72..79501d9863 100644 --- a/pyrogram/session/internals/seq_no.py +++ b/pyrogram/session/internals/seq_no.py @@ -16,19 +16,15 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from threading import Lock - class SeqNo: def __init__(self): self.content_related_messages_sent = 0 - self.lock = Lock() def __call__(self, is_content_related: bool) -> int: - with self.lock: - seq_no = (self.content_related_messages_sent * 2) + (1 if is_content_related else 0) + seq_no = (self.content_related_messages_sent * 2) + (1 if is_content_related else 0) - if is_content_related: - self.content_related_messages_sent += 1 + if is_content_related: + self.content_related_messages_sent += 1 - return seq_no + return seq_no From 13094f1d8b92b83366f652274b2efa678ee29aa7 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 25 Dec 2022 10:31:40 +0100 Subject: [PATCH 1099/1185] Update Pyrogram to v2.0.78 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 313bc9ace9..3d5656dfea 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.77" +__version__ = "2.0.78" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From ae028ab4b67b2c7aab9726a48aeabb0b5432be91 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 25 Dec 2022 14:55:40 +0100 Subject: [PATCH 1100/1185] Switch back to local system time synchronization perf_counter will stop counting when the system goes to sleep, causing the generation of invalid message ids after waking up which in turn put the client into a never ending reconnecting loop due to check mismatches caused by the time not being synced anymore. It's also unclear whether perf_counter stays in sync during long runs. --- pyrogram/session/internals/msg_id.py | 20 +++++--------------- pyrogram/session/session.py | 3 --- 2 files changed, 5 insertions(+), 18 deletions(-) diff --git a/pyrogram/session/internals/msg_id.py b/pyrogram/session/internals/msg_id.py index 871a10b6c8..58e3087c51 100644 --- a/pyrogram/session/internals/msg_id.py +++ b/pyrogram/session/internals/msg_id.py @@ -17,29 +17,19 @@ # along with Pyrogram. If not, see . import logging -from datetime import datetime -from time import perf_counter +import time log = logging.getLogger(__name__) class MsgId: - reference_clock = 0 last_time = 0 - msg_id_offset = 0 - server_time = 0 + offset = 0 def __new__(cls) -> int: - now = perf_counter() - cls.reference_clock + cls.server_time - cls.msg_id_offset = cls.msg_id_offset + 4 if now == cls.last_time else 0 - msg_id = int(now * 2 ** 32) + cls.msg_id_offset + now = time.time() + cls.offset = (cls.offset + 4) if now == cls.last_time else 0 + msg_id = int(now * 2 ** 32) + cls.offset cls.last_time = now return msg_id - - @classmethod - def set_server_time(cls, server_time: int): - if not cls.server_time: - cls.reference_clock = perf_counter() - cls.server_time = server_time - log.info(f"Time synced: {datetime.utcfromtimestamp(server_time)} UTC") diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index 5e5d93fa01..6c3dbc03f2 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -203,9 +203,6 @@ async def handle_packet(self, packet): log.debug(data) for msg in messages: - if msg.seq_no == 0: - MsgId.set_server_time(msg.msg_id / (2 ** 32)) - if msg.seq_no % 2 != 0: if msg.msg_id in self.pending_acks: continue From b23e34494ee5fc25214167fdb49a449eaa040426 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 25 Dec 2022 15:08:02 +0100 Subject: [PATCH 1101/1185] Add messages for mismatched checks --- pyrogram/client.py | 5 ++++- pyrogram/crypto/mtproto.py | 29 ++++++++++++------------ pyrogram/errors/__init__.py | 12 +++++----- pyrogram/session/auth.py | 44 +++++++++++++++++++++++++++---------- pyrogram/session/session.py | 3 ++- 5 files changed, 59 insertions(+), 34 deletions(-) diff --git a/pyrogram/client.py b/pyrogram/client.py index 238b2efbf5..5ff18f29a5 100644 --- a/pyrogram/client.py +++ b/pyrogram/client.py @@ -982,7 +982,10 @@ async def get_file( # https://core.telegram.org/cdn#verifying-files for i, h in enumerate(hashes): cdn_chunk = decrypted_chunk[h.limit * i: h.limit * (i + 1)] - CDNFileHashMismatch.check(h.hash == sha256(cdn_chunk).digest()) + CDNFileHashMismatch.check( + h.hash == sha256(cdn_chunk).digest(), + "h.hash == sha256(cdn_chunk).digest()" + ) yield decrypted_chunk diff --git a/pyrogram/crypto/mtproto.py b/pyrogram/crypto/mtproto.py index c893e3e538..e147c22a3e 100644 --- a/pyrogram/crypto/mtproto.py +++ b/pyrogram/crypto/mtproto.py @@ -62,7 +62,7 @@ def unpack( auth_key_id: bytes, stored_msg_ids: List[int] ) -> Message: - SecurityCheckMismatch.check(b.read(8) == auth_key_id) + SecurityCheckMismatch.check(b.read(8) == auth_key_id, "b.read(8) == auth_key_id") msg_key = b.read(16) aes_key, aes_iv = kdf(auth_key, msg_key, False) @@ -70,7 +70,7 @@ def unpack( data.read(8) # Salt # https://core.telegram.org/mtproto/security_guidelines#checking-session-id - SecurityCheckMismatch.check(data.read(8) == session_id) + SecurityCheckMismatch.check(data.read(8) == session_id, "data.read(8) == session_id") try: message = Message.read(data) @@ -88,39 +88,40 @@ def unpack( # https://core.telegram.org/mtproto/security_guidelines#checking-sha256-hash-value-of-msg-key # 96 = 88 + 8 (incoming message) - SecurityCheckMismatch.check(msg_key == sha256(auth_key[96:96 + 32] + data.getvalue()).digest()[8:24]) + SecurityCheckMismatch.check( + msg_key == sha256(auth_key[96:96 + 32] + data.getvalue()).digest()[8:24], + "msg_key == sha256(auth_key[96:96 + 32] + data.getvalue()).digest()[8:24]" + ) # https://core.telegram.org/mtproto/security_guidelines#checking-message-length data.seek(32) # Get to the payload, skip salt (8) + session_id (8) + msg_id (8) + seq_no (4) + length (4) payload = data.read() padding = payload[message.length:] - SecurityCheckMismatch.check(12 <= len(padding) <= 1024) - SecurityCheckMismatch.check(len(payload) % 4 == 0) + SecurityCheckMismatch.check(12 <= len(padding) <= 1024, "12 <= len(padding) <= 1024") + SecurityCheckMismatch.check(len(payload) % 4 == 0, "len(payload) % 4 == 0") # https://core.telegram.org/mtproto/security_guidelines#checking-msg-id - SecurityCheckMismatch.check(message.msg_id % 2 != 0) + SecurityCheckMismatch.check(message.msg_id % 2 != 0, "message.msg_id % 2 != 0") if len(stored_msg_ids) > STORED_MSG_IDS_MAX_SIZE: del stored_msg_ids[:STORED_MSG_IDS_MAX_SIZE // 2] if stored_msg_ids: - # Ignored message: msg_id is lower than all of the stored values if message.msg_id < stored_msg_ids[0]: - raise SecurityCheckMismatch + raise SecurityCheckMismatch("The msg_id is lower than all the stored values") - # Ignored message: msg_id is equal to any of the stored values if message.msg_id in stored_msg_ids: - raise SecurityCheckMismatch + raise SecurityCheckMismatch("The msg_id is equal to any of the stored values") time_diff = (message.msg_id - MsgId()) / 2 ** 32 - # Ignored message: msg_id belongs over 30 seconds in the future if time_diff > 30: - raise SecurityCheckMismatch + raise SecurityCheckMismatch("The msg_id belongs to over 30 seconds in the future. " + "Most likely the client time has to be synchronized.") - # Ignored message: msg_id belongs over 300 seconds in the past if time_diff < -300: - raise SecurityCheckMismatch + raise SecurityCheckMismatch("The msg_id belongs to over 300 seconds in the past. " + "Most likely the client time has to be synchronized.") bisect.insort(stored_msg_ids, message.msg_id) diff --git a/pyrogram/errors/__init__.py b/pyrogram/errors/__init__.py index 0ae393620e..aa3a042c54 100644 --- a/pyrogram/errors/__init__.py +++ b/pyrogram/errors/__init__.py @@ -45,21 +45,21 @@ class SecurityError(Exception): """Generic security error.""" @classmethod - def check(cls, cond: bool): + def check(cls, cond: bool, msg: str): """Raises this exception if the condition is false""" if not cond: - raise cls + raise cls(f"Check failed: {msg}") class SecurityCheckMismatch(SecurityError): """Raised when a security check mismatch occurs.""" - def __init__(self): - super().__init__("A security check mismatch has occurred.") + def __init__(self, msg: str = None): + super().__init__("A security check mismatch has occurred." if msg is None else msg) class CDNFileHashMismatch(SecurityError): """Raised when a CDN file hash mismatch occurs.""" - def __init__(self): - super().__init__("A CDN file hash mismatch has occurred.") + def __init__(self, msg: str = None): + super().__init__("A CDN file hash mismatch has occurred." if msg is None else msg) diff --git a/pyrogram/session/auth.py b/pyrogram/session/auth.py index 0bb393985b..973bad8bbe 100644 --- a/pyrogram/session/auth.py +++ b/pyrogram/session/auth.py @@ -211,33 +211,51 @@ async def create(self): # Security checks ####################### - SecurityCheckMismatch.check(dh_prime == prime.CURRENT_DH_PRIME) + SecurityCheckMismatch.check(dh_prime == prime.CURRENT_DH_PRIME, "dh_prime == prime.CURRENT_DH_PRIME") log.debug("DH parameters check: OK") # https://core.telegram.org/mtproto/security_guidelines#g-a-and-g-b-validation g_b = int.from_bytes(g_b, "big") - SecurityCheckMismatch.check(1 < g < dh_prime - 1) - SecurityCheckMismatch.check(1 < g_a < dh_prime - 1) - SecurityCheckMismatch.check(1 < g_b < dh_prime - 1) - SecurityCheckMismatch.check(2 ** (2048 - 64) < g_a < dh_prime - 2 ** (2048 - 64)) - SecurityCheckMismatch.check(2 ** (2048 - 64) < g_b < dh_prime - 2 ** (2048 - 64)) + SecurityCheckMismatch.check(1 < g < dh_prime - 1, "1 < g < dh_prime - 1") + SecurityCheckMismatch.check(1 < g_a < dh_prime - 1, "1 < g_a < dh_prime - 1") + SecurityCheckMismatch.check(1 < g_b < dh_prime - 1, "1 < g_b < dh_prime - 1") + SecurityCheckMismatch.check( + 2 ** (2048 - 64) < g_a < dh_prime - 2 ** (2048 - 64), + "2 ** (2048 - 64) < g_a < dh_prime - 2 ** (2048 - 64)" + ) + SecurityCheckMismatch.check( + 2 ** (2048 - 64) < g_b < dh_prime - 2 ** (2048 - 64), + "2 ** (2048 - 64) < g_b < dh_prime - 2 ** (2048 - 64)" + ) log.debug("g_a and g_b validation: OK") # https://core.telegram.org/mtproto/security_guidelines#checking-sha1-hash-values answer = server_dh_inner_data.write() # Call .write() to remove padding - SecurityCheckMismatch.check(answer_with_hash[:20] == sha1(answer).digest()) + SecurityCheckMismatch.check( + answer_with_hash[:20] == sha1(answer).digest(), + "answer_with_hash[:20] == sha1(answer).digest()" + ) log.debug("SHA1 hash values check: OK") # https://core.telegram.org/mtproto/security_guidelines#checking-nonce-server-nonce-and-new-nonce-fields # 1st message - SecurityCheckMismatch.check(nonce == res_pq.nonce) + SecurityCheckMismatch.check(nonce == res_pq.nonce, "nonce == res_pq.nonce") # 2nd message server_nonce = int.from_bytes(server_nonce, "little", signed=True) - SecurityCheckMismatch.check(nonce == server_dh_params.nonce) - SecurityCheckMismatch.check(server_nonce == server_dh_params.server_nonce) + SecurityCheckMismatch.check(nonce == server_dh_params.nonce, "nonce == server_dh_params.nonce") + SecurityCheckMismatch.check( + server_nonce == server_dh_params.server_nonce, + "server_nonce == server_dh_params.server_nonce" + ) # 3rd message - SecurityCheckMismatch.check(nonce == set_client_dh_params_answer.nonce) - SecurityCheckMismatch.check(server_nonce == set_client_dh_params_answer.server_nonce) + SecurityCheckMismatch.check( + nonce == set_client_dh_params_answer.nonce, + "nonce == set_client_dh_params_answer.nonce" + ) + SecurityCheckMismatch.check( + server_nonce == set_client_dh_params_answer.server_nonce, + "server_nonce == set_client_dh_params_answer.server_nonce" + ) server_nonce = server_nonce.to_bytes(16, "little", signed=True) log.debug("Nonce fields check: OK") @@ -248,6 +266,8 @@ async def create(self): log.info(f"Done auth key exchange: {set_client_dh_params_answer.__class__.__name__}") except Exception as e: + log.info(f"Retrying due to {type(e).__name__}: {e}") + if retries_left: retries_left -= 1 else: diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index 6c3dbc03f2..ed4982458b 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -188,7 +188,8 @@ async def handle_packet(self, packet): self.auth_key_id, self.stored_msg_ids ) - except SecurityCheckMismatch: + except SecurityCheckMismatch as e: + log.info(f"Discarding packet: {e}") return messages = ( From ce8c242eb45d0d0e84fea3180249addeb3ece6ff Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 25 Dec 2022 15:14:12 +0100 Subject: [PATCH 1102/1185] Revert to triggering a reconnection when skipping invalid packets --- pyrogram/session/session.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index ed4982458b..fe13c743de 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -190,6 +190,7 @@ async def handle_packet(self, packet): ) except SecurityCheckMismatch as e: log.info(f"Discarding packet: {e}") + await self.connection.close() return messages = ( From 3d5e9d841f2625a4262c2129cb9ff44bdbd5740e Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 25 Dec 2022 15:28:26 +0100 Subject: [PATCH 1103/1185] Update Pyrogram to v2.0.79 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 3d5656dfea..dc3cb7c555 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.78" +__version__ = "2.0.79" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From 9eaaf105c18f18db12857b782ecf4d0081997847 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 26 Dec 2022 16:18:41 +0100 Subject: [PATCH 1104/1185] Update requirements.txt --- requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index bf6641539c..68abeee58d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1 @@ pyaes==1.6.1 -pysocks==1.7.1 From 7e5d59354493e46cfd6acfe75ea72b8ce23e015c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 26 Dec 2022 16:19:26 +0100 Subject: [PATCH 1105/1185] Keep lang_code lowercase --- pyrogram/client.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/client.py b/pyrogram/client.py index 5ff18f29a5..9a0bce94ec 100644 --- a/pyrogram/client.py +++ b/pyrogram/client.py @@ -227,7 +227,7 @@ def __init__( self.app_version = app_version self.device_model = device_model self.system_version = system_version - self.lang_code = lang_code + self.lang_code = lang_code.lower() self.ipv6 = ipv6 self.proxy = proxy self.test_mode = test_mode From 9ee1807e42ba1c02b9f052c9d5695a3faaea865c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 26 Dec 2022 16:23:57 +0100 Subject: [PATCH 1106/1185] Don't raise write() and close() exceptions --- pyrogram/connection/transport/tcp/tcp.py | 27 +++++++++++------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/pyrogram/connection/transport/tcp/tcp.py b/pyrogram/connection/transport/tcp/tcp.py index 67b5385d20..c3185a5fa0 100644 --- a/pyrogram/connection/transport/tcp/tcp.py +++ b/pyrogram/connection/transport/tcp/tcp.py @@ -93,19 +93,13 @@ async def close(self): await self.send_task try: - self.writer.close() - except AttributeError: - try: - self.socket.shutdown(socket.SHUT_RDWR) - except OSError: - pass - finally: - # A tiny sleep placed here helps avoiding .recv(n) hanging until the timeout. - # This is a workaround that seems to fix the occasional delayed stop of a client. - time.sleep(0.001) - self.socket.close() - - async def send(self, data: Optional[bytes]): + if self.writer is not None: + self.writer.close() + await asyncio.wait_for(self.writer.wait_closed(), TCP.TIMEOUT) + except Exception as e: + log.info("Close exception: %s %s", type(e).__name__, e) + + async def send(self, data: bytes): await self.send_queue.put(data) async def send_worker(self): @@ -115,8 +109,11 @@ async def send_worker(self): if data is None: break - self.writer.write(data) - await self.writer.drain() + try: + self.writer.write(data) + await self.writer.drain() + except Exception as e: + log.info("Send exception: %s %s", type(e).__name__, e) async def recv(self, length: int = 0): data = b"" From 6aae3a9c77e52102de905556f17227f15f2ff5fe Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 26 Dec 2022 16:25:22 +0100 Subject: [PATCH 1107/1185] Always use non-blocking sockets --- pyrogram/connection/transport/tcp/tcp.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/pyrogram/connection/transport/tcp/tcp.py b/pyrogram/connection/transport/tcp/tcp.py index c3185a5fa0..213922e603 100644 --- a/pyrogram/connection/transport/tcp/tcp.py +++ b/pyrogram/connection/transport/tcp/tcp.py @@ -42,8 +42,11 @@ class TCP: def __init__(self, ipv6: bool, proxy: dict): self.socket = None - self.reader = None # type: asyncio.StreamReader - self.writer = None # type: asyncio.StreamWriter + self.reader = None + self.writer = None + + self.send_queue = asyncio.Queue() + self.send_task = None self.loop = asyncio.get_event_loop() @@ -75,16 +78,16 @@ def __init__(self, ipv6: bool, proxy: dict): else socket.AF_INET ) - self.socket.settimeout(TCP.TIMEOUT) - - self.send_queue = asyncio.Queue() - self.send_task = None + self.socket.setblocking(False) async def connect(self, address: tuple): - await asyncio.get_event_loop().sock_connect(self.socket, address) + try: + await asyncio.wait_for(asyncio.get_event_loop().sock_connect(self.socket, address), TCP.TIMEOUT) + except asyncio.TimeoutError: # Re-raise as TimeoutError. asyncio.TimeoutError is deprecated in 3.11 + raise TimeoutError("Connection timed out") + self.reader, self.writer = await asyncio.open_connection(sock=self.socket) self.send_task = asyncio.create_task(self.send_worker()) - self.socket.setblocking(False) async def close(self): await self.send_queue.put(None) From dd4e41f63f998c64d269989111ca6d3b42b95588 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 26 Dec 2022 16:28:06 +0100 Subject: [PATCH 1108/1185] Make the use of proxies an optional dependency --- pyrogram/connection/transport/tcp/tcp.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/pyrogram/connection/transport/tcp/tcp.py b/pyrogram/connection/transport/tcp/tcp.py index 213922e603..a59e1081a9 100644 --- a/pyrogram/connection/transport/tcp/tcp.py +++ b/pyrogram/connection/transport/tcp/tcp.py @@ -20,18 +20,11 @@ import ipaddress import logging import socket -import time -from typing import Optional try: import socks -except ImportError as e: - e.msg = ( - "PySocks is missing and Pyrogram can't run without. " - "Please install it using \"pip3 install pysocks\"." - ) - - raise e +except ImportError: + socks = None log = logging.getLogger(__name__) @@ -50,7 +43,10 @@ def __init__(self, ipv6: bool, proxy: dict): self.loop = asyncio.get_event_loop() - if proxy: + if proxy and not socks: + log.warning("Can't use proxy because pysocks is not installed") + + if proxy and socks: hostname = proxy.get("hostname") try: @@ -71,7 +67,7 @@ def __init__(self, ipv6: bool, proxy: dict): password=proxy.get("password", None) ) - log.info(f"Using proxy {hostname}") + log.info("Using proxy %s", hostname) else: self.socket = socket.socket( socket.AF_INET6 if ipv6 From 7182a7cff755fa068b42bdf1eaaba27b1b5859f5 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 26 Dec 2022 16:29:05 +0100 Subject: [PATCH 1109/1185] Update connection.py --- pyrogram/connection/connection.py | 44 +++++++++++-------------------- 1 file changed, 15 insertions(+), 29 deletions(-) diff --git a/pyrogram/connection/connection.py b/pyrogram/connection/connection.py index 73c2312f83..69cbb813a1 100644 --- a/pyrogram/connection/connection.py +++ b/pyrogram/connection/connection.py @@ -20,67 +20,53 @@ import logging from typing import Optional -from .transport import * +from .transport import TCP, TCPAbridgedO from ..session.internals import DataCenter log = logging.getLogger(__name__) class Connection: - MAX_RETRIES = 3 + MAX_CONNECTION_ATTEMPTS = 3 - MODES = { - 0: TCPFull, - 1: TCPAbridged, - 2: TCPIntermediate, - 3: TCPAbridgedO, - 4: TCPIntermediateO - } - - def __init__(self, dc_id: int, test_mode: bool, ipv6: bool, proxy: dict, media: bool = False, mode: int = 3): + def __init__(self, dc_id: int, test_mode: bool, ipv6: bool, proxy: dict, media: bool = False): self.dc_id = dc_id self.test_mode = test_mode self.ipv6 = ipv6 self.proxy = proxy self.media = media - self.address = DataCenter(dc_id, test_mode, ipv6, media) - self.mode = self.MODES.get(mode, TCPAbridged) - self.protocol = None # type: TCP + self.address = DataCenter(dc_id, test_mode, ipv6, media) + self.protocol: TCP = None async def connect(self): - for i in range(Connection.MAX_RETRIES): - self.protocol = self.mode(self.ipv6, self.proxy) + for i in range(Connection.MAX_CONNECTION_ATTEMPTS): + self.protocol = TCPAbridgedO(self.ipv6, self.proxy) try: log.info("Connecting...") await self.protocol.connect(self.address) except OSError as e: - log.warning(f"Unable to connect due to network issues: {e}") + log.warning("Unable to connect due to network issues: %s", e) await self.protocol.close() await asyncio.sleep(1) else: - log.info("Connected! {} DC{}{} - IPv{} - {}".format( - "Test" if self.test_mode else "Production", - self.dc_id, - " (media)" if self.media else "", - "6" if self.ipv6 else "4", - self.mode.__name__, - )) + log.info("Connected! %s DC%s%s - IPv%s", + "Test" if self.test_mode else "Production", + self.dc_id, + " (media)" if self.media else "", + "6" if self.ipv6 else "4") break else: log.warning("Connection failed! Trying again...") - raise TimeoutError + raise ConnectionError async def close(self): await self.protocol.close() log.info("Disconnected") async def send(self, data: bytes): - try: - await self.protocol.send(data) - except Exception as e: - raise OSError(e) + await self.protocol.send(data) async def recv(self) -> Optional[bytes]: return await self.protocol.recv() From d298c62c6dea4c6fc0a5d8384a123481a21df2b0 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 26 Dec 2022 16:34:49 +0100 Subject: [PATCH 1110/1185] Update session.py --- pyrogram/session/session.py | 97 +++++++++++++++++-------------------- 1 file changed, 44 insertions(+), 53 deletions(-) diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index fe13c743de..2a22c2bdbb 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -32,7 +32,7 @@ ) from pyrogram.raw.all import layer from pyrogram.raw.core import TLObject, MsgContainer, Int, FutureSalts -from .internals import MsgId, MsgFactory +from .internals import MsgFactory log = logging.getLogger(__name__) @@ -85,9 +85,9 @@ def __init__( self.ping_task = None self.ping_task_event = asyncio.Event() - self.network_task = None + self.recv_task = None - self.is_connected = asyncio.Event() + self.is_started = asyncio.Event() self.loop = asyncio.get_event_loop() @@ -104,7 +104,7 @@ async def start(self): try: await self.connection.connect() - self.network_task = self.loop.create_task(self.network_worker()) + self.recv_task = self.loop.create_task(self.recv_worker()) await self.send(raw.functions.Ping(ping_id=0), timeout=self.START_TIMEOUT) @@ -128,14 +128,13 @@ async def start(self): self.ping_task = self.loop.create_task(self.ping_worker()) - log.info(f"Session initialized: Layer {layer}") - log.info(f"Device: {self.client.device_model} - {self.client.app_version}") - log.info(f"System: {self.client.system_version} ({self.client.lang_code.upper()})") - + log.info("Session initialized: Layer %s", layer) + log.info("Device: %s - %s", self.client.device_model, self.client.app_version) + log.info("System: %s (%s)", self.client.system_version, self.client.lang_code) except AuthKeyDuplicated as e: await self.stop() raise e - except (OSError, TimeoutError, RPCError): + except (OSError, RPCError): await self.stop() except Exception as e: await self.stop() @@ -143,12 +142,12 @@ async def start(self): else: break - self.is_connected.set() + self.is_started.set() log.info("Session started") async def stop(self): - self.is_connected.clear() + self.is_started.clear() self.ping_task_event.set() @@ -159,17 +158,14 @@ async def stop(self): await self.connection.close() - if self.network_task: - await self.network_task - - for i in self.results.values(): - i.event.set() + if self.recv_task: + await self.recv_task if not self.is_media and callable(self.client.disconnect_handler): try: await self.client.disconnect_handler(self.client) except Exception as e: - log.error(e, exc_info=True) + log.exception(e) log.info("Session stopped") @@ -189,7 +185,7 @@ async def handle_packet(self, packet): self.stored_msg_ids ) except SecurityCheckMismatch as e: - log.info(f"Discarding packet: {e}") + log.info("Discarding packet: %s", e) await self.connection.close() return @@ -199,10 +195,7 @@ async def handle_packet(self, packet): else [data] ) - # Call log.debug twice because calling it once by appending "data" to the previous string (i.e. f"Kind: {data}") - # will cause "data" to be evaluated as string every time instead of only when debug is actually enabled. - log.debug("Received:") - log.debug(data) + log.debug("Received: %s", data) for msg in messages: if msg.seq_no % 2 != 0: @@ -235,11 +228,11 @@ async def handle_packet(self, packet): self.results[msg_id].event.set() if len(self.pending_acks) >= self.ACKS_THRESHOLD: - log.debug(f"Send {len(self.pending_acks)} acks") + log.debug("Sending %s acks", len(self.pending_acks)) try: await self.send(raw.types.MsgsAck(msg_ids=list(self.pending_acks)), False) - except (OSError, TimeoutError): + except OSError: pass else: self.pending_acks.clear() @@ -261,12 +254,12 @@ async def ping_worker(self): ping_id=0, disconnect_delay=self.WAIT_TIMEOUT + 10 ), False ) - except (OSError, TimeoutError, RPCError): + except (OSError, RPCError): pass log.info("PingTask stopped") - async def network_worker(self): + async def recv_worker(self): log.info("NetworkTask started") while True: @@ -274,9 +267,9 @@ async def network_worker(self): if packet is None or len(packet) == 4: if packet: - log.warning(f'Server sent "{Int.read(BytesIO(packet))}"') + log.warning('Server sent "%s"', Int.read(BytesIO(packet))) - if self.is_connected.is_set(): + if self.is_started.is_set(): self.loop.create_task(self.restart()) break @@ -289,13 +282,7 @@ async def send(self, data: TLObject, wait_response: bool = True, timeout: float message = self.msg_factory(data) msg_id = message.msg_id - if wait_response: - self.results[msg_id] = Result() - - # Call log.debug twice because calling it once by appending "data" to the previous string (i.e. f"Kind: {data}") - # will cause "data" to be evaluated as string every time instead of only when debug is actually enabled. - log.debug(f"Sent:") - log.debug(message) + log.debug("Sent: %s", message) payload = await self.loop.run_in_executor( pyrogram.crypto_executor, @@ -307,34 +294,35 @@ async def send(self, data: TLObject, wait_response: bool = True, timeout: float self.auth_key_id ) - try: - await self.connection.send(payload) - except OSError as e: - self.results.pop(msg_id, None) - raise e + await self.connection.send(payload) if wait_response: + self.results[msg_id] = Result() + try: await asyncio.wait_for(self.results[msg_id].event.wait(), timeout) except asyncio.TimeoutError: pass - finally: - result = self.results.pop(msg_id).value + + result = self.results.pop(msg_id).value if result is None: - raise TimeoutError - elif isinstance(result, raw.types.RpcError): + raise TimeoutError("Response timed out") + + if isinstance(result, raw.types.RpcError): if isinstance(data, (raw.functions.InvokeWithoutUpdates, raw.functions.InvokeWithTakeout)): data = data.query RPCError.raise_it(result, type(data)) - elif isinstance(result, raw.types.BadMsgNotification): + + if isinstance(result, raw.types.BadMsgNotification): raise BadMsgNotification(result.error_code) - elif isinstance(result, raw.types.BadServerSalt): + + if isinstance(result, raw.types.BadServerSalt): self.salt = result.new_server_salt return await self.send(data, wait_response, timeout) - else: - return result + + return result async def invoke( self, @@ -344,7 +332,7 @@ async def invoke( sleep_threshold: float = SLEEP_THRESHOLD ): try: - await asyncio.wait_for(self.is_connected.wait(), self.WAIT_TIMEOUT) + await asyncio.wait_for(self.is_started.wait(), self.WAIT_TIMEOUT) except asyncio.TimeoutError: pass @@ -364,16 +352,19 @@ async def invoke( if amount > sleep_threshold >= 0: raise - log.warning(f'[{self.client.name}] Waiting for {amount} seconds before continuing ' - f'(required by "{query_name}")') + log.warning('[%s] Waiting for %s seconds before continuing (required by "%s")', + self.client.name, amount, query_name) await asyncio.sleep(amount) - except (OSError, TimeoutError, InternalServerError, ServiceUnavailable) as e: + except (OSError, InternalServerError, ServiceUnavailable) as e: if retries == 0: raise e from None (log.warning if retries < 2 else log.info)( - f'[{Session.MAX_RETRIES - retries + 1}] Retrying "{query_name}" due to {str(e) or repr(e)}') + '[%s] Retrying "%s" due to: %s', + Session.MAX_RETRIES - retries + 1, + query_name, str(e) or repr(e) + ) await asyncio.sleep(0.5) From 01cd8bb57f5ade27abb287cf01bf146181985a8d Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 26 Dec 2022 16:38:12 +0100 Subject: [PATCH 1111/1185] Optimize log calls --- pyrogram/client.py | 14 ++++++------ pyrogram/dispatcher.py | 10 ++++---- pyrogram/methods/advanced/save_file.py | 4 ++-- pyrogram/methods/auth/terminate.py | 2 +- pyrogram/methods/utilities/start.py | 2 +- pyrogram/parser/html.py | 4 ++-- pyrogram/session/auth.py | 24 ++++++++++---------- pyrogram/types/messages_and_media/message.py | 10 ++++---- 8 files changed, 35 insertions(+), 35 deletions(-) diff --git a/pyrogram/client.py b/pyrogram/client.py index 9a0bce94ec..36ab4e4cf7 100644 --- a/pyrogram/client.py +++ b/pyrogram/client.py @@ -396,7 +396,7 @@ async def authorize(self) -> User: except BadRequest as e: print(e.MESSAGE) except Exception as e: - log.error(e, exc_info=True) + log.exception(e) raise else: self.password = None @@ -684,11 +684,11 @@ def load_plugins(self): try: module = import_module(module_path) except ImportError: - log.warning(f'[{self.name}] [LOAD] Ignoring non-existent module "{module_path}"') + log.warning('[%s] [LOAD] Ignoring non-existent module "%s"', self.name, module_path) continue if "__path__" in dir(module): - log.warning(f'[{self.name}] [LOAD] Ignoring namespace "{module_path}"') + log.warning('[%s] [LOAD] Ignoring namespace "%s"', self.name, module_path) continue if handlers is None: @@ -719,11 +719,11 @@ def load_plugins(self): try: module = import_module(module_path) except ImportError: - log.warning(f'[{self.name}] [UNLOAD] Ignoring non-existent module "{module_path}"') + log.warning('[%s] [UNLOAD] Ignoring non-existent module "%s"', self.name, module_path) continue if "__path__" in dir(module): - log.warning(f'[{self.name}] [UNLOAD] Ignoring namespace "{module_path}"') + log.warning('[%s] [UNLOAD] Ignoring namespace "%s"', self.name, module_path) continue if handlers is None: @@ -750,7 +750,7 @@ def load_plugins(self): log.info('[{}] Successfully loaded {} plugin{} from "{}"'.format( self.name, count, "s" if count > 1 else "", root)) else: - log.warning(f'[{self.name}] No plugin loaded from "{root}"') + log.warning('[%s] No plugin loaded from "%s"', self.name, root) async def handle_download(self, packet): file_id, directory, file_name, in_memory, file_size, progress, progress_args = packet @@ -1012,7 +1012,7 @@ async def get_file( except pyrogram.StopTransmission: raise except Exception as e: - log.error(e, exc_info=True) + log.exception(e) def guess_mime_type(self, filename: str) -> Optional[str]: return self.mimetypes.guess_type(filename)[0] diff --git a/pyrogram/dispatcher.py b/pyrogram/dispatcher.py index b0ad87f71a..6e503cebb3 100644 --- a/pyrogram/dispatcher.py +++ b/pyrogram/dispatcher.py @@ -151,7 +151,7 @@ async def start(self): self.loop.create_task(self.handler_worker(self.locks_list[-1])) ) - log.info(f"Started {self.client.workers} HandlerTasks") + log.info("Started %s HandlerTasks", self.client.workers) async def stop(self): if not self.client.no_updates: @@ -164,7 +164,7 @@ async def stop(self): self.handler_worker_tasks.clear() self.groups.clear() - log.info(f"Stopped {self.client.workers} HandlerTasks") + log.info("Stopped %s HandlerTasks", self.client.workers) def add_handler(self, handler, group: int): async def fn(): @@ -226,7 +226,7 @@ async def handler_worker(self, lock): if await handler.check(self.client, parsed_update): args = (parsed_update,) except Exception as e: - log.error(e, exc_info=True) + log.exception(e) continue elif isinstance(handler, RawUpdateHandler): @@ -250,10 +250,10 @@ async def handler_worker(self, lock): except pyrogram.ContinuePropagation: continue except Exception as e: - log.error(e, exc_info=True) + log.exception(e) break except pyrogram.StopPropagation: pass except Exception as e: - log.error(e, exc_info=True) + log.exception(e) diff --git a/pyrogram/methods/advanced/save_file.py b/pyrogram/methods/advanced/save_file.py index c7f3a953e8..b99a3c43b5 100644 --- a/pyrogram/methods/advanced/save_file.py +++ b/pyrogram/methods/advanced/save_file.py @@ -107,7 +107,7 @@ async def worker(session): try: await session.invoke(data) except Exception as e: - log.error(e) + log.exception(e) part_size = 512 * 1024 @@ -201,7 +201,7 @@ async def worker(session): except StopTransmission: raise except Exception as e: - log.error(e, exc_info=True) + log.exception(e) else: if is_big: return raw.types.InputFileBig( diff --git a/pyrogram/methods/auth/terminate.py b/pyrogram/methods/auth/terminate.py index d70103d03e..70cfc80e65 100644 --- a/pyrogram/methods/auth/terminate.py +++ b/pyrogram/methods/auth/terminate.py @@ -41,7 +41,7 @@ async def terminate( if self.takeout_id: await self.invoke(raw.functions.account.FinishTakeoutSession()) - log.warning(f"Takeout session {self.takeout_id} finished") + log.warning("Takeout session %s finished", self.takeout_id) await self.storage.save() await self.dispatcher.stop() diff --git a/pyrogram/methods/utilities/start.py b/pyrogram/methods/utilities/start.py index 95cd9fc585..19a7eb7cf3 100644 --- a/pyrogram/methods/utilities/start.py +++ b/pyrogram/methods/utilities/start.py @@ -63,7 +63,7 @@ async def main(): if not await self.storage.is_bot() and self.takeout: self.takeout_id = (await self.invoke(raw.functions.account.InitTakeoutSession())).id - log.warning(f"Takeout session {self.takeout_id} initiated") + log.warning("Takeout session %s initiated", self.takeout_id) await self.invoke(raw.functions.updates.GetState()) except (Exception, KeyboardInterrupt): diff --git a/pyrogram/parser/html.py b/pyrogram/parser/html.py index 987281b177..7edb7f3c99 100644 --- a/pyrogram/parser/html.py +++ b/pyrogram/parser/html.py @@ -103,7 +103,7 @@ def handle_endtag(self, tag): line, offset = self.getpos() offset += 1 - log.debug(f"Unmatched closing tag at line {line}:{offset}") + log.debug("Unmatched closing tag at line %s:%s", tag, line, offset) else: if not self.tag_entities[tag]: self.tag_entities.pop(tag) @@ -131,7 +131,7 @@ async def parse(self, text: str): for tag, entities in parser.tag_entities.items(): unclosed_tags.append(f"<{tag}> (x{len(entities)})") - log.warning(f"Unclosed tags: {', '.join(unclosed_tags)}") + log.warning("Unclosed tags: %s", ", ".join(unclosed_tags)) entities = [] diff --git a/pyrogram/session/auth.py b/pyrogram/session/auth.py index 973bad8bbe..c5d9cd9a50 100644 --- a/pyrogram/session/auth.py +++ b/pyrogram/session/auth.py @@ -79,34 +79,34 @@ async def create(self): self.connection = Connection(self.dc_id, self.test_mode, self.ipv6, self.proxy) try: - log.info(f"Start creating a new auth key on DC{self.dc_id}") + log.info("Start creating a new auth key on DC%s", self.dc_id) await self.connection.connect() # Step 1; Step 2 nonce = int.from_bytes(urandom(16), "little", signed=True) - log.debug(f"Send req_pq: {nonce}") + log.debug("Send req_pq: %s", nonce) res_pq = await self.invoke(raw.functions.ReqPqMulti(nonce=nonce)) - log.debug(f"Got ResPq: {res_pq.server_nonce}") - log.debug(f"Server public key fingerprints: {res_pq.server_public_key_fingerprints}") + log.debug("Got ResPq: %s", res_pq.server_nonce) + log.debug("Server public key fingerprints: %s", res_pq.server_public_key_fingerprints) for i in res_pq.server_public_key_fingerprints: if i in rsa.server_public_keys: - log.debug(f"Using fingerprint: {i}") + log.debug("Using fingerprint: %s", i) public_key_fingerprint = i break else: - log.debug(f"Fingerprint unknown: {i}") + log.debug("Fingerprint unknown: %s", i) else: raise Exception("Public key not found") # Step 3 pq = int.from_bytes(res_pq.pq, "big") - log.debug(f"Start PQ factorization: {pq}") + log.debug("Start PQ factorization: %s", pq) start = time.time() g = prime.decompose(pq) p, q = sorted((g, pq // g)) # p < q - log.debug(f"Done PQ factorization ({round(time.time() - start, 3)}s): {p} {q}") + log.debug("Done PQ factorization (%ss): %s %s", round(time.time() - start, 3), p, q) # Step 4 server_nonce = res_pq.server_nonce @@ -168,7 +168,7 @@ async def create(self): dh_prime = int.from_bytes(server_dh_inner_data.dh_prime, "big") delta_time = server_dh_inner_data.server_time - time.time() - log.debug(f"Delta time: {round(delta_time, 3)}") + log.debug("Delta time: %s", round(delta_time, 3)) # Step 6 g = server_dh_inner_data.g @@ -262,11 +262,11 @@ async def create(self): # Step 9 server_salt = aes.xor(new_nonce[:8], server_nonce[:8]) - log.debug(f"Server salt: {int.from_bytes(server_salt, 'little')}") + log.debug("Server salt: %s", int.from_bytes(server_salt, "little")) - log.info(f"Done auth key exchange: {set_client_dh_params_answer.__class__.__name__}") + log.info("Done auth key exchange: %s", set_client_dh_params_answer.__class__.__name__) except Exception as e: - log.info(f"Retrying due to {type(e).__name__}: {e}") + log.info("Retrying due to %s: %s", type(e).__name__, e) if retries_left: retries_left -= 1 diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index d844b06054..ef249771fd 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -3045,13 +3045,13 @@ async def copy( RPCError: In case of a Telegram RPC error. """ if self.service: - log.warning(f"Service messages cannot be copied. " - f"chat_id: {self.chat.id}, message_id: {self.id}") + log.warning("Service messages cannot be copied. chat_id: %s, message_id: %s", + self.chat.id, self.id) elif self.game and not await self._client.storage.is_bot(): - log.warning(f"Users cannot send messages with Game media type. " - f"chat_id: {self.chat.id}, message_id: {self.id}") + log.warning("Users cannot send messages with Game media type. chat_id: %s, message_id: %s", + self.chat.id, self.id) elif self.empty: - log.warning(f"Empty messages cannot be copied. ") + log.warning("Empty messages cannot be copied.") elif self.text: return await self._client.send_message( chat_id, From 6b54467a0d6611f3dd60ec76a9dacdb170a25082 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 26 Dec 2022 16:38:58 +0100 Subject: [PATCH 1112/1185] Update Pyrogram to v2.0.80 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index dc3cb7c555..af92632364 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.79" +__version__ = "2.0.80" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From ab45707f0f5ab9a7a20475701ab20e385692bcf4 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 26 Dec 2022 16:47:37 +0100 Subject: [PATCH 1113/1185] Update requirements.txt --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 68abeee58d..bf6641539c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ pyaes==1.6.1 +pysocks==1.7.1 From 8b87c6ace3d867c3003b7a9c03d93648b96ef18d Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 26 Dec 2022 16:48:03 +0100 Subject: [PATCH 1114/1185] Update tcp.py --- pyrogram/connection/transport/tcp/tcp.py | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/pyrogram/connection/transport/tcp/tcp.py b/pyrogram/connection/transport/tcp/tcp.py index a59e1081a9..13b6e7ded0 100644 --- a/pyrogram/connection/transport/tcp/tcp.py +++ b/pyrogram/connection/transport/tcp/tcp.py @@ -20,11 +20,7 @@ import ipaddress import logging import socket - -try: - import socks -except ImportError: - socks = None +import socks log = logging.getLogger(__name__) @@ -43,10 +39,7 @@ def __init__(self, ipv6: bool, proxy: dict): self.loop = asyncio.get_event_loop() - if proxy and not socks: - log.warning("Can't use proxy because pysocks is not installed") - - if proxy and socks: + if proxy: hostname = proxy.get("hostname") try: From 916be081163ea64902d99b4f40da47072caca68d Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 26 Dec 2022 16:48:30 +0100 Subject: [PATCH 1115/1185] Update Pyrogram to v2.0.81 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index af92632364..7d63406e9c 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.80" +__version__ = "2.0.81" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From 235dde2251a28df7149795563184c8920f0ca9d5 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 26 Dec 2022 20:11:58 +0100 Subject: [PATCH 1116/1185] Update exception message --- 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 2a22c2bdbb..3899aa528d 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -307,7 +307,7 @@ async def send(self, data: TLObject, wait_response: bool = True, timeout: float result = self.results.pop(msg_id).value if result is None: - raise TimeoutError("Response timed out") + raise TimeoutError("Request timed out") if isinstance(result, raw.types.RpcError): if isinstance(data, (raw.functions.InvokeWithoutUpdates, raw.functions.InvokeWithTakeout)): From a81b8a22545f2b18c7df7f59cd8d458e1c7589f3 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 26 Dec 2022 20:12:20 +0100 Subject: [PATCH 1117/1185] Update Pyrogram to v2.0.82 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 7d63406e9c..57483b6337 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.81" +__version__ = "2.0.82" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From bff583ed7562c20c3f1818a3cf05bf6051987320 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 26 Dec 2022 22:26:55 +0100 Subject: [PATCH 1118/1185] Revert some of the latest changes --- pyrogram/client.py | 25 --------- pyrogram/connection/connection.py | 13 +++-- pyrogram/connection/transport/tcp/tcp.py | 64 ++++++++++-------------- pyrogram/methods/auth/initialize.py | 3 -- pyrogram/methods/auth/terminate.py | 7 --- pyrogram/session/auth.py | 2 +- pyrogram/session/internals/seq_no.py | 12 +++-- pyrogram/session/session.py | 36 +++++++------ pyrogram/storage/file_storage.py | 9 ++-- pyrogram/storage/sqlite_storage.py | 26 ++++++---- 10 files changed, 86 insertions(+), 111 deletions(-) diff --git a/pyrogram/client.py b/pyrogram/client.py index 36ab4e4cf7..63e4b47273 100644 --- a/pyrogram/client.py +++ b/pyrogram/client.py @@ -26,7 +26,6 @@ import shutil import sys from concurrent.futures.thread import ThreadPoolExecutor -from datetime import datetime, timedelta from hashlib import sha256 from importlib import import_module from io import StringIO, BytesIO @@ -186,9 +185,6 @@ class Client(Methods): WORKERS = min(32, (os.cpu_count() or 0) + 4) # os.cpu_count() can be None WORKDIR = PARENT_DIR - # Interval of seconds in which the updates watchdog will kick in - UPDATES_WATCHDOG_INTERVAL = 5 * 60 - mimetypes = MimeTypes() mimetypes.readfp(StringIO(mime_types)) @@ -277,13 +273,6 @@ def __init__( self.message_cache = Cache(10000) - # Sometimes, for some reason, the server will stop sending updates and will only respond to pings. - # This watchdog will invoke updates.GetState in order to wake up the server and enable it sending updates again - # after some idle time has been detected. - self.updates_watchdog_task = None - self.updates_watchdog_event = asyncio.Event() - self.last_update_time = datetime.now() - self.loop = asyncio.get_event_loop() def __enter__(self): @@ -304,18 +293,6 @@ async def __aexit__(self, *args): except ConnectionError: pass - async def updates_watchdog(self): - while True: - try: - await asyncio.wait_for(self.updates_watchdog_event.wait(), self.UPDATES_WATCHDOG_INTERVAL) - except asyncio.TimeoutError: - pass - else: - break - - if datetime.now() - self.last_update_time > timedelta(seconds=self.UPDATES_WATCHDOG_INTERVAL): - await self.invoke(raw.functions.updates.GetState()) - async def authorize(self) -> User: if self.bot_token: return await self.sign_in_bot(self.bot_token) @@ -508,8 +485,6 @@ async def fetch_peers(self, peers: List[Union[raw.types.User, raw.types.Chat, ra return is_min async def handle_updates(self, updates): - self.last_update_time = datetime.now() - if isinstance(updates, (raw.types.Updates, raw.types.UpdatesCombined)): is_min = any(( await self.fetch_peers(updates.users), diff --git a/pyrogram/connection/connection.py b/pyrogram/connection/connection.py index 69cbb813a1..051d3c526b 100644 --- a/pyrogram/connection/connection.py +++ b/pyrogram/connection/connection.py @@ -48,7 +48,7 @@ async def connect(self): await self.protocol.connect(self.address) except OSError as e: log.warning("Unable to connect due to network issues: %s", e) - await self.protocol.close() + self.protocol.close() await asyncio.sleep(1) else: log.info("Connected! %s DC%s%s - IPv%s", @@ -59,14 +59,17 @@ async def connect(self): break else: log.warning("Connection failed! Trying again...") - raise ConnectionError + raise TimeoutError - async def close(self): - await self.protocol.close() + def close(self): + self.protocol.close() log.info("Disconnected") async def send(self, data: bytes): - await self.protocol.send(data) + try: + await self.protocol.send(data) + except Exception as e: + raise OSError(e) async def recv(self) -> Optional[bytes]: return await self.protocol.recv() diff --git a/pyrogram/connection/transport/tcp/tcp.py b/pyrogram/connection/transport/tcp/tcp.py index 13b6e7ded0..beb2e58a2a 100644 --- a/pyrogram/connection/transport/tcp/tcp.py +++ b/pyrogram/connection/transport/tcp/tcp.py @@ -20,6 +20,9 @@ import ipaddress import logging import socket +import time +from concurrent.futures import ThreadPoolExecutor + import socks log = logging.getLogger(__name__) @@ -31,12 +34,10 @@ class TCP: def __init__(self, ipv6: bool, proxy: dict): self.socket = None - self.reader = None - self.writer = None - - self.send_queue = asyncio.Queue() - self.send_task = None + self.reader = None # type: asyncio.StreamReader + self.writer = None # type: asyncio.StreamWriter + self.lock = asyncio.Lock() self.loop = asyncio.get_event_loop() if proxy: @@ -62,50 +63,39 @@ def __init__(self, ipv6: bool, proxy: dict): log.info("Using proxy %s", hostname) else: - self.socket = socket.socket( + self.socket = socks.socksocket( socket.AF_INET6 if ipv6 else socket.AF_INET ) - self.socket.setblocking(False) + self.socket.settimeout(TCP.TIMEOUT) async def connect(self, address: tuple): - try: - await asyncio.wait_for(asyncio.get_event_loop().sock_connect(self.socket, address), TCP.TIMEOUT) - except asyncio.TimeoutError: # Re-raise as TimeoutError. asyncio.TimeoutError is deprecated in 3.11 - raise TimeoutError("Connection timed out") + # The socket used by the whole logic is blocking and thus it blocks when connecting. + # Offload the task to a thread executor to avoid blocking the main event loop. + with ThreadPoolExecutor(1) as executor: + await self.loop.run_in_executor(executor, self.socket.connect, address) self.reader, self.writer = await asyncio.open_connection(sock=self.socket) - self.send_task = asyncio.create_task(self.send_worker()) - - async def close(self): - await self.send_queue.put(None) - - if self.send_task is not None: - await self.send_task + def close(self): try: - if self.writer is not None: - self.writer.close() - await asyncio.wait_for(self.writer.wait_closed(), TCP.TIMEOUT) - except Exception as e: - log.info("Close exception: %s %s", type(e).__name__, e) + self.writer.close() + except AttributeError: + try: + self.socket.shutdown(socket.SHUT_RDWR) + except OSError: + pass + finally: + # A tiny sleep placed here helps avoiding .recv(n) hanging until the timeout. + # This is a workaround that seems to fix the occasional delayed stop of a client. + time.sleep(0.001) + self.socket.close() async def send(self, data: bytes): - await self.send_queue.put(data) - - async def send_worker(self): - while True: - data = await self.send_queue.get() - - if data is None: - break - - try: - self.writer.write(data) - await self.writer.drain() - except Exception as e: - log.info("Send exception: %s %s", type(e).__name__, e) + async with self.lock: + self.writer.write(data) + await self.writer.drain() async def recv(self, length: int = 0): data = b"" diff --git a/pyrogram/methods/auth/initialize.py b/pyrogram/methods/auth/initialize.py index 7188b66817..1e7915e00e 100644 --- a/pyrogram/methods/auth/initialize.py +++ b/pyrogram/methods/auth/initialize.py @@ -16,7 +16,6 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -import asyncio import logging import pyrogram @@ -47,6 +46,4 @@ async def initialize( await self.dispatcher.start() - self.updates_watchdog_task = asyncio.create_task(self.updates_watchdog()) - self.is_initialized = True diff --git a/pyrogram/methods/auth/terminate.py b/pyrogram/methods/auth/terminate.py index 70cfc80e65..5ecb6758ef 100644 --- a/pyrogram/methods/auth/terminate.py +++ b/pyrogram/methods/auth/terminate.py @@ -51,11 +51,4 @@ async def terminate( self.media_sessions.clear() - self.updates_watchdog_event.set() - - if self.updates_watchdog_task is not None: - await self.updates_watchdog_task - - self.updates_watchdog_event.clear() - self.is_initialized = False diff --git a/pyrogram/session/auth.py b/pyrogram/session/auth.py index c5d9cd9a50..d51e18f843 100644 --- a/pyrogram/session/auth.py +++ b/pyrogram/session/auth.py @@ -278,4 +278,4 @@ async def create(self): else: return auth_key finally: - await self.connection.close() + self.connection.close() diff --git a/pyrogram/session/internals/seq_no.py b/pyrogram/session/internals/seq_no.py index 79501d9863..0abc4a2f72 100644 --- a/pyrogram/session/internals/seq_no.py +++ b/pyrogram/session/internals/seq_no.py @@ -16,15 +16,19 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +from threading import Lock + class SeqNo: def __init__(self): self.content_related_messages_sent = 0 + self.lock = Lock() def __call__(self, is_content_related: bool) -> int: - seq_no = (self.content_related_messages_sent * 2) + (1 if is_content_related else 0) + with self.lock: + seq_no = (self.content_related_messages_sent * 2) + (1 if is_content_related else 0) - if is_content_related: - self.content_related_messages_sent += 1 + if is_content_related: + self.content_related_messages_sent += 1 - return seq_no + return seq_no diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index 3899aa528d..5135af6987 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -156,11 +156,14 @@ async def stop(self): self.ping_task_event.clear() - await self.connection.close() + self.connection.close() if self.recv_task: await self.recv_task + for i in self.results.values(): + i.event.set() + if not self.is_media and callable(self.client.disconnect_handler): try: await self.client.disconnect_handler(self.client) @@ -185,8 +188,7 @@ async def handle_packet(self, packet): self.stored_msg_ids ) except SecurityCheckMismatch as e: - log.info("Discarding packet: %s", e) - await self.connection.close() + log.warning("Discarding packet: %s", e) return messages = ( @@ -282,6 +284,9 @@ async def send(self, data: TLObject, wait_response: bool = True, timeout: float message = self.msg_factory(data) msg_id = message.msg_id + if wait_response: + self.results[msg_id] = Result() + log.debug("Sent: %s", message) payload = await self.loop.run_in_executor( @@ -294,35 +299,34 @@ async def send(self, data: TLObject, wait_response: bool = True, timeout: float self.auth_key_id ) - await self.connection.send(payload) + try: + await self.connection.send(payload) + except OSError as e: + self.results.pop(msg_id, None) + raise e if wait_response: - self.results[msg_id] = Result() - try: await asyncio.wait_for(self.results[msg_id].event.wait(), timeout) except asyncio.TimeoutError: pass - - result = self.results.pop(msg_id).value + finally: + result = self.results.pop(msg_id).value if result is None: raise TimeoutError("Request timed out") - - if isinstance(result, raw.types.RpcError): + elif isinstance(result, raw.types.RpcError): if isinstance(data, (raw.functions.InvokeWithoutUpdates, raw.functions.InvokeWithTakeout)): data = data.query RPCError.raise_it(result, type(data)) - - if isinstance(result, raw.types.BadMsgNotification): + elif isinstance(result, raw.types.BadMsgNotification): raise BadMsgNotification(result.error_code) - - if isinstance(result, raw.types.BadServerSalt): + elif isinstance(result, raw.types.BadServerSalt): self.salt = result.new_server_salt return await self.send(data, wait_response, timeout) - - return result + else: + return result async def invoke( self, diff --git a/pyrogram/storage/file_storage.py b/pyrogram/storage/file_storage.py index aebe917671..986787cd95 100644 --- a/pyrogram/storage/file_storage.py +++ b/pyrogram/storage/file_storage.py @@ -38,13 +38,13 @@ def update(self): version = self.version() if version == 1: - with self.conn: + with self.lock, self.conn: self.conn.execute("DELETE FROM peers") version += 1 if version == 2: - with self.conn: + with self.lock, self.conn: self.conn.execute("ALTER TABLE sessions ADD api_id INTEGER") version += 1 @@ -63,7 +63,10 @@ async def open(self): self.update() with self.conn: - self.conn.execute("VACUUM") + try: # Python 3.6.0 (exactly this version) is bugged and won't successfully execute the vacuum + self.conn.execute("VACUUM") + except sqlite3.OperationalError: + pass async def delete(self): os.remove(self.database) diff --git a/pyrogram/storage/sqlite_storage.py b/pyrogram/storage/sqlite_storage.py index e28b9b746e..15e5ddc0c5 100644 --- a/pyrogram/storage/sqlite_storage.py +++ b/pyrogram/storage/sqlite_storage.py @@ -19,6 +19,7 @@ import inspect import sqlite3 import time +from threading import Lock from typing import List, Tuple, Any from pyrogram import raw @@ -97,9 +98,10 @@ def __init__(self, name: str): super().__init__(name) self.conn = None # type: sqlite3.Connection + self.lock = Lock() def create(self): - with self.conn: + with self.lock, self.conn: self.conn.executescript(SCHEMA) self.conn.execute( @@ -117,20 +119,24 @@ async def open(self): async def save(self): await self.date(int(time.time())) - self.conn.commit() + + with self.lock: + self.conn.commit() async def close(self): - self.conn.close() + with self.lock: + self.conn.close() async def delete(self): raise NotImplementedError async def update_peers(self, peers: List[Tuple[int, int, str, str, str]]): - self.conn.executemany( - "REPLACE INTO peers (id, access_hash, type, username, phone_number)" - "VALUES (?, ?, ?, ?, ?)", - peers - ) + with self.lock: + self.conn.executemany( + "REPLACE INTO peers (id, access_hash, type, username, phone_number)" + "VALUES (?, ?, ?, ?, ?)", + peers + ) async def get_peer_by_id(self, peer_id: int): r = self.conn.execute( @@ -179,7 +185,7 @@ def _get(self): def _set(self, value: Any): attr = inspect.stack()[2].function - with self.conn: + with self.lock, self.conn: self.conn.execute( f"UPDATE sessions SET {attr} = ?", (value,) @@ -215,7 +221,7 @@ def version(self, value: int = object): "SELECT number FROM version" ).fetchone()[0] else: - with self.conn: + with self.lock, self.conn: self.conn.execute( "UPDATE version SET number = ?", (value,) From 03d60cdfe656d527c510a7924a0b2332fb8398e7 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 26 Dec 2022 22:30:14 +0100 Subject: [PATCH 1119/1185] Update Pyrogram to v2.0.83 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 57483b6337..a1accaf2ee 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.82" +__version__ = "2.0.83" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From 9bf742abc0bb6342749ce108fa49dd9c2bedb646 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 27 Dec 2022 13:40:42 +0100 Subject: [PATCH 1120/1185] Introduce back some previously reverted changes --- pyrogram/client.py | 25 +++++++++++++ pyrogram/connection/connection.py | 13 +++---- pyrogram/connection/transport/tcp/tcp.py | 45 +++++++++++------------- pyrogram/methods/auth/initialize.py | 3 ++ pyrogram/methods/auth/terminate.py | 7 ++++ pyrogram/session/auth.py | 2 +- pyrogram/session/internals/msg_id.py | 4 +-- pyrogram/session/internals/seq_no.py | 12 +++---- pyrogram/session/session.py | 40 ++++++++++----------- pyrogram/storage/file_storage.py | 9 ++--- pyrogram/storage/sqlite_storage.py | 26 ++++++-------- 11 files changed, 98 insertions(+), 88 deletions(-) diff --git a/pyrogram/client.py b/pyrogram/client.py index 63e4b47273..36ab4e4cf7 100644 --- a/pyrogram/client.py +++ b/pyrogram/client.py @@ -26,6 +26,7 @@ import shutil import sys from concurrent.futures.thread import ThreadPoolExecutor +from datetime import datetime, timedelta from hashlib import sha256 from importlib import import_module from io import StringIO, BytesIO @@ -185,6 +186,9 @@ class Client(Methods): WORKERS = min(32, (os.cpu_count() or 0) + 4) # os.cpu_count() can be None WORKDIR = PARENT_DIR + # Interval of seconds in which the updates watchdog will kick in + UPDATES_WATCHDOG_INTERVAL = 5 * 60 + mimetypes = MimeTypes() mimetypes.readfp(StringIO(mime_types)) @@ -273,6 +277,13 @@ def __init__( self.message_cache = Cache(10000) + # Sometimes, for some reason, the server will stop sending updates and will only respond to pings. + # This watchdog will invoke updates.GetState in order to wake up the server and enable it sending updates again + # after some idle time has been detected. + self.updates_watchdog_task = None + self.updates_watchdog_event = asyncio.Event() + self.last_update_time = datetime.now() + self.loop = asyncio.get_event_loop() def __enter__(self): @@ -293,6 +304,18 @@ async def __aexit__(self, *args): except ConnectionError: pass + async def updates_watchdog(self): + while True: + try: + await asyncio.wait_for(self.updates_watchdog_event.wait(), self.UPDATES_WATCHDOG_INTERVAL) + except asyncio.TimeoutError: + pass + else: + break + + if datetime.now() - self.last_update_time > timedelta(seconds=self.UPDATES_WATCHDOG_INTERVAL): + await self.invoke(raw.functions.updates.GetState()) + async def authorize(self) -> User: if self.bot_token: return await self.sign_in_bot(self.bot_token) @@ -485,6 +508,8 @@ async def fetch_peers(self, peers: List[Union[raw.types.User, raw.types.Chat, ra return is_min async def handle_updates(self, updates): + self.last_update_time = datetime.now() + if isinstance(updates, (raw.types.Updates, raw.types.UpdatesCombined)): is_min = any(( await self.fetch_peers(updates.users), diff --git a/pyrogram/connection/connection.py b/pyrogram/connection/connection.py index 051d3c526b..69cbb813a1 100644 --- a/pyrogram/connection/connection.py +++ b/pyrogram/connection/connection.py @@ -48,7 +48,7 @@ async def connect(self): await self.protocol.connect(self.address) except OSError as e: log.warning("Unable to connect due to network issues: %s", e) - self.protocol.close() + await self.protocol.close() await asyncio.sleep(1) else: log.info("Connected! %s DC%s%s - IPv%s", @@ -59,17 +59,14 @@ async def connect(self): break else: log.warning("Connection failed! Trying again...") - raise TimeoutError + raise ConnectionError - def close(self): - self.protocol.close() + async def close(self): + await self.protocol.close() log.info("Disconnected") async def send(self, data: bytes): - try: - await self.protocol.send(data) - except Exception as e: - raise OSError(e) + await self.protocol.send(data) async def recv(self) -> Optional[bytes]: return await self.protocol.recv() diff --git a/pyrogram/connection/transport/tcp/tcp.py b/pyrogram/connection/transport/tcp/tcp.py index beb2e58a2a..6aff86af22 100644 --- a/pyrogram/connection/transport/tcp/tcp.py +++ b/pyrogram/connection/transport/tcp/tcp.py @@ -20,9 +20,6 @@ import ipaddress import logging import socket -import time -from concurrent.futures import ThreadPoolExecutor - import socks log = logging.getLogger(__name__) @@ -34,8 +31,8 @@ class TCP: def __init__(self, ipv6: bool, proxy: dict): self.socket = None - self.reader = None # type: asyncio.StreamReader - self.writer = None # type: asyncio.StreamWriter + self.reader = None + self.writer = None self.lock = asyncio.Lock() self.loop = asyncio.get_event_loop() @@ -63,39 +60,37 @@ def __init__(self, ipv6: bool, proxy: dict): log.info("Using proxy %s", hostname) else: - self.socket = socks.socksocket( + self.socket = socket.socket( socket.AF_INET6 if ipv6 else socket.AF_INET ) - self.socket.settimeout(TCP.TIMEOUT) + self.socket.setblocking(False) async def connect(self, address: tuple): - # The socket used by the whole logic is blocking and thus it blocks when connecting. - # Offload the task to a thread executor to avoid blocking the main event loop. - with ThreadPoolExecutor(1) as executor: - await self.loop.run_in_executor(executor, self.socket.connect, address) + try: + await asyncio.wait_for(asyncio.get_event_loop().sock_connect(self.socket, address), TCP.TIMEOUT) + except asyncio.TimeoutError: # Re-raise as TimeoutError. asyncio.TimeoutError is deprecated in 3.11 + raise TimeoutError("Connection timed out") self.reader, self.writer = await asyncio.open_connection(sock=self.socket) - def close(self): + async def close(self): try: - self.writer.close() - except AttributeError: - try: - self.socket.shutdown(socket.SHUT_RDWR) - except OSError: - pass - finally: - # A tiny sleep placed here helps avoiding .recv(n) hanging until the timeout. - # This is a workaround that seems to fix the occasional delayed stop of a client. - time.sleep(0.001) - self.socket.close() + if self.writer is not None: + self.writer.close() + await asyncio.wait_for(self.writer.wait_closed(), TCP.TIMEOUT) + except Exception as e: + log.warning("Close exception: %s %s", type(e).__name__, e) async def send(self, data: bytes): async with self.lock: - self.writer.write(data) - await self.writer.drain() + try: + if self.writer is not None: + self.writer.write(data) + await self.writer.drain() + except Exception as e: + log.warning("Send exception: %s %s", type(e).__name__, e) async def recv(self, length: int = 0): data = b"" diff --git a/pyrogram/methods/auth/initialize.py b/pyrogram/methods/auth/initialize.py index 1e7915e00e..7188b66817 100644 --- a/pyrogram/methods/auth/initialize.py +++ b/pyrogram/methods/auth/initialize.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 pyrogram @@ -46,4 +47,6 @@ async def initialize( await self.dispatcher.start() + self.updates_watchdog_task = asyncio.create_task(self.updates_watchdog()) + self.is_initialized = True diff --git a/pyrogram/methods/auth/terminate.py b/pyrogram/methods/auth/terminate.py index 5ecb6758ef..70cfc80e65 100644 --- a/pyrogram/methods/auth/terminate.py +++ b/pyrogram/methods/auth/terminate.py @@ -51,4 +51,11 @@ async def terminate( self.media_sessions.clear() + self.updates_watchdog_event.set() + + if self.updates_watchdog_task is not None: + await self.updates_watchdog_task + + self.updates_watchdog_event.clear() + self.is_initialized = False diff --git a/pyrogram/session/auth.py b/pyrogram/session/auth.py index d51e18f843..c5d9cd9a50 100644 --- a/pyrogram/session/auth.py +++ b/pyrogram/session/auth.py @@ -278,4 +278,4 @@ async def create(self): else: return auth_key finally: - self.connection.close() + await self.connection.close() diff --git a/pyrogram/session/internals/msg_id.py b/pyrogram/session/internals/msg_id.py index 58e3087c51..da2e264ff6 100644 --- a/pyrogram/session/internals/msg_id.py +++ b/pyrogram/session/internals/msg_id.py @@ -27,9 +27,9 @@ class MsgId: offset = 0 def __new__(cls) -> int: - now = time.time() + now = int(time.time()) cls.offset = (cls.offset + 4) if now == cls.last_time else 0 - msg_id = int(now * 2 ** 32) + cls.offset + msg_id = (now * 2 ** 32) + cls.offset cls.last_time = now return msg_id diff --git a/pyrogram/session/internals/seq_no.py b/pyrogram/session/internals/seq_no.py index 0abc4a2f72..79501d9863 100644 --- a/pyrogram/session/internals/seq_no.py +++ b/pyrogram/session/internals/seq_no.py @@ -16,19 +16,15 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -from threading import Lock - class SeqNo: def __init__(self): self.content_related_messages_sent = 0 - self.lock = Lock() def __call__(self, is_content_related: bool) -> int: - with self.lock: - seq_no = (self.content_related_messages_sent * 2) + (1 if is_content_related else 0) + seq_no = (self.content_related_messages_sent * 2) + (1 if is_content_related else 0) - if is_content_related: - self.content_related_messages_sent += 1 + if is_content_related: + self.content_related_messages_sent += 1 - return seq_no + return seq_no diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index 5135af6987..df7ae6c483 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -44,11 +44,11 @@ def __init__(self): class Session: - START_TIMEOUT = 1 + START_TIMEOUT = 5 WAIT_TIMEOUT = 15 SLEEP_THRESHOLD = 10 - MAX_RETRIES = 5 - ACKS_THRESHOLD = 8 + MAX_RETRIES = 10 + ACKS_THRESHOLD = 10 PING_INTERVAL = 5 def __init__( @@ -156,14 +156,11 @@ async def stop(self): self.ping_task_event.clear() - self.connection.close() + await self.connection.close() if self.recv_task: await self.recv_task - for i in self.results.values(): - i.event.set() - if not self.is_media and callable(self.client.disconnect_handler): try: await self.client.disconnect_handler(self.client) @@ -189,6 +186,7 @@ async def handle_packet(self, packet): ) except SecurityCheckMismatch as e: log.warning("Discarding packet: %s", e) + await self.connection.close() return messages = ( @@ -284,9 +282,6 @@ async def send(self, data: TLObject, wait_response: bool = True, timeout: float message = self.msg_factory(data) msg_id = message.msg_id - if wait_response: - self.results[msg_id] = Result() - log.debug("Sent: %s", message) payload = await self.loop.run_in_executor( @@ -299,34 +294,35 @@ async def send(self, data: TLObject, wait_response: bool = True, timeout: float self.auth_key_id ) - try: - await self.connection.send(payload) - except OSError as e: - self.results.pop(msg_id, None) - raise e + await self.connection.send(payload) if wait_response: + self.results[msg_id] = Result() + try: await asyncio.wait_for(self.results[msg_id].event.wait(), timeout) except asyncio.TimeoutError: pass - finally: - result = self.results.pop(msg_id).value + + result = self.results.pop(msg_id).value if result is None: raise TimeoutError("Request timed out") - elif isinstance(result, raw.types.RpcError): + + if isinstance(result, raw.types.RpcError): if isinstance(data, (raw.functions.InvokeWithoutUpdates, raw.functions.InvokeWithTakeout)): data = data.query RPCError.raise_it(result, type(data)) - elif isinstance(result, raw.types.BadMsgNotification): + + if isinstance(result, raw.types.BadMsgNotification): raise BadMsgNotification(result.error_code) - elif isinstance(result, raw.types.BadServerSalt): + + if isinstance(result, raw.types.BadServerSalt): self.salt = result.new_server_salt return await self.send(data, wait_response, timeout) - else: - return result + + return result async def invoke( self, diff --git a/pyrogram/storage/file_storage.py b/pyrogram/storage/file_storage.py index 986787cd95..aebe917671 100644 --- a/pyrogram/storage/file_storage.py +++ b/pyrogram/storage/file_storage.py @@ -38,13 +38,13 @@ def update(self): version = self.version() if version == 1: - with self.lock, self.conn: + with self.conn: self.conn.execute("DELETE FROM peers") version += 1 if version == 2: - with self.lock, self.conn: + with self.conn: self.conn.execute("ALTER TABLE sessions ADD api_id INTEGER") version += 1 @@ -63,10 +63,7 @@ async def open(self): self.update() with self.conn: - try: # Python 3.6.0 (exactly this version) is bugged and won't successfully execute the vacuum - self.conn.execute("VACUUM") - except sqlite3.OperationalError: - pass + self.conn.execute("VACUUM") async def delete(self): os.remove(self.database) diff --git a/pyrogram/storage/sqlite_storage.py b/pyrogram/storage/sqlite_storage.py index 15e5ddc0c5..e28b9b746e 100644 --- a/pyrogram/storage/sqlite_storage.py +++ b/pyrogram/storage/sqlite_storage.py @@ -19,7 +19,6 @@ import inspect import sqlite3 import time -from threading import Lock from typing import List, Tuple, Any from pyrogram import raw @@ -98,10 +97,9 @@ def __init__(self, name: str): super().__init__(name) self.conn = None # type: sqlite3.Connection - self.lock = Lock() def create(self): - with self.lock, self.conn: + with self.conn: self.conn.executescript(SCHEMA) self.conn.execute( @@ -119,24 +117,20 @@ async def open(self): async def save(self): await self.date(int(time.time())) - - with self.lock: - self.conn.commit() + self.conn.commit() async def close(self): - with self.lock: - self.conn.close() + self.conn.close() async def delete(self): raise NotImplementedError async def update_peers(self, peers: List[Tuple[int, int, str, str, str]]): - with self.lock: - self.conn.executemany( - "REPLACE INTO peers (id, access_hash, type, username, phone_number)" - "VALUES (?, ?, ?, ?, ?)", - peers - ) + self.conn.executemany( + "REPLACE INTO peers (id, access_hash, type, username, phone_number)" + "VALUES (?, ?, ?, ?, ?)", + peers + ) async def get_peer_by_id(self, peer_id: int): r = self.conn.execute( @@ -185,7 +179,7 @@ def _get(self): def _set(self, value: Any): attr = inspect.stack()[2].function - with self.lock, self.conn: + with self.conn: self.conn.execute( f"UPDATE sessions SET {attr} = ?", (value,) @@ -221,7 +215,7 @@ def version(self, value: int = object): "SELECT number FROM version" ).fetchone()[0] else: - with self.lock, self.conn: + with self.conn: self.conn.execute( "UPDATE version SET number = ?", (value,) From dc3b8a5e01ff7a94243ef158b415d1dca5b15431 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 27 Dec 2022 14:55:07 +0100 Subject: [PATCH 1121/1185] Tweak file upload settings Multiple sessions as used in the current implementation were causing a variety of network related issues. Use one session only instead. Multiple workers within the same session are fine as long as they are not too many, otherwise the server will start replying with -429 (too many requests). Setting the queue size to 1 helps in having a more linear upload progress. --- pyrogram/methods/advanced/save_file.py | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/pyrogram/methods/advanced/save_file.py b/pyrogram/methods/advanced/save_file.py index b99a3c43b5..5ecac6d80e 100644 --- a/pyrogram/methods/advanced/save_file.py +++ b/pyrogram/methods/advanced/save_file.py @@ -134,23 +134,19 @@ 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, await self.storage.dc_id(), await self.storage.auth_key(), - await self.storage.test_mode(), is_media=True - ) for _ in range(pool_size) - ] - workers = [self.loop.create_task(worker(session)) for session in pool for _ in range(workers_count)] - queue = asyncio.Queue(16) + session = Session( + self, await self.storage.dc_id(), await self.storage.auth_key(), + await self.storage.test_mode(), is_media=True + ) + workers = [self.loop.create_task(worker(session)) for _ in range(workers_count)] + queue = asyncio.Queue(1) try: - for session in pool: - await session.start() + await session.start() fp.seek(part_size * file_part) @@ -223,8 +219,7 @@ async def worker(session): await asyncio.gather(*workers) - for session in pool: - await session.stop() + await session.stop() if isinstance(path, (str, PurePath)): fp.close() From 52effe19d58368bf0d8884272f07fe5d2148aab8 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 27 Dec 2022 14:56:34 +0100 Subject: [PATCH 1122/1185] Update Pyrogram to v2.0.84 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index a1accaf2ee..70f9bd931c 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.83" +__version__ = "2.0.84" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From cf06939a55f37ffa05b0bca1155981e1fa09f206 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 27 Dec 2022 22:23:05 +0100 Subject: [PATCH 1123/1185] Workaround proxy sockets not timing out properly --- pyrogram/connection/transport/tcp/tcp.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyrogram/connection/transport/tcp/tcp.py b/pyrogram/connection/transport/tcp/tcp.py index 6aff86af22..22b367ae45 100644 --- a/pyrogram/connection/transport/tcp/tcp.py +++ b/pyrogram/connection/transport/tcp/tcp.py @@ -20,6 +20,7 @@ import ipaddress import logging import socket + import socks log = logging.getLogger(__name__) @@ -58,6 +59,8 @@ def __init__(self, ipv6: bool, proxy: dict): password=proxy.get("password", None) ) + self.socket.settimeout(TCP.TIMEOUT) + log.info("Using proxy %s", hostname) else: self.socket = socket.socket( @@ -65,7 +68,7 @@ def __init__(self, ipv6: bool, proxy: dict): else socket.AF_INET ) - self.socket.setblocking(False) + self.socket.setblocking(False) async def connect(self, address: tuple): try: From 1fa6f3b924d95cc4a6a5b37bcebf924acd75ba0d Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Tue, 27 Dec 2022 22:24:21 +0100 Subject: [PATCH 1124/1185] Update Pyrogram to v2.0.85 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 70f9bd931c..9d4bd6f9e4 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.84" +__version__ = "2.0.85" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From 2dca5aeac20b2e3a6762c01e0b28a375ad97e6a6 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 28 Dec 2022 00:14:04 +0100 Subject: [PATCH 1125/1185] Handle proxy socket connections using thread executors --- pyrogram/connection/transport/tcp/tcp.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/pyrogram/connection/transport/tcp/tcp.py b/pyrogram/connection/transport/tcp/tcp.py index 22b367ae45..168f06a52e 100644 --- a/pyrogram/connection/transport/tcp/tcp.py +++ b/pyrogram/connection/transport/tcp/tcp.py @@ -20,6 +20,7 @@ import ipaddress import logging import socket +from concurrent.futures import ThreadPoolExecutor import socks @@ -38,6 +39,8 @@ def __init__(self, ipv6: bool, proxy: dict): self.lock = asyncio.Lock() self.loop = asyncio.get_event_loop() + self.proxy = proxy + if proxy: hostname = proxy.get("hostname") @@ -71,10 +74,14 @@ def __init__(self, ipv6: bool, proxy: dict): self.socket.setblocking(False) async def connect(self, address: tuple): - try: - await asyncio.wait_for(asyncio.get_event_loop().sock_connect(self.socket, address), TCP.TIMEOUT) - except asyncio.TimeoutError: # Re-raise as TimeoutError. asyncio.TimeoutError is deprecated in 3.11 - raise TimeoutError("Connection timed out") + if self.proxy: + with ThreadPoolExecutor(1) as executor: + await self.loop.run_in_executor(executor, self.socket.connect, address) + else: + try: + await asyncio.wait_for(asyncio.get_event_loop().sock_connect(self.socket, address), TCP.TIMEOUT) + except asyncio.TimeoutError: # Re-raise as TimeoutError. asyncio.TimeoutError is deprecated in 3.11 + raise TimeoutError("Connection timed out") self.reader, self.writer = await asyncio.open_connection(sock=self.socket) From 5ca422b31425da9c0d3cb070d1856496bd046170 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 28 Dec 2022 00:19:28 +0100 Subject: [PATCH 1126/1185] Create a future result before sending its request --- pyrogram/session/session.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index df7ae6c483..e64ea7930b 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -282,6 +282,9 @@ async def send(self, data: TLObject, wait_response: bool = True, timeout: float message = self.msg_factory(data) msg_id = message.msg_id + if wait_response: + self.results[msg_id] = Result() + log.debug("Sent: %s", message) payload = await self.loop.run_in_executor( @@ -297,8 +300,6 @@ async def send(self, data: TLObject, wait_response: bool = True, timeout: float await self.connection.send(payload) if wait_response: - self.results[msg_id] = Result() - try: await asyncio.wait_for(self.results[msg_id].event.wait(), timeout) except asyncio.TimeoutError: From 1daa05a35cd2c089c4aed946dddb7ae2bef92d3d Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 28 Dec 2022 00:21:05 +0100 Subject: [PATCH 1127/1185] Raise and handle send errors in order to immediately act upon --- pyrogram/connection/transport/tcp/tcp.py | 1 + pyrogram/session/session.py | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/pyrogram/connection/transport/tcp/tcp.py b/pyrogram/connection/transport/tcp/tcp.py index 168f06a52e..08a435cee9 100644 --- a/pyrogram/connection/transport/tcp/tcp.py +++ b/pyrogram/connection/transport/tcp/tcp.py @@ -101,6 +101,7 @@ async def send(self, data: bytes): await self.writer.drain() except Exception as e: log.warning("Send exception: %s %s", type(e).__name__, e) + raise OSError(e) async def recv(self, length: int = 0): data = b"" diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index e64ea7930b..6a7fc505be 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -297,7 +297,11 @@ async def send(self, data: TLObject, wait_response: bool = True, timeout: float self.auth_key_id ) - await self.connection.send(payload) + try: + await self.connection.send(payload) + except OSError as e: + self.results.pop(msg_id, None) + raise e if wait_response: try: From 82c81c10bdc9ca1bba13b7efcea39b5219334603 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 28 Dec 2022 00:22:19 +0100 Subject: [PATCH 1128/1185] Update Pyrogram to v2.0.86 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 9d4bd6f9e4..1a53bb76a0 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.85" +__version__ = "2.0.86" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From d890e5346cc9e6946b6e42addf1aad980e8369a9 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 28 Dec 2022 17:44:02 +0100 Subject: [PATCH 1129/1185] Clear stored_msg_ids on session stop --- pyrogram/session/session.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index 6a7fc505be..3ce96a8ff3 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -149,6 +149,8 @@ async def start(self): async def stop(self): self.is_started.clear() + self.stored_msg_ids.clear() + self.ping_task_event.set() if self.ping_task is not None: From 7ee47b220d103bf1725f69913654f73a9a57b8e9 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Wed, 28 Dec 2022 17:44:25 +0100 Subject: [PATCH 1130/1185] Update Pyrogram to v2.0.87 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 1a53bb76a0..4b55e4f15e 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.86" +__version__ = "2.0.87" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From cf1e31c413dc6b5ad03b4b78a73fc256b3e537c7 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 29 Dec 2022 23:33:58 +0100 Subject: [PATCH 1131/1185] Apply security checks to each message in the container --- pyrogram/crypto/mtproto.py | 30 +------------------- pyrogram/session/session.py | 55 ++++++++++++++++++++++++++----------- 2 files changed, 40 insertions(+), 45 deletions(-) diff --git a/pyrogram/crypto/mtproto.py b/pyrogram/crypto/mtproto.py index e147c22a3e..6d1521a47b 100644 --- a/pyrogram/crypto/mtproto.py +++ b/pyrogram/crypto/mtproto.py @@ -16,18 +16,13 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -import bisect from hashlib import sha256 from io import BytesIO from os import urandom -from typing import List from pyrogram.errors import SecurityCheckMismatch from pyrogram.raw.core import Message, Long from . import aes -from ..session.internals import MsgId - -STORED_MSG_IDS_MAX_SIZE = 1000 * 2 def kdf(auth_key: bytes, msg_key: bytes, outgoing: bool) -> tuple: @@ -59,8 +54,7 @@ def unpack( b: BytesIO, session_id: bytes, auth_key: bytes, - auth_key_id: bytes, - stored_msg_ids: List[int] + auth_key_id: bytes ) -> Message: SecurityCheckMismatch.check(b.read(8) == auth_key_id, "b.read(8) == auth_key_id") @@ -103,26 +97,4 @@ def unpack( # https://core.telegram.org/mtproto/security_guidelines#checking-msg-id SecurityCheckMismatch.check(message.msg_id % 2 != 0, "message.msg_id % 2 != 0") - if len(stored_msg_ids) > STORED_MSG_IDS_MAX_SIZE: - del stored_msg_ids[:STORED_MSG_IDS_MAX_SIZE // 2] - - if stored_msg_ids: - if message.msg_id < stored_msg_ids[0]: - raise SecurityCheckMismatch("The msg_id is lower than all the stored values") - - if message.msg_id in stored_msg_ids: - raise SecurityCheckMismatch("The msg_id is equal to any of the stored values") - - time_diff = (message.msg_id - MsgId()) / 2 ** 32 - - if time_diff > 30: - raise SecurityCheckMismatch("The msg_id belongs to over 30 seconds in the future. " - "Most likely the client time has to be synchronized.") - - if time_diff < -300: - raise SecurityCheckMismatch("The msg_id belongs to over 300 seconds in the past. " - "Most likely the client time has to be synchronized.") - - bisect.insort(stored_msg_ids, message.msg_id) - return message diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index 3ce96a8ff3..54814906fa 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -17,6 +17,7 @@ # along with Pyrogram. If not, see . import asyncio +import bisect import logging import os from hashlib import sha1 @@ -32,7 +33,7 @@ ) from pyrogram.raw.all import layer from pyrogram.raw.core import TLObject, MsgContainer, Int, FutureSalts -from .internals import MsgFactory +from .internals import MsgId, MsgFactory log = logging.getLogger(__name__) @@ -50,6 +51,7 @@ class Session: MAX_RETRIES = 10 ACKS_THRESHOLD = 10 PING_INTERVAL = 5 + STORED_MSG_IDS_MAX_SIZE = 1000 * 2 def __init__( self, @@ -176,20 +178,14 @@ async def restart(self): await self.start() async def handle_packet(self, packet): - try: - data = await self.loop.run_in_executor( - pyrogram.crypto_executor, - mtproto.unpack, - BytesIO(packet), - self.session_id, - self.auth_key, - self.auth_key_id, - self.stored_msg_ids - ) - except SecurityCheckMismatch as e: - log.warning("Discarding packet: %s", e) - await self.connection.close() - return + data = await self.loop.run_in_executor( + pyrogram.crypto_executor, + mtproto.unpack, + BytesIO(packet), + self.session_id, + self.auth_key, + self.auth_key_id + ) messages = ( data.body.messages @@ -206,6 +202,33 @@ async def handle_packet(self, packet): else: self.pending_acks.add(msg.msg_id) + try: + if len(self.stored_msg_ids) > Session.STORED_MSG_IDS_MAX_SIZE: + del self.stored_msg_ids[:Session.STORED_MSG_IDS_MAX_SIZE // 2] + + if self.stored_msg_ids: + if msg.msg_id < self.stored_msg_ids[0]: + raise SecurityCheckMismatch("The msg_id is lower than all the stored values") + + if msg.msg_id in self.stored_msg_ids: + raise SecurityCheckMismatch("The msg_id is equal to any of the stored values") + + time_diff = (msg.msg_id - MsgId()) / 2 ** 32 + + if time_diff > 30: + raise SecurityCheckMismatch("The msg_id belongs to over 30 seconds in the future. " + "Most likely the client time has to be synchronized.") + + if time_diff < -300: + raise SecurityCheckMismatch("The msg_id belongs to over 300 seconds in the past. " + "Most likely the client time has to be synchronized.") + except SecurityCheckMismatch as e: + log.warning("Discarding packet: %s", e) + await self.connection.close() + return + else: + bisect.insort(self.stored_msg_ids, msg.msg_id) + if isinstance(msg.body, (raw.types.MsgDetailedInfo, raw.types.MsgNewDetailedInfo)): self.pending_acks.add(msg.body.answer_msg_id) continue @@ -323,7 +346,7 @@ async def send(self, data: TLObject, wait_response: bool = True, timeout: float RPCError.raise_it(result, type(data)) if isinstance(result, raw.types.BadMsgNotification): - raise BadMsgNotification(result.error_code) + log.warning("%s: %s", BadMsgNotification.__name__, BadMsgNotification(result.error_code)) if isinstance(result, raw.types.BadServerSalt): self.salt = result.new_server_salt From fbf722d2653064afe3c751841e10dc5d5a830a1c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 29 Dec 2022 23:35:06 +0100 Subject: [PATCH 1132/1185] Update Pyrogram to v2.0.88 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 4b55e4f15e..35bf819bbd 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.87" +__version__ = "2.0.88" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From e0dda5ab26ed2952e0ba70ffb0b147adb86b1c3b Mon Sep 17 00:00:00 2001 From: Nick <64551534+null-nick@users.noreply.github.com> Date: Fri, 30 Dec 2022 14:10:37 +0100 Subject: [PATCH 1133/1185] Update API schema to Layer 151 (#1182) --- compiler/api/source/main_api.tl | 43 ++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl index e4f1ac0e9c..df85bdf82c 100644 --- a/compiler/api/source/main_api.tl +++ b/compiler/api/source/main_api.tl @@ -36,15 +36,15 @@ inputFile#f52ff27f id:long parts:int name:string md5_checksum:string = InputFile inputFileBig#fa4f0bb5 id:long parts:int name:string = InputFile; inputMediaEmpty#9664f57f = InputMedia; -inputMediaUploadedPhoto#1e287d04 flags:# file:InputFile stickers:flags.0?Vector ttl_seconds:flags.1?int = InputMedia; -inputMediaPhoto#b3ba0635 flags:# id:InputPhoto ttl_seconds:flags.0?int = InputMedia; +inputMediaUploadedPhoto#1e287d04 flags:# spoiler:flags.2?true file:InputFile stickers:flags.0?Vector ttl_seconds:flags.1?int = InputMedia; +inputMediaPhoto#b3ba0635 flags:# spoiler:flags.1?true id:InputPhoto ttl_seconds:flags.0?int = InputMedia; inputMediaGeoPoint#f9c44144 geo_point:InputGeoPoint = InputMedia; inputMediaContact#f8ab7dfb phone_number:string first_name:string last_name:string vcard:string = InputMedia; -inputMediaUploadedDocument#5b38c6c1 flags:# nosound_video:flags.3?true force_file:flags.4?true file:InputFile thumb:flags.2?InputFile mime_type:string attributes:Vector stickers:flags.0?Vector ttl_seconds:flags.1?int = InputMedia; -inputMediaDocument#33473058 flags:# id:InputDocument ttl_seconds:flags.0?int query:flags.1?string = InputMedia; +inputMediaUploadedDocument#5b38c6c1 flags:# nosound_video:flags.3?true force_file:flags.4?true spoiler:flags.5?true file:InputFile thumb:flags.2?InputFile mime_type:string attributes:Vector stickers:flags.0?Vector ttl_seconds:flags.1?int = InputMedia; +inputMediaDocument#33473058 flags:# spoiler:flags.2?true id:InputDocument ttl_seconds:flags.0?int query:flags.1?string = InputMedia; inputMediaVenue#c13d1c11 geo_point:InputGeoPoint title:string address:string provider:string venue_id:string venue_type:string = InputMedia; -inputMediaPhotoExternal#e5bbfe1a flags:# url:string ttl_seconds:flags.0?int = InputMedia; -inputMediaDocumentExternal#fb52dc99 flags:# url:string ttl_seconds:flags.0?int = InputMedia; +inputMediaPhotoExternal#e5bbfe1a flags:# spoiler:flags.1?true url:string ttl_seconds:flags.0?int = InputMedia; +inputMediaDocumentExternal#fb52dc99 flags:# spoiler:flags.1?true url:string ttl_seconds:flags.0?int = InputMedia; inputMediaGame#d33f43f3 id:InputGame = InputMedia; inputMediaInvoice#8eb5a6d5 flags:# title:string description:string photo:flags.0?InputWebDocument invoice:Invoice payload:bytes provider:string provider_data:DataJSON start_param:flags.1?string extended_media:flags.2?InputMedia = InputMedia; inputMediaGeoLive#971fa843 flags:# stopped:flags.0?true geo_point:InputGeoPoint heading:flags.2?int period:flags.1?int proximity_notification_radius:flags.3?int = InputMedia; @@ -91,7 +91,7 @@ userEmpty#d3bc4b7a id:long = User; user#8f97c628 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true apply_min_photo:flags.25?true fake:flags.26?true bot_attach_menu:flags.27?true premium:flags.28?true attach_menu_enabled:flags.29?true flags2:# id:long access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector bot_inline_placeholder:flags.19?string lang_code:flags.22?string emoji_status:flags.30?EmojiStatus usernames:flags2.0?Vector = User; userProfilePhotoEmpty#4f11bae1 = UserProfilePhoto; -userProfilePhoto#82d1f706 flags:# has_video:flags.0?true photo_id:long stripped_thumb:flags.1?bytes dc_id:int = UserProfilePhoto; +userProfilePhoto#82d1f706 flags:# has_video:flags.0?true personal:flags.2?true photo_id:long stripped_thumb:flags.1?bytes dc_id:int = UserProfilePhoto; userStatusEmpty#9d05049 = UserStatus; userStatusOnline#edb93949 expires:int = UserStatus; @@ -107,7 +107,7 @@ channel#83259464 flags:# creator:flags.0?true left:flags.2?true broadcast:flags. channelForbidden#17d493d5 flags:# broadcast:flags.5?true megagroup:flags.8?true id:long access_hash:long title:string until_date:flags.16?int = Chat; chatFull#c9d31138 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:long about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer theme_emoticon:flags.16?string requests_pending:flags.17?int recent_requesters:flags.17?Vector available_reactions:flags.18?ChatReactions = ChatFull; -channelFull#f2355507 flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true flags2:# can_delete_channel:flags2.0?true antispam:flags2.1?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector default_send_as:flags.29?Peer available_reactions:flags.30?ChatReactions = ChatFull; +channelFull#f2355507 flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true flags2:# can_delete_channel:flags2.0?true antispam:flags2.1?true participants_hidden:flags2.2?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector default_send_as:flags.29?Peer available_reactions:flags.30?ChatReactions = ChatFull; chatParticipant#c02d4007 user_id:long inviter_id:long date:int = ChatParticipant; chatParticipantCreator#e46bcee4 user_id:long = ChatParticipant; @@ -124,11 +124,11 @@ message#38116ee0 flags:# out:flags.1?true mentioned:flags.4?true media_unread:fl messageService#2b085862 flags:# out:flags.1?true mentioned:flags.4?true media_unread:flags.5?true silent:flags.13?true post:flags.14?true legacy:flags.19?true id:int from_id:flags.8?Peer peer_id:Peer reply_to:flags.3?MessageReplyHeader date:int action:MessageAction ttl_period:flags.25?int = Message; messageMediaEmpty#3ded6320 = MessageMedia; -messageMediaPhoto#695150d7 flags:# photo:flags.0?Photo ttl_seconds:flags.2?int = MessageMedia; +messageMediaPhoto#695150d7 flags:# spoiler:flags.3?true photo:flags.0?Photo ttl_seconds:flags.2?int = MessageMedia; messageMediaGeo#56e0d474 geo:GeoPoint = MessageMedia; messageMediaContact#70322949 phone_number:string first_name:string last_name:string vcard:string user_id:long = MessageMedia; messageMediaUnsupported#9f84f49e = MessageMedia; -messageMediaDocument#9cb070d7 flags:# nopremium:flags.3?true document:flags.0?Document ttl_seconds:flags.2?int = MessageMedia; +messageMediaDocument#9cb070d7 flags:# nopremium:flags.3?true spoiler:flags.4?true document:flags.0?Document ttl_seconds:flags.2?int = MessageMedia; messageMediaWebPage#a32dd600 webpage:WebPage = MessageMedia; messageMediaVenue#2ec0533f geo:GeoPoint title:string address:string provider:string venue_id:string venue_type:string = MessageMedia; messageMediaGame#fdb19008 game:Game = MessageMedia; @@ -172,6 +172,8 @@ messageActionWebViewDataSent#b4c38cb5 text:string = MessageAction; messageActionGiftPremium#aba0f5c6 currency:string amount:long months:int = MessageAction; messageActionTopicCreate#d999256 flags:# title:string icon_color:int icon_emoji_id:flags.0?long = MessageAction; messageActionTopicEdit#c0944820 flags:# title:flags.0?string icon_emoji_id:flags.1?long closed:flags.2?Bool hidden:flags.3?Bool = MessageAction; +messageActionSuggestProfilePhoto#57de635e photo:Photo = MessageAction; +messageActionAttachMenuBotAllowed#e7e75f97 = MessageAction; dialog#d58a08c6 flags:# pinned:flags.2?true unread_mark:flags.3?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int unread_reactions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int ttl_period:flags.5?int = Dialog; dialogFolder#71bd134c flags:# pinned:flags.2?true folder:Folder peer:Peer top_message:int unread_muted_peers_count:int unread_unmuted_peers_count:int unread_muted_messages_count:int unread_unmuted_messages_count:int = Dialog; @@ -222,7 +224,7 @@ inputReportReasonFake#f5ddd6e7 = ReportReason; inputReportReasonIllegalDrugs#a8eb2be = ReportReason; inputReportReasonPersonalDetails#9ec7863d = ReportReason; -userFull#c4b1fc3f flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true voice_messages_forbidden:flags.20?true id:long about:flags.1?string settings:PeerSettings profile_photo:flags.2?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int ttl_period:flags.14?int theme_emoticon:flags.15?string private_forward_name:flags.16?string bot_group_admin_rights:flags.17?ChatAdminRights bot_broadcast_admin_rights:flags.18?ChatAdminRights premium_gifts:flags.19?Vector = UserFull; +userFull#f8d32aed flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true voice_messages_forbidden:flags.20?true id:long about:flags.1?string settings:PeerSettings personal_photo:flags.21?Photo profile_photo:flags.2?Photo fallback_photo:flags.22?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int ttl_period:flags.14?int theme_emoticon:flags.15?string private_forward_name:flags.16?string bot_group_admin_rights:flags.17?ChatAdminRights bot_broadcast_admin_rights:flags.18?ChatAdminRights premium_gifts:flags.19?Vector = UserFull; contact#145ade0b user_id:long mutual:Bool = Contact; @@ -280,7 +282,6 @@ updateChatUserTyping#83487af0 chat_id:long from_id:Peer action:SendMessageAction updateChatParticipants#7761198 participants:ChatParticipants = Update; updateUserStatus#e5bdf8de user_id:long status:UserStatus = Update; updateUserName#a7848924 user_id:long first_name:string last_name:string usernames:Vector = Update; -updateUserPhoto#f227868c user_id:long date:int photo:UserProfilePhoto previous:Bool = Update; updateNewEncryptedMessage#12bcbd9a message:EncryptedMessage qts:int = Update; updateEncryptedChatTyping#1710f156 chat_id:int = Update; updateEncryption#b4a2e88d chat:EncryptedChat date:int = Update; @@ -381,6 +382,7 @@ updateMoveStickerSetToTop#86fccf85 flags:# masks:flags.0?true emojis:flags.1?tru updateMessageExtendedMedia#5a73a98c peer:Peer msg_id:int extended_media:MessageExtendedMedia = Update; updateChannelPinnedTopic#192efbe3 flags:# pinned:flags.0?true channel_id:long topic_id:int = Update; updateChannelPinnedTopics#fe198602 flags:# channel_id:long order:flags.0?Vector = Update; +updateUser#20529438 user_id:long = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -525,7 +527,7 @@ documentAttributeVideo#ef02ce6 flags:# round_message:flags.0?true supports_strea documentAttributeAudio#9852f9c6 flags:# voice:flags.10?true duration:int title:flags.0?string performer:flags.1?string waveform:flags.2?bytes = DocumentAttribute; documentAttributeFilename#15590068 file_name:string = DocumentAttribute; documentAttributeHasStickers#9801d2f7 = DocumentAttribute; -documentAttributeCustomEmoji#fd149899 flags:# free:flags.0?true alt:string stickerset:InputStickerSet = DocumentAttribute; +documentAttributeCustomEmoji#fd149899 flags:# free:flags.0?true text_color:flags.1?true alt:string stickerset:InputStickerSet = DocumentAttribute; messages.stickersNotModified#f1749a22 = messages.Stickers; messages.stickers#30a6ec7e hash:long stickers:Vector = messages.Stickers; @@ -603,7 +605,7 @@ keyboardButtonRow#77608b83 buttons:Vector = KeyboardButtonRow; replyKeyboardHide#a03e5b85 flags:# selective:flags.2?true = ReplyMarkup; replyKeyboardForceReply#86b40b08 flags:# single_use:flags.1?true selective:flags.2?true placeholder:flags.3?string = ReplyMarkup; -replyKeyboardMarkup#85dd99d1 flags:# resize:flags.0?true single_use:flags.1?true selective:flags.2?true rows:Vector placeholder:flags.3?string = ReplyMarkup; +replyKeyboardMarkup#85dd99d1 flags:# resize:flags.0?true single_use:flags.1?true selective:flags.2?true persistent:flags.4?true rows:Vector placeholder:flags.3?string = ReplyMarkup; replyInlineMarkup#48a30254 rows:Vector = ReplyMarkup; messageEntityUnknown#bb92ba95 offset:int length:int = MessageEntity; @@ -758,6 +760,7 @@ messages.stickerSetInstallResultArchive#35e410a8 sets:Vector stickerSetCovered#6410a5d2 set:StickerSet cover:Document = StickerSetCovered; stickerSetMultiCovered#3407e51b set:StickerSet covers:Vector = StickerSetCovered; stickerSetFullCovered#40d13c0e set:StickerSet packs:Vector keywords:Vector documents:Vector = StickerSetCovered; +stickerSetNoCovered#77b15d1c set:StickerSet = StickerSetCovered; maskCoords#aed6dbb2 n:int x:double y:double zoom:double = MaskCoords; @@ -1357,7 +1360,7 @@ attachMenuBotIconColor#4576f3f0 name:string color:int = AttachMenuBotIconColor; attachMenuBotIcon#b2a7386b flags:# name:string icon:Document colors:flags.0?Vector = AttachMenuBotIcon; -attachMenuBot#c8aa2cd2 flags:# inactive:flags.0?true has_settings:flags.1?true bot_id:long short_name:string peer_types:Vector icons:Vector = AttachMenuBot; +attachMenuBot#c8aa2cd2 flags:# inactive:flags.0?true has_settings:flags.1?true request_write_access:flags.2?true bot_id:long short_name:string peer_types:Vector icons:Vector = AttachMenuBot; attachMenuBotsNotModified#f1d88a5c = AttachMenuBots; attachMenuBots#3c4301c0 hash:long bots:Vector users:Vector = AttachMenuBots; @@ -1759,7 +1762,7 @@ messages.readReactions#54aa7f8e flags:# peer:InputPeer top_msg_id:flags.0?int = messages.searchSentMedia#107e31a0 q:string filter:MessagesFilter limit:int = messages.Messages; messages.getAttachMenuBots#16fcc2cb hash:long = AttachMenuBots; messages.getAttachMenuBot#77216192 bot:InputUser = AttachMenuBotsBot; -messages.toggleBotInAttachMenu#1aee33af bot:InputUser enabled:Bool = Bool; +messages.toggleBotInAttachMenu#69f59d69 flags:# write_allowed:flags.0?true bot:InputUser enabled:Bool = Bool; messages.requestWebView#178b480b flags:# from_bot_menu:flags.4?true silent:flags.5?true peer:InputPeer bot:InputUser url:flags.1?string start_param:flags.3?string theme_params:flags.2?DataJSON platform:string reply_to_msg_id:flags.0?int top_msg_id:flags.9?int send_as:flags.13?InputPeer = WebViewResult; messages.prolongWebView#7ff34309 flags:# silent:flags.5?true peer:InputPeer bot:InputUser query_id:long reply_to_msg_id:flags.0?int top_msg_id:flags.9?int send_as:flags.13?InputPeer = Bool; messages.requestSimpleWebView#299bec8e flags:# bot:InputUser url:string theme_params:flags.0?DataJSON platform:string = SimpleWebViewResult; @@ -1782,10 +1785,11 @@ updates.getState#edd4882a = updates.State; updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference; updates.getChannelDifference#3173d78 flags:# force:flags.0?true channel:InputChannel filter:ChannelMessagesFilter pts:int limit:int = updates.ChannelDifference; -photos.updateProfilePhoto#72d4742c id:InputPhoto = photos.Photo; -photos.uploadProfilePhoto#89f30f69 flags:# file:flags.0?InputFile video:flags.1?InputFile video_start_ts:flags.2?double = photos.Photo; +photos.updateProfilePhoto#1c3d5956 flags:# fallback:flags.0?true id:InputPhoto = photos.Photo; +photos.uploadProfilePhoto#89f30f69 flags:# fallback:flags.3?true file:flags.0?InputFile video:flags.1?InputFile video_start_ts:flags.2?double = photos.Photo; photos.deletePhotos#87cf7f2f id:Vector = Vector; photos.getUserPhotos#91cd32a8 user_id:InputUser offset:int max_id:long limit:int = photos.Photos; +photos.uploadContactProfilePhoto#b91a83bf flags:# suggest:flags.3?true save:flags.4?true user_id:InputUser file:flags.0?InputFile video:flags.1?InputFile video_start_ts:flags.2?double = photos.Photo; upload.saveFilePart#b304a621 file_id:long file_part:int bytes:bytes = Bool; upload.getFile#be5335be flags:# precise:flags.0?true cdn_supported:flags.1?true location:InputFileLocation offset:long limit:int = upload.File; @@ -1874,6 +1878,7 @@ channels.deleteTopicHistory#34435f2d channel:InputChannel top_msg_id:int = messa channels.reorderPinnedForumTopics#2950a18f flags:# force:flags.0?true channel:InputChannel order:Vector = Updates; channels.toggleAntiSpam#68f3e4eb channel:InputChannel enabled:Bool = Updates; channels.reportAntiSpamFalsePositive#a850a693 channel:InputChannel msg_id:int = Bool; +channels.toggleParticipantsHidden#6a6e7854 channel:InputChannel enabled:Bool = Updates; bots.sendCustomRequest#aa2769ed custom_method:string params:DataJSON = DataJSON; bots.answerWebhookJSONQuery#e6213f4d query_id:long data:DataJSON = Bool; @@ -1952,4 +1957,4 @@ stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats; -// LAYER 150 \ No newline at end of file +// LAYER 151 From ef29b3c519b8459ba207b603a528acd5590014c4 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 30 Dec 2022 15:06:51 +0100 Subject: [PATCH 1134/1185] Add the field has_media_spoiler to the class Message --- pyrogram/types/messages_and_media/message.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index ef249771fd..6397d6f43d 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -136,6 +136,9 @@ class Message(Object, Update): has_protected_content (``bool``, *optional*): True, if the message can't be forwarded. + has_media_spoiler (``bool``, *optional*): + True, if the message media is covered by a spoiler animation. + text (``str``, *optional*): For text messages, the actual UTF-8 text of the message, 0-4096 characters. If the message contains entities (bold, italic, ...) you can access *text.markdown* or @@ -332,6 +335,7 @@ def __init__( media_group_id: str = None, author_signature: str = None, has_protected_content: bool = None, + has_media_spoiler: bool = None, text: Str = None, entities: List["types.MessageEntity"] = None, caption_entities: List["types.MessageEntity"] = None, @@ -408,6 +412,7 @@ def __init__( self.media_group_id = media_group_id self.author_signature = author_signature self.has_protected_content = has_protected_content + self.has_media_spoiler = has_media_spoiler self.text = text self.entities = entities self.caption_entities = caption_entities @@ -777,6 +782,7 @@ async def _parse( ), author_signature=message.post_author, has_protected_content=message.noforwards, + has_media_spoiler=media and media.spoiler, forward_from=forward_from, forward_sender_name=forward_sender_name, forward_from_chat=forward_from_chat, From c707a4baaee5cccb79c618f715020983b966b8bf Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 30 Dec 2022 15:08:55 +0100 Subject: [PATCH 1135/1185] Add the parameter has_spoiler to relevant send_* media methods - send_photo() - send_video() - send_animation() --- pyrogram/methods/messages/send_animation.py | 9 ++++++++- pyrogram/methods/messages/send_photo.py | 13 ++++++++++--- pyrogram/methods/messages/send_video.py | 9 ++++++++- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/pyrogram/methods/messages/send_animation.py b/pyrogram/methods/messages/send_animation.py index ec85dc0569..bac16bac9f 100644 --- a/pyrogram/methods/messages/send_animation.py +++ b/pyrogram/methods/messages/send_animation.py @@ -39,6 +39,7 @@ async def send_animation( unsave: bool = False, parse_mode: Optional["enums.ParseMode"] = None, caption_entities: List["types.MessageEntity"] = None, + has_spoiler: bool = None, duration: int = 0, width: int = 0, height: int = 0, @@ -88,6 +89,9 @@ async def send_animation( caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): List of special entities that appear in the caption, which can be specified instead of *parse_mode*. + has_spoiler (``bool``, *optional*): + Pass True if the animation needs to be covered with a spoiler animation. + duration (``int``, *optional*): Duration of sent animation in seconds. @@ -180,6 +184,7 @@ async def progress(current, total): mime_type=self.guess_mime_type(animation) or "video/mp4", file=file, thumb=thumb, + spoiler=has_spoiler, attributes=[ raw.types.DocumentAttributeVideo( supports_streaming=True, @@ -193,7 +198,8 @@ async def progress(current, total): ) elif re.match("^https?://", animation): media = raw.types.InputMediaDocumentExternal( - url=animation + url=animation, + spoiler=has_spoiler ) else: media = utils.get_input_media_from_file_id(animation, FileType.ANIMATION) @@ -204,6 +210,7 @@ async def progress(current, total): mime_type=self.guess_mime_type(file_name or animation.name) or "video/mp4", file=file, thumb=thumb, + spoiler=has_spoiler, attributes=[ raw.types.DocumentAttributeVideo( supports_streaming=True, diff --git a/pyrogram/methods/messages/send_photo.py b/pyrogram/methods/messages/send_photo.py index 994f0c9322..61298a5c68 100644 --- a/pyrogram/methods/messages/send_photo.py +++ b/pyrogram/methods/messages/send_photo.py @@ -37,6 +37,7 @@ async def send_photo( caption: str = "", parse_mode: Optional["enums.ParseMode"] = None, caption_entities: List["types.MessageEntity"] = None, + has_spoiler: bool = None, ttl_seconds: int = None, disable_notification: bool = None, reply_to_message_id: int = None, @@ -78,6 +79,9 @@ async def send_photo( caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): List of special entities that appear in the caption, which can be specified instead of *parse_mode*. + has_spoiler (``bool``, *optional*): + Pass True if the photo needs to be covered with a spoiler animation. + ttl_seconds (``int``, *optional*): Self-Destruct Timer. If you set a timer, the photo will self-destruct in *ttl_seconds* @@ -149,12 +153,14 @@ async def send_photo( file = await self.save_file(photo, progress=progress, progress_args=progress_args) media = raw.types.InputMediaUploadedPhoto( file=file, - ttl_seconds=ttl_seconds + ttl_seconds=ttl_seconds, + spoiler=has_spoiler, ) elif re.match("^https?://", photo): media = raw.types.InputMediaPhotoExternal( url=photo, - ttl_seconds=ttl_seconds + ttl_seconds=ttl_seconds, + spoiler=has_spoiler ) else: media = utils.get_input_media_from_file_id(photo, FileType.PHOTO, ttl_seconds=ttl_seconds) @@ -162,7 +168,8 @@ async def send_photo( file = await self.save_file(photo, progress=progress, progress_args=progress_args) media = raw.types.InputMediaUploadedPhoto( file=file, - ttl_seconds=ttl_seconds + ttl_seconds=ttl_seconds, + spoiler=has_spoiler ) while True: diff --git a/pyrogram/methods/messages/send_video.py b/pyrogram/methods/messages/send_video.py index c20530641c..e869dd172d 100644 --- a/pyrogram/methods/messages/send_video.py +++ b/pyrogram/methods/messages/send_video.py @@ -38,6 +38,7 @@ async def send_video( caption: str = "", parse_mode: Optional["enums.ParseMode"] = None, caption_entities: List["types.MessageEntity"] = None, + has_spoiler: bool = None, ttl_seconds: int = None, duration: int = 0, width: int = 0, @@ -85,6 +86,9 @@ async def send_video( caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): List of special entities that appear in the caption, which can be specified instead of *parse_mode*. + has_spoiler (``bool``, *optional*): + Pass True if the video needs to be covered with a spoiler animation. + ttl_seconds (``int``, *optional*): Self-Destruct Timer. If you set a timer, the video will self-destruct in *ttl_seconds* @@ -185,6 +189,7 @@ async def progress(current, total): mime_type=self.guess_mime_type(video) or "video/mp4", file=file, ttl_seconds=ttl_seconds, + spoiler=has_spoiler, thumb=thumb, attributes=[ raw.types.DocumentAttributeVideo( @@ -199,7 +204,8 @@ async def progress(current, total): elif re.match("^https?://", video): media = raw.types.InputMediaDocumentExternal( url=video, - ttl_seconds=ttl_seconds + ttl_seconds=ttl_seconds, + spoiler=has_spoiler ) else: media = utils.get_input_media_from_file_id(video, FileType.VIDEO, ttl_seconds=ttl_seconds) @@ -210,6 +216,7 @@ async def progress(current, total): mime_type=self.guess_mime_type(file_name or video.name) or "video/mp4", file=file, ttl_seconds=ttl_seconds, + spoiler=has_spoiler, thumb=thumb, attributes=[ raw.types.DocumentAttributeVideo( From 06996d24ff7353c6a5a1118b273a48c6abe02c03 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 30 Dec 2022 15:09:21 +0100 Subject: [PATCH 1136/1185] Add media_spoiler filter --- pyrogram/filters.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/pyrogram/filters.py b/pyrogram/filters.py index ac2dbf20fd..b52dfe601d 100644 --- a/pyrogram/filters.py +++ b/pyrogram/filters.py @@ -425,6 +425,17 @@ async def dice_filter(_, __, m: Message): """Filter messages that contain :obj:`~pyrogram.types.Dice` objects.""" +# endregion + +# region media_spoiler +async def media_spoiler_filter(_, __, m: Message): + return bool(m.has_media_spoiler) + + +media_spoiler = create(media_spoiler_filter) +"""Filter media messages that contain a spoiler.""" + + # endregion # region private_filter @@ -731,6 +742,7 @@ async def linked_channel_filter(_, __, m: Message): linked_channel = create(linked_channel_filter) """Filter messages that are automatically forwarded from the linked channel to the group chat.""" + # endregion From a116ea42c8427ea48f1ed38856016bb943ca234f Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 30 Dec 2022 15:10:20 +0100 Subject: [PATCH 1137/1185] Add the field has_spoiler to relevant InputMedia* classes - InputMediaPhoto - InputMediaVideo - InputMediaAnimation --- pyrogram/types/input_media/input_media_animation.py | 7 ++++++- pyrogram/types/input_media/input_media_photo.py | 8 +++++++- pyrogram/types/input_media/input_media_video.py | 7 ++++++- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/pyrogram/types/input_media/input_media_animation.py b/pyrogram/types/input_media/input_media_animation.py index 2eae1a66f0..2e91a2147b 100644 --- a/pyrogram/types/input_media/input_media_animation.py +++ b/pyrogram/types/input_media/input_media_animation.py @@ -59,6 +59,9 @@ class InputMediaAnimation(InputMedia): duration (``int``, *optional*): Animation duration. + + has_spoiler (``bool``, *optional*): + Pass True if the photo needs to be covered with a spoiler animation. """ def __init__( @@ -70,7 +73,8 @@ def __init__( caption_entities: List[MessageEntity] = None, width: int = 0, height: int = 0, - duration: int = 0 + duration: int = 0, + has_spoiler: bool = None ): super().__init__(media, caption, parse_mode, caption_entities) @@ -78,3 +82,4 @@ def __init__( self.width = width self.height = height self.duration = duration + self.has_spoiler = has_spoiler diff --git a/pyrogram/types/input_media/input_media_photo.py b/pyrogram/types/input_media/input_media_photo.py index ce8b41a218..f4fd0e0305 100644 --- a/pyrogram/types/input_media/input_media_photo.py +++ b/pyrogram/types/input_media/input_media_photo.py @@ -45,6 +45,9 @@ class InputMediaPhoto(InputMedia): caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): List of special entities that appear in the caption, which can be specified instead of *parse_mode*. + + has_spoiler (``bool``, *optional*): + Pass True if the photo needs to be covered with a spoiler animation. """ def __init__( @@ -52,6 +55,9 @@ def __init__( media: Union[str, BinaryIO], caption: str = "", parse_mode: Optional["enums.ParseMode"] = None, - caption_entities: List[MessageEntity] = None + caption_entities: List[MessageEntity] = None, + has_spoiler: bool = None ): super().__init__(media, caption, parse_mode, caption_entities) + + self.has_spoiler = has_spoiler diff --git a/pyrogram/types/input_media/input_media_video.py b/pyrogram/types/input_media/input_media_video.py index c163cba9fb..ab1823d339 100644 --- a/pyrogram/types/input_media/input_media_video.py +++ b/pyrogram/types/input_media/input_media_video.py @@ -63,6 +63,9 @@ class InputMediaVideo(InputMedia): supports_streaming (``bool``, *optional*): Pass True, if the uploaded video is suitable for streaming. + + has_spoiler (``bool``, *optional*): + Pass True if the photo needs to be covered with a spoiler animation. """ def __init__( @@ -75,7 +78,8 @@ def __init__( width: int = 0, height: int = 0, duration: int = 0, - supports_streaming: bool = True + supports_streaming: bool = True, + has_spoiler: bool = None, ): super().__init__(media, caption, parse_mode, caption_entities) @@ -84,3 +88,4 @@ def __init__( self.height = height self.duration = duration self.supports_streaming = supports_streaming + self.has_spoiler = has_spoiler From 50d87bf5e9665bb91b7d3bf1b976ccec3959d9ca Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 30 Dec 2022 15:10:53 +0100 Subject: [PATCH 1138/1185] Add the field is_persistent to the class ReplyKeyboardMarkup --- .../types/bots_and_keyboards/reply_keyboard_markup.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pyrogram/types/bots_and_keyboards/reply_keyboard_markup.py b/pyrogram/types/bots_and_keyboards/reply_keyboard_markup.py index b62f6dcff7..2949c3e206 100644 --- a/pyrogram/types/bots_and_keyboards/reply_keyboard_markup.py +++ b/pyrogram/types/bots_and_keyboards/reply_keyboard_markup.py @@ -31,6 +31,10 @@ class ReplyKeyboardMarkup(Object): keyboard (List of List of :obj:`~pyrogram.types.KeyboardButton`): List of button rows, each represented by a List of KeyboardButton objects. + is_persistent (``bool``, *optional*): + Requests clients to always show the keyboard when the regular keyboard is hidden. + Defaults to false, in which case the custom keyboard can be hidden and opened with a keyboard icon. + resize_keyboard (``bool``, *optional*): Requests clients to resize the keyboard vertically for optimal fit (e.g., make the keyboard smaller if there are just two rows of buttons). Defaults to false, in which case the custom keyboard is always of @@ -55,6 +59,7 @@ class ReplyKeyboardMarkup(Object): def __init__( self, keyboard: List[List[Union["types.KeyboardButton", str]]], + is_persistent: bool = None, resize_keyboard: bool = None, one_time_keyboard: bool = None, selective: bool = None, @@ -63,13 +68,14 @@ def __init__( super().__init__() self.keyboard = keyboard + self.is_persistent = is_persistent self.resize_keyboard = resize_keyboard self.one_time_keyboard = one_time_keyboard self.selective = selective self.placeholder = placeholder @staticmethod - def read(kb): + def read(kb: "raw.base.ReplyMarkup"): keyboard = [] for i in kb.rows: @@ -82,6 +88,7 @@ def read(kb): return ReplyKeyboardMarkup( keyboard=keyboard, + is_persistent=kb.persistent, resize_keyboard=kb.resize, one_time_keyboard=kb.single_use, selective=kb.selective, @@ -100,5 +107,6 @@ async def write(self, _: "pyrogram.Client"): resize=self.resize_keyboard or None, single_use=self.one_time_keyboard or None, selective=self.selective or None, + persistent=self.is_persistent or None, placeholder=self.placeholder or None ) From e8bd6396344cb08310f32610150943f7c73a213f Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 30 Dec 2022 15:12:42 +0100 Subject: [PATCH 1139/1185] Add media spoiler support for other relevant methods - send_media_group() - edit_message_media() - edit_inline_media() --- .../methods/messages/edit_inline_media.py | 77 ++++++++++--------- .../methods/messages/edit_message_media.py | 49 +++++++----- pyrogram/methods/messages/send_media_group.py | 32 +++++--- 3 files changed, 91 insertions(+), 67 deletions(-) diff --git a/pyrogram/methods/messages/edit_inline_media.py b/pyrogram/methods/messages/edit_inline_media.py index 77fa673ae6..7ab424a4f2 100644 --- a/pyrogram/methods/messages/edit_inline_media.py +++ b/pyrogram/methods/messages/edit_inline_media.py @@ -16,11 +16,10 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -import os -import re -import io import asyncio import io +import os +import re import pyrogram from pyrogram import raw @@ -80,8 +79,6 @@ async def edit_inline_media( caption = media.caption parse_mode = media.parse_mode - is_photo = isinstance(media, types.InputMediaPhoto) - is_bytes_io = isinstance(media.media, io.BytesIO) is_uploaded_file = is_bytes_io or os.path.isfile(media.media) @@ -99,15 +96,16 @@ async def edit_inline_media( else: filename_attribute = [] - - if is_photo: + if isinstance(media, types.InputMediaPhoto): if is_uploaded_file: media = raw.types.InputMediaUploadedPhoto( - file=await self.save_file(media.media) + file=await self.save_file(media.media), + spoiler=media.has_spoiler ) elif is_external_url: media = raw.types.InputMediaPhotoExternal( - url=media.media + url=media.media, + spoiler=media.has_spoiler ) else: media = utils.get_input_media_from_file_id(media.media, FileType.PHOTO) @@ -117,18 +115,20 @@ async def edit_inline_media( mime_type=(None if is_bytes_io else self.guess_mime_type(media.media)) or "video/mp4", thumb=await self.save_file(media.thumb), file=await self.save_file(media.media), + spoiler=media.has_spoiler, attributes=[ - raw.types.DocumentAttributeVideo( - supports_streaming=media.supports_streaming or None, - duration=media.duration, - w=media.width, - h=media.height - ) - ] + filename_attribute + raw.types.DocumentAttributeVideo( + supports_streaming=media.supports_streaming or None, + duration=media.duration, + w=media.width, + h=media.height + ) + ] + filename_attribute ) elif is_external_url: media = raw.types.InputMediaDocumentExternal( - url=media.media + url=media.media, + spoiler=media.has_spoiler ) else: media = utils.get_input_media_from_file_id(media.media, FileType.VIDEO) @@ -139,12 +139,12 @@ async def edit_inline_media( thumb=await self.save_file(media.thumb), file=await self.save_file(media.media), attributes=[ - raw.types.DocumentAttributeAudio( - duration=media.duration, - performer=media.performer, - title=media.title - ) - ] + filename_attribute + raw.types.DocumentAttributeAudio( + duration=media.duration, + performer=media.performer, + title=media.title + ) + ] + filename_attribute ) elif is_external_url: media = raw.types.InputMediaDocumentExternal( @@ -158,20 +158,22 @@ async def edit_inline_media( mime_type=(None if is_bytes_io else self.guess_mime_type(media.media)) or "video/mp4", thumb=await self.save_file(media.thumb), file=await self.save_file(media.media), + spoiler=media.has_spoiler, attributes=[ - raw.types.DocumentAttributeVideo( - supports_streaming=True, - duration=media.duration, - w=media.width, - h=media.height - ), - raw.types.DocumentAttributeAnimated() - ] + filename_attribute, + raw.types.DocumentAttributeVideo( + supports_streaming=True, + duration=media.duration, + w=media.width, + h=media.height + ), + raw.types.DocumentAttributeAnimated() + ] + filename_attribute, nosound_video=True ) elif is_external_url: media = raw.types.InputMediaDocumentExternal( - url=media.media + url=media.media, + spoiler=media.has_spoiler ) else: media = utils.get_input_media_from_file_id(media.media, FileType.ANIMATION) @@ -196,7 +198,6 @@ async def edit_inline_media( session = await get_session(self, dc_id) - if is_uploaded_file: uploaded_media = await self.invoke( raw.functions.messages.UploadMedia( @@ -210,13 +211,15 @@ async def edit_inline_media( id=uploaded_media.photo.id, access_hash=uploaded_media.photo.access_hash, file_reference=uploaded_media.photo.file_reference - ) - ) if is_photo else raw.types.InputMediaDocument( + ), + spoiler=getattr(media, "has_spoiler", None) + ) if isinstance(media, types.InputMediaPhoto) else raw.types.InputMediaDocument( id=raw.types.InputDocument( id=uploaded_media.document.id, access_hash=uploaded_media.document.access_hash, - file_reference=uploaded_media.document.file_reference - ) + file_reference=uploaded_media.document.file_reference + ), + spoiler=getattr(media, "has_spoiler", None) ) else: actual_media = media diff --git a/pyrogram/methods/messages/edit_message_media.py b/pyrogram/methods/messages/edit_message_media.py index 16efb5b858..5a34f13875 100644 --- a/pyrogram/methods/messages/edit_message_media.py +++ b/pyrogram/methods/messages/edit_message_media.py @@ -16,9 +16,9 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . +import io import os import re -import io from typing import Union import pyrogram @@ -93,36 +93,40 @@ async def edit_message_media( if isinstance(media, types.InputMediaPhoto): if isinstance(media.media, io.BytesIO) or os.path.isfile(media.media): - media = await self.invoke( + uploaded_media = await self.invoke( raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), media=raw.types.InputMediaUploadedPhoto( - file=await self.save_file(media.media) + file=await self.save_file(media.media), + spoiler=media.has_spoiler ) ) ) media = raw.types.InputMediaPhoto( id=raw.types.InputPhoto( - id=media.photo.id, - access_hash=media.photo.access_hash, - file_reference=media.photo.file_reference - ) + id=uploaded_media.photo.id, + access_hash=uploaded_media.photo.access_hash, + file_reference=uploaded_media.photo.file_reference + ), + spoiler=media.has_spoiler ) elif re.match("^https?://", media.media): media = raw.types.InputMediaPhotoExternal( - url=media.media + url=media.media, + spoiler=media.has_spoiler ) else: media = utils.get_input_media_from_file_id(media.media, FileType.PHOTO) elif isinstance(media, types.InputMediaVideo): if isinstance(media.media, io.BytesIO) or os.path.isfile(media.media): - media = await self.invoke( + uploaded_media = await self.invoke( raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), media=raw.types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(media.media) or "video/mp4", thumb=await self.save_file(media.thumb), + spoiler=media.has_spoiler, file=await self.save_file(media.media), attributes=[ raw.types.DocumentAttributeVideo( @@ -141,14 +145,16 @@ async def edit_message_media( media = raw.types.InputMediaDocument( id=raw.types.InputDocument( - id=media.document.id, - access_hash=media.document.access_hash, - file_reference=media.document.file_reference - ) + id=uploaded_media.document.id, + access_hash=uploaded_media.document.access_hash, + file_reference=uploaded_media.document.file_reference + ), + spoiler=media.has_spoiler ) elif re.match("^https?://", media.media): media = raw.types.InputMediaDocumentExternal( - url=media.media + url=media.media, + spoiler=media.has_spoiler ) else: media = utils.get_input_media_from_file_id(media.media, FileType.VIDEO) @@ -190,12 +196,13 @@ async def edit_message_media( media = utils.get_input_media_from_file_id(media.media, FileType.AUDIO) elif isinstance(media, types.InputMediaAnimation): if isinstance(media.media, io.BytesIO) or os.path.isfile(media.media): - media = await self.invoke( + uploaded_media = await self.invoke( raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), media=raw.types.InputMediaUploadedDocument( mime_type=self.guess_mime_type(media.media) or "video/mp4", thumb=await self.save_file(media.thumb), + spoiler=media.has_spoiler, file=await self.save_file(media.media), attributes=[ raw.types.DocumentAttributeVideo( @@ -215,14 +222,16 @@ async def edit_message_media( media = raw.types.InputMediaDocument( id=raw.types.InputDocument( - id=media.document.id, - access_hash=media.document.access_hash, - file_reference=media.document.file_reference - ) + id=uploaded_media.document.id, + access_hash=uploaded_media.document.access_hash, + file_reference=uploaded_media.document.file_reference + ), + spoiler=media.has_spoiler ) elif re.match("^https?://", media.media): media = raw.types.InputMediaDocumentExternal( - url=media.media + url=media.media, + spoiler=media.has_spoiler ) else: media = utils.get_input_media_from_file_id(media.media, FileType.ANIMATION) diff --git a/pyrogram/methods/messages/send_media_group.py b/pyrogram/methods/messages/send_media_group.py index 0dfbbaa236..a8b905de23 100644 --- a/pyrogram/methods/messages/send_media_group.py +++ b/pyrogram/methods/messages/send_media_group.py @@ -100,7 +100,8 @@ async def send_media_group( raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), media=raw.types.InputMediaUploadedPhoto( - file=await self.save_file(i.media) + file=await self.save_file(i.media), + spoiler=i.has_spoiler ) ) ) @@ -110,14 +111,16 @@ async def send_media_group( id=media.photo.id, access_hash=media.photo.access_hash, file_reference=media.photo.file_reference - ) + ), + spoiler=i.has_spoiler ) elif re.match("^https?://", i.media): media = await self.invoke( raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), media=raw.types.InputMediaPhotoExternal( - url=i.media + url=i.media, + spoiler=i.has_spoiler ) ) ) @@ -127,7 +130,8 @@ async def send_media_group( id=media.photo.id, access_hash=media.photo.access_hash, file_reference=media.photo.file_reference - ) + ), + spoiler=i.has_spoiler ) else: media = utils.get_input_media_from_file_id(i.media, FileType.PHOTO) @@ -136,7 +140,8 @@ async def send_media_group( raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), media=raw.types.InputMediaUploadedPhoto( - file=await self.save_file(i.media) + file=await self.save_file(i.media), + spoiler=i.has_spoiler ) ) ) @@ -146,7 +151,8 @@ async def send_media_group( id=media.photo.id, access_hash=media.photo.access_hash, file_reference=media.photo.file_reference - ) + ), + spoiler=i.has_spoiler ) elif isinstance(i, types.InputMediaVideo): if isinstance(i.media, str): @@ -157,6 +163,7 @@ async def send_media_group( media=raw.types.InputMediaUploadedDocument( file=await self.save_file(i.media), thumb=await self.save_file(i.thumb), + spoiler=i.has_spoiler, mime_type=self.guess_mime_type(i.media) or "video/mp4", attributes=[ raw.types.DocumentAttributeVideo( @@ -176,14 +183,16 @@ async def send_media_group( id=media.document.id, access_hash=media.document.access_hash, file_reference=media.document.file_reference - ) + ), + spoiler=i.has_spoiler ) elif re.match("^https?://", i.media): media = await self.invoke( raw.functions.messages.UploadMedia( peer=await self.resolve_peer(chat_id), media=raw.types.InputMediaDocumentExternal( - url=i.media + url=i.media, + spoiler=i.has_spoiler ) ) ) @@ -193,7 +202,8 @@ async def send_media_group( id=media.document.id, access_hash=media.document.access_hash, file_reference=media.document.file_reference - ) + ), + spoiler=i.has_spoiler ) else: media = utils.get_input_media_from_file_id(i.media, FileType.VIDEO) @@ -204,6 +214,7 @@ async def send_media_group( media=raw.types.InputMediaUploadedDocument( file=await self.save_file(i.media), thumb=await self.save_file(i.thumb), + spoiler=i.has_spoiler, mime_type=self.guess_mime_type(getattr(i.media, "name", "video.mp4")) or "video/mp4", attributes=[ raw.types.DocumentAttributeVideo( @@ -223,7 +234,8 @@ async def send_media_group( id=media.document.id, access_hash=media.document.access_hash, file_reference=media.document.file_reference - ) + ), + spoiler=i.has_spoiler ) elif isinstance(i, types.InputMediaAudio): if isinstance(i.media, str): From 5d3abd3ab03e8b64ff143028ecbeb224f676512a Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 30 Dec 2022 15:14:42 +0100 Subject: [PATCH 1140/1185] Update Pyrogram to v2.0.89 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 35bf819bbd..0ef8af6d1a 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.88" +__version__ = "2.0.89" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From 1e6209da3be0cee0306a53dcc1a22e115eea0a27 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 30 Dec 2022 15:55:52 +0100 Subject: [PATCH 1141/1185] Add support for email sign in codes Fixes #1183 --- pyrogram/client.py | 1 + pyrogram/enums/sent_code_type.py | 3 +++ 2 files changed, 4 insertions(+) diff --git a/pyrogram/client.py b/pyrogram/client.py index 36ab4e4cf7..f4f6eee3ee 100644 --- a/pyrogram/client.py +++ b/pyrogram/client.py @@ -358,6 +358,7 @@ async def authorize(self) -> User: enums.SentCodeType.CALL: "phone call", enums.SentCodeType.FLASH_CALL: "phone flash call", enums.SentCodeType.FRAGMENT_SMS: "Fragment SMS", + enums.SentCodeType.EMAIL_CODE: "email code" } print(f"The confirmation code has been sent via {sent_code_descriptions[sent_code.type]}") diff --git a/pyrogram/enums/sent_code_type.py b/pyrogram/enums/sent_code_type.py index e3ec61120a..474ed6b0f3 100644 --- a/pyrogram/enums/sent_code_type.py +++ b/pyrogram/enums/sent_code_type.py @@ -40,3 +40,6 @@ class SentCodeType(AutoName): FRAGMENT_SMS = raw.types.auth.SentCodeTypeFragmentSms "The code was sent via Fragment SMS." + + EMAIL_CODE = raw.types.auth.SentCodeTypeEmailCode + "The code was sent via email." From 3b0dee7dd5409614c32b3572423aef9172a73d54 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 30 Dec 2022 15:56:19 +0100 Subject: [PATCH 1142/1185] Update Pyrogram to v2.0.90 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 0ef8af6d1a..0b126c94ab 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.89" +__version__ = "2.0.90" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From 2de8f1921cd68a88d5b3a6755c10baf85d1685fe Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 30 Dec 2022 18:07:15 +0100 Subject: [PATCH 1143/1185] Fix resolving peers for users with multiple usernames This currently makes it work for the first available username only --- pyrogram/client.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pyrogram/client.py b/pyrogram/client.py index f4f6eee3ee..7848c1f5b9 100644 --- a/pyrogram/client.py +++ b/pyrogram/client.py @@ -487,7 +487,11 @@ async def fetch_peers(self, peers: List[Union[raw.types.User, raw.types.Chat, ra if isinstance(peer, raw.types.User): peer_id = peer.id access_hash = peer.access_hash - username = (peer.username or "").lower() or None + username = ( + peer.username.lower() if peer.username + else peer.usernames[0].username.lower() if peer.usernames + else None + ) phone_number = peer.phone peer_type = "bot" if peer.bot else "user" elif isinstance(peer, (raw.types.Chat, raw.types.ChatForbidden)): From fbd62f959658a7da6ce76e589ca9419f02702a77 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 30 Dec 2022 18:07:55 +0100 Subject: [PATCH 1144/1185] Update Pyrogram to v2.0.91 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 0b126c94ab..be51f7bb0a 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.90" +__version__ = "2.0.91" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From a09c5a3b987be316a6deacc97699fc2ae9515f2a Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 30 Dec 2022 20:16:25 +0100 Subject: [PATCH 1145/1185] Set has_media_spoiler only in case of Photo, Video or Animation media --- pyrogram/types/messages_and_media/message.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index 6397d6f43d..1835f63225 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -663,11 +663,13 @@ async def _parse( media = message.media media_type = None + has_media_spoiler = None if media: if isinstance(media, raw.types.MessageMediaPhoto): photo = types.Photo._parse(client, media.photo, media.ttl_seconds) media_type = enums.MessageMediaType.PHOTO + has_media_spoiler = media.spoiler elif isinstance(media, raw.types.MessageMediaGeo): location = types.Location._parse(client, media.geo) media_type = enums.MessageMediaType.LOCATION @@ -696,6 +698,7 @@ async def _parse( video_attributes = attributes.get(raw.types.DocumentAttributeVideo, None) animation = types.Animation._parse(client, doc, video_attributes, file_name) media_type = enums.MessageMediaType.ANIMATION + has_media_spoiler = media.spoiler elif raw.types.DocumentAttributeSticker in attributes: sticker = await types.Sticker._parse(client, doc, attributes) media_type = enums.MessageMediaType.STICKER @@ -708,6 +711,7 @@ async def _parse( else: video = types.Video._parse(client, doc, video_attributes, file_name, media.ttl_seconds) media_type = enums.MessageMediaType.VIDEO + has_media_spoiler = media.spoiler elif raw.types.DocumentAttributeAudio in attributes: audio_attributes = attributes[raw.types.DocumentAttributeAudio] @@ -782,7 +786,7 @@ async def _parse( ), author_signature=message.post_author, has_protected_content=message.noforwards, - has_media_spoiler=media and media.spoiler, + has_media_spoiler=has_media_spoiler, forward_from=forward_from, forward_sender_name=forward_sender_name, forward_from_chat=forward_from_chat, From 526aaa0f9dc2918773796058557aa041185b0ac1 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 30 Dec 2022 20:18:23 +0100 Subject: [PATCH 1146/1185] Update Pyrogram to v2.0.92 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index be51f7bb0a..d5497ab586 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.91" +__version__ = "2.0.92" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From 6752af8796041b1a8f0810fd7ea5ac5dee807149 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 31 Dec 2022 19:01:42 +0100 Subject: [PATCH 1147/1185] Add error messages for transport errors --- pyrogram/session/session.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index 54814906fa..0dd3430539 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -53,6 +53,12 @@ class Session: PING_INTERVAL = 5 STORED_MSG_IDS_MAX_SIZE = 1000 * 2 + TRANSPORT_ERRORS = { + 404: "auth key not found", + 429: "transport flood", + 444: "invalid DC" + } + def __init__( self, client: "pyrogram.Client", @@ -292,7 +298,12 @@ async def recv_worker(self): if packet is None or len(packet) == 4: if packet: - log.warning('Server sent "%s"', Int.read(BytesIO(packet))) + error_code = -Int.read(BytesIO(packet)) + + log.warning( + "Server sent transport error: %s (%s)", + error_code, Session.TRANSPORT_ERRORS.get(error_code, "unknown error") + ) if self.is_started.is_set(): self.loop.create_task(self.restart()) From b19764d5dc9e2d59a4ccbb7f520f78505800656b Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 31 Dec 2022 19:02:20 +0100 Subject: [PATCH 1148/1185] Update Pyrogram to v2.0.93 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index d5497ab586..5b8f537c1d 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.92" +__version__ = "2.0.93" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From 8441ce2f4790f81655fe48f6fda57dad432ee798 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 8 Jan 2023 17:11:02 +0100 Subject: [PATCH 1149/1185] Limit the amount of concurrent transmissions --- pyrogram/client.py | 356 ++++++++++++------------- pyrogram/methods/advanced/save_file.py | 227 ++++++++-------- 2 files changed, 285 insertions(+), 298 deletions(-) diff --git a/pyrogram/client.py b/pyrogram/client.py index 7848c1f5b9..81828f5a6d 100644 --- a/pyrogram/client.py +++ b/pyrogram/client.py @@ -44,7 +44,7 @@ from pyrogram.errors import ( SessionPasswordNeeded, VolumeLocNotFound, ChannelPrivate, - AuthBytesInvalid, BadRequest + BadRequest ) from pyrogram.handlers.handler import Handler from pyrogram.methods import Methods @@ -266,6 +266,9 @@ def __init__( self.media_sessions = {} self.media_sessions_lock = asyncio.Lock() + self.save_file_lock = asyncio.Lock() + self.get_file_lock = asyncio.Lock() + self.is_connected = None self.is_initialized = None @@ -795,204 +798,93 @@ async def get_file( progress: Callable = None, progress_args: tuple = () ) -> Optional[AsyncGenerator[bytes, None]]: - dc_id = file_id.dc_id - - async with self.media_sessions_lock: - session = self.media_sessions.get(dc_id, None) + async with self.get_file_lock: + file_type = file_id.file_type - if session is None: - if dc_id != await self.storage.dc_id(): - session = Session( - self, dc_id, await Auth(self, dc_id, await self.storage.test_mode()).create(), - await self.storage.test_mode(), is_media=True + if file_type == FileType.CHAT_PHOTO: + if file_id.chat_id > 0: + peer = raw.types.InputPeerUser( + user_id=file_id.chat_id, + access_hash=file_id.chat_access_hash ) - await session.start() - - for _ in range(3): - exported_auth = await self.invoke( - raw.functions.auth.ExportAuthorization( - dc_id=dc_id - ) + else: + if file_id.chat_access_hash == 0: + peer = raw.types.InputPeerChat( + chat_id=-file_id.chat_id ) - - try: - await session.invoke( - raw.functions.auth.ImportAuthorization( - id=exported_auth.id, - bytes=exported_auth.bytes - ) - ) - except AuthBytesInvalid: - continue - else: - break else: - await session.stop() - raise AuthBytesInvalid - else: - session = Session( - self, dc_id, await self.storage.auth_key(), - await self.storage.test_mode(), is_media=True - ) - await session.start() - - self.media_sessions[dc_id] = session - - file_type = file_id.file_type + peer = raw.types.InputPeerChannel( + channel_id=utils.get_channel_id(file_id.chat_id), + access_hash=file_id.chat_access_hash + ) - if file_type == FileType.CHAT_PHOTO: - if file_id.chat_id > 0: - peer = raw.types.InputPeerUser( - user_id=file_id.chat_id, - access_hash=file_id.chat_access_hash + location = raw.types.InputPeerPhotoFileLocation( + peer=peer, + photo_id=file_id.media_id, + big=file_id.thumbnail_source == ThumbnailSource.CHAT_PHOTO_BIG + ) + elif file_type == FileType.PHOTO: + location = raw.types.InputPhotoFileLocation( + id=file_id.media_id, + access_hash=file_id.access_hash, + file_reference=file_id.file_reference, + thumb_size=file_id.thumbnail_size ) else: - if file_id.chat_access_hash == 0: - peer = raw.types.InputPeerChat( - chat_id=-file_id.chat_id - ) - else: - peer = raw.types.InputPeerChannel( - channel_id=utils.get_channel_id(file_id.chat_id), - access_hash=file_id.chat_access_hash - ) + location = raw.types.InputDocumentFileLocation( + id=file_id.media_id, + access_hash=file_id.access_hash, + file_reference=file_id.file_reference, + thumb_size=file_id.thumbnail_size + ) - location = raw.types.InputPeerPhotoFileLocation( - peer=peer, - photo_id=file_id.media_id, - big=file_id.thumbnail_source == ThumbnailSource.CHAT_PHOTO_BIG - ) - elif file_type == FileType.PHOTO: - location = raw.types.InputPhotoFileLocation( - id=file_id.media_id, - access_hash=file_id.access_hash, - file_reference=file_id.file_reference, - thumb_size=file_id.thumbnail_size - ) - else: - location = raw.types.InputDocumentFileLocation( - id=file_id.media_id, - access_hash=file_id.access_hash, - file_reference=file_id.file_reference, - thumb_size=file_id.thumbnail_size - ) + current = 0 + total = abs(limit) or (1 << 31) - 1 + chunk_size = 1024 * 1024 + offset_bytes = abs(offset) * chunk_size - current = 0 - total = abs(limit) or (1 << 31) - 1 - chunk_size = 1024 * 1024 - offset_bytes = abs(offset) * chunk_size + dc_id = file_id.dc_id - try: - r = await session.invoke( - raw.functions.upload.GetFile( - location=location, - offset=offset_bytes, - limit=chunk_size - ), - sleep_threshold=30 + session = Session( + self, dc_id, + await Auth(self, dc_id, await self.storage.test_mode()).create() + if dc_id != await self.storage.dc_id() + else await self.storage.auth_key(), + await self.storage.test_mode(), + is_media=True ) - if isinstance(r, raw.types.upload.File): - while True: - chunk = r.bytes - - yield chunk - - current += 1 - offset_bytes += chunk_size + try: + await session.start() - if progress: - func = functools.partial( - progress, - min(offset_bytes, file_size) - if file_size != 0 - else offset_bytes, - file_size, - *progress_args + if dc_id != await self.storage.dc_id(): + exported_auth = await self.invoke( + raw.functions.auth.ExportAuthorization( + dc_id=dc_id ) - - if inspect.iscoroutinefunction(progress): - await func() - else: - await self.loop.run_in_executor(self.executor, func) - - if len(chunk) < chunk_size or current >= total: - break - - r = await session.invoke( - raw.functions.upload.GetFile( - location=location, - offset=offset_bytes, - limit=chunk_size - ), - sleep_threshold=30 ) - elif isinstance(r, raw.types.upload.FileCdnRedirect): - async with self.media_sessions_lock: - cdn_session = self.media_sessions.get(r.dc_id, None) - - if cdn_session is None: - cdn_session = Session( - self, r.dc_id, await Auth(self, r.dc_id, await self.storage.test_mode()).create(), - await self.storage.test_mode(), is_media=True, is_cdn=True + await session.invoke( + raw.functions.auth.ImportAuthorization( + id=exported_auth.id, + bytes=exported_auth.bytes ) + ) - await cdn_session.start() - - self.media_sessions[r.dc_id] = cdn_session + r = await session.invoke( + raw.functions.upload.GetFile( + location=location, + offset=offset_bytes, + limit=chunk_size + ), + sleep_threshold=30 + ) - try: + if isinstance(r, raw.types.upload.File): while True: - r2 = await cdn_session.invoke( - raw.functions.upload.GetCdnFile( - file_token=r.file_token, - offset=offset_bytes, - limit=chunk_size - ) - ) - - if isinstance(r2, raw.types.upload.CdnFileReuploadNeeded): - try: - await session.invoke( - raw.functions.upload.ReuploadCdnFile( - file_token=r.file_token, - request_token=r2.request_token - ) - ) - except VolumeLocNotFound: - break - else: - continue - - chunk = r2.bytes + chunk = r.bytes - # https://core.telegram.org/cdn#decrypting-files - decrypted_chunk = aes.ctr256_decrypt( - chunk, - r.encryption_key, - bytearray( - r.encryption_iv[:-4] - + (offset_bytes // 16).to_bytes(4, "big") - ) - ) - - hashes = await session.invoke( - raw.functions.upload.GetCdnFileHashes( - file_token=r.file_token, - offset=offset_bytes - ) - ) - - # https://core.telegram.org/cdn#verifying-files - for i, h in enumerate(hashes): - cdn_chunk = decrypted_chunk[h.limit * i: h.limit * (i + 1)] - CDNFileHashMismatch.check( - h.hash == sha256(cdn_chunk).digest(), - "h.hash == sha256(cdn_chunk).digest()" - ) - - yield decrypted_chunk + yield chunk current += 1 offset_bytes += chunk_size @@ -1000,7 +892,9 @@ async def get_file( if progress: func = functools.partial( progress, - min(offset_bytes, file_size) if file_size != 0 else offset_bytes, + min(offset_bytes, file_size) + if file_size != 0 + else offset_bytes, file_size, *progress_args ) @@ -1012,12 +906,104 @@ async def get_file( if len(chunk) < chunk_size or current >= total: break - except Exception as e: - raise e - except pyrogram.StopTransmission: - raise - except Exception as e: - log.exception(e) + + r = await session.invoke( + raw.functions.upload.GetFile( + location=location, + offset=offset_bytes, + limit=chunk_size + ), + sleep_threshold=30 + ) + + elif isinstance(r, raw.types.upload.FileCdnRedirect): + cdn_session = Session( + self, r.dc_id, await Auth(self, r.dc_id, await self.storage.test_mode()).create(), + await self.storage.test_mode(), is_media=True, is_cdn=True + ) + + try: + await cdn_session.start() + + while True: + r2 = await cdn_session.invoke( + raw.functions.upload.GetCdnFile( + file_token=r.file_token, + offset=offset_bytes, + limit=chunk_size + ) + ) + + if isinstance(r2, raw.types.upload.CdnFileReuploadNeeded): + try: + await session.invoke( + raw.functions.upload.ReuploadCdnFile( + file_token=r.file_token, + request_token=r2.request_token + ) + ) + except VolumeLocNotFound: + break + else: + continue + + chunk = r2.bytes + + # https://core.telegram.org/cdn#decrypting-files + decrypted_chunk = aes.ctr256_decrypt( + chunk, + r.encryption_key, + bytearray( + r.encryption_iv[:-4] + + (offset_bytes // 16).to_bytes(4, "big") + ) + ) + + hashes = await session.invoke( + raw.functions.upload.GetCdnFileHashes( + file_token=r.file_token, + offset=offset_bytes + ) + ) + + # https://core.telegram.org/cdn#verifying-files + for i, h in enumerate(hashes): + cdn_chunk = decrypted_chunk[h.limit * i: h.limit * (i + 1)] + CDNFileHashMismatch.check( + h.hash == sha256(cdn_chunk).digest(), + "h.hash == sha256(cdn_chunk).digest()" + ) + + yield decrypted_chunk + + current += 1 + offset_bytes += chunk_size + + if progress: + func = functools.partial( + progress, + min(offset_bytes, file_size) if file_size != 0 else offset_bytes, + file_size, + *progress_args + ) + + if inspect.iscoroutinefunction(progress): + await func() + else: + await self.loop.run_in_executor(self.executor, func) + + if len(chunk) < chunk_size or current >= total: + break + except Exception as e: + raise e + finally: + await cdn_session.stop() + except pyrogram.StopTransmission: + raise + except Exception as e: + log.exception(e) + finally: + await session.stop() def guess_mime_type(self, filename: str) -> Optional[str]: return self.mimetypes.guess_type(filename)[0] diff --git a/pyrogram/methods/advanced/save_file.py b/pyrogram/methods/advanced/save_file.py index 5ecac6d80e..e683fe52d9 100644 --- a/pyrogram/methods/advanced/save_file.py +++ b/pyrogram/methods/advanced/save_file.py @@ -94,132 +94,133 @@ async def save_file( Raises: RPCError: In case of a Telegram RPC error. """ - if path is None: - return None + async with self.save_file_lock: + if path is None: + return None - async def worker(session): - while True: - data = await queue.get() + async def worker(session): + while True: + data = await queue.get() - if data is None: - return + if data is None: + return - try: - await session.invoke(data) - except Exception as e: - log.exception(e) + try: + await session.invoke(data) + except Exception as e: + log.exception(e) - part_size = 512 * 1024 + part_size = 512 * 1024 - if isinstance(path, (str, PurePath)): - fp = open(path, "rb") - elif isinstance(path, io.IOBase): - fp = path - else: - raise ValueError("Invalid file. Expected a file path as string or a binary (not text) file pointer") - - file_name = getattr(fp, "name", "file.jpg") - - fp.seek(0, os.SEEK_END) - file_size = fp.tell() - fp.seek(0) - - if file_size == 0: - raise ValueError("File size equals to 0 B") - - file_size_limit_mib = 4000 if self.me.is_premium else 2000 - - if file_size > file_size_limit_mib * 1024 * 1024: - raise ValueError(f"Can't upload files bigger than {file_size_limit_mib} MiB") - - file_total_parts = int(math.ceil(file_size / part_size)) - is_big = file_size > 10 * 1024 * 1024 - 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 - session = Session( - self, await self.storage.dc_id(), await self.storage.auth_key(), - await self.storage.test_mode(), is_media=True - ) - workers = [self.loop.create_task(worker(session)) for _ in range(workers_count)] - queue = asyncio.Queue(1) - - try: - await session.start() + if isinstance(path, (str, PurePath)): + fp = open(path, "rb") + elif isinstance(path, io.IOBase): + fp = path + else: + raise ValueError("Invalid file. Expected a file path as string or a binary (not text) file pointer") + + file_name = getattr(fp, "name", "file.jpg") + + fp.seek(0, os.SEEK_END) + file_size = fp.tell() + fp.seek(0) + + if file_size == 0: + raise ValueError("File size equals to 0 B") + + file_size_limit_mib = 4000 if self.me.is_premium else 2000 + + if file_size > file_size_limit_mib * 1024 * 1024: + raise ValueError(f"Can't upload files bigger than {file_size_limit_mib} MiB") + + file_total_parts = int(math.ceil(file_size / part_size)) + is_big = file_size > 10 * 1024 * 1024 + 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 + session = Session( + self, await self.storage.dc_id(), await self.storage.auth_key(), + await self.storage.test_mode(), is_media=True + ) + workers = [self.loop.create_task(worker(session)) for _ in range(workers_count)] + queue = asyncio.Queue(1) + + try: + await session.start() + + fp.seek(part_size * file_part) + + while True: + chunk = fp.read(part_size) + + if not chunk: + if not is_big and not is_missing_part: + md5_sum = "".join([hex(i)[2:].zfill(2) for i in md5_sum.digest()]) + break + + if is_big: + rpc = raw.functions.upload.SaveBigFilePart( + file_id=file_id, + file_part=file_part, + file_total_parts=file_total_parts, + bytes=chunk + ) + else: + rpc = raw.functions.upload.SaveFilePart( + file_id=file_id, + file_part=file_part, + bytes=chunk + ) - fp.seek(part_size * file_part) + await queue.put(rpc) - while True: - chunk = fp.read(part_size) + if is_missing_part: + return - if not chunk: if not is_big and not is_missing_part: - md5_sum = "".join([hex(i)[2:].zfill(2) for i in md5_sum.digest()]) - break - + md5_sum.update(chunk) + + file_part += 1 + + if progress: + func = functools.partial( + progress, + min(file_part * part_size, file_size), + file_size, + *progress_args + ) + + if inspect.iscoroutinefunction(progress): + await func() + else: + await self.loop.run_in_executor(self.executor, func) + except StopTransmission: + raise + except Exception as e: + log.exception(e) + else: if is_big: - rpc = raw.functions.upload.SaveBigFilePart( - file_id=file_id, - file_part=file_part, - file_total_parts=file_total_parts, - bytes=chunk + return raw.types.InputFileBig( + id=file_id, + parts=file_total_parts, + name=file_name, + ) else: - rpc = raw.functions.upload.SaveFilePart( - file_id=file_id, - file_part=file_part, - bytes=chunk + return raw.types.InputFile( + id=file_id, + parts=file_total_parts, + name=file_name, + md5_checksum=md5_sum ) + finally: + for _ in workers: + await queue.put(None) - await queue.put(rpc) - - if is_missing_part: - return - - if not is_big and not is_missing_part: - md5_sum.update(chunk) + await asyncio.gather(*workers) - file_part += 1 + await session.stop() - if progress: - func = functools.partial( - progress, - min(file_part * part_size, file_size), - file_size, - *progress_args - ) - - if inspect.iscoroutinefunction(progress): - await func() - else: - await self.loop.run_in_executor(self.executor, func) - except StopTransmission: - raise - except Exception as e: - log.exception(e) - else: - if is_big: - return raw.types.InputFileBig( - id=file_id, - parts=file_total_parts, - name=file_name, - - ) - else: - return raw.types.InputFile( - id=file_id, - parts=file_total_parts, - name=file_name, - md5_checksum=md5_sum - ) - finally: - for _ in workers: - await queue.put(None) - - await asyncio.gather(*workers) - - await session.stop() - - if isinstance(path, (str, PurePath)): - fp.close() + if isinstance(path, (str, PurePath)): + fp.close() From aa2f18922540225de9eb266ebeee72d4ca8905d4 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 8 Jan 2023 17:11:22 +0100 Subject: [PATCH 1150/1185] Update Pyrogram to v2.0.94 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 5b8f537c1d..5e3a6b086f 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.93" +__version__ = "2.0.94" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From 388c534e1f7e45b75ddca536a865dfcdfe4d6991 Mon Sep 17 00:00:00 2001 From: Nick <64551534+null-nick@users.noreply.github.com> Date: Sun, 8 Jan 2023 17:20:36 +0100 Subject: [PATCH 1151/1185] Add BOT_ONESIDE_NOT_AVAIL to known errors (#1073) * `BOT_ONESIDE_NOT_AVAIL` * Update 400_BAD_REQUEST.tsv Co-authored-by: Dan <14043624+delivrance@users.noreply.github.com> --- compiler/errors/source/400_BAD_REQUEST.tsv | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/errors/source/400_BAD_REQUEST.tsv b/compiler/errors/source/400_BAD_REQUEST.tsv index 1ad4c6a593..52d229ecc3 100644 --- a/compiler/errors/source/400_BAD_REQUEST.tsv +++ b/compiler/errors/source/400_BAD_REQUEST.tsv @@ -355,3 +355,4 @@ WEBPAGE_CURL_FAILED Telegram server could not fetch the provided URL WEBPAGE_MEDIA_EMPTY The URL doesn't contain any valid media YOU_BLOCKED_USER You blocked this user ENTITY_BOUNDS_INVALID The message entity bounds are invalid +BOT_ONESIDE_NOT_AVAIL Bots can't pin messages for one side only in private chats From 24018532c887a61171cabc7c233caec31b41f1d3 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 8 Jan 2023 17:22:15 +0100 Subject: [PATCH 1152/1185] Add INVITE_REQUEST_SENT to known errors --- compiler/errors/source/400_BAD_REQUEST.tsv | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/errors/source/400_BAD_REQUEST.tsv b/compiler/errors/source/400_BAD_REQUEST.tsv index 52d229ecc3..7ba6e90afa 100644 --- a/compiler/errors/source/400_BAD_REQUEST.tsv +++ b/compiler/errors/source/400_BAD_REQUEST.tsv @@ -356,3 +356,4 @@ WEBPAGE_MEDIA_EMPTY The URL doesn't contain any valid media YOU_BLOCKED_USER You blocked this user ENTITY_BOUNDS_INVALID The message entity bounds are invalid BOT_ONESIDE_NOT_AVAIL Bots can't pin messages for one side only in private chats +INVITE_REQUEST_SENT The request to join this chat or channel has been successfully sent From f9a9673011bdd4d589d7b493a3f0bdd214059645 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 8 Jan 2023 17:22:42 +0100 Subject: [PATCH 1153/1185] Update Pyrogram to v2.0.95 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 5e3a6b086f..fe075d685a 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.94" +__version__ = "2.0.95" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From 043734f02b2dc3a239525028ec186354c251037b Mon Sep 17 00:00:00 2001 From: Mahesh <44301650+Mahesh0253@users.noreply.github.com> Date: Mon, 9 Jan 2023 19:32:20 +0530 Subject: [PATCH 1154/1185] Add BUTTON_USER_PRIVACY_RESTRICTED to the list of known errors (#1193) * Add BUTTON_USER_PRIVACY_RESTRICTED to known errors * Update 400_BAD_REQUEST.tsv Co-authored-by: Dan <14043624+delivrance@users.noreply.github.com> --- compiler/errors/source/400_BAD_REQUEST.tsv | 1 + 1 file changed, 1 insertion(+) diff --git a/compiler/errors/source/400_BAD_REQUEST.tsv b/compiler/errors/source/400_BAD_REQUEST.tsv index 7ba6e90afa..3707dee461 100644 --- a/compiler/errors/source/400_BAD_REQUEST.tsv +++ b/compiler/errors/source/400_BAD_REQUEST.tsv @@ -38,6 +38,7 @@ BROADCAST_REQUIRED The request can only be used with a channel BUTTON_DATA_INVALID The button callback data is invalid or too large BUTTON_TYPE_INVALID The type of one of the buttons you provided is invalid BUTTON_URL_INVALID The button url is invalid +BUTTON_USER_PRIVACY_RESTRICTED The privacy settings of the user specified in a keyboard button do not allow creating such button CALL_ALREADY_ACCEPTED The call is already accepted CALL_ALREADY_DECLINED The call is already declined CALL_PEER_INVALID The provided call peer object is invalid From d53e1c235b0814e6a2d5260fd4668f42c92b5348 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 9 Jan 2023 15:19:55 +0100 Subject: [PATCH 1155/1185] Lower the logging level of some log calls --- pyrogram/client.py | 2 +- pyrogram/connection/transport/tcp/tcp.py | 4 ++-- pyrogram/methods/auth/terminate.py | 2 +- pyrogram/methods/utilities/start.py | 2 +- pyrogram/parser/html.py | 2 +- pyrogram/session/session.py | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/pyrogram/client.py b/pyrogram/client.py index 81828f5a6d..90aeb1f90e 100644 --- a/pyrogram/client.py +++ b/pyrogram/client.py @@ -540,7 +540,7 @@ async def handle_updates(self, updates): pts_count = getattr(update, "pts_count", None) if isinstance(update, raw.types.UpdateChannelTooLong): - log.warning(update) + log.info(update) if isinstance(update, raw.types.UpdateNewChannelMessage) and is_min: message = update.message diff --git a/pyrogram/connection/transport/tcp/tcp.py b/pyrogram/connection/transport/tcp/tcp.py index 08a435cee9..82ef033be4 100644 --- a/pyrogram/connection/transport/tcp/tcp.py +++ b/pyrogram/connection/transport/tcp/tcp.py @@ -91,7 +91,7 @@ async def close(self): self.writer.close() await asyncio.wait_for(self.writer.wait_closed(), TCP.TIMEOUT) except Exception as e: - log.warning("Close exception: %s %s", type(e).__name__, e) + log.info("Close exception: %s %s", type(e).__name__, e) async def send(self, data: bytes): async with self.lock: @@ -100,7 +100,7 @@ async def send(self, data: bytes): self.writer.write(data) await self.writer.drain() except Exception as e: - log.warning("Send exception: %s %s", type(e).__name__, e) + log.info("Send exception: %s %s", type(e).__name__, e) raise OSError(e) async def recv(self, length: int = 0): diff --git a/pyrogram/methods/auth/terminate.py b/pyrogram/methods/auth/terminate.py index 70cfc80e65..d5fd949cba 100644 --- a/pyrogram/methods/auth/terminate.py +++ b/pyrogram/methods/auth/terminate.py @@ -41,7 +41,7 @@ async def terminate( if self.takeout_id: await self.invoke(raw.functions.account.FinishTakeoutSession()) - log.warning("Takeout session %s finished", self.takeout_id) + log.info("Takeout session %s finished", self.takeout_id) await self.storage.save() await self.dispatcher.stop() diff --git a/pyrogram/methods/utilities/start.py b/pyrogram/methods/utilities/start.py index 19a7eb7cf3..d8314da182 100644 --- a/pyrogram/methods/utilities/start.py +++ b/pyrogram/methods/utilities/start.py @@ -63,7 +63,7 @@ async def main(): if not await self.storage.is_bot() and self.takeout: self.takeout_id = (await self.invoke(raw.functions.account.InitTakeoutSession())).id - log.warning("Takeout session %s initiated", self.takeout_id) + log.info("Takeout session %s initiated", self.takeout_id) await self.invoke(raw.functions.updates.GetState()) except (Exception, KeyboardInterrupt): diff --git a/pyrogram/parser/html.py b/pyrogram/parser/html.py index 7edb7f3c99..46722a8c40 100644 --- a/pyrogram/parser/html.py +++ b/pyrogram/parser/html.py @@ -131,7 +131,7 @@ async def parse(self, text: str): for tag, entities in parser.tag_entities.items(): unclosed_tags.append(f"<{tag}> (x{len(entities)})") - log.warning("Unclosed tags: %s", ", ".join(unclosed_tags)) + log.info("Unclosed tags: %s", ", ".join(unclosed_tags)) entities = [] diff --git a/pyrogram/session/session.py b/pyrogram/session/session.py index 0dd3430539..664416be41 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -229,7 +229,7 @@ async def handle_packet(self, packet): raise SecurityCheckMismatch("The msg_id belongs to over 300 seconds in the past. " "Most likely the client time has to be synchronized.") except SecurityCheckMismatch as e: - log.warning("Discarding packet: %s", e) + log.info("Discarding packet: %s", e) await self.connection.close() return else: From a58e19827a524b713222c1e0e2da2fb7eb3a79ae Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 9 Jan 2023 15:22:10 +0100 Subject: [PATCH 1156/1185] Sort the lists of known errors --- compiler/errors/source/400_BAD_REQUEST.tsv | 10 +++++----- compiler/errors/source/403_FORBIDDEN.tsv | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/compiler/errors/source/400_BAD_REQUEST.tsv b/compiler/errors/source/400_BAD_REQUEST.tsv index 3707dee461..08ecb7a676 100644 --- a/compiler/errors/source/400_BAD_REQUEST.tsv +++ b/compiler/errors/source/400_BAD_REQUEST.tsv @@ -28,6 +28,7 @@ BOT_INLINE_DISABLED The inline feature of the bot is disabled BOT_INVALID This is not a valid bot BOT_METHOD_INVALID The method can't be used by bots BOT_MISSING This method can only be run by a bot +BOT_ONESIDE_NOT_AVAIL Bots can't pin messages for one side only in private chats BOT_PAYMENTS_DISABLED This method can only be run by a bot BOT_POLLS_DISABLED Sending polls by bots has been disabled BOT_RESPONSE_TIMEOUT The bot did not answer to the callback query in time @@ -101,6 +102,7 @@ ENCRYPTION_ALREADY_DECLINED The secret chat is already declined ENCRYPTION_DECLINED The secret chat was declined ENCRYPTION_ID_INVALID The provided secret chat id is invalid ENTITIES_TOO_LONG The entity provided contains data that is too long, or you passed too many entities to this message +ENTITY_BOUNDS_INVALID The message entity bounds are invalid ENTITY_MENTION_USER_INVALID The mentioned entity is not an user ERROR_TEXT_EMPTY The provided error message is empty EXPIRE_DATE_INVALID The expiration date is invalid @@ -154,6 +156,7 @@ INPUT_USER_DEACTIVATED The target user has been deleted/deactivated INVITE_HASH_EMPTY The invite hash is empty INVITE_HASH_EXPIRED The chat invite link is no longer valid INVITE_HASH_INVALID The invite link hash is invalid +INVITE_REQUEST_SENT The request to join this chat or channel has been successfully sent INVITE_REVOKED_MISSING The action required a chat invite link to be revoked first LANG_PACK_INVALID The provided language pack is invalid LASTNAME_INVALID The last name is invalid @@ -297,8 +300,8 @@ STICKER_INVALID The provided sticker is invalid STICKER_PNG_DIMENSIONS The sticker png dimensions are invalid STICKER_PNG_NOPNG Stickers must be png files but the provided image was not a png STICKER_TGS_NOTGS A tgs sticker file was expected, but something else was provided -STICKER_VIDEO_NOWEBM A webm video file was expected, but something else was provided STICKER_THUMB_PNG_NOPNG A png sticker thumbnail file was expected, but something else was provided +STICKER_VIDEO_NOWEBM A webm video file was expected, but something else was provided TAKEOUT_INVALID The takeout id is invalid TAKEOUT_REQUIRED The method must be invoked inside a takeout session TEMP_AUTH_KEY_EMPTY The temporary auth key provided is empty @@ -354,7 +357,4 @@ WEBDOCUMENT_URL_EMPTY The web document URL is empty WEBDOCUMENT_URL_INVALID The web document URL is invalid WEBPAGE_CURL_FAILED Telegram server could not fetch the provided URL WEBPAGE_MEDIA_EMPTY The URL doesn't contain any valid media -YOU_BLOCKED_USER You blocked this user -ENTITY_BOUNDS_INVALID The message entity bounds are invalid -BOT_ONESIDE_NOT_AVAIL Bots can't pin messages for one side only in private chats -INVITE_REQUEST_SENT The request to join this chat or channel has been successfully sent +YOU_BLOCKED_USER You blocked this user \ No newline at end of file diff --git a/compiler/errors/source/403_FORBIDDEN.tsv b/compiler/errors/source/403_FORBIDDEN.tsv index 69777cd249..027f2e852e 100644 --- a/compiler/errors/source/403_FORBIDDEN.tsv +++ b/compiler/errors/source/403_FORBIDDEN.tsv @@ -15,6 +15,7 @@ INLINE_BOT_REQUIRED The action must be performed through an inline bot callback MESSAGE_AUTHOR_REQUIRED You are not the author of this message MESSAGE_DELETE_FORBIDDEN You don't have rights to delete messages in this chat, most likely because you are not the author of them POLL_VOTE_REQUIRED Cast a vote in the poll before calling this method +PREMIUM_ACCOUNT_REQUIRED This action requires a premium account RIGHT_FORBIDDEN You don't have enough rights for this action, or you tried to set one or more admin rights that can't be applied to this kind of chat (channel or supergroup) SENSITIVE_CHANGE_FORBIDDEN Your sensitive content settings can't be changed at this time TAKEOUT_REQUIRED The method must be invoked inside a takeout session @@ -24,5 +25,4 @@ USER_INVALID The provided user is invalid USER_IS_BLOCKED The user is blocked USER_NOT_MUTUAL_CONTACT The provided user is not a mutual contact USER_PRIVACY_RESTRICTED The user's privacy settings is preventing you to perform this action -USER_RESTRICTED You are limited/restricted. You can't perform this action -PREMIUM_ACCOUNT_REQUIRED This action requires a premium account \ No newline at end of file +USER_RESTRICTED You are limited/restricted. You can't perform this action \ No newline at end of file From 2a7110e257171858ed701625660df227cfba7a85 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 9 Jan 2023 15:22:36 +0100 Subject: [PATCH 1157/1185] Update Pyrogram to v2.0.96 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index fe075d685a..db401a85c3 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.95" +__version__ = "2.0.96" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From 82b8c7792e028a7ea5f855f2e7e1fe13bdabbfcb Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 12 Jan 2023 18:19:15 +0100 Subject: [PATCH 1158/1185] Allow to specify a limit to concurrent transmissions --- pyrogram/client.py | 17 +++++++++++++---- pyrogram/methods/advanced/save_file.py | 2 +- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/pyrogram/client.py b/pyrogram/client.py index 90aeb1f90e..9de55621f8 100644 --- a/pyrogram/client.py +++ b/pyrogram/client.py @@ -172,6 +172,11 @@ class Client(Methods): Pass True to hide the password when typing it during the login. Defaults to False, because ``getpass`` (the library used) is known to be problematic in some terminal environments. + + max_concurrent_transmissions (``bool``, *optional*): + Set the maximum amount of concurrent transmissions (uploads & downloads). + A value that is too high may result in network related issues. + Defaults to 1. """ APP_VERSION = f"Pyrogram {__version__}" @@ -189,6 +194,8 @@ class Client(Methods): # Interval of seconds in which the updates watchdog will kick in UPDATES_WATCHDOG_INTERVAL = 5 * 60 + MAX_CONCURRENT_TRANSMISSIONS = 1 + mimetypes = MimeTypes() mimetypes.readfp(StringIO(mime_types)) @@ -217,7 +224,8 @@ def __init__( no_updates: bool = None, takeout: bool = None, sleep_threshold: int = Session.SLEEP_THRESHOLD, - hide_password: bool = False + hide_password: bool = False, + max_concurrent_transmissions: int = MAX_CONCURRENT_TRANSMISSIONS ): super().__init__() @@ -245,6 +253,7 @@ def __init__( self.takeout = takeout self.sleep_threshold = sleep_threshold self.hide_password = hide_password + self.max_concurrent_transmissions = max_concurrent_transmissions self.executor = ThreadPoolExecutor(self.workers, thread_name_prefix="Handler") @@ -266,8 +275,8 @@ def __init__( self.media_sessions = {} self.media_sessions_lock = asyncio.Lock() - self.save_file_lock = asyncio.Lock() - self.get_file_lock = asyncio.Lock() + self.save_file_semaphore = asyncio.Semaphore(self.max_concurrent_transmissions) + self.get_file_semaphore = asyncio.Semaphore(self.max_concurrent_transmissions) self.is_connected = None self.is_initialized = None @@ -798,7 +807,7 @@ async def get_file( progress: Callable = None, progress_args: tuple = () ) -> Optional[AsyncGenerator[bytes, None]]: - async with self.get_file_lock: + async with self.get_file_semaphore: file_type = file_id.file_type if file_type == FileType.CHAT_PHOTO: diff --git a/pyrogram/methods/advanced/save_file.py b/pyrogram/methods/advanced/save_file.py index e683fe52d9..453a62af17 100644 --- a/pyrogram/methods/advanced/save_file.py +++ b/pyrogram/methods/advanced/save_file.py @@ -94,7 +94,7 @@ async def save_file( Raises: RPCError: In case of a Telegram RPC error. """ - async with self.save_file_lock: + async with self.save_file_semaphore: if path is None: return None From 283246a6b828706e3f2a66c19f62a41665a60338 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 12 Jan 2023 18:25:10 +0100 Subject: [PATCH 1159/1185] Change connection mode --- pyrogram/connection/connection.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/connection/connection.py b/pyrogram/connection/connection.py index 69cbb813a1..1107673f1a 100644 --- a/pyrogram/connection/connection.py +++ b/pyrogram/connection/connection.py @@ -20,7 +20,7 @@ import logging from typing import Optional -from .transport import TCP, TCPAbridgedO +from .transport import TCP, TCPAbridged from ..session.internals import DataCenter log = logging.getLogger(__name__) @@ -41,7 +41,7 @@ def __init__(self, dc_id: int, test_mode: bool, ipv6: bool, proxy: dict, media: async def connect(self): for i in range(Connection.MAX_CONNECTION_ATTEMPTS): - self.protocol = TCPAbridgedO(self.ipv6, self.proxy) + self.protocol = TCPAbridged(self.ipv6, self.proxy) try: log.info("Connecting...") From 245b7e653db22fdb5273c04af646bf3bc69db5e0 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 12 Jan 2023 18:25:42 +0100 Subject: [PATCH 1160/1185] Tweak Session timeouts --- 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 664416be41..0ed967a1a2 100644 --- a/pyrogram/session/session.py +++ b/pyrogram/session/session.py @@ -45,7 +45,7 @@ def __init__(self): class Session: - START_TIMEOUT = 5 + START_TIMEOUT = 2 WAIT_TIMEOUT = 15 SLEEP_THRESHOLD = 10 MAX_RETRIES = 10 From cfbc848dcf0c73acc279516e168651f95b2e2a4a Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Thu, 12 Jan 2023 18:26:08 +0100 Subject: [PATCH 1161/1185] Update Pyrogram to v2.0.97 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index db401a85c3..e280c304a1 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.96" +__version__ = "2.0.97" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From 22e2b7ff62bfe7ea0fd3cff71f35af8e2c04683c Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 10 Feb 2023 12:30:08 +0100 Subject: [PATCH 1162/1185] Update bug_report.yml --- .github/ISSUE_TEMPLATE/bug_report.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index b03ff7dc5e..1150af6099 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -4,6 +4,7 @@ body: - type: checkboxes attributes: label: Checklist + description: Invalid, incomplete or inadequate issue reports will not be considered options: - label: I am sure the error is coming from Pyrogram's code and not elsewhere required: true @@ -34,7 +35,7 @@ body: - type: textarea attributes: label: Code example - description: Provide a [minimal, reproducible](https://stackoverflow.com/help/minimal-reproducible-example) and properly formatted example (if applicable) + description: Provide a [minimal, complete, consistently reproducible](https://stackoverflow.com/help/minimal-reproducible-example) and properly formatted example involving normal usages (if applicable) placeholder: | from pyrogram import Client ... From 5c5dce662025b0cc59106f526b7f1d3fe949361a Mon Sep 17 00:00:00 2001 From: Nick <64551534+null-nick@users.noreply.github.com> Date: Fri, 10 Feb 2023 12:34:43 +0100 Subject: [PATCH 1163/1185] Update API schema to Layer 152 (#1207) --- compiler/api/source/main_api.tl | 72 +++++++++++++++++++++++++-------- 1 file changed, 55 insertions(+), 17 deletions(-) diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl index df85bdf82c..670bd8dc91 100644 --- a/compiler/api/source/main_api.tl +++ b/compiler/api/source/main_api.tl @@ -52,7 +52,7 @@ inputMediaPoll#f94e5f1 flags:# poll:Poll correct_answers:flags.0?Vector s inputMediaDice#e66fbf7b emoticon:string = InputMedia; inputChatPhotoEmpty#1ca48f57 = InputChatPhoto; -inputChatUploadedPhoto#c642724e flags:# file:flags.0?InputFile video:flags.1?InputFile video_start_ts:flags.2?double = InputChatPhoto; +inputChatUploadedPhoto#bdcdaec0 flags:# file:flags.0?InputFile video:flags.1?InputFile video_start_ts:flags.2?double video_emoji_markup:flags.3?VideoSize = InputChatPhoto; inputChatPhoto#8953ad37 id:InputPhoto = InputChatPhoto; inputGeoPointEmpty#e4c123d6 = InputGeoPoint; @@ -106,8 +106,8 @@ chatForbidden#6592a1a7 id:long title:string = Chat; channel#83259464 flags:# creator:flags.0?true left:flags.2?true broadcast:flags.5?true verified:flags.7?true megagroup:flags.8?true restricted:flags.9?true signatures:flags.11?true min:flags.12?true scam:flags.19?true has_link:flags.20?true has_geo:flags.21?true slowmode_enabled:flags.22?true call_active:flags.23?true call_not_empty:flags.24?true fake:flags.25?true gigagroup:flags.26?true noforwards:flags.27?true join_to_send:flags.28?true join_request:flags.29?true forum:flags.30?true flags2:# id:long access_hash:flags.13?long title:string username:flags.6?string photo:ChatPhoto date:int restriction_reason:flags.9?Vector admin_rights:flags.14?ChatAdminRights banned_rights:flags.15?ChatBannedRights default_banned_rights:flags.18?ChatBannedRights participants_count:flags.17?int usernames:flags2.0?Vector = Chat; channelForbidden#17d493d5 flags:# broadcast:flags.5?true megagroup:flags.8?true id:long access_hash:long title:string until_date:flags.16?int = Chat; -chatFull#c9d31138 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true id:long about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer theme_emoticon:flags.16?string requests_pending:flags.17?int recent_requesters:flags.17?Vector available_reactions:flags.18?ChatReactions = ChatFull; -channelFull#f2355507 flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true flags2:# can_delete_channel:flags2.0?true antispam:flags2.1?true participants_hidden:flags2.2?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector default_send_as:flags.29?Peer available_reactions:flags.30?ChatReactions = ChatFull; +chatFull#c9d31138 flags:# can_set_username:flags.7?true has_scheduled:flags.8?true translations_disabled:flags.19?true id:long about:string participants:ChatParticipants chat_photo:flags.2?Photo notify_settings:PeerNotifySettings exported_invite:flags.13?ExportedChatInvite bot_info:flags.3?Vector pinned_msg_id:flags.6?int folder_id:flags.11?int call:flags.12?InputGroupCall ttl_period:flags.14?int groupcall_default_join_as:flags.15?Peer theme_emoticon:flags.16?string requests_pending:flags.17?int recent_requesters:flags.17?Vector available_reactions:flags.18?ChatReactions = ChatFull; +channelFull#f2355507 flags:# can_view_participants:flags.3?true can_set_username:flags.6?true can_set_stickers:flags.7?true hidden_prehistory:flags.10?true can_set_location:flags.16?true has_scheduled:flags.19?true can_view_stats:flags.20?true blocked:flags.22?true flags2:# can_delete_channel:flags2.0?true antispam:flags2.1?true participants_hidden:flags2.2?true translations_disabled:flags2.3?true id:long about:string participants_count:flags.0?int admins_count:flags.1?int kicked_count:flags.2?int banned_count:flags.2?int online_count:flags.13?int read_inbox_max_id:int read_outbox_max_id:int unread_count:int chat_photo:Photo notify_settings:PeerNotifySettings exported_invite:flags.23?ExportedChatInvite bot_info:Vector migrated_from_chat_id:flags.4?long migrated_from_max_id:flags.4?int pinned_msg_id:flags.5?int stickerset:flags.8?StickerSet available_min_id:flags.9?int folder_id:flags.11?int linked_chat_id:flags.14?long location:flags.15?ChannelLocation slowmode_seconds:flags.17?int slowmode_next_send_date:flags.18?int stats_dc:flags.12?int pts:int call:flags.21?InputGroupCall ttl_period:flags.24?int pending_suggestions:flags.25?Vector groupcall_default_join_as:flags.26?Peer theme_emoticon:flags.27?string requests_pending:flags.28?int recent_requesters:flags.28?Vector default_send_as:flags.29?Peer available_reactions:flags.30?ChatReactions = ChatFull; chatParticipant#c02d4007 user_id:long inviter_id:long date:int = ChatParticipant; chatParticipantCreator#e46bcee4 user_id:long = ChatParticipant; @@ -174,6 +174,7 @@ messageActionTopicCreate#d999256 flags:# title:string icon_color:int icon_emoji_ messageActionTopicEdit#c0944820 flags:# title:flags.0?string icon_emoji_id:flags.1?long closed:flags.2?Bool hidden:flags.3?Bool = MessageAction; messageActionSuggestProfilePhoto#57de635e photo:Photo = MessageAction; messageActionAttachMenuBotAllowed#e7e75f97 = MessageAction; +messageActionRequestedPeer#fe77345d button_id:int peer:Peer = MessageAction; dialog#d58a08c6 flags:# pinned:flags.2?true unread_mark:flags.3?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int unread_reactions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int ttl_period:flags.5?int = Dialog; dialogFolder#71bd134c flags:# pinned:flags.2?true folder:Folder peer:Peer top_message:int unread_muted_peers_count:int unread_unmuted_peers_count:int unread_muted_messages_count:int unread_unmuted_messages_count:int = Dialog; @@ -192,8 +193,9 @@ geoPointEmpty#1117dd5f = GeoPoint; geoPoint#b2a2f663 flags:# long:double lat:double access_hash:long accuracy_radius:flags.0?int = GeoPoint; auth.sentCode#5e002502 flags:# type:auth.SentCodeType phone_code_hash:string next_type:flags.1?auth.CodeType timeout:flags.2?int = auth.SentCode; +auth.sentCodeSuccess#2390fe44 authorization:auth.Authorization = auth.SentCode; -auth.authorization#33fb7bb8 flags:# setup_password_required:flags.1?true otherwise_relogin_days:flags.1?int tmp_sessions:flags.0?int user:User = auth.Authorization; +auth.authorization#2ea2c0d4 flags:# setup_password_required:flags.1?true otherwise_relogin_days:flags.1?int tmp_sessions:flags.0?int future_auth_token:flags.2?bytes user:User = auth.Authorization; auth.authorizationSignUpRequired#44747e9a flags:# terms_of_service:flags.0?help.TermsOfService = auth.Authorization; auth.exportedAuthorization#b434e2b8 id:long bytes:bytes = auth.ExportedAuthorization; @@ -224,7 +226,7 @@ inputReportReasonFake#f5ddd6e7 = ReportReason; inputReportReasonIllegalDrugs#a8eb2be = ReportReason; inputReportReasonPersonalDetails#9ec7863d = ReportReason; -userFull#f8d32aed flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true voice_messages_forbidden:flags.20?true id:long about:flags.1?string settings:PeerSettings personal_photo:flags.21?Photo profile_photo:flags.2?Photo fallback_photo:flags.22?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int ttl_period:flags.14?int theme_emoticon:flags.15?string private_forward_name:flags.16?string bot_group_admin_rights:flags.17?ChatAdminRights bot_broadcast_admin_rights:flags.18?ChatAdminRights premium_gifts:flags.19?Vector = UserFull; +userFull#f8d32aed flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true voice_messages_forbidden:flags.20?true translations_disabled:flags.23?true id:long about:flags.1?string settings:PeerSettings personal_photo:flags.21?Photo profile_photo:flags.2?Photo fallback_photo:flags.22?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int ttl_period:flags.14?int theme_emoticon:flags.15?string private_forward_name:flags.16?string bot_group_admin_rights:flags.17?ChatAdminRights bot_broadcast_admin_rights:flags.18?ChatAdminRights premium_gifts:flags.19?Vector = UserFull; contact#145ade0b user_id:long mutual:Bool = Contact; @@ -383,6 +385,7 @@ updateMessageExtendedMedia#5a73a98c peer:Peer msg_id:int extended_media:MessageE updateChannelPinnedTopic#192efbe3 flags:# pinned:flags.0?true channel_id:long topic_id:int = Update; updateChannelPinnedTopics#fe198602 flags:# channel_id:long order:flags.0?Vector = Update; updateUser#20529438 user_id:long = Update; +updateAutoSaveSettings#ec05b097 = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -600,6 +603,7 @@ inputKeyboardButtonUserProfile#e988037b text:string user_id:InputUser = Keyboard keyboardButtonUserProfile#308660c1 text:string user_id:long = KeyboardButton; keyboardButtonWebView#13767230 text:string url:string = KeyboardButton; keyboardButtonSimpleWebView#a0c0505c text:string url:string = KeyboardButton; +keyboardButtonRequestPeer#d0b468c text:string button_id:int peer_type:RequestPeerType = KeyboardButton; keyboardButtonRow#77608b83 buttons:Vector = KeyboardButtonRow; @@ -714,6 +718,7 @@ auth.sentCodeTypeMissedCall#82006484 prefix:string length:int = auth.SentCodeTyp auth.sentCodeTypeEmailCode#5a159841 flags:# apple_signin_allowed:flags.0?true google_signin_allowed:flags.1?true email_pattern:string length:int next_phone_login_date:flags.2?int = auth.SentCodeType; auth.sentCodeTypeSetUpEmailRequired#a5491dea flags:# apple_signin_allowed:flags.0?true google_signin_allowed:flags.1?true = auth.SentCodeType; auth.sentCodeTypeFragmentSms#d9565c39 url:string length:int = auth.SentCodeType; +auth.sentCodeTypeFirebaseSms#e57b1432 flags:# nonce:flags.0?bytes receipt:flags.1?string push_timeout:flags.1?int length:int = auth.SentCodeType; messages.botCallbackAnswer#36585ea4 flags:# alert:flags.1?true has_url:flags.3?true native_ui:flags.4?true message:flags.0?string url:flags.2?string cache_time:int = messages.BotCallbackAnswer; @@ -1114,7 +1119,7 @@ statsURL#47a971e0 url:string = StatsURL; chatAdminRights#5fb224d5 flags:# change_info:flags.0?true post_messages:flags.1?true edit_messages:flags.2?true delete_messages:flags.3?true ban_users:flags.4?true invite_users:flags.5?true pin_messages:flags.7?true add_admins:flags.9?true anonymous:flags.10?true manage_call:flags.11?true other:flags.12?true manage_topics:flags.13?true = ChatAdminRights; -chatBannedRights#9f120418 flags:# view_messages:flags.0?true send_messages:flags.1?true send_media:flags.2?true send_stickers:flags.3?true send_gifs:flags.4?true send_games:flags.5?true send_inline:flags.6?true embed_links:flags.7?true send_polls:flags.8?true change_info:flags.10?true invite_users:flags.15?true pin_messages:flags.17?true manage_topics:flags.18?true until_date:int = ChatBannedRights; +chatBannedRights#9f120418 flags:# view_messages:flags.0?true send_messages:flags.1?true send_media:flags.2?true send_stickers:flags.3?true send_gifs:flags.4?true send_games:flags.5?true send_inline:flags.6?true embed_links:flags.7?true send_polls:flags.8?true change_info:flags.10?true invite_users:flags.15?true pin_messages:flags.17?true manage_topics:flags.18?true send_photos:flags.19?true send_videos:flags.20?true send_roundvideos:flags.21?true send_audios:flags.22?true send_voices:flags.23?true send_docs:flags.24?true send_plain:flags.25?true until_date:int = ChatBannedRights; inputWallPaper#e630b979 id:long access_hash:long = InputWallPaper; inputWallPaperSlug#72091c80 slug:string = InputWallPaper; @@ -1123,7 +1128,7 @@ inputWallPaperNoFile#967a462e id:long = InputWallPaper; account.wallPapersNotModified#1c199183 = account.WallPapers; account.wallPapers#cdc3858c hash:long wallpapers:Vector = account.WallPapers; -codeSettings#8a6469c2 flags:# allow_flashcall:flags.0?true current_number:flags.1?true allow_app_hash:flags.4?true allow_missed_call:flags.5?true logout_tokens:flags.6?Vector = CodeSettings; +codeSettings#ad253d78 flags:# allow_flashcall:flags.0?true current_number:flags.1?true allow_app_hash:flags.4?true allow_missed_call:flags.5?true allow_firebase:flags.7?true logout_tokens:flags.6?Vector token:flags.8?string app_sandbox:flags.8?Bool = CodeSettings; wallPaperSettings#1dc1bca4 flags:# blur:flags.1?true motion:flags.2?true background_color:flags.0?int second_background_color:flags.4?int third_background_color:flags.5?int fourth_background_color:flags.6?int intensity:flags.3?int rotation:flags.4?int = WallPaperSettings; @@ -1221,6 +1226,8 @@ help.promoDataEmpty#98f6ac75 expires:int = help.PromoData; help.promoData#8c39793f flags:# proxy:flags.0?true expires:int peer:Peer chats:Vector users:Vector psa_type:flags.1?string psa_message:flags.2?string = help.PromoData; videoSize#de33b094 flags:# type:string w:int h:int size:int video_start_ts:flags.0?double = VideoSize; +videoSizeEmojiMarkup#f85c413c emoji_id:long background_colors:Vector = VideoSize; +videoSizeStickerMarkup#da082fe stickerset:InputStickerSet sticker_id:long background_colors:Vector = VideoSize; statsGroupTopPoster#9d04af9b user_id:long messages:int avg_chars:int = StatsGroupTopPoster; @@ -1345,9 +1352,6 @@ availableReaction#c077ec01 flags:# inactive:flags.0?true premium:flags.2?true re messages.availableReactionsNotModified#9f071957 = messages.AvailableReactions; messages.availableReactions#768e3aad hash:int reactions:Vector = messages.AvailableReactions; -messages.translateNoResult#67ca4737 = messages.TranslatedText; -messages.translateResultText#a214f7d0 text:string = messages.TranslatedText; - messagePeerReaction#b156fe9c flags:# big:flags.0?true unread:flags.1?true peer_id:Peer reaction:Reaction = MessagePeerReaction; groupCallStreamChannel#80eb48af channel:int scale:int last_timestamp_ms:long = GroupCallStreamChannel; @@ -1403,7 +1407,7 @@ messages.transcribedAudio#93752c52 flags:# pending:flags.0?true transcription_id help.premiumPromo#5334759c status_text:string status_entities:Vector video_sections:Vector videos:Vector period_options:Vector users:Vector = help.PremiumPromo; -inputStorePaymentPremiumSubscription#a6751e66 flags:# restore:flags.0?true = InputStorePaymentPurpose; +inputStorePaymentPremiumSubscription#a6751e66 flags:# restore:flags.0?true upgrade:flags.1?true = InputStorePaymentPurpose; inputStorePaymentGiftPremium#616f7fe8 user_id:InputUser currency:string amount:long = InputStorePaymentPurpose; premiumGiftOption#74c34319 flags:# months:int currency:string amount:long bot_url:string store_product:flags.0?string = PremiumGiftOption; @@ -1439,7 +1443,7 @@ emailVerificationApple#96d074fd token:string = EmailVerification; account.emailVerified#2b96cd1b email:string = account.EmailVerified; account.emailVerifiedLogin#e1bb0d61 email:string sent_code:auth.SentCode = account.EmailVerified; -premiumSubscriptionOption#b6f11ebe flags:# current:flags.1?true can_purchase_upgrade:flags.2?true months:int currency:string amount:long bot_url:string store_product:flags.0?string = PremiumSubscriptionOption; +premiumSubscriptionOption#5f2d1df2 flags:# current:flags.1?true can_purchase_upgrade:flags.2?true transaction:flags.3?string months:int currency:string amount:long bot_url:string store_product:flags.0?string = PremiumSubscriptionOption; sendAsPeer#b81c7034 flags:# premium_required:flags.0?true peer:Peer = SendAsPeer; @@ -1459,6 +1463,28 @@ defaultHistoryTTL#43b46b20 period:int = DefaultHistoryTTL; exportedContactToken#41bf109b url:string expires:int = ExportedContactToken; +requestPeerTypeUser#5f3b8a00 flags:# bot:flags.0?Bool premium:flags.1?Bool = RequestPeerType; +requestPeerTypeChat#c9f06e1b flags:# creator:flags.0?true bot_participant:flags.5?true has_username:flags.3?Bool forum:flags.4?Bool user_admin_rights:flags.1?ChatAdminRights bot_admin_rights:flags.2?ChatAdminRights = RequestPeerType; +requestPeerTypeBroadcast#339bef6c flags:# creator:flags.0?true has_username:flags.3?Bool user_admin_rights:flags.1?ChatAdminRights bot_admin_rights:flags.2?ChatAdminRights = RequestPeerType; + +emojiListNotModified#481eadfa = EmojiList; +emojiList#7a1e11d1 hash:long document_id:Vector = EmojiList; + +emojiGroup#7a9abda9 title:string icon_emoji_id:long emoticons:Vector = EmojiGroup; + +messages.emojiGroupsNotModified#6fb4ad87 = messages.EmojiGroups; +messages.emojiGroups#881fb94b hash:int groups:Vector = messages.EmojiGroups; + +textWithEntities#751f3146 text:string entities:Vector = TextWithEntities; + +messages.translateResult#33db32f8 result:Vector = messages.TranslatedText; + +autoSaveSettings#c84834ce flags:# photos:flags.0?true videos:flags.1?true video_max_size:flags.2?long = AutoSaveSettings; + +autoSaveException#81602d47 peer:Peer settings:AutoSaveSettings = AutoSaveException; + +account.autoSaveSettings#4c3e069d users_settings:AutoSaveSettings chats_settings:AutoSaveSettings broadcasts_settings:AutoSaveSettings exceptions:Vector chats:Vector users:Vector = account.AutoSaveSettings; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -1489,6 +1515,7 @@ auth.importLoginToken#95ac5ce4 token:bytes = auth.LoginToken; auth.acceptLoginToken#e894ad4d token:bytes = Authorization; auth.checkRecoveryPassword#d36bf79 code:string = Bool; auth.importWebTokenAuthorization#2db873a9 api_id:int api_hash:string web_auth_token:string = auth.Authorization; +auth.requestFirebaseSms#89464b50 flags:# phone_number:string phone_code_hash:string safety_net_token:flags.0?string ios_push_secret:flags.1?string = Bool; account.registerDevice#ec86017a flags:# no_muted:flags.0?true token_type:int token:string app_sandbox:Bool secret:bytes other_uids:Vector = Bool; account.unregisterDevice#6a0d3206 token_type:int token:string other_uids:Vector = Bool; @@ -1572,6 +1599,11 @@ account.getRecentEmojiStatuses#f578105 hash:long = account.EmojiStatuses; account.clearRecentEmojiStatuses#18201aae = Bool; account.reorderUsernames#ef500eab order:Vector = Bool; account.toggleUsername#58d6b376 username:string active:Bool = Bool; +account.getDefaultProfilePhotoEmojis#e2750328 hash:long = EmojiList; +account.getDefaultGroupPhotoEmojis#915860ae hash:long = EmojiList; +account.getAutoSaveSettings#adcbbcda = account.AutoSaveSettings; +account.saveAutoSaveSettings#d69b8361 flags:# users:flags.0?true chats:flags.1?true broadcasts:flags.2?true peer:flags.3?InputPeer settings:AutoSaveSettings = Bool; +account.deleteAutoSaveExceptions#53bc0020 = Bool; users.getUsers#d91a548 id:Vector = Vector; users.getFullUser#b60f5918 id:InputUser = users.UserFull; @@ -1756,7 +1788,7 @@ messages.getMessageReactionsList#461b3f48 flags:# peer:InputPeer id:int reaction messages.setChatAvailableReactions#feb16771 peer:InputPeer available_reactions:ChatReactions = Updates; messages.getAvailableReactions#18dea0ac hash:int = messages.AvailableReactions; messages.setDefaultReaction#4f47a016 reaction:Reaction = Bool; -messages.translateText#24ce6dee flags:# peer:flags.0?InputPeer msg_id:flags.0?int text:flags.1?string from_lang:flags.2?string to_lang:string = messages.TranslatedText; +messages.translateText#63183030 flags:# peer:flags.0?InputPeer id:flags.0?Vector text:flags.1?Vector to_lang:string = messages.TranslatedText; messages.getUnreadReactions#3223495b flags:# peer:InputPeer top_msg_id:flags.0?int offset_id:int add_offset:int limit:int max_id:int min_id:int = messages.Messages; messages.readReactions#54aa7f8e flags:# peer:InputPeer top_msg_id:flags.0?int = messages.AffectedHistory; messages.searchSentMedia#107e31a0 q:string filter:MessagesFilter limit:int = messages.Messages; @@ -1780,16 +1812,22 @@ messages.clearRecentReactions#9dfeefb4 = Bool; messages.getExtendedMedia#84f80814 peer:InputPeer id:Vector = Updates; messages.setDefaultHistoryTTL#9eb51445 period:int = Bool; messages.getDefaultHistoryTTL#658b7188 = DefaultHistoryTTL; +messages.sendBotRequestedPeer#fe38d01b peer:InputPeer msg_id:int button_id:int requested_peer:InputPeer = Updates; +messages.getEmojiGroups#7488ce5b hash:int = messages.EmojiGroups; +messages.getEmojiStatusGroups#2ecd56cd hash:int = messages.EmojiGroups; +messages.getEmojiProfilePhotoGroups#21a548f3 hash:int = messages.EmojiGroups; +messages.searchCustomEmoji#2c11c0d7 emoticon:string hash:long = EmojiList; +messages.togglePeerTranslations#e47cb579 flags:# disabled:flags.0?true peer:InputPeer = Bool; updates.getState#edd4882a = updates.State; updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference; updates.getChannelDifference#3173d78 flags:# force:flags.0?true channel:InputChannel filter:ChannelMessagesFilter pts:int limit:int = updates.ChannelDifference; photos.updateProfilePhoto#1c3d5956 flags:# fallback:flags.0?true id:InputPhoto = photos.Photo; -photos.uploadProfilePhoto#89f30f69 flags:# fallback:flags.3?true file:flags.0?InputFile video:flags.1?InputFile video_start_ts:flags.2?double = photos.Photo; +photos.uploadProfilePhoto#93c9a51 flags:# fallback:flags.3?true file:flags.0?InputFile video:flags.1?InputFile video_start_ts:flags.2?double video_emoji_markup:flags.4?VideoSize = photos.Photo; photos.deletePhotos#87cf7f2f id:Vector = Vector; photos.getUserPhotos#91cd32a8 user_id:InputUser offset:int max_id:long limit:int = photos.Photos; -photos.uploadContactProfilePhoto#b91a83bf flags:# suggest:flags.3?true save:flags.4?true user_id:InputUser file:flags.0?InputFile video:flags.1?InputFile video_start_ts:flags.2?double = photos.Photo; +photos.uploadContactProfilePhoto#e14c4a71 flags:# suggest:flags.3?true save:flags.4?true user_id:InputUser file:flags.0?InputFile video:flags.1?InputFile video_start_ts:flags.2?double video_emoji_markup:flags.5?VideoSize = photos.Photo; upload.saveFilePart#b304a621 file_id:long file_part:int bytes:bytes = Bool; upload.getFile#be5335be flags:# precise:flags.0?true cdn_supported:flags.1?true location:InputFileLocation offset:long limit:int = upload.File; @@ -1832,7 +1870,7 @@ channels.getParticipants#77ced9d0 channel:InputChannel filter:ChannelParticipant channels.getParticipant#a0ab6cc6 channel:InputChannel participant:InputPeer = channels.ChannelParticipant; channels.getChannels#a7f6bbb id:Vector = messages.Chats; channels.getFullChannel#8736a09 channel:InputChannel = messages.ChatFull; -channels.createChannel#91006707 flags:# broadcast:flags.0?true megagroup:flags.1?true for_import:flags.3?true title:string about:string geo_point:flags.2?InputGeoPoint address:flags.2?string ttl_period:flags.4?int = Updates; +channels.createChannel#91006707 flags:# broadcast:flags.0?true megagroup:flags.1?true for_import:flags.3?true forum:flags.5?true title:string about:string geo_point:flags.2?InputGeoPoint address:flags.2?string ttl_period:flags.4?int = Updates; channels.editAdmin#d33c8902 channel:InputChannel user_id:InputUser admin_rights:ChatAdminRights rank:string = Updates; channels.editTitle#566decd0 channel:InputChannel title:string = Updates; channels.editPhoto#f12e57c9 channel:InputChannel photo:InputChatPhoto = Updates; @@ -1957,4 +1995,4 @@ stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats; -// LAYER 151 +// LAYER 152 From 53cb3f29c7d98dec6deccc32dfe04b26affa2f3b Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 10 Feb 2023 12:44:07 +0100 Subject: [PATCH 1164/1185] Update Pyrogram to v2.0.98 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index e280c304a1..2c3b460e9d 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.97" +__version__ = "2.0.98" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From f040aefc9f074c59b387ec08cc80deea42760b98 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 11 Feb 2023 10:03:09 +0100 Subject: [PATCH 1165/1185] Update bug_report.yml --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 1150af6099..3b0ff4ee2d 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -4,7 +4,7 @@ body: - type: checkboxes attributes: label: Checklist - description: Invalid, incomplete or inadequate issue reports will not be considered + description: Invalid, incomplete or inadequate issue reports may not be taken into consideration options: - label: I am sure the error is coming from Pyrogram's code and not elsewhere required: true From 2e82fcecff01dee0df2969135a96c317a39a1894 Mon Sep 17 00:00:00 2001 From: Jins Mathew <68688737+jinspalakkattu@users.noreply.github.com> Date: Sat, 11 Feb 2023 12:08:29 +0300 Subject: [PATCH 1166/1185] Add missing has_spoiler parameter to reply_{animation,photo,video} --- pyrogram/types/messages_and_media/message.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/pyrogram/types/messages_and_media/message.py b/pyrogram/types/messages_and_media/message.py index 1835f63225..62a85b7593 100644 --- a/pyrogram/types/messages_and_media/message.py +++ b/pyrogram/types/messages_and_media/message.py @@ -989,6 +989,7 @@ async def reply_animation( caption: str = "", parse_mode: Optional["enums.ParseMode"] = None, caption_entities: List["types.MessageEntity"] = None, + has_spoiler: bool = None, duration: int = 0, width: int = 0, height: int = 0, @@ -1042,6 +1043,9 @@ async def reply_animation( caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): List of special entities that appear in the caption, which can be specified instead of *parse_mode*. + has_spoiler (``bool``, *optional*): + Pass True if the animation needs to be covered with a spoiler animation. + duration (``int``, *optional*): Duration of sent animation in seconds. @@ -1110,6 +1114,7 @@ async def reply_animation( caption=caption, parse_mode=parse_mode, caption_entities=caption_entities, + has_spoiler=has_spoiler, duration=duration, width=width, height=height, @@ -1886,6 +1891,7 @@ async def reply_photo( caption: str = "", parse_mode: Optional["enums.ParseMode"] = None, caption_entities: List["types.MessageEntity"] = None, + has_spoiler: bool = None, ttl_seconds: int = None, disable_notification: bool = None, reply_to_message_id: int = None, @@ -1936,6 +1942,9 @@ async def reply_photo( caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): List of special entities that appear in the caption, which can be specified instead of *parse_mode*. + has_spoiler (``bool``, *optional*): + Pass True if the photo needs to be covered with a spoiler animation. + ttl_seconds (``int``, *optional*): Self-Destruct Timer. If you set a timer, the photo will self-destruct in *ttl_seconds* @@ -1994,6 +2003,7 @@ async def reply_photo( caption=caption, parse_mode=parse_mode, caption_entities=caption_entities, + has_spoiler=has_spoiler, ttl_seconds=ttl_seconds, disable_notification=disable_notification, reply_to_message_id=reply_to_message_id, @@ -2352,6 +2362,7 @@ async def reply_video( caption: str = "", parse_mode: Optional["enums.ParseMode"] = None, caption_entities: List["types.MessageEntity"] = None, + has_spoiler: bool = None, ttl_seconds: int = None, duration: int = 0, width: int = 0, @@ -2407,6 +2418,9 @@ async def reply_video( caption_entities (List of :obj:`~pyrogram.types.MessageEntity`): List of special entities that appear in the caption, which can be specified instead of *parse_mode*. + has_spoiler (``bool``, *optional*): + Pass True if the video needs to be covered with a spoiler animation. + ttl_seconds (``int``, *optional*): Self-Destruct Timer. If you set a timer, the video will self-destruct in *ttl_seconds* @@ -2483,6 +2497,7 @@ async def reply_video( caption=caption, parse_mode=parse_mode, caption_entities=caption_entities, + has_spoiler=has_spoiler, ttl_seconds=ttl_seconds, duration=duration, width=width, From 96ffc7efcd32a65b73ea1bf609faf08492d93b99 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 11 Feb 2023 10:09:03 +0100 Subject: [PATCH 1167/1185] Update Pyrogram to v2.0.99 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 2c3b460e9d..3e9848420c 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.98" +__version__ = "2.0.99" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From 787eabd940aa1439086b67fc96cb922fe12ef24e Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 26 Feb 2023 11:08:27 +0100 Subject: [PATCH 1168/1185] Update send_inline_bot_result return type hint --- pyrogram/methods/bots/send_inline_bot_result.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyrogram/methods/bots/send_inline_bot_result.py b/pyrogram/methods/bots/send_inline_bot_result.py index 0f31880636..f29d8eb9cd 100644 --- a/pyrogram/methods/bots/send_inline_bot_result.py +++ b/pyrogram/methods/bots/send_inline_bot_result.py @@ -30,7 +30,7 @@ async def send_inline_bot_result( result_id: str, disable_notification: bool = None, reply_to_message_id: int = None - ): + ) -> "raw.base.Updates": """Send an inline bot result. Bot results can be retrieved using :meth:`~pyrogram.Client.get_inline_bot_results` @@ -56,7 +56,7 @@ async def send_inline_bot_result( If the message is a reply, ID of the original message. Returns: - :obj:`~pyrogram.types.Message`: On success, the sent inline result message is returned. + :obj:`~pyrogram.raw.base.Updates`: Currently, on success, a raw result is returned. Example: .. code-block:: python From d6476ce57e85cc4b655a4cb23b8f1e8958eab8de Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 26 Feb 2023 11:09:21 +0100 Subject: [PATCH 1169/1185] Update Pyrogram to v2.0.100 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 3e9848420c..a0a56ef54e 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.99" +__version__ = "2.0.100" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From a3a4a0204c382bc2e4038f8c6cb9a7e215603c48 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 11 Mar 2023 16:45:32 +0100 Subject: [PATCH 1170/1185] Update chat username parsing in case of multiple usernames --- pyrogram/client.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pyrogram/client.py b/pyrogram/client.py index 9de55621f8..f213c57a76 100644 --- a/pyrogram/client.py +++ b/pyrogram/client.py @@ -513,7 +513,11 @@ async def fetch_peers(self, peers: List[Union[raw.types.User, raw.types.Chat, ra elif isinstance(peer, (raw.types.Channel, raw.types.ChannelForbidden)): peer_id = utils.get_channel_id(peer.id) access_hash = peer.access_hash - username = (getattr(peer, "username", None) or "").lower() or None + username = ( + peer.username.lower() if peer.username + else peer.usernames[0].username.lower() if peer.usernames + else None + ) peer_type = "channel" if peer.broadcast else "supergroup" else: continue From fb3f9acc181cb176386e695a2ec942f28d9669b6 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sat, 11 Mar 2023 16:45:56 +0100 Subject: [PATCH 1171/1185] Update Pyrogram to v2.0.101 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index a0a56ef54e..a07e75f330 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.100" +__version__ = "2.0.101" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From 0a90d54010df1712048c270fe1378337f275e483 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 12 Mar 2023 17:52:03 +0100 Subject: [PATCH 1172/1185] Separate cases between Channel and ChannelForbidden --- pyrogram/client.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/pyrogram/client.py b/pyrogram/client.py index f213c57a76..c74634ea8e 100644 --- a/pyrogram/client.py +++ b/pyrogram/client.py @@ -510,7 +510,7 @@ async def fetch_peers(self, peers: List[Union[raw.types.User, raw.types.Chat, ra peer_id = -peer.id access_hash = 0 peer_type = "group" - elif isinstance(peer, (raw.types.Channel, raw.types.ChannelForbidden)): + elif isinstance(peer, raw.types.Channel): peer_id = utils.get_channel_id(peer.id) access_hash = peer.access_hash username = ( @@ -519,6 +519,10 @@ async def fetch_peers(self, peers: List[Union[raw.types.User, raw.types.Chat, ra else None ) peer_type = "channel" if peer.broadcast else "supergroup" + elif isinstance(peer, raw.types.ChannelForbidden): + peer_id = utils.get_channel_id(peer.id) + access_hash = peer.access_hash + peer_type = "channel" if peer.broadcast else "supergroup" else: continue From abed55aea9d6687a8054282d8ee2d964ec39e23a Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 12 Mar 2023 17:52:37 +0100 Subject: [PATCH 1173/1185] Update Pyrogram to v2.0.102 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index a07e75f330..4b74f269ec 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.101" +__version__ = "2.0.102" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From 8cff5594c0eefe67f8aa36d6b12689a76e5d54ee Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 3 Apr 2023 16:29:04 +0200 Subject: [PATCH 1174/1185] Update API schema to Layer 155 --- compiler/api/source/main_api.tl | 52 ++++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 14 deletions(-) diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl index 670bd8dc91..8c5c12ecf3 100644 --- a/compiler/api/source/main_api.tl +++ b/compiler/api/source/main_api.tl @@ -156,7 +156,7 @@ messageActionPaymentSent#96163f56 flags:# recurring_init:flags.2?true recurring_ messageActionPhoneCall#80e11a7f flags:# video:flags.2?true call_id:long reason:flags.0?PhoneCallDiscardReason duration:flags.1?int = MessageAction; messageActionScreenshotTaken#4792929b = MessageAction; messageActionCustomAction#fae69f56 message:string = MessageAction; -messageActionBotAllowed#abe9affe domain:string = MessageAction; +messageActionBotAllowed#c516d679 flags:# attach_menu:flags.1?true domain:flags.0?string app:flags.2?BotApp = MessageAction; messageActionSecureValuesSentMe#1b287353 values:Vector credentials:SecureCredentialsEncrypted = MessageAction; messageActionSecureValuesSent#d95c6154 types:Vector = MessageAction; messageActionContactSignUp#f3f25f76 = MessageAction; @@ -173,7 +173,6 @@ messageActionGiftPremium#aba0f5c6 currency:string amount:long months:int = Messa messageActionTopicCreate#d999256 flags:# title:string icon_color:int icon_emoji_id:flags.0?long = MessageAction; messageActionTopicEdit#c0944820 flags:# title:flags.0?string icon_emoji_id:flags.1?long closed:flags.2?Bool hidden:flags.3?Bool = MessageAction; messageActionSuggestProfilePhoto#57de635e photo:Photo = MessageAction; -messageActionAttachMenuBotAllowed#e7e75f97 = MessageAction; messageActionRequestedPeer#fe77345d button_id:int peer:Peer = MessageAction; dialog#d58a08c6 flags:# pinned:flags.2?true unread_mark:flags.3?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int unread_reactions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int ttl_period:flags.5?int = Dialog; @@ -386,6 +385,7 @@ updateChannelPinnedTopic#192efbe3 flags:# pinned:flags.0?true channel_id:long to updateChannelPinnedTopics#fe198602 flags:# channel_id:long order:flags.0?Vector = Update; updateUser#20529438 user_id:long = Update; updateAutoSaveSettings#ec05b097 = Update; +updateGroupInvitePrivacyForbidden#ccf08ad6 user_id:long = Update; updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State; @@ -412,7 +412,7 @@ upload.fileCdnRedirect#f18cda44 dc_id:int file_token:bytes encryption_key:bytes dcOption#18b7a10d flags:# ipv6:flags.0?true media_only:flags.1?true tcpo_only:flags.2?true cdn:flags.3?true static:flags.4?true this_port_only:flags.5?true id:int ip_address:string port:int secret:flags.10?bytes = DcOption; -config#232566ac flags:# phonecalls_enabled:flags.1?true default_p2p_contacts:flags.3?true preload_featured_stickers:flags.4?true ignore_phone_entities:flags.5?true revoke_pm_inbox:flags.6?true blocked_mode:flags.8?true pfs_enabled:flags.13?true force_try_ipv6:flags.14?true date:int expires:int test_mode:Bool this_dc:int dc_options:Vector dc_txt_domain_name:string chat_size_max:int megagroup_size_max:int forwarded_count_max:int online_update_period_ms:int offline_blur_timeout_ms:int offline_idle_timeout_ms:int online_cloud_timeout_ms:int notify_cloud_delay_ms:int notify_default_delay_ms:int push_chat_period_ms:int push_chat_limit:int saved_gifs_limit:int edit_time_limit:int revoke_time_limit:int revoke_pm_time_limit:int rating_e_decay:int stickers_recent_limit:int stickers_faved_limit:int channels_read_media_period:int tmp_sessions:flags.0?int pinned_dialogs_count_max:int pinned_infolder_count_max:int call_receive_timeout_ms:int call_ring_timeout_ms:int call_connect_timeout_ms:int call_packet_timeout_ms:int me_url_prefix:string autoupdate_url_prefix:flags.7?string gif_search_username:flags.9?string venue_search_username:flags.10?string img_search_username:flags.11?string static_maps_provider:flags.12?string caption_length_max:int message_length_max:int webfile_dc_id:int suggested_lang_code:flags.2?string lang_pack_version:flags.2?int base_lang_pack_version:flags.2?int reactions_default:flags.15?Reaction = Config; +config#cc1a241e flags:# default_p2p_contacts:flags.3?true preload_featured_stickers:flags.4?true revoke_pm_inbox:flags.6?true blocked_mode:flags.8?true force_try_ipv6:flags.14?true date:int expires:int test_mode:Bool this_dc:int dc_options:Vector dc_txt_domain_name:string chat_size_max:int megagroup_size_max:int forwarded_count_max:int online_update_period_ms:int offline_blur_timeout_ms:int offline_idle_timeout_ms:int online_cloud_timeout_ms:int notify_cloud_delay_ms:int notify_default_delay_ms:int push_chat_period_ms:int push_chat_limit:int edit_time_limit:int revoke_time_limit:int revoke_pm_time_limit:int rating_e_decay:int stickers_recent_limit:int channels_read_media_period:int tmp_sessions:flags.0?int call_receive_timeout_ms:int call_ring_timeout_ms:int call_connect_timeout_ms:int call_packet_timeout_ms:int me_url_prefix:string autoupdate_url_prefix:flags.7?string gif_search_username:flags.9?string venue_search_username:flags.10?string img_search_username:flags.11?string static_maps_provider:flags.12?string caption_length_max:int message_length_max:int webfile_dc_id:int suggested_lang_code:flags.2?string lang_pack_version:flags.2?int base_lang_pack_version:flags.2?int reactions_default:flags.15?Reaction autologin_token:flags.16?string = Config; nearestDc#8e1a1775 country:string this_dc:int nearest_dc:int = NearestDc; @@ -698,7 +698,7 @@ botInlineMessageMediaInvoice#354a9b09 flags:# shipping_address_requested:flags.1 botInlineResult#11965f3a flags:# id:string type:string title:flags.1?string description:flags.2?string url:flags.3?string thumb:flags.4?WebDocument content:flags.5?WebDocument send_message:BotInlineMessage = BotInlineResult; botInlineMediaResult#17db940b flags:# id:string type:string photo:flags.0?Photo document:flags.1?Document title:flags.2?string description:flags.3?string send_message:BotInlineMessage = BotInlineResult; -messages.botResults#947ca848 flags:# gallery:flags.0?true query_id:long next_offset:flags.1?string switch_pm:flags.2?InlineBotSwitchPM results:Vector cache_time:int users:Vector = messages.BotResults; +messages.botResults#e021f2f6 flags:# gallery:flags.0?true query_id:long next_offset:flags.1?string switch_pm:flags.2?InlineBotSwitchPM switch_webview:flags.3?InlineBotWebView results:Vector cache_time:int users:Vector = messages.BotResults; exportedMessageLink#5dab1af4 link:string html:string = ExportedMessageLink; @@ -878,7 +878,7 @@ account.tmpPassword#db64fd34 tmp_password:bytes valid_until:int = account.TmpPas shippingOption#b6213cdf id:string title:string prices:Vector = ShippingOption; -inputStickerSetItem#ffa0a496 flags:# document:InputDocument emoji:string mask_coords:flags.0?MaskCoords = InputStickerSetItem; +inputStickerSetItem#32da9e9c flags:# document:InputDocument emoji:string mask_coords:flags.0?MaskCoords keywords:flags.1?string = InputStickerSetItem; inputPhoneCall#1e36fded id:long access_hash:long = InputPhoneCall; @@ -1320,7 +1320,7 @@ account.resetPasswordFailedWait#e3779861 retry_date:int = account.ResetPasswordR account.resetPasswordRequestedWait#e9effc7d until_date:int = account.ResetPasswordResult; account.resetPasswordOk#e926d63e = account.ResetPasswordResult; -sponsoredMessage#3a836df8 flags:# recommended:flags.5?true show_peer_photo:flags.6?true random_id:bytes from_id:flags.3?Peer chat_invite:flags.4?ChatInvite chat_invite_hash:flags.4?string channel_post:flags.2?int start_param:flags.0?string message:string entities:flags.1?Vector = SponsoredMessage; +sponsoredMessage#fc25b828 flags:# recommended:flags.5?true show_peer_photo:flags.6?true random_id:bytes from_id:flags.3?Peer chat_invite:flags.4?ChatInvite chat_invite_hash:flags.4?string channel_post:flags.2?int start_param:flags.0?string message:string entities:flags.1?Vector sponsor_info:flags.7?string additional_info:flags.8?string = SponsoredMessage; messages.sponsoredMessages#c9ee1d87 flags:# posts_between:flags.0?int messages:Vector chats:Vector users:Vector = messages.SponsoredMessages; messages.sponsoredMessagesEmpty#1839490f = messages.SponsoredMessages; @@ -1352,7 +1352,7 @@ availableReaction#c077ec01 flags:# inactive:flags.0?true premium:flags.2?true re messages.availableReactionsNotModified#9f071957 = messages.AvailableReactions; messages.availableReactions#768e3aad hash:int reactions:Vector = messages.AvailableReactions; -messagePeerReaction#b156fe9c flags:# big:flags.0?true unread:flags.1?true peer_id:Peer reaction:Reaction = MessagePeerReaction; +messagePeerReaction#8c79b63c flags:# big:flags.0?true unread:flags.1?true peer_id:Peer date:int reaction:Reaction = MessagePeerReaction; groupCallStreamChannel#80eb48af channel:int scale:int last_timestamp_ms:long = GroupCallStreamChannel; @@ -1485,6 +1485,23 @@ autoSaveException#81602d47 peer:Peer settings:AutoSaveSettings = AutoSaveExcepti account.autoSaveSettings#4c3e069d users_settings:AutoSaveSettings chats_settings:AutoSaveSettings broadcasts_settings:AutoSaveSettings exceptions:Vector chats:Vector users:Vector = account.AutoSaveSettings; +help.appConfigNotModified#7cde641d = help.AppConfig; +help.appConfig#dd18782e hash:int config:JSONValue = help.AppConfig; + +inputBotAppID#a920bd7a id:long access_hash:long = InputBotApp; +inputBotAppShortName#908c0407 bot_id:InputUser short_name:string = InputBotApp; + +botAppNotModified#5da674b7 = BotApp; +botApp#95fcd1d6 flags:# id:long access_hash:long short_name:string title:string description:string photo:Photo document:flags.0?Document hash:long = BotApp; + +messages.botApp#eb50adf5 flags:# inactive:flags.0?true request_write_access:flags.1?true app:BotApp = messages.BotApp; + +appWebViewResultUrl#3c1b4f0d url:string = AppWebViewResult; + +inlineBotWebView#b57295d5 text:string url:string = InlineBotWebView; + +readParticipantDate#4a4ff172 user_id:long date:int = ReadParticipantDate; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -1686,7 +1703,7 @@ messages.getDocumentByHash#b1f2061f sha256:bytes size:long mime_type:string = Do messages.getSavedGifs#5cf09635 hash:long = messages.SavedGifs; messages.saveGif#327a30cb id:InputDocument unsave:Bool = Bool; messages.getInlineBotResults#514e999d flags:# bot:InputUser peer:InputPeer geo_point:flags.0?InputGeoPoint query:string offset:string = messages.BotResults; -messages.setInlineBotResults#eb5ea206 flags:# gallery:flags.0?true private:flags.1?true query_id:long results:Vector cache_time:int next_offset:flags.2?string switch_pm:flags.3?InlineBotSwitchPM = Bool; +messages.setInlineBotResults#bb12a419 flags:# gallery:flags.0?true private:flags.1?true query_id:long results:Vector cache_time:int next_offset:flags.2?string switch_pm:flags.3?InlineBotSwitchPM switch_webview:flags.4?InlineBotWebView = Bool; messages.sendInlineBotResult#d3fbdccb flags:# silent:flags.5?true background:flags.6?true clear_draft:flags.7?true hide_via:flags.11?true peer:InputPeer reply_to_msg_id:flags.0?int top_msg_id:flags.9?int random_id:long query_id:long id:string schedule_date:flags.10?int send_as:flags.13?InputPeer = Updates; messages.getMessageEditData#fda68d36 peer:InputPeer id:int = messages.MessageEditData; messages.editMessage#48f71778 flags:# no_webpage:flags.1?true peer:InputPeer id:int message:flags.11?string media:flags.14?InputMedia reply_markup:flags.2?ReplyMarkup entities:flags.3?Vector schedule_date:flags.15?int = Updates; @@ -1775,7 +1792,7 @@ messages.getChatInviteImporters#df04dd4e flags:# requested:flags.0?true peer:Inp messages.setHistoryTTL#b80e5fe4 peer:InputPeer period:int = Updates; messages.checkHistoryImportPeer#5dc60f03 peer:InputPeer = messages.CheckedHistoryImportPeer; messages.setChatTheme#e63be13f peer:InputPeer emoticon:string = Updates; -messages.getMessageReadParticipants#2c6f97b7 peer:InputPeer msg_id:int = Vector; +messages.getMessageReadParticipants#31c1c44f peer:InputPeer msg_id:int = Vector; messages.getSearchResultsCalendar#49f0bde9 peer:InputPeer filter:MessagesFilter offset_id:int offset_date:int = messages.SearchResultsCalendar; messages.getSearchResultsPositions#6e9583a3 peer:InputPeer filter:MessagesFilter offset_id:int limit:int = messages.SearchResultsPositions; messages.hideChatJoinRequest#7fe7e815 flags:# approved:flags.0?true peer:InputPeer user_id:InputUser = Updates; @@ -1797,7 +1814,7 @@ messages.getAttachMenuBot#77216192 bot:InputUser = AttachMenuBotsBot; messages.toggleBotInAttachMenu#69f59d69 flags:# write_allowed:flags.0?true bot:InputUser enabled:Bool = Bool; messages.requestWebView#178b480b flags:# from_bot_menu:flags.4?true silent:flags.5?true peer:InputPeer bot:InputUser url:flags.1?string start_param:flags.3?string theme_params:flags.2?DataJSON platform:string reply_to_msg_id:flags.0?int top_msg_id:flags.9?int send_as:flags.13?InputPeer = WebViewResult; messages.prolongWebView#7ff34309 flags:# silent:flags.5?true peer:InputPeer bot:InputUser query_id:long reply_to_msg_id:flags.0?int top_msg_id:flags.9?int send_as:flags.13?InputPeer = Bool; -messages.requestSimpleWebView#299bec8e flags:# bot:InputUser url:string theme_params:flags.0?DataJSON platform:string = SimpleWebViewResult; +messages.requestSimpleWebView#299bec8e flags:# from_switch_webview:flags.1?true bot:InputUser url:string theme_params:flags.0?DataJSON platform:string = SimpleWebViewResult; messages.sendWebViewResultMessage#a4314f5 bot_query_id:string result:InputBotInlineResult = WebViewMessageSent; messages.sendWebViewData#dc0242c8 bot:InputUser random_id:long button_text:string data:string = Updates; messages.transcribeAudio#269e9a49 peer:InputPeer msg_id:int = messages.TranscribedAudio; @@ -1818,6 +1835,8 @@ messages.getEmojiStatusGroups#2ecd56cd hash:int = messages.EmojiGroups; messages.getEmojiProfilePhotoGroups#21a548f3 hash:int = messages.EmojiGroups; messages.searchCustomEmoji#2c11c0d7 emoticon:string hash:long = EmojiList; messages.togglePeerTranslations#e47cb579 flags:# disabled:flags.0?true peer:InputPeer = Bool; +messages.getBotApp#34fdc5c3 app:InputBotApp hash:long = messages.BotApp; +messages.requestAppWebView#8c5a3b3c flags:# write_allowed:flags.0?true peer:InputPeer app:InputBotApp start_param:flags.1?string theme_params:flags.2?DataJSON platform:string = AppWebViewResult; updates.getState#edd4882a = updates.State; updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference; @@ -1850,7 +1869,7 @@ help.getRecentMeUrls#3dc0f114 referer:string = help.RecentMeUrls; help.getTermsOfServiceUpdate#2ca51fd1 = help.TermsOfServiceUpdate; help.acceptTermsOfService#ee72f79a id:DataJSON = Bool; help.getDeepLinkInfo#3fedc75f path:string = help.DeepLinkInfo; -help.getAppConfig#98914110 = JSONValue; +help.getAppConfig#61e3f854 hash:int = help.AppConfig; help.saveAppLog#6f02f748 events:Vector = Bool; help.getPassportConfig#c661ad08 hash:int = help.PassportConfig; help.getSupportName#d360e72c = help.SupportName; @@ -1927,6 +1946,8 @@ bots.setBotMenuButton#4504d54f user_id:InputUser button:BotMenuButton = Bool; bots.getBotMenuButton#9c60eb28 user_id:InputUser = BotMenuButton; bots.setBotBroadcastDefaultAdminRights#788464e1 admin_rights:ChatAdminRights = Bool; bots.setBotGroupDefaultAdminRights#925ec9ea admin_rights:ChatAdminRights = Bool; +bots.setBotInfo#a365df7a flags:# lang_code:string about:flags.0?string description:flags.1?string = Bool; +bots.getBotInfo#75ec12e6 lang_code:string = Vector; payments.getPaymentForm#37148dbb flags:# invoice:InputInvoice theme_params:flags.0?DataJSON = payments.PaymentForm; payments.getPaymentReceipt#2478d1cc peer:InputPeer msg_id:int = payments.PaymentReceipt; @@ -1940,13 +1961,16 @@ payments.assignAppStoreTransaction#80ed747d receipt:bytes purpose:InputStorePaym payments.assignPlayMarketTransaction#dffd50d3 receipt:DataJSON purpose:InputStorePaymentPurpose = Updates; payments.canPurchasePremium#9fc19eb6 purpose:InputStorePaymentPurpose = Bool; -stickers.createStickerSet#9021ab67 flags:# masks:flags.0?true animated:flags.1?true videos:flags.4?true user_id:InputUser title:string short_name:string thumb:flags.2?InputDocument stickers:Vector software:flags.3?string = messages.StickerSet; +stickers.createStickerSet#9021ab67 flags:# masks:flags.0?true animated:flags.1?true videos:flags.4?true emojis:flags.5?true text_color:flags.6?true user_id:InputUser title:string short_name:string thumb:flags.2?InputDocument stickers:Vector software:flags.3?string = messages.StickerSet; stickers.removeStickerFromSet#f7760f51 sticker:InputDocument = messages.StickerSet; stickers.changeStickerPosition#ffb6d4ca sticker:InputDocument position:int = messages.StickerSet; stickers.addStickerToSet#8653febe stickerset:InputStickerSet sticker:InputStickerSetItem = messages.StickerSet; -stickers.setStickerSetThumb#9a364e30 stickerset:InputStickerSet thumb:InputDocument = messages.StickerSet; +stickers.setStickerSetThumb#a76a5392 flags:# stickerset:InputStickerSet thumb:flags.0?InputDocument thumb_document_id:flags.1?long = messages.StickerSet; stickers.checkShortName#284b3639 short_name:string = Bool; stickers.suggestShortName#4dafc503 title:string = stickers.SuggestedShortName; +stickers.changeSticker#f5537ebc flags:# sticker:InputDocument emoji:flags.0?string mask_coords:flags.1?MaskCoords keywords:flags.2?string = messages.StickerSet; +stickers.renameStickerSet#124b1c00 stickerset:InputStickerSet title:string = messages.StickerSet; +stickers.deleteStickerSet#87704394 stickerset:InputStickerSet = Bool; phone.getCallConfig#55451fa9 = DataJSON; phone.requestCall#42ff96ed flags:# video:flags.0?true user_id:InputUser random_id:int g_a_hash:bytes protocol:PhoneCallProtocol = phone.PhoneCall; @@ -1995,4 +2019,4 @@ stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats; -// LAYER 152 +// LAYER 155 From 68c7bd6e12c5339919f68f346208cebe1aed28d4 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 3 Apr 2023 16:29:32 +0200 Subject: [PATCH 1175/1185] Update Pyrogram to v2.0.103 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 4b74f269ec..e4e23fd03f 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.102" +__version__ = "2.0.103" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From bb678a4fb6ca41fe3d7c441989aab84273445546 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 21 Apr 2023 14:56:12 +0200 Subject: [PATCH 1176/1185] Update API schema to Layer 158 --- compiler/api/source/main_api.tl | 64 +++++++++++++++++++++++++-------- 1 file changed, 49 insertions(+), 15 deletions(-) diff --git a/compiler/api/source/main_api.tl b/compiler/api/source/main_api.tl index 8c5c12ecf3..7fd74edb3d 100644 --- a/compiler/api/source/main_api.tl +++ b/compiler/api/source/main_api.tl @@ -88,7 +88,7 @@ storage.fileMp4#b3cea0e4 = storage.FileType; storage.fileWebp#1081464c = storage.FileType; userEmpty#d3bc4b7a id:long = User; -user#8f97c628 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true apply_min_photo:flags.25?true fake:flags.26?true bot_attach_menu:flags.27?true premium:flags.28?true attach_menu_enabled:flags.29?true flags2:# id:long access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector bot_inline_placeholder:flags.19?string lang_code:flags.22?string emoji_status:flags.30?EmojiStatus usernames:flags2.0?Vector = User; +user#8f97c628 flags:# self:flags.10?true contact:flags.11?true mutual_contact:flags.12?true deleted:flags.13?true bot:flags.14?true bot_chat_history:flags.15?true bot_nochats:flags.16?true verified:flags.17?true restricted:flags.18?true min:flags.20?true bot_inline_geo:flags.21?true support:flags.23?true scam:flags.24?true apply_min_photo:flags.25?true fake:flags.26?true bot_attach_menu:flags.27?true premium:flags.28?true attach_menu_enabled:flags.29?true flags2:# bot_can_edit:flags2.1?true id:long access_hash:flags.0?long first_name:flags.1?string last_name:flags.2?string username:flags.3?string phone:flags.4?string photo:flags.5?UserProfilePhoto status:flags.6?UserStatus bot_info_version:flags.14?int restriction_reason:flags.18?Vector bot_inline_placeholder:flags.19?string lang_code:flags.22?string emoji_status:flags.30?EmojiStatus usernames:flags2.0?Vector = User; userProfilePhotoEmpty#4f11bae1 = UserProfilePhoto; userProfilePhoto#82d1f706 flags:# has_video:flags.0?true personal:flags.2?true photo_id:long stripped_thumb:flags.1?bytes dc_id:int = UserProfilePhoto; @@ -169,11 +169,13 @@ messageActionSetChatTheme#aa786345 emoticon:string = MessageAction; messageActionChatJoinedByRequest#ebbca3cb = MessageAction; messageActionWebViewDataSentMe#47dd8079 text:string data:string = MessageAction; messageActionWebViewDataSent#b4c38cb5 text:string = MessageAction; -messageActionGiftPremium#aba0f5c6 currency:string amount:long months:int = MessageAction; +messageActionGiftPremium#c83d6aec flags:# currency:string amount:long months:int crypto_currency:flags.0?string crypto_amount:flags.0?long = MessageAction; messageActionTopicCreate#d999256 flags:# title:string icon_color:int icon_emoji_id:flags.0?long = MessageAction; messageActionTopicEdit#c0944820 flags:# title:flags.0?string icon_emoji_id:flags.1?long closed:flags.2?Bool hidden:flags.3?Bool = MessageAction; messageActionSuggestProfilePhoto#57de635e photo:Photo = MessageAction; messageActionRequestedPeer#fe77345d button_id:int peer:Peer = MessageAction; +messageActionSetChatWallPaper#bc44a927 wallpaper:WallPaper = MessageAction; +messageActionSetSameChatWallPaper#c0787d6d wallpaper:WallPaper = MessageAction; dialog#d58a08c6 flags:# pinned:flags.2?true unread_mark:flags.3?true peer:Peer top_message:int read_inbox_max_id:int read_outbox_max_id:int unread_count:int unread_mentions_count:int unread_reactions_count:int notify_settings:PeerNotifySettings pts:flags.0?int draft:flags.1?DraftMessage folder_id:flags.4?int ttl_period:flags.5?int = Dialog; dialogFolder#71bd134c flags:# pinned:flags.2?true folder:Folder peer:Peer top_message:int unread_muted_peers_count:int unread_unmuted_peers_count:int unread_muted_messages_count:int unread_unmuted_messages_count:int = Dialog; @@ -225,7 +227,7 @@ inputReportReasonFake#f5ddd6e7 = ReportReason; inputReportReasonIllegalDrugs#a8eb2be = ReportReason; inputReportReasonPersonalDetails#9ec7863d = ReportReason; -userFull#f8d32aed flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true voice_messages_forbidden:flags.20?true translations_disabled:flags.23?true id:long about:flags.1?string settings:PeerSettings personal_photo:flags.21?Photo profile_photo:flags.2?Photo fallback_photo:flags.22?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int ttl_period:flags.14?int theme_emoticon:flags.15?string private_forward_name:flags.16?string bot_group_admin_rights:flags.17?ChatAdminRights bot_broadcast_admin_rights:flags.18?ChatAdminRights premium_gifts:flags.19?Vector = UserFull; +userFull#93eadb53 flags:# blocked:flags.0?true phone_calls_available:flags.4?true phone_calls_private:flags.5?true can_pin_message:flags.7?true has_scheduled:flags.12?true video_calls_available:flags.13?true voice_messages_forbidden:flags.20?true translations_disabled:flags.23?true id:long about:flags.1?string settings:PeerSettings personal_photo:flags.21?Photo profile_photo:flags.2?Photo fallback_photo:flags.22?Photo notify_settings:PeerNotifySettings bot_info:flags.3?BotInfo pinned_msg_id:flags.6?int common_chats_count:int folder_id:flags.11?int ttl_period:flags.14?int theme_emoticon:flags.15?string private_forward_name:flags.16?string bot_group_admin_rights:flags.17?ChatAdminRights bot_broadcast_admin_rights:flags.18?ChatAdminRights premium_gifts:flags.19?Vector wallpaper:flags.24?WallPaper = UserFull; contact#145ade0b user_id:long mutual:Bool = Contact; @@ -363,7 +365,7 @@ updateGroupCallParticipants#f2ebdb4e call:InputGroupCall participants:Vector = Update; @@ -593,7 +595,7 @@ keyboardButtonUrl#258aff05 text:string url:string = KeyboardButton; keyboardButtonCallback#35bbdb6b flags:# requires_password:flags.0?true text:string data:bytes = KeyboardButton; keyboardButtonRequestPhone#b16a6c29 text:string = KeyboardButton; keyboardButtonRequestGeoLocation#fc796b3f text:string = KeyboardButton; -keyboardButtonSwitchInline#568a748 flags:# same_peer:flags.0?true text:string query:string = KeyboardButton; +keyboardButtonSwitchInline#93b9fbb5 flags:# same_peer:flags.0?true text:string query:string peer_types:flags.1?Vector = KeyboardButton; keyboardButtonGame#50f41ccf text:string = KeyboardButton; keyboardButtonBuy#afd93fbb text:string = KeyboardButton; keyboardButtonUrlAuth#10b78d29 flags:# text:string fwd_text:flags.0?string url:string button_id:int = KeyboardButton; @@ -715,7 +717,7 @@ auth.sentCodeTypeSms#c000bba2 length:int = auth.SentCodeType; auth.sentCodeTypeCall#5353e5a7 length:int = auth.SentCodeType; auth.sentCodeTypeFlashCall#ab03c6d9 pattern:string = auth.SentCodeType; auth.sentCodeTypeMissedCall#82006484 prefix:string length:int = auth.SentCodeType; -auth.sentCodeTypeEmailCode#5a159841 flags:# apple_signin_allowed:flags.0?true google_signin_allowed:flags.1?true email_pattern:string length:int next_phone_login_date:flags.2?int = auth.SentCodeType; +auth.sentCodeTypeEmailCode#f450f59b flags:# apple_signin_allowed:flags.0?true google_signin_allowed:flags.1?true email_pattern:string length:int reset_available_period:flags.3?int reset_pending_date:flags.4?int = auth.SentCodeType; auth.sentCodeTypeSetUpEmailRequired#a5491dea flags:# apple_signin_allowed:flags.0?true google_signin_allowed:flags.1?true = auth.SentCodeType; auth.sentCodeTypeFragmentSms#d9565c39 url:string length:int = auth.SentCodeType; auth.sentCodeTypeFirebaseSms#e57b1432 flags:# nonce:flags.0?bytes receipt:flags.1?string push_timeout:flags.1?int length:int = auth.SentCodeType; @@ -937,7 +939,7 @@ channelAdminLogEventActionDiscardGroupCall#db9f9140 call:InputGroupCall = Channe channelAdminLogEventActionParticipantMute#f92424d2 participant:GroupCallParticipant = ChannelAdminLogEventAction; channelAdminLogEventActionParticipantUnmute#e64429c0 participant:GroupCallParticipant = ChannelAdminLogEventAction; channelAdminLogEventActionToggleGroupCallSetting#56d6a247 join_muted:Bool = ChannelAdminLogEventAction; -channelAdminLogEventActionParticipantJoinByInvite#5cdada77 invite:ExportedChatInvite = ChannelAdminLogEventAction; +channelAdminLogEventActionParticipantJoinByInvite#fe9fc158 flags:# via_chatlist:flags.0?true invite:ExportedChatInvite = ChannelAdminLogEventAction; channelAdminLogEventActionExportedInviteDelete#5a50fca4 invite:ExportedChatInvite = ChannelAdminLogEventAction; channelAdminLogEventActionExportedInviteRevoke#410a134e invite:ExportedChatInvite = ChannelAdminLogEventAction; channelAdminLogEventActionExportedInviteEdit#e90ebb59 prev_invite:ExportedChatInvite new_invite:ExportedChatInvite = ChannelAdminLogEventAction; @@ -1205,6 +1207,7 @@ payments.bankCardData#3e24e573 title:string open_urls:Vector = dialogFilter#7438f7e8 flags:# contacts:flags.0?true non_contacts:flags.1?true groups:flags.2?true broadcasts:flags.3?true bots:flags.4?true exclude_muted:flags.11?true exclude_read:flags.12?true exclude_archived:flags.13?true id:int title:string emoticon:flags.25?string pinned_peers:Vector include_peers:Vector exclude_peers:Vector = DialogFilter; dialogFilterDefault#363293ae = DialogFilter; +dialogFilterChatlist#d64a04a8 flags:# has_my_invites:flags.26?true id:int title:string emoticon:flags.25?string pinned_peers:Vector include_peers:Vector = DialogFilter; dialogFilterSuggested#77744d4a filter:DialogFilter description:string = DialogFilterSuggested; @@ -1276,6 +1279,7 @@ inlineQueryPeerTypePM#833c0fac = InlineQueryPeerType; inlineQueryPeerTypeChat#d766c50a = InlineQueryPeerType; inlineQueryPeerTypeMegagroup#5ec4be43 = InlineQueryPeerType; inlineQueryPeerTypeBroadcast#6334ee9a = InlineQueryPeerType; +inlineQueryPeerTypeBotPM#e3b2d0c = InlineQueryPeerType; messages.historyImport#1662af0b id:long = messages.HistoryImport; @@ -1283,7 +1287,7 @@ messages.historyImportParsed#5e0fb7b9 flags:# pm:flags.0?true group:flags.1?true messages.affectedFoundMessages#ef8d3e6c pts:int pts_count:int offset:int messages:Vector = messages.AffectedFoundMessages; -chatInviteImporter#8c5adfd9 flags:# requested:flags.0?true user_id:long date:int about:flags.2?string approved_by:flags.1?long = ChatInviteImporter; +chatInviteImporter#8c5adfd9 flags:# requested:flags.0?true via_chatlist:flags.3?true user_id:long date:int about:flags.2?string approved_by:flags.1?long = ChatInviteImporter; messages.exportedChatInvites#bdc62dcc count:int invites:Vector users:Vector = messages.ExportedChatInvites; @@ -1502,6 +1506,21 @@ inlineBotWebView#b57295d5 text:string url:string = InlineBotWebView; readParticipantDate#4a4ff172 user_id:long date:int = ReadParticipantDate; +inputChatlistDialogFilter#f3e0da33 filter_id:int = InputChatlist; + +exportedChatlistInvite#c5181ac flags:# title:string url:string peers:Vector = ExportedChatlistInvite; + +chatlists.exportedChatlistInvite#10e6e3a6 filter:DialogFilter invite:ExportedChatlistInvite = chatlists.ExportedChatlistInvite; + +chatlists.exportedInvites#10ab6dc7 invites:Vector chats:Vector users:Vector = chatlists.ExportedInvites; + +chatlists.chatlistInviteAlready#fa87f659 filter_id:int missing_peers:Vector already_peers:Vector chats:Vector users:Vector = chatlists.ChatlistInvite; +chatlists.chatlistInvite#1dcd839d flags:# title:string emoticon:flags.0?string peers:Vector chats:Vector users:Vector = chatlists.ChatlistInvite; + +chatlists.chatlistUpdates#93bd878d missing_peers:Vector chats:Vector users:Vector = chatlists.ChatlistUpdates; + +bots.botInfo#e8a775b0 name:string about:string description:string = bots.BotInfo; + ---functions--- invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X; @@ -1533,6 +1552,7 @@ auth.acceptLoginToken#e894ad4d token:bytes = Authorization; auth.checkRecoveryPassword#d36bf79 code:string = Bool; auth.importWebTokenAuthorization#2db873a9 api_id:int api_hash:string web_auth_token:string = auth.Authorization; auth.requestFirebaseSms#89464b50 flags:# phone_number:string phone_code_hash:string safety_net_token:flags.0?string ios_push_secret:flags.1?string = Bool; +auth.resetLoginEmail#7e960193 phone_number:string phone_code_hash:string = auth.SentCode; account.registerDevice#ec86017a flags:# no_muted:flags.0?true token_type:int token:string app_sandbox:Bool secret:bytes other_uids:Vector = Bool; account.unregisterDevice#6a0d3206 token_type:int token:string other_uids:Vector = Bool; @@ -1583,7 +1603,7 @@ account.getContactSignUpNotification#9f07c728 = Bool; account.setContactSignUpNotification#cff43f61 silent:Bool = Bool; account.getNotifyExceptions#53577479 flags:# compare_sound:flags.1?true peer:flags.0?InputNotifyPeer = Updates; account.getWallPaper#fc8ddbea wallpaper:InputWallPaper = WallPaper; -account.uploadWallPaper#dd853661 file:InputFile mime_type:string settings:WallPaperSettings = WallPaper; +account.uploadWallPaper#e39a8f03 flags:# for_chat:flags.0?true file:InputFile mime_type:string settings:WallPaperSettings = WallPaper; account.saveWallPaper#6c5a5b37 wallpaper:InputWallPaper unsave:Bool settings:WallPaperSettings = Bool; account.installWallPaper#feed5769 wallpaper:InputWallPaper settings:WallPaperSettings = Bool; account.resetWallPapers#bb3b9804 = Bool; @@ -1837,13 +1857,14 @@ messages.searchCustomEmoji#2c11c0d7 emoticon:string hash:long = EmojiList; messages.togglePeerTranslations#e47cb579 flags:# disabled:flags.0?true peer:InputPeer = Bool; messages.getBotApp#34fdc5c3 app:InputBotApp hash:long = messages.BotApp; messages.requestAppWebView#8c5a3b3c flags:# write_allowed:flags.0?true peer:InputPeer app:InputBotApp start_param:flags.1?string theme_params:flags.2?DataJSON platform:string = AppWebViewResult; +messages.setChatWallPaper#8ffacae1 flags:# peer:InputPeer wallpaper:flags.0?InputWallPaper settings:flags.2?WallPaperSettings id:flags.1?int = Updates; updates.getState#edd4882a = updates.State; updates.getDifference#25939651 flags:# pts:int pts_total_limit:flags.0?int date:int qts:int = updates.Difference; updates.getChannelDifference#3173d78 flags:# force:flags.0?true channel:InputChannel filter:ChannelMessagesFilter pts:int limit:int = updates.ChannelDifference; -photos.updateProfilePhoto#1c3d5956 flags:# fallback:flags.0?true id:InputPhoto = photos.Photo; -photos.uploadProfilePhoto#93c9a51 flags:# fallback:flags.3?true file:flags.0?InputFile video:flags.1?InputFile video_start_ts:flags.2?double video_emoji_markup:flags.4?VideoSize = photos.Photo; +photos.updateProfilePhoto#9e82039 flags:# fallback:flags.0?true bot:flags.1?InputUser id:InputPhoto = photos.Photo; +photos.uploadProfilePhoto#388a3b5 flags:# fallback:flags.3?true bot:flags.5?InputUser file:flags.0?InputFile video:flags.1?InputFile video_start_ts:flags.2?double video_emoji_markup:flags.4?VideoSize = photos.Photo; photos.deletePhotos#87cf7f2f id:Vector = Vector; photos.getUserPhotos#91cd32a8 user_id:InputUser offset:int max_id:long limit:int = photos.Photos; photos.uploadContactProfilePhoto#e14c4a71 flags:# suggest:flags.3?true save:flags.4?true user_id:InputUser file:flags.0?InputFile video:flags.1?InputFile video_start_ts:flags.2?double video_emoji_markup:flags.5?VideoSize = photos.Photo; @@ -1946,8 +1967,10 @@ bots.setBotMenuButton#4504d54f user_id:InputUser button:BotMenuButton = Bool; bots.getBotMenuButton#9c60eb28 user_id:InputUser = BotMenuButton; bots.setBotBroadcastDefaultAdminRights#788464e1 admin_rights:ChatAdminRights = Bool; bots.setBotGroupDefaultAdminRights#925ec9ea admin_rights:ChatAdminRights = Bool; -bots.setBotInfo#a365df7a flags:# lang_code:string about:flags.0?string description:flags.1?string = Bool; -bots.getBotInfo#75ec12e6 lang_code:string = Vector; +bots.setBotInfo#10cf3123 flags:# bot:flags.2?InputUser lang_code:string name:flags.3?string about:flags.0?string description:flags.1?string = Bool; +bots.getBotInfo#dcd914fd flags:# bot:flags.0?InputUser lang_code:string = bots.BotInfo; +bots.reorderUsernames#9709b1c2 bot:InputUser order:Vector = Bool; +bots.toggleUsername#53ca973 bot:InputUser username:string active:Bool = Bool; payments.getPaymentForm#37148dbb flags:# invoice:InputInvoice theme_params:flags.0?DataJSON = payments.PaymentForm; payments.getPaymentReceipt#2478d1cc peer:InputPeer msg_id:int = payments.PaymentReceipt; @@ -2011,7 +2034,6 @@ langpack.getLanguages#42c6978f lang_pack:string = Vector; langpack.getLanguage#6a596502 lang_pack:string lang_code:string = LangPackLanguage; folders.editPeerFolders#6847d0ab folder_peers:Vector = Updates; -folders.deleteFolder#1c295881 folder_id:int = Updates; stats.getBroadcastStats#ab42441a flags:# dark:flags.0?true channel:InputChannel = stats.BroadcastStats; stats.loadAsyncGraph#621d5fa0 flags:# token:string x:flags.0?long = StatsGraph; @@ -2019,4 +2041,16 @@ stats.getMegagroupStats#dcdf8607 flags:# dark:flags.0?true channel:InputChannel stats.getMessagePublicForwards#5630281b channel:InputChannel msg_id:int offset_rate:int offset_peer:InputPeer offset_id:int limit:int = messages.Messages; stats.getMessageStats#b6e0a3f5 flags:# dark:flags.0?true channel:InputChannel msg_id:int = stats.MessageStats; -// LAYER 155 +chatlists.exportChatlistInvite#8472478e chatlist:InputChatlist title:string peers:Vector = chatlists.ExportedChatlistInvite; +chatlists.deleteExportedInvite#719c5c5e chatlist:InputChatlist slug:string = Bool; +chatlists.editExportedInvite#653db63d flags:# chatlist:InputChatlist slug:string title:flags.1?string peers:flags.2?Vector = ExportedChatlistInvite; +chatlists.getExportedInvites#ce03da83 chatlist:InputChatlist = chatlists.ExportedInvites; +chatlists.checkChatlistInvite#41c10fff slug:string = chatlists.ChatlistInvite; +chatlists.joinChatlistInvite#a6b1e39a slug:string peers:Vector = Updates; +chatlists.getChatlistUpdates#89419521 chatlist:InputChatlist = chatlists.ChatlistUpdates; +chatlists.joinChatlistUpdates#e089f8f5 chatlist:InputChatlist peers:Vector = Updates; +chatlists.hideChatlistUpdates#66e486fb chatlist:InputChatlist = Bool; +chatlists.getLeaveChatlistSuggestions#fdbcd714 chatlist:InputChatlist = Vector; +chatlists.leaveChatlist#74fae13a chatlist:InputChatlist peers:Vector = Updates; + +// LAYER 158 From cea21ad6d035957b52d693b56433e1a7ccd97b3b Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Fri, 21 Apr 2023 14:56:56 +0200 Subject: [PATCH 1177/1185] Update Pyrogram to v2.0.104 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index e4e23fd03f..6f9d1fce71 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.103" +__version__ = "2.0.104" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From e24d5b1cf56cdaec5c1cc0e3993b135f529f17ce Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 30 Apr 2023 19:54:39 +0200 Subject: [PATCH 1178/1185] Add RSA public keys & IP addresses for some CDN DCs --- pyrogram/crypto/rsa.py | 80 ++++++++++++++++++----- pyrogram/session/internals/data_center.py | 6 +- 2 files changed, 68 insertions(+), 18 deletions(-) diff --git a/pyrogram/crypto/rsa.py b/pyrogram/crypto/rsa.py index 3fd7d67ba3..25c2322957 100644 --- a/pyrogram/crypto/rsa.py +++ b/pyrogram/crypto/rsa.py @@ -153,25 +153,25 @@ int("010001", 16) # Exponent ), - # 6427105915145367799 - 0x15931aac70e0d30f7 - (1 << 64): PublicKey( # CDN DC-121 + # -7395192255793472640 + 0x995effd323b5db80 - (1 << 64): PublicKey( # CDN DC-121 # -----BEGIN RSA PUBLIC KEY----- - # MIIBCgKCAQEA+Lf3PvgE1yxbJUCMaEAkV0QySTVpnaDjiednB5RbtNWjCeqSVakY - # HbqqGMIIv5WCGdFdrqOfMNcNSstPtSU6R9UmRw6tquOIykpSuUOje9H+4XVIKquj - # yL2ISdK+4ZOMl4hCMkqauw4bP1Sbr03vZRQbU6qEA04V4j879BAyBVhr3WG9+Zi+ - # t5XfGSTgSExPYEl8rZNHYNV5RB+BuroVH2HLTOpT/mJVfikYpgjfWF5ldezV4Wo9 - # LSH0cZGSFIaeJl8d0A8Eiy5B9gtBO8mL+XfQRKOOmr7a4BM4Ro2de5rr2i2od7hY - # Xd3DO9FRSl4y1zA8Am48Rfd95WHF3N/OmQIDAQAB + # MIIBCgKCAQEA4tWHcGJlElkxuxKQJwFjJaulmVHgdxNA3wgI2E8XbNnA88y51Xog + # V5m8BEYuTSP4llXZY4ZSJW5VlFXnmsJT/hmjyeFqqTajyAW6nb9vwZX291QvqD/1 + # ZCFBy7TLvCM0lbNIEhcLMf33ZV8AetLAd+uRLF6QHosys5w0iJ7x+UbGwDxyfeic + # 8EJJnsKaXrUOwRycMRN+V/zDySa0EYl1u1EB1MDX1/jIV1IQEbLvdBH4vsVTVEdW + # KHlzOcFzT9qX/g8XibCPiHLJvqQb8hVibvs9NaANyClcBEt3mOucG1/46Lilkc/K + # d4nlCcohk0jIHNp8symUzNWRPUGmTs3SPwIDAQAB # -----END RSA PUBLIC KEY----- int( - "F8B7F73EF804D72C5B25408C6840245744324935699DA0E389E76707945BB4D5" - "A309EA9255A9181DBAAA18C208BF958219D15DAEA39F30D70D4ACB4FB5253A47" - "D526470EADAAE388CA4A52B943A37BD1FEE175482AABA3C8BD8849D2BEE1938C" - "978842324A9ABB0E1B3F549BAF4DEF65141B53AA84034E15E23F3BF410320558" - "6BDD61BDF998BEB795DF1924E0484C4F60497CAD934760D579441F81BABA151F" - "61CB4CEA53FE62557E2918A608DF585E6575ECD5E16A3D2D21F471919214869E" - "265F1DD00F048B2E41F60B413BC98BF977D044A38E9ABEDAE01338468D9D7B9A" - "EBDA2DA877B8585DDDC33BD1514A5E32D7303C026E3C45F77DE561C5DCDFCE99", + "E2D587706265125931BB129027016325ABA59951E0771340DF0808D84F176CD9" + "C0F3CCB9D57A205799BC04462E4D23F89655D9638652256E559455E79AC253FE" + "19A3C9E16AA936A3C805BA9DBF6FC195F6F7542FA83FF5642141CBB4CBBC2334" + "95B34812170B31FDF7655F007AD2C077EB912C5E901E8B32B39C34889EF1F946" + "C6C03C727DE89CF042499EC29A5EB50EC11C9C31137E57FCC3C926B4118975BB" + "5101D4C0D7D7F8C857521011B2EF7411F8BEC55354475628797339C1734FDA97" + "FE0F1789B08F8872C9BEA41BF215626EFB3D35A00DC8295C044B7798EB9C1B5F" + "F8E8B8A591CFCA7789E509CA219348C81CDA7CB32994CCD5913D41A64ECDD23F", 16 ), # Modulus int("010001", 16) # Exponent @@ -199,6 +199,54 @@ 16 ), # Modulus int("010001", 16) # Exponent + ), + + # -3997872768018684475 + 0xc884b3e62d09e5c5 - (1 << 64): PublicKey( # CDN DC-201 + # -----BEGIN RSA PUBLIC KEY----- + # MIIBCgKCAQEAug6fETVb7NkXYYu5ueZuM0pqw1heuqUrZNYomQN0lS0o7i6mAWwb + # 1/FiscFK+y4LQSSEx+oUzXAhjmll9fmb4e7PbUiXo8MuXO0Rj3e5416DXfTiOYGW + # XlFRV0aQzu8agy1epKwkFDidnmy7g5rJJV0q1+3eR+Jk2OEc/B6lMAOv3fBU6xhE + # ZByN9gqc6fvkNo13PQ8JYZUSGttzLlYy76uFmvFBhRsJU+LNQ2+bsTHwafSffVYl + # Z2boJOblvqbRWe453CzssaSWywGXOQmWvVbEe7F8q1ki/s7S8BxYWrhSLJ6bsu9V + # ZWnIHD9vB34QF8IABPRE93mhCOHBqJxSBQIDAQAB + # -----END RSA PUBLIC KEY----- + int( + "BA0E9F11355BECD917618BB9B9E66E334A6AC3585EBAA52B64D628990374952D" + "28EE2EA6016C1BD7F162B1C14AFB2E0B412484C7EA14CD70218E6965F5F99BE1" + "EECF6D4897A3C32E5CED118F77B9E35E835DF4E23981965E5151574690CEEF1A" + "832D5EA4AC2414389D9E6CBB839AC9255D2AD7EDDE47E264D8E11CFC1EA53003" + "AFDDF054EB1844641C8DF60A9CE9FBE4368D773D0F096195121ADB732E5632EF" + "AB859AF141851B0953E2CD436F9BB131F069F49F7D56256766E824E6E5BEA6D1" + "59EE39DC2CECB1A496CB0197390996BD56C47BB17CAB5922FECED2F01C585AB8" + "522C9E9BB2EF556569C81C3F6F077E1017C20004F444F779A108E1C1A89C5205", + 16 + ), # Modulus + int("010001", 16) # Exponent + ), + + # -4960899639492471258 + 0xbb27580fd5b01626 - (1 << 64): PublicKey( # CDN DC-203 + # -----BEGIN RSA PUBLIC KEY----- + # MIIBCgKCAQEAv/L6td+mj7Dl81NHfu+Xf1KNtvZPR1tS5xFqkiUson1u7D2ulK05 + # jM8HKvpV1o+1HPPqhaXhasvsX90u3TIHRQ0zuJKJxKAiZo3GK7phHozjAJ9VUFbO + # 7jKAa5BTE9tXgA5ZwJAiQWb3U6ykwRzk3fFRe5WaW7xfVUiepxyWGdr1eecoWCfB + # af1TCXfcS7vcyljNT03pwt2YyS5iXE5IB5wBB5yqSSm4GYtWWR67UjIsXBd77TRp + # foLGpfOdUHxBz4ZSj8D76m1zlpID5J2pF6bH4+ZCz0SUpv3j7bE8WFlvgMfwEPhw + # xMYidRGayq9YlLlYd4D+Yoq0U6jS3MWTRQIDAQAB + # -----END RSA PUBLIC KEY----- + int( + "BFF2FAB5DFA68FB0E5F353477EEF977F528DB6F64F475B52E7116A92252CA27D" + "6EEC3DAE94AD398CCF072AFA55D68FB51CF3EA85A5E16ACBEC5FDD2EDD320745" + "0D33B89289C4A022668DC62BBA611E8CE3009F555056CEEE32806B905313DB57" + "800E59C090224166F753ACA4C11CE4DDF1517B959A5BBC5F55489EA71C9619DA" + "F579E7285827C169FD530977DC4BBBDCCA58CD4F4DE9C2DD98C92E625C4E4807" + "9C01079CAA4929B8198B56591EBB52322C5C177BED34697E82C6A5F39D507C41" + "CF86528FC0FBEA6D73969203E49DA917A6C7E3E642CF4494A6FDE3EDB13C5859" + "6F80C7F010F870C4C62275119ACAAF5894B9587780FE628AB453A8D2DCC59345", + 16 + ), # Modulus + int("010001", 16) # Exponent ) } diff --git a/pyrogram/session/internals/data_center.py b/pyrogram/session/internals/data_center.py index 4fce19aa24..187fde7d9f 100644 --- a/pyrogram/session/internals/data_center.py +++ b/pyrogram/session/internals/data_center.py @@ -31,7 +31,8 @@ class DataCenter: 2: "149.154.167.51", 3: "149.154.175.100", 4: "149.154.167.91", - 5: "91.108.56.130" + 5: "91.108.56.130", + 203: "91.105.192.100" } PROD_MEDIA = { @@ -55,7 +56,8 @@ class DataCenter: PROD_IPV6_MEDIA = { 2: "2001:067c:04e8:f002:0000:0000:0000:000b", - 4: "2001:067c:04e8:f004:0000:0000:0000:000b" + 4: "2001:067c:04e8:f004:0000:0000:0000:000b", + 203: "2a0a:f280:0203:000a:5000:0000:0000:0100" } def __new__(cls, dc_id: int, test_mode: bool, ipv6: bool, media: bool) -> Tuple[str, int]: From d9d68529aa6ca24066244e054d0bf87b0606d2c8 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 30 Apr 2023 19:55:05 +0200 Subject: [PATCH 1179/1185] Update Pyrogram to v2.0.105 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index 6f9d1fce71..db59589fa0 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.104" +__version__ = "2.0.105" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From 2ff67c72aa97447b4439f52e56cd5362b8c77492 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 30 Apr 2023 20:23:42 +0200 Subject: [PATCH 1180/1185] Move the CDN DC IPv6 to the correct mapping --- pyrogram/session/internals/data_center.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pyrogram/session/internals/data_center.py b/pyrogram/session/internals/data_center.py index 187fde7d9f..d314626352 100644 --- a/pyrogram/session/internals/data_center.py +++ b/pyrogram/session/internals/data_center.py @@ -51,13 +51,13 @@ class DataCenter: 2: "2001:67c:4e8:f002::a", 3: "2001:b28:f23d:f003::a", 4: "2001:67c:4e8:f004::a", - 5: "2001:b28:f23f:f005::a" + 5: "2001:b28:f23f:f005::a", + 203: "2a0a:f280:0203:000a:5000:0000:0000:0100" } PROD_IPV6_MEDIA = { 2: "2001:067c:04e8:f002:0000:0000:0000:000b", - 4: "2001:067c:04e8:f004:0000:0000:0000:000b", - 203: "2a0a:f280:0203:000a:5000:0000:0000:0100" + 4: "2001:067c:04e8:f004:0000:0000:0000:000b" } def __new__(cls, dc_id: int, test_mode: bool, ipv6: bool, media: bool) -> Tuple[str, int]: From efac17198b5fcaec1c2628c4bba0c288a4d617d4 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Sun, 30 Apr 2023 20:24:00 +0200 Subject: [PATCH 1181/1185] Update Pyrogram to v2.0.106 --- pyrogram/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyrogram/__init__.py b/pyrogram/__init__.py index db59589fa0..9a6a44accb 100644 --- a/pyrogram/__init__.py +++ b/pyrogram/__init__.py @@ -16,7 +16,7 @@ # You should have received a copy of the GNU Lesser General Public License # along with Pyrogram. If not, see . -__version__ = "2.0.105" +__version__ = "2.0.106" __license__ = "GNU Lesser General Public License v3.0 (LGPL-3.0)" __copyright__ = "Copyright (C) 2017-present Dan " From f67ce7833b98780f3456c909caef8b8f45757156 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 23 Dec 2024 21:16:06 +0100 Subject: [PATCH 1182/1185] Update Makefile --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 930d3be406..32f30a8520 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ TAG = v$(shell grep -E '__version__ = ".*"' pyrogram/__init__.py | cut -d\" -f2) RM := rm -rf -.PHONY: venv clean-build clean-api clean api build +.PHONY: venv clean-build clean-api clean api build tag dtag venv: $(RM) $(VENV) From dc7379f5ebd3403b069aca4f93f10ad1db6ae793 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 23 Dec 2024 21:16:42 +0100 Subject: [PATCH 1183/1185] Update MANIFEST.in --- MANIFEST.in | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index b1f5bc04f3..395ca54ee9 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,11 +1,13 @@ -## Include +# Include files include README.md COPYING COPYING.lesser NOTICE requirements.txt recursive-include compiler *.py *.tl *.tsv *.txt recursive-include tests *.py -## Exclude +# Exclude files +exclude pyrogram/raw/all.py + +# Prune directories prune pyrogram/errors/exceptions prune pyrogram/raw/functions prune pyrogram/raw/types prune pyrogram/raw/base -exclude pyrogram/raw/all.py From f5788f350d6bc1dae8c32ec8613977b0c36906bc Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 23 Dec 2024 21:19:55 +0100 Subject: [PATCH 1184/1185] Update .github templates --- .github/FUNDING.yml | 3 --- .github/ISSUE_TEMPLATE/config.yml | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) delete mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index 19d0cd78d1..0000000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1,3 +0,0 @@ -github: delivrance -liberapay: delivrance -open_collective: pyrogram diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 453151d8bd..80d40a188d 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -2,7 +2,7 @@ blank_issues_enabled: false contact_links: - name: Ask Pyrogram related questions url: https://stackoverflow.com/questions/tagged/pyrogram - about: This place is only for reporting issues about Pyrogram. You can ask questions at StackOverflow. + about: This place is only for reporting issues about Pyrogram. You can ask questions on StackOverflow. - name: Join the Telegram channel url: https://t.me/pyrogram about: Join the official channel and stay tuned for news, updates and announcements. \ No newline at end of file From 30de1e21e3e8b5949971ba0bba56bb818ba43b71 Mon Sep 17 00:00:00 2001 From: Dan <14043624+delivrance@users.noreply.github.com> Date: Mon, 23 Dec 2024 21:20:42 +0100 Subject: [PATCH 1185/1185] Update README.md --- README.md | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 6b904e2987..e250b40668 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

- Pyrogram + Pyrogram
Telegram MTProto API Framework for Python @@ -24,6 +24,9 @@ ## Pyrogram +> [!NOTE] +> The project is no longer maintained or supported. Thanks for appreciating it. + > Elegant, modern and asynchronous Telegram MTProto API framework in Python for users and bots ``` python @@ -44,14 +47,6 @@ app.run() framework. It enables you to easily interact with the main Telegram API through a user account (custom client) or a bot identity (bot API alternative) using Python. -### Support - -If you'd like to support Pyrogram, you can consider: - -- [Become a GitHub sponsor](https://github.com/sponsors/delivrance). -- [Become a LiberaPay patron](https://liberapay.com/delivrance). -- [Become an OpenCollective backer](https://opencollective.com/pyrogram). - ### Key Features - **Ready**: Install Pyrogram with pip and start building your applications right away.